feat(ipc): implement ShowOverlay and drop settings that never worked
ShowOverlay was declared in the Command enum but the daemon dispatcher had
no arm for it, so it fell through to `_ => {}`. Nothing could open the HUD
once it was closed except the --menu toggle path.
- daemon: spawn_overlay_once() checks the overlay pid file for a live
process and launches `scythe --menu` only when none is running, so
repeated requests cannot stack overlays
- cli: `scythe --show-overlay` sends the command, falling back to a direct
launch when the daemon is unreachable
Fourteen config keys were persisted and shown in the settings UI while no
capture, encoder, muxer or daemon path ever read them. Removed from
ScytheConfig, from the overlay state, and from their UI panels:
capture_source, replay_storage, change_resolution, resolution_w/h,
container_format, show_scroll_lock_led, autostart_on_game,
save_folder_by_game, name_by_game, language, ui_color_theme, record_enabled.
Unknown JSON keys are ignored by serde, so existing configs still load.
The e2e script also unlinked a live daemon's socket: it ran rm -f on the
socket path unconditionally and only stopped the systemd unit, so a daemon
started by execs.lua or by hand kept its flock while losing its path entry,
leaving the session broken until the process died. It now sends StopDaemon,
waits for the socket to disappear, and aborts without unlinking if the
daemon will not stop.
This commit is contained in:
parent
d01cdcb449
commit
6c9b8a8d82
5 changed files with 95 additions and 196 deletions
|
|
@ -10,8 +10,6 @@ fn default_fps() -> u32 { 60 }
|
||||||
fn default_video_codec() -> String { "h264".to_string() }
|
fn default_video_codec() -> String { "h264".to_string() }
|
||||||
fn default_audio_device() -> String { "default".to_string() }
|
fn default_audio_device() -> String { "default".to_string() }
|
||||||
fn default_audio_mode() -> String { "system".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_save_hotkey() -> String { "Ctrl+Shift+R".to_string() }
|
||||||
fn default_menu_hotkey() -> String { "Alt+Z".to_string() }
|
fn default_menu_hotkey() -> String { "Alt+Z".to_string() }
|
||||||
fn default_record_hotkey() -> String { "Ctrl+Shift+F9".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_stream_url() -> String { "rtmp://live.twitch.tv/app/".to_string() }
|
||||||
fn default_screenshot_hotkey() -> String { "Ctrl+Shift+S".to_string() }
|
fn default_screenshot_hotkey() -> String { "Ctrl+Shift+S".to_string() }
|
||||||
fn default_screenshot_format() -> String { "png".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_video_quality_mode() -> String { "cbr".to_string() }
|
||||||
fn default_color_range() -> String { "limited".to_string() }
|
fn default_color_range() -> String { "limited".to_string() }
|
||||||
fn default_audio_codec() -> String { "opus".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_quality() -> u32 { 90 }
|
||||||
fn default_screenshot_preset() -> String { "balanced".to_string() }
|
fn default_screenshot_preset() -> String { "balanced".to_string() }
|
||||||
fn default_pause_hotkey() -> String { "Ctrl+Shift+F8".to_string() }
|
fn default_pause_hotkey() -> String { "Ctrl+Shift+F8".to_string() }
|
||||||
|
|
@ -50,8 +43,6 @@ pub struct ScytheConfig {
|
||||||
#[serde(default = "default_replay_bitrate")]
|
#[serde(default = "default_replay_bitrate")]
|
||||||
pub replay_bitrate_kbps: u32,
|
pub replay_bitrate_kbps: u32,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub record_enabled: bool,
|
|
||||||
#[serde(default = "default_record_bitrate")]
|
#[serde(default = "default_record_bitrate")]
|
||||||
pub record_bitrate_kbps: u32,
|
pub record_bitrate_kbps: u32,
|
||||||
#[serde(default = "default_fps")]
|
#[serde(default = "default_fps")]
|
||||||
|
|
@ -77,10 +68,6 @@ pub struct ScytheConfig {
|
||||||
pub autostart_replay: bool,
|
pub autostart_replay: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub autostart_overlay: bool,
|
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")]
|
#[serde(default = "default_save_hotkey")]
|
||||||
pub save_hotkey: String,
|
pub save_hotkey: String,
|
||||||
#[serde(default = "default_menu_hotkey")]
|
#[serde(default = "default_menu_hotkey")]
|
||||||
|
|
@ -109,40 +96,20 @@ pub struct ScytheConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub screenshot_cursor: bool,
|
pub screenshot_cursor: bool,
|
||||||
|
|
||||||
#[serde(default = "default_capture_source")]
|
|
||||||
pub capture_source: String,
|
|
||||||
#[serde(default = "default_video_quality_mode")]
|
#[serde(default = "default_video_quality_mode")]
|
||||||
pub video_quality_mode: String,
|
pub video_quality_mode: String,
|
||||||
#[serde(default = "default_color_range")]
|
#[serde(default = "default_color_range")]
|
||||||
pub color_range: String,
|
pub color_range: String,
|
||||||
#[serde(default = "default_audio_codec")]
|
#[serde(default = "default_audio_codec")]
|
||||||
pub audio_codec: String,
|
pub audio_codec: String,
|
||||||
#[serde(default = "default_replay_storage")]
|
|
||||||
pub replay_storage: String,
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub record_low_power: bool,
|
pub record_low_power: bool,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub show_replay_notifications: bool,
|
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")]
|
#[serde(default = "default_true")]
|
||||||
pub restart_replay_on_save: bool,
|
pub restart_replay_on_save: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub show_scroll_lock_led: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub autostart_on_game: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
pub autostart_power_only: bool,
|
pub autostart_power_only: bool,
|
||||||
|
|
||||||
#[serde(default = "default_screenshot_quality")]
|
#[serde(default = "default_screenshot_quality")]
|
||||||
|
|
@ -171,7 +138,6 @@ impl Default for ScytheConfig {
|
||||||
replay_duration_sec: 60,
|
replay_duration_sec: 60,
|
||||||
replay_bitrate_kbps: 18000,
|
replay_bitrate_kbps: 18000,
|
||||||
|
|
||||||
record_enabled: false,
|
|
||||||
record_bitrate_kbps: 20000,
|
record_bitrate_kbps: 20000,
|
||||||
fps: 60,
|
fps: 60,
|
||||||
video_codec: "h264".to_string(),
|
video_codec: "h264".to_string(),
|
||||||
|
|
@ -185,8 +151,6 @@ impl Default for ScytheConfig {
|
||||||
autostart: false,
|
autostart: false,
|
||||||
autostart_replay: false,
|
autostart_replay: false,
|
||||||
autostart_overlay: false,
|
autostart_overlay: false,
|
||||||
language: "en".to_string(),
|
|
||||||
ui_color_theme: "dark".to_string(),
|
|
||||||
save_hotkey: "Ctrl+Shift+R".to_string(),
|
save_hotkey: "Ctrl+Shift+R".to_string(),
|
||||||
menu_hotkey: "Alt+Z".to_string(),
|
menu_hotkey: "Alt+Z".to_string(),
|
||||||
record_hotkey: "Ctrl+Shift+F9".to_string(),
|
record_hotkey: "Ctrl+Shift+F9".to_string(),
|
||||||
|
|
@ -202,23 +166,13 @@ impl Default for ScytheConfig {
|
||||||
screenshot_format: default_screenshot_format(),
|
screenshot_format: default_screenshot_format(),
|
||||||
screenshot_cursor: false,
|
screenshot_cursor: false,
|
||||||
|
|
||||||
capture_source: default_capture_source(),
|
|
||||||
video_quality_mode: default_video_quality_mode(),
|
video_quality_mode: default_video_quality_mode(),
|
||||||
color_range: default_color_range(),
|
color_range: default_color_range(),
|
||||||
audio_codec: default_audio_codec(),
|
audio_codec: default_audio_codec(),
|
||||||
replay_storage: default_replay_storage(),
|
|
||||||
record_low_power: false,
|
record_low_power: false,
|
||||||
show_replay_notifications: true,
|
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,
|
restart_replay_on_save: true,
|
||||||
show_scroll_lock_led: false,
|
|
||||||
autostart_on_game: false,
|
|
||||||
autostart_power_only: false,
|
autostart_power_only: false,
|
||||||
|
|
||||||
screenshot_quality: default_screenshot_quality(),
|
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_url, "rtmp://live.twitch.tv/app/");
|
||||||
assert_eq!(parsed.stream_key, "");
|
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.restart_replay_on_save);
|
||||||
assert!(!parsed.show_scroll_lock_led);
|
|
||||||
assert!(!parsed.autostart_on_game);
|
|
||||||
assert!(!parsed.autostart_power_only);
|
assert!(!parsed.autostart_power_only);
|
||||||
assert_eq!(parsed.capture_source, "focused");
|
|
||||||
assert_eq!(parsed.video_quality_mode, "cbr");
|
assert_eq!(parsed.video_quality_mode, "cbr");
|
||||||
assert_eq!(parsed.color_range, "limited");
|
assert_eq!(parsed.color_range, "limited");
|
||||||
assert_eq!(parsed.audio_codec, "opus");
|
assert_eq!(parsed.audio_codec, "opus");
|
||||||
assert_eq!(parsed.replay_storage, "ram");
|
|
||||||
assert!(!parsed.record_low_power);
|
assert!(!parsed.record_low_power);
|
||||||
assert!(parsed.show_replay_notifications);
|
assert!(parsed.show_replay_notifications);
|
||||||
assert_eq!(parsed.screenshot_hotkey, "Ctrl+Shift+S");
|
assert_eq!(parsed.screenshot_hotkey, "Ctrl+Shift+S");
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,67 @@ use std::io::{Read, Write};
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
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::<i32>() 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 {
|
fn mask_stream_url(url: &str) -> String {
|
||||||
if let Some((base, key)) = url.rsplit_once('/')
|
if let Some((base, key)) = url.rsplit_once('/')
|
||||||
&& key.len() > 4 {
|
&& key.len() > 4 {
|
||||||
|
|
@ -278,6 +339,10 @@ async fn run_daemon_async() -> Result<(), Box<dyn std::error::Error + Send + Syn
|
||||||
println!("Audio mode cycled to: {}", cfg.audio_mode);
|
println!("Audio mode cycled to: {}", cfg.audio_mode);
|
||||||
let _ = cmd_tx_ipc.try_send(Command::ReloadConfig);
|
let _ = cmd_tx_ipc.try_send(Command::ReloadConfig);
|
||||||
},
|
},
|
||||||
|
Command::ShowOverlay => {
|
||||||
|
println!("ShowOverlay requested: opening HUD overlay.");
|
||||||
|
std::thread::spawn(spawn_overlay_once);
|
||||||
|
},
|
||||||
Command::StopDaemon => {
|
Command::StopDaemon => {
|
||||||
println!("StopDaemon requested: Finalizing active recordings...");
|
println!("StopDaemon requested: Finalizing active recordings...");
|
||||||
crate::hyprland_binds::unregister_hyprland_binds(&crate::config::ScytheConfig::load());
|
crate::hyprland_binds::unregister_hyprland_binds(&crate::config::ScytheConfig::load());
|
||||||
|
|
|
||||||
12
src/main.rs
12
src/main.rs
|
|
@ -444,6 +444,17 @@ fn run_client(args: Vec<String>) -> Result<(), Box<dyn std::error::Error + Send
|
||||||
"--pause" => {
|
"--pause" => {
|
||||||
return handle_toggle_recording();
|
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" => {
|
"--toast" => {
|
||||||
let title = args.get(2).map(|s| s.as_str()).unwrap_or("SCYTHE");
|
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("");
|
let subtitle = args.get(3).map(|s| s.as_str()).unwrap_or("");
|
||||||
|
|
@ -540,6 +551,7 @@ fn run_client(args: Vec<String>) -> Result<(), Box<dyn std::error::Error + Send
|
||||||
println!(" scythe-ui --record Toggle normal recording on/off (with notification)");
|
println!(" scythe-ui --record Toggle normal recording on/off (with notification)");
|
||||||
println!(" scythe-ui --stream Toggle livestreaming on/off (with notification)");
|
println!(" scythe-ui --stream Toggle livestreaming on/off (with notification)");
|
||||||
println!(" scythe-ui --cursor Toggle mouse cursor recording on/off (with notification)");
|
println!(" scythe-ui --cursor Toggle mouse cursor recording on/off (with notification)");
|
||||||
|
println!(" scythe-ui --show-overlay Open the HUD overlay via the daemon (no toggle)");
|
||||||
println!(" scythe-ui --start Start normal recording");
|
println!(" scythe-ui --start Start normal recording");
|
||||||
println!(" scythe-ui --stop Stop normal recording");
|
println!(" scythe-ui --stop Stop normal recording");
|
||||||
println!(" scythe-ui --reload Reload daemon configuration");
|
println!(" scythe-ui --reload Reload daemon configuration");
|
||||||
|
|
|
||||||
|
|
@ -1131,21 +1131,11 @@ pub struct ScytheOverlayApp {
|
||||||
pub screenshot_dir: String,
|
pub screenshot_dir: String,
|
||||||
pub screenshot_format: String,
|
pub screenshot_format: String,
|
||||||
pub screenshot_cursor: bool,
|
pub screenshot_cursor: bool,
|
||||||
pub capture_source: String,
|
|
||||||
pub video_quality_mode: String,
|
pub video_quality_mode: String,
|
||||||
pub color_range: String,
|
pub color_range: String,
|
||||||
pub replay_storage: String,
|
|
||||||
pub record_low_power: bool,
|
pub record_low_power: bool,
|
||||||
pub show_replay_notifications: bool,
|
pub show_replay_notifications: bool,
|
||||||
pub container_format: String,
|
|
||||||
pub change_resolution: bool,
|
|
||||||
pub resolution_w: u32,
|
|
||||||
pub resolution_h: u32,
|
|
||||||
pub res_w_str: String,
|
|
||||||
pub res_h_str: String,
|
|
||||||
pub restart_replay_on_save: bool,
|
pub restart_replay_on_save: bool,
|
||||||
pub show_scroll_lock_led: bool,
|
|
||||||
pub autostart_on_game: bool,
|
|
||||||
pub autostart_power_only: bool,
|
pub autostart_power_only: bool,
|
||||||
pub screenshot_quality: u32,
|
pub screenshot_quality: u32,
|
||||||
pub screenshot_preset: String,
|
pub screenshot_preset: String,
|
||||||
|
|
@ -1225,22 +1215,12 @@ impl ScytheOverlayApp {
|
||||||
let screenshot_dir = config.screenshot_directory.clone();
|
let screenshot_dir = config.screenshot_directory.clone();
|
||||||
let screenshot_format = config.screenshot_format.clone();
|
let screenshot_format = config.screenshot_format.clone();
|
||||||
let screenshot_cursor = config.screenshot_cursor;
|
let screenshot_cursor = config.screenshot_cursor;
|
||||||
let capture_source = config.capture_source.clone();
|
|
||||||
let video_quality_mode = config.video_quality_mode.clone();
|
let video_quality_mode = config.video_quality_mode.clone();
|
||||||
let color_range = config.color_range.clone();
|
let color_range = config.color_range.clone();
|
||||||
let replay_storage = config.replay_storage.clone();
|
|
||||||
let record_low_power = config.record_low_power;
|
let record_low_power = config.record_low_power;
|
||||||
let show_replay_notifications = config.show_replay_notifications;
|
let show_replay_notifications = config.show_replay_notifications;
|
||||||
|
|
||||||
let container_format = config.container_format.clone();
|
|
||||||
let change_resolution = config.change_resolution;
|
|
||||||
let resolution_w = config.resolution_w;
|
|
||||||
let resolution_h = config.resolution_h;
|
|
||||||
let res_w_str = resolution_w.to_string();
|
|
||||||
let res_h_str = resolution_h.to_string();
|
|
||||||
let restart_replay_on_save = config.restart_replay_on_save;
|
let restart_replay_on_save = config.restart_replay_on_save;
|
||||||
let show_scroll_lock_led = config.show_scroll_lock_led;
|
|
||||||
let autostart_on_game = config.autostart_on_game;
|
|
||||||
let autostart_power_only = config.autostart_power_only;
|
let autostart_power_only = config.autostart_power_only;
|
||||||
let screenshot_quality = config.screenshot_quality;
|
let screenshot_quality = config.screenshot_quality;
|
||||||
let screenshot_preset = config.screenshot_preset.clone();
|
let screenshot_preset = config.screenshot_preset.clone();
|
||||||
|
|
@ -1307,21 +1287,11 @@ impl ScytheOverlayApp {
|
||||||
screenshot_dir,
|
screenshot_dir,
|
||||||
screenshot_format,
|
screenshot_format,
|
||||||
screenshot_cursor,
|
screenshot_cursor,
|
||||||
capture_source,
|
|
||||||
video_quality_mode,
|
video_quality_mode,
|
||||||
color_range,
|
color_range,
|
||||||
replay_storage,
|
|
||||||
record_low_power,
|
record_low_power,
|
||||||
show_replay_notifications,
|
show_replay_notifications,
|
||||||
container_format,
|
|
||||||
change_resolution,
|
|
||||||
resolution_w,
|
|
||||||
resolution_h,
|
|
||||||
res_w_str,
|
|
||||||
res_h_str,
|
|
||||||
restart_replay_on_save,
|
restart_replay_on_save,
|
||||||
show_scroll_lock_led,
|
|
||||||
autostart_on_game,
|
|
||||||
autostart_power_only,
|
autostart_power_only,
|
||||||
screenshot_quality,
|
screenshot_quality,
|
||||||
screenshot_preset,
|
screenshot_preset,
|
||||||
|
|
@ -1362,19 +1332,11 @@ impl ScytheOverlayApp {
|
||||||
self.config.screenshot_directory = self.screenshot_dir.clone();
|
self.config.screenshot_directory = self.screenshot_dir.clone();
|
||||||
self.config.screenshot_format = self.screenshot_format.clone();
|
self.config.screenshot_format = self.screenshot_format.clone();
|
||||||
self.config.screenshot_cursor = self.screenshot_cursor;
|
self.config.screenshot_cursor = self.screenshot_cursor;
|
||||||
self.config.capture_source = self.capture_source.clone();
|
|
||||||
self.config.video_quality_mode = self.video_quality_mode.clone();
|
self.config.video_quality_mode = self.video_quality_mode.clone();
|
||||||
self.config.color_range = self.color_range.clone();
|
self.config.color_range = self.color_range.clone();
|
||||||
self.config.replay_storage = self.replay_storage.clone();
|
|
||||||
self.config.record_low_power = self.record_low_power;
|
self.config.record_low_power = self.record_low_power;
|
||||||
self.config.show_replay_notifications = self.show_replay_notifications;
|
self.config.show_replay_notifications = self.show_replay_notifications;
|
||||||
self.config.container_format = "mp4".to_string();
|
|
||||||
self.config.change_resolution = self.change_resolution;
|
|
||||||
self.config.resolution_w = self.resolution_w;
|
|
||||||
self.config.resolution_h = self.resolution_h;
|
|
||||||
self.config.restart_replay_on_save = self.restart_replay_on_save;
|
self.config.restart_replay_on_save = self.restart_replay_on_save;
|
||||||
self.config.show_scroll_lock_led = self.show_scroll_lock_led;
|
|
||||||
self.config.autostart_on_game = self.autostart_on_game;
|
|
||||||
self.config.autostart_power_only = self.autostart_power_only;
|
self.config.autostart_power_only = self.autostart_power_only;
|
||||||
self.config.screenshot_quality = self.screenshot_quality;
|
self.config.screenshot_quality = self.screenshot_quality;
|
||||||
self.config.screenshot_preset = self.screenshot_preset.clone();
|
self.config.screenshot_preset = self.screenshot_preset.clone();
|
||||||
|
|
@ -1405,21 +1367,11 @@ impl ScytheOverlayApp {
|
||||||
self.screenshot_dir = def.screenshot_directory;
|
self.screenshot_dir = def.screenshot_directory;
|
||||||
self.screenshot_format = def.screenshot_format;
|
self.screenshot_format = def.screenshot_format;
|
||||||
self.screenshot_cursor = def.screenshot_cursor;
|
self.screenshot_cursor = def.screenshot_cursor;
|
||||||
self.capture_source = def.capture_source;
|
|
||||||
self.video_quality_mode = def.video_quality_mode;
|
self.video_quality_mode = def.video_quality_mode;
|
||||||
self.color_range = def.color_range;
|
self.color_range = def.color_range;
|
||||||
self.replay_storage = def.replay_storage;
|
|
||||||
self.record_low_power = def.record_low_power;
|
self.record_low_power = def.record_low_power;
|
||||||
self.show_replay_notifications = def.show_replay_notifications;
|
self.show_replay_notifications = def.show_replay_notifications;
|
||||||
self.container_format = "mp4".to_string();
|
|
||||||
self.change_resolution = def.change_resolution;
|
|
||||||
self.resolution_w = def.resolution_w;
|
|
||||||
self.resolution_h = def.resolution_h;
|
|
||||||
self.res_w_str = def.resolution_w.to_string();
|
|
||||||
self.res_h_str = def.resolution_h.to_string();
|
|
||||||
self.restart_replay_on_save = def.restart_replay_on_save;
|
self.restart_replay_on_save = def.restart_replay_on_save;
|
||||||
self.show_scroll_lock_led = def.show_scroll_lock_led;
|
|
||||||
self.autostart_on_game = def.autostart_on_game;
|
|
||||||
self.autostart_power_only = def.autostart_power_only;
|
self.autostart_power_only = def.autostart_power_only;
|
||||||
self.screenshot_quality = def.screenshot_quality;
|
self.screenshot_quality = def.screenshot_quality;
|
||||||
self.screenshot_preset = def.screenshot_preset;
|
self.screenshot_preset = def.screenshot_preset;
|
||||||
|
|
@ -2467,18 +2419,6 @@ impl ScytheOverlayApp {
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
||||||
render_section_card(ui, "General", accent, |ui| {
|
render_section_card(ui, "General", accent, |ui| {
|
||||||
ui.label(egui::RichText::new("Where should temporary replay data be stored?").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
|
||||||
ui.add_space(4.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if squared_button(ui, "RAM", self.replay_storage == "ram", accent) {
|
|
||||||
self.replay_storage = "ram".to_string();
|
|
||||||
}
|
|
||||||
if squared_button(ui, "Disk (Not recommended on SSDs)", self.replay_storage == "disk", accent) {
|
|
||||||
self.replay_storage = "disk".to_string();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let mut rrs = self.restart_replay_on_save;
|
let mut rrs = self.restart_replay_on_save;
|
||||||
if toggle_switch(ui, &mut rrs, accent).changed() {
|
if toggle_switch(ui, &mut rrs, accent).changed() {
|
||||||
|
|
@ -2552,14 +2492,6 @@ impl ScytheOverlayApp {
|
||||||
}
|
}
|
||||||
ui.label(egui::RichText::new("Show replay notifications").size(11.5).color(Color32::WHITE));
|
ui.label(egui::RichText::new("Show replay notifications").size(11.5).color(Color32::WHITE));
|
||||||
});
|
});
|
||||||
ui.add_space(4.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut led_on = self.show_scroll_lock_led;
|
|
||||||
if toggle_switch(ui, &mut led_on, accent).changed() {
|
|
||||||
self.show_scroll_lock_led = led_on;
|
|
||||||
}
|
|
||||||
ui.label(egui::RichText::new("Show replay status with scroll lock LED").size(11.5).color(Color32::WHITE));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
@ -2596,52 +2528,6 @@ impl ScytheOverlayApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tab_record(&mut self, ui: &mut egui::Ui, accent: Color32) {
|
fn render_tab_record(&mut self, ui: &mut egui::Ui, accent: Color32) {
|
||||||
render_section_card(ui, "Capture", accent, |ui| {
|
|
||||||
ui.label(egui::RichText::new("Capture source:").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
|
||||||
ui.add_space(3.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
if squared_button(ui, "Focused monitor", self.capture_source == "focused", accent) {
|
|
||||||
self.capture_source = "focused".to_string();
|
|
||||||
}
|
|
||||||
if squared_button(ui, "Monitor eDP-1 (1920x1080)", self.capture_source == "monitor", accent) {
|
|
||||||
self.capture_source = "monitor".to_string();
|
|
||||||
}
|
|
||||||
if squared_button(ui, "Window", self.capture_source == "window", accent) {
|
|
||||||
self.capture_source = "window".to_string();
|
|
||||||
}
|
|
||||||
if squared_button(ui, "Region", self.capture_source == "region", accent) {
|
|
||||||
self.capture_source = "region".to_string();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let mut chg_res = self.change_resolution;
|
|
||||||
if toggle_switch(ui, &mut chg_res, accent).changed() {
|
|
||||||
self.change_resolution = chg_res;
|
|
||||||
}
|
|
||||||
ui.label(egui::RichText::new("Change video resolution").size(11.5).color(Color32::WHITE));
|
|
||||||
});
|
|
||||||
|
|
||||||
if self.change_resolution {
|
|
||||||
ui.add_space(6.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label(egui::RichText::new("Resolution:").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
|
||||||
let mut w = self.resolution_w;
|
|
||||||
if numeric_input_box(ui, &mut self.res_w_str, &mut w, 640, 7680, 160, "W", accent) {
|
|
||||||
self.resolution_w = w;
|
|
||||||
}
|
|
||||||
ui.label(egui::RichText::new("x").size(11.5).color(Color32::WHITE));
|
|
||||||
let mut h = self.resolution_h;
|
|
||||||
if numeric_input_box(ui, &mut self.res_h_str, &mut h, 480, 4320, 90, "H", accent) {
|
|
||||||
self.resolution_h = h;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
|
|
||||||
self.render_audio_controls(ui, accent);
|
self.render_audio_controls(ui, accent);
|
||||||
|
|
||||||
ui.add_space(10.0);
|
ui.add_space(10.0);
|
||||||
|
|
@ -2840,16 +2726,6 @@ impl ScytheOverlayApp {
|
||||||
ui.label(egui::RichText::new("Capture mouse cursor in screenshot").size(11.5).color(Color32::WHITE));
|
ui.label(egui::RichText::new("Capture mouse cursor in screenshot").size(11.5).color(Color32::WHITE));
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.add_space(8.0);
|
|
||||||
ui.label(egui::RichText::new("Capture source:").size(11.5).color(Color32::from_rgb(180, 185, 190)));
|
|
||||||
ui.add_space(4.0);
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
for (src_id, label) in [("focused", "Focused monitor"), ("full", "Full desktop")] {
|
|
||||||
if squared_button(ui, label, self.capture_source == src_id, accent) {
|
|
||||||
self.capture_source = src_id.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(8.0);
|
ui.add_space(8.0);
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
|
|
@ -4458,13 +4334,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_overlay_app_options_synchronization() {
|
fn test_overlay_app_options_synchronization() {
|
||||||
let mut app = ScytheOverlayApp::new();
|
let mut app = ScytheOverlayApp::new();
|
||||||
app.container_format = "mp4".to_string();
|
|
||||||
app.change_resolution = true;
|
|
||||||
app.resolution_w = 2560;
|
|
||||||
app.resolution_h = 1440;
|
|
||||||
app.restart_replay_on_save = false;
|
app.restart_replay_on_save = false;
|
||||||
app.show_scroll_lock_led = true;
|
|
||||||
app.capture_source = "window".to_string();
|
|
||||||
app.video_quality_mode = "vbr".to_string();
|
app.video_quality_mode = "vbr".to_string();
|
||||||
app.color_range = "full".to_string();
|
app.color_range = "full".to_string();
|
||||||
app.config.audio_codec = "opus".to_string();
|
app.config.audio_codec = "opus".to_string();
|
||||||
|
|
@ -4474,13 +4344,7 @@ mod tests {
|
||||||
|
|
||||||
app.save_and_apply_settings();
|
app.save_and_apply_settings();
|
||||||
|
|
||||||
assert_eq!(app.config.container_format, "mp4");
|
|
||||||
assert!(app.config.change_resolution);
|
|
||||||
assert_eq!(app.config.resolution_w, 2560);
|
|
||||||
assert_eq!(app.config.resolution_h, 1440);
|
|
||||||
assert!(!app.config.restart_replay_on_save);
|
assert!(!app.config.restart_replay_on_save);
|
||||||
assert!(app.config.show_scroll_lock_led);
|
|
||||||
assert_eq!(app.config.capture_source, "window");
|
|
||||||
assert_eq!(app.config.video_quality_mode, "vbr");
|
assert_eq!(app.config.video_quality_mode, "vbr");
|
||||||
assert_eq!(app.config.color_range, "full");
|
assert_eq!(app.config.color_range, "full");
|
||||||
assert_eq!(app.config.audio_codec, "opus");
|
assert_eq!(app.config.audio_codec, "opus");
|
||||||
|
|
@ -4489,11 +4353,7 @@ mod tests {
|
||||||
assert_eq!(app.config.screenshot_resolution, "1080p");
|
assert_eq!(app.config.screenshot_resolution, "1080p");
|
||||||
|
|
||||||
app.reset_settings_to_defaults();
|
app.reset_settings_to_defaults();
|
||||||
assert_eq!(app.container_format, "mp4");
|
|
||||||
assert!(!app.change_resolution);
|
|
||||||
assert!(app.restart_replay_on_save);
|
assert!(app.restart_replay_on_save);
|
||||||
assert!(!app.show_scroll_lock_led);
|
|
||||||
assert_eq!(app.capture_source, "focused");
|
|
||||||
assert_eq!(app.video_quality_mode, "cbr");
|
assert_eq!(app.video_quality_mode, "cbr");
|
||||||
assert_eq!(app.color_range, "limited");
|
assert_eq!(app.color_range, "limited");
|
||||||
assert_eq!(app.config.audio_codec, "opus");
|
assert_eq!(app.config.audio_codec, "opus");
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,24 @@ echo "[1/6] Building release binaries..."
|
||||||
cargo build --release --quiet
|
cargo build --release --quiet
|
||||||
|
|
||||||
SOCKET_PATH="${XDG_RUNTIME_DIR:-/tmp}/scythe.sock"
|
SOCKET_PATH="${XDG_RUNTIME_DIR:-/tmp}/scythe.sock"
|
||||||
|
|
||||||
|
# A live daemon owns this socket. Removing the path entry while it runs leaves the
|
||||||
|
# kernel socket bound but unreachable, breaking the user's session until restart.
|
||||||
|
# Shut it down gracefully instead, and refuse to run if it will not stop.
|
||||||
|
if [ -S "$SOCKET_PATH" ] && target/release/scythe --status >/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 "$SOCKET_PATH"
|
||||||
rm -f record_*.mp4 replay_*.mp4
|
rm -f record_*.mp4 replay_*.mp4
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue