feat(arch): consolidate daemon and UI into singular multicall binary scythe
This commit is contained in:
parent
425a9b1003
commit
3cc718fa69
8 changed files with 101 additions and 87 deletions
|
|
@ -4,12 +4,8 @@ version = "0.2.18"
|
|||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "scythe-daemon"
|
||||
path = "src/bin/scythe-daemon.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "scythe-ui"
|
||||
path = "src/bin/scythe-ui.rs"
|
||||
name = "scythe"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.5.16"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ This checklist tracks known bugs, UI refinements, optimizations, and technical d
|
|||
|
||||
## 1. UI Refinements & Imperfections
|
||||
|
||||
- [x] **Singular Multicall Binary Architecture (`scythe`)**:
|
||||
- **Issue**: Distributing separate `scythe-daemon` and `scythe-ui` binaries required cross-process path discovery in `$PATH`, complicated Windows bundling with multiple executables, and prevented atomic single-file updates.
|
||||
- **Resolution**:
|
||||
- Consolidated daemon engine and UI overlay/CLI into a single binary (`scythe`) in [src/main.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/main.rs) and [src/daemon.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/daemon.rs).
|
||||
- Implemented multicall dispatch: inspects `argv[0]` and subcommands (`scythe daemon`, `scythe [options]`, or symlinks `scythe-daemon` / `scythe-ui`).
|
||||
- Configured [install.sh](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/install.sh) with backward-compatible symlinks.
|
||||
- Updated release packages: both Linux and Windows distributions now ship a single executable.
|
||||
|
||||
- [x] **Screenshot Resolution Presets (Native, 1080p, 1440p, 2160p, 720p) & Lanczos Downscaling**:
|
||||
- **Issue**: Vague compression percentages and presets confused users instead of providing clear resolution controls.
|
||||
- **Resolution**: Replaced vague compression sliders with clean resolution presets (`Native (Source)`, `1080p (FHD)`, `1440p (QHD)`, `2160p (4K UHD)`, `720p (HD)`) in [src/overlay_egui.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/overlay_egui.rs). When a target resolution is selected, [src/bin/scythe-ui.rs](file:///home/oiupoyt/my%20stuff%20yea/projects/scythe/src/bin/scythe-ui.rs) executes pristine Lanczos hardware-accelerated scaling via FFmpeg.
|
||||
|
|
|
|||
18
install.sh
18
install.sh
|
|
@ -15,15 +15,15 @@ APP_DIR="$HOME/.local/share/applications"
|
|||
mkdir -p "$BIN_DIR"
|
||||
mkdir -p "$APP_DIR"
|
||||
|
||||
echo "[2/4] Installing binaries to $BIN_DIR..."
|
||||
install -m 755 target/release/scythe-daemon "$BIN_DIR/scythe-daemon"
|
||||
install -m 755 target/release/scythe-ui "$BIN_DIR/scythe-ui"
|
||||
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/scythe"
|
||||
echo "[2/4] Installing singular binary to $BIN_DIR..."
|
||||
install -m 755 target/release/scythe "$BIN_DIR/scythe"
|
||||
|
||||
# Backward-compatible symlinks
|
||||
ln -sf "$BIN_DIR/scythe-daemon" "$BIN_DIR/vrec-daemon"
|
||||
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec-ui"
|
||||
ln -sf "$BIN_DIR/scythe-ui" "$BIN_DIR/vrec"
|
||||
# Backward-compatible multicall symlinks
|
||||
ln -sf "$BIN_DIR/scythe" "$BIN_DIR/scythe-daemon"
|
||||
ln -sf "$BIN_DIR/scythe" "$BIN_DIR/scythe-ui"
|
||||
ln -sf "$BIN_DIR/scythe" "$BIN_DIR/vrec-daemon"
|
||||
ln -sf "$BIN_DIR/scythe" "$BIN_DIR/vrec-ui"
|
||||
ln -sf "$BIN_DIR/scythe" "$BIN_DIR/vrec"
|
||||
|
||||
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
|
||||
mkdir -p "$SYSTEMD_USER_DIR"
|
||||
|
|
@ -35,7 +35,7 @@ Wants=xdg-desktop-portal.service
|
|||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=$BIN_DIR/scythe-daemon
|
||||
ExecStart=$BIN_DIR/scythe daemon
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
Environment="XDG_RUNTIME_DIR=/run/user/1000"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
||||
#![allow(clippy::chunks_exact_to_as_chunks)]
|
||||
|
||||
use scythe::capture::{Frame, FrameSource};
|
||||
use scythe::encoder::VideoEncoder;
|
||||
use scythe::ring::Packet;
|
||||
use scythe::muxer::Muxer;
|
||||
use scythe::ipc::{Command, DaemonStatus};
|
||||
use crate::capture::{Frame, FrameSource};
|
||||
use crate::encoder::VideoEncoder;
|
||||
use crate::ring::Packet;
|
||||
use crate::muxer::Muxer;
|
||||
use crate::ipc::{Command, DaemonStatus};
|
||||
use ringbuf::HeapRb;
|
||||
use ringbuf::traits::{RingBuffer, Consumer, Observer};
|
||||
use crossbeam_channel::bounded;
|
||||
|
|
@ -79,12 +77,18 @@ fn spawn_stream_worker(
|
|||
}
|
||||
|
||||
fn ensure_wayland_env() {
|
||||
scythe::overlay::ensure_wayland_env();
|
||||
crate::overlay::ensure_wayland_env();
|
||||
}
|
||||
|
||||
pub fn run_daemon() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()?
|
||||
.block_on(run_daemon_async())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[allow(unused_assignments)]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
async fn run_daemon_async() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
use windows::Win32::System::Console::FreeConsole;
|
||||
|
|
@ -123,11 +127,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
Err(_) => None,
|
||||
}
|
||||
};
|
||||
let initial_config = scythe::config::ScytheConfig::load();
|
||||
let initial_config = crate::config::ScytheConfig::load();
|
||||
|
||||
// Automatically register dynamic keybindings on Hyprland if active (no config edits needed)
|
||||
scythe::hyprland_binds::register_hyprland_binds(&initial_config);
|
||||
scythe::hyprland_binds::spawn_hyprland_reload_watcher();
|
||||
crate::hyprland_binds::register_hyprland_binds(&initial_config);
|
||||
crate::hyprland_binds::spawn_hyprland_reload_watcher();
|
||||
|
||||
let (frame_tx, frame_rx) = bounded::<Frame>(2);
|
||||
let (cmd_tx, cmd_rx) = bounded::<Command>(32);
|
||||
|
|
@ -140,11 +144,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
let stream_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));
|
||||
let audio_levels = Arc::new(scythe::capture::audio::AudioLevels::new());
|
||||
let audio_levels = Arc::new(crate::capture::audio::AudioLevels::new());
|
||||
|
||||
#[cfg(unix)]
|
||||
let listener = {
|
||||
let socket_path = scythe::ipc::get_socket_path();
|
||||
let socket_path = crate::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);
|
||||
|
|
@ -162,7 +166,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
if scythe::ipc::query_status_port(port).is_ok() {
|
||||
if crate::ipc::query_status_port(port).is_ok() {
|
||||
println!("Another instance of scythe-daemon is already running on port {}. Exiting cleanly.", port);
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -172,7 +176,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
match bound {
|
||||
Some((l, p)) => {
|
||||
println!("Daemon listening on TCP IPC: 127.0.0.1:{}", p);
|
||||
let _ = std::fs::write(scythe::ipc::get_ipc_port_path(), p.to_string());
|
||||
let _ = std::fs::write(crate::ipc::get_ipc_port_path(), p.to_string());
|
||||
let _ = std::fs::write(std::env::temp_dir().join("vrec-ipc.port"), p.to_string());
|
||||
l
|
||||
}
|
||||
|
|
@ -230,7 +234,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
} else {
|
||||
0
|
||||
};
|
||||
let cfg = scythe::config::ScytheConfig::load();
|
||||
let cfg = crate::config::ScytheConfig::load();
|
||||
let status = DaemonStatus {
|
||||
is_recording: rec,
|
||||
recording_duration_sec: duration,
|
||||
|
|
@ -252,7 +256,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
}
|
||||
},
|
||||
Command::ToggleCursor => {
|
||||
let mut cfg = scythe::config::ScytheConfig::load();
|
||||
let mut cfg = crate::config::ScytheConfig::load();
|
||||
cfg.show_cursor = !cfg.show_cursor;
|
||||
let _ = cfg.save();
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
@ -275,7 +279,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
println!("Cursor display toggled to: {}", cfg.show_cursor);
|
||||
},
|
||||
Command::CycleAudioMode => {
|
||||
let mut cfg = scythe::config::ScytheConfig::load();
|
||||
let mut cfg = crate::config::ScytheConfig::load();
|
||||
cfg.audio_mode = match cfg.audio_mode.as_str() {
|
||||
"system" => "mic",
|
||||
"mic" => "both",
|
||||
|
|
@ -288,7 +292,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
},
|
||||
Command::StopDaemon => {
|
||||
println!("StopDaemon requested: Finalizing active recordings...");
|
||||
scythe::hyprland_binds::unregister_hyprland_binds(&scythe::config::ScytheConfig::load());
|
||||
crate::hyprland_binds::unregister_hyprland_binds(&crate::config::ScytheConfig::load());
|
||||
let _ = cmd_tx_ipc.send(Command::StopRecording);
|
||||
std::thread::sleep(std::time::Duration::from_millis(350));
|
||||
std::process::exit(0);
|
||||
|
|
@ -314,7 +318,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
println!("\nReceived shutdown signal. Finalizing recordings...");
|
||||
let _ = cmd_tx_sig.send(Command::StopRecording);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(350)).await;
|
||||
scythe::hyprland_binds::unregister_hyprland_binds(&scythe::config::ScytheConfig::load());
|
||||
crate::hyprland_binds::unregister_hyprland_binds(&crate::config::ScytheConfig::load());
|
||||
std::process::exit(0);
|
||||
}
|
||||
});
|
||||
|
|
@ -322,7 +326,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string());
|
||||
println!("Detected session type: {}", session_type);
|
||||
|
||||
let audio_capture_initial = scythe::capture::audio::AudioCapture::new_with_device_mode_volumes_and_levels(
|
||||
let audio_capture_initial = crate::capture::audio::AudioCapture::new_with_device_mode_volumes_and_levels(
|
||||
audio_tx.clone(),
|
||||
Some(&initial_config.audio_device),
|
||||
&initial_config.audio_mode,
|
||||
|
|
@ -335,12 +339,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
|
||||
if std::env::args().any(|a| a == "--mock") {
|
||||
println!("Initializing MOCK capture...");
|
||||
source_opt = Some(Box::new(scythe::capture::mock::MockCapture::new()));
|
||||
source_opt = Some(Box::new(crate::capture::mock::MockCapture::new()));
|
||||
} else if cfg!(target_os = "windows") {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
println!("Initializing Windows DXGI capture...");
|
||||
if let Ok(c) = scythe::capture::windows::WindowsCapture::new() {
|
||||
if let Ok(c) = crate::capture::windows::WindowsCapture::new() {
|
||||
source_opt = Some(Box::new(c));
|
||||
}
|
||||
}
|
||||
|
|
@ -354,7 +358,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
.args(["--user", "start", "xdg-desktop-portal-hyprland"])
|
||||
.status();
|
||||
|
||||
match scythe::capture::wayland::WaylandCapture::new_with_cursor(initial_config.show_cursor).await {
|
||||
match crate::capture::wayland::WaylandCapture::new_with_cursor(initial_config.show_cursor).await {
|
||||
Ok(cap) => {
|
||||
source_opt = Some(Box::new(cap));
|
||||
break;
|
||||
|
|
@ -375,7 +379,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
println!("Initializing X11 capture...");
|
||||
if let Ok(c) = scythe::capture::x11::X11Capture::new() {
|
||||
if let Ok(c) = crate::capture::x11::X11Capture::new() {
|
||||
source_opt = Some(Box::new(c));
|
||||
}
|
||||
}
|
||||
|
|
@ -387,7 +391,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
None => {
|
||||
if is_mock_cli {
|
||||
eprintln!("Warning: Mock capture mode active via --mock flag.");
|
||||
Box::new(scythe::capture::mock::MockCapture::new())
|
||||
Box::new(crate::capture::mock::MockCapture::new())
|
||||
} else {
|
||||
eprintln!("Error: Hardware capture failed to initialize on {} session. Exiting for restart.", session_type);
|
||||
std::process::exit(1);
|
||||
|
|
@ -455,7 +459,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
let mut audio_capture = audio_capture_initial.map(|(c, _, _)| c);
|
||||
|
||||
let recorder_handle = thread::spawn(move || {
|
||||
let mut config = scythe::config::ScytheConfig::load();
|
||||
let mut config = crate::config::ScytheConfig::load();
|
||||
replay_state_clone.store(config.replay_enabled, Ordering::SeqCst);
|
||||
|
||||
let active_bitrate = if config.replay_enabled {
|
||||
|
|
@ -477,9 +481,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
println!("Background recorder engine active and ready ({} fps, replay: {}).", config.fps, config.replay_enabled);
|
||||
|
||||
let mut audio_encoder = if let Some((sr, ch)) = audio_info {
|
||||
scythe::encoder::AudioEncoder::new_with_codec(sr as i32, ch as i32, &config.audio_codec).ok()
|
||||
crate::encoder::AudioEncoder::new_with_codec(sr as i32, ch as i32, &config.audio_codec).ok()
|
||||
} else {
|
||||
scythe::encoder::AudioEncoder::new_with_codec(48000, 2, &config.audio_codec).ok()
|
||||
crate::encoder::AudioEncoder::new_with_codec(48000, 2, &config.audio_codec).ok()
|
||||
};
|
||||
let audio_codec_ctx_ptr = audio_encoder.as_ref().map(|e| e.codec_ctx() as usize);
|
||||
|
||||
|
|
@ -511,8 +515,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
|
||||
thread::spawn(move || {
|
||||
while let Ok(drain) = mux_rx.recv() {
|
||||
let filename = scythe::config::ScytheConfig::format_video_filename("Replay", "mp4");
|
||||
let full_path = scythe::config::ScytheConfig::load().resolve_save_path(&filename);
|
||||
let filename = crate::config::ScytheConfig::format_video_filename("Replay", "mp4");
|
||||
let full_path = crate::config::ScytheConfig::load().resolve_save_path(&filename);
|
||||
println!("Saving replay to {}...", full_path);
|
||||
|
||||
// Find the first video IDR keyframe (ignore audio packets which always have keyframe flag set)
|
||||
|
|
@ -639,7 +643,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
rec_base_video_pts = pkt.pts();
|
||||
rec_base_audio_pts = -1;
|
||||
let codec_ctx = codec_ctx_ptr as *mut ffmpeg_next::ffi::AVCodecContext;
|
||||
let filename = scythe::config::ScytheConfig::format_video_filename("Recording", "mp4");
|
||||
let filename = crate::config::ScytheConfig::format_video_filename("Recording", "mp4");
|
||||
let full_path = config.resolve_save_path(&filename);
|
||||
let audio_codec_ctx = audio_codec_ctx_ptr.map(|p| p as *mut ffmpeg_next::ffi::AVCodecContext);
|
||||
match unsafe { Muxer::new(&full_path, codec_ctx, audio_codec_ctx) } {
|
||||
|
|
@ -696,7 +700,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
if let Ok(cmd) = cmd_res {
|
||||
match cmd {
|
||||
Command::ReloadConfig => {
|
||||
let new_config = scythe::config::ScytheConfig::load();
|
||||
let new_config = crate::config::ScytheConfig::load();
|
||||
let video_changed = new_config.show_cursor != config.show_cursor
|
||||
|| new_config.fps != config.fps
|
||||
|| new_config.video_codec != config.video_codec
|
||||
|
|
@ -754,8 +758,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
#[cfg(target_os = "linux")]
|
||||
unsafe { libc::malloc_trim(0); }
|
||||
}
|
||||
scythe::hyprland_binds::register_hyprland_binds(&config);
|
||||
audio_capture = match scythe::capture::audio::AudioCapture::new_with_device_mode_volumes_and_levels(
|
||||
crate::hyprland_binds::register_hyprland_binds(&config);
|
||||
audio_capture = match crate::capture::audio::AudioCapture::new_with_device_mode_volumes_and_levels(
|
||||
audio_tx_clone.clone(),
|
||||
Some(&config.audio_device),
|
||||
&config.audio_mode,
|
||||
|
|
@ -856,7 +860,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
println!("Audio mute toggled: {}", !cur);
|
||||
},
|
||||
Command::StartStreaming => {
|
||||
let cfg = scythe::config::ScytheConfig::load();
|
||||
let cfg = crate::config::ScytheConfig::load();
|
||||
let key = cfg.stream_key.trim();
|
||||
if key.is_empty() {
|
||||
eprintln!("Cannot start livestream: stream key is empty in config.");
|
||||
|
|
@ -903,7 +907,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
stream_start_clone.store(0, Ordering::SeqCst);
|
||||
println!("ToggleStreaming: Livestream stopped.");
|
||||
} else {
|
||||
let cfg = scythe::config::ScytheConfig::load();
|
||||
let cfg = crate::config::ScytheConfig::load();
|
||||
let key = cfg.stream_key.trim();
|
||||
if key.is_empty() {
|
||||
eprintln!("Cannot start livestream: stream key is empty in config.");
|
||||
|
|
@ -1162,6 +1166,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
|
||||
let _ = recorder_handle.join();
|
||||
#[cfg(windows)]
|
||||
let _ = std::fs::remove_file(scythe::ipc::get_ipc_port_path());
|
||||
let _ = std::fs::remove_file(crate::ipc::get_ipc_port_path());
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -11,3 +11,4 @@ pub mod config;
|
|||
pub mod overlay_egui;
|
||||
pub mod hyprland_binds;
|
||||
pub mod updater;
|
||||
pub mod daemon;
|
||||
|
|
|
|||
|
|
@ -414,7 +414,24 @@ fn handle_toggle_streaming() -> Result<(), Box<dyn std::error::Error + Send + Sy
|
|||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let exe_name = env::current_exe()
|
||||
.ok()
|
||||
.and_then(|p| p.file_stem().map(|s| s.to_string_lossy().to_string()))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Multicall dispatch: scythe-daemon executable, symlink, or subcommand
|
||||
let is_daemon = exe_name == "scythe-daemon"
|
||||
|| exe_name == "vrec-daemon"
|
||||
|| (args.len() > 1 && (args[1] == "daemon" || args[1] == "--daemon" || args[1] == "--mock"));
|
||||
|
||||
if is_daemon {
|
||||
return scythe::daemon::run_daemon();
|
||||
}
|
||||
|
||||
run_client(args)
|
||||
}
|
||||
|
||||
fn run_client(args: Vec<String>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
#[cfg(target_os = "windows")]
|
||||
unsafe {
|
||||
let is_cli = args.iter().any(|a| a == "--status" || a == "--help" || a == "-h");
|
||||
|
|
@ -428,7 +445,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
|
||||
if args.len() > 1 {
|
||||
match args[1].as_str() {
|
||||
"--save" | "--save-replay" => {
|
||||
"--save" | "--save-replay" | "save" => {
|
||||
return handle_save_replay();
|
||||
}
|
||||
"--notify-save" => {
|
||||
|
|
@ -443,7 +460,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
show_shadowplay_toast("RECORDING", "Recording saved", ToastIcon::Save);
|
||||
return Ok(());
|
||||
}
|
||||
"--screenshot" => {
|
||||
"--screenshot" | "screenshot" => {
|
||||
return handle_screenshot();
|
||||
}
|
||||
"--mute-mic" => {
|
||||
|
|
@ -462,7 +479,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
scythe::overlay_egui::run_egui_toast(title, subtitle, ToastIcon::from_name(icon_name));
|
||||
return Ok(());
|
||||
}
|
||||
"--menu" => {
|
||||
"--menu" | "menu" => {
|
||||
ensure_daemon_running_async();
|
||||
ensure_hotkeys_running();
|
||||
if check_and_toggle_overlay() {
|
||||
|
|
@ -492,7 +509,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
"--hotkeys" | "--background" => {
|
||||
// fall through to hotkey loop
|
||||
}
|
||||
"--record" | "--toggle" => {
|
||||
"--record" | "--toggle" | "record" => {
|
||||
return handle_toggle_recording();
|
||||
}
|
||||
"--stream" | "--toggle-stream" => {
|
||||
|
|
@ -511,7 +528,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
ensure_daemon_running();
|
||||
return send_command(Command::ReloadConfig);
|
||||
}
|
||||
"--status" => {
|
||||
"--status" | "status" => {
|
||||
ensure_daemon_running();
|
||||
match query_status() {
|
||||
Ok(st) => {
|
||||
|
|
@ -531,7 +548,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
}
|
||||
}
|
||||
}
|
||||
"--quit" => {
|
||||
"--quit" | "quit" => {
|
||||
return send_command(Command::StopDaemon);
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
|
|
@ -570,26 +570,14 @@ fn pick_folder(current_dir: &str, tx: Sender<String>, is_active: Arc<AtomicBool>
|
|||
}
|
||||
|
||||
fn spawn_daemon_process() {
|
||||
let mut cmd = if let Ok(mut path) = std::env::current_exe() {
|
||||
path.pop();
|
||||
#[cfg(target_os = "windows")]
|
||||
let primary = path.join("scythe-daemon.exe");
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let primary = path.join("scythe-daemon");
|
||||
|
||||
if primary.exists() {
|
||||
std::process::Command::new(primary)
|
||||
} else {
|
||||
#[cfg(target_os = "windows")]
|
||||
{ std::process::Command::new("scythe-daemon.exe") }
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{ std::process::Command::new("scythe-daemon") }
|
||||
}
|
||||
let mut cmd = if let Ok(self_exe) = std::env::current_exe() {
|
||||
let mut c = std::process::Command::new(self_exe);
|
||||
c.arg("daemon");
|
||||
c
|
||||
} else {
|
||||
#[cfg(target_os = "windows")]
|
||||
{ std::process::Command::new("scythe-daemon.exe") }
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{ std::process::Command::new("scythe-daemon") }
|
||||
let mut c = std::process::Command::new("scythe");
|
||||
c.arg("daemon");
|
||||
c
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ if systemctl --user is-active --quiet scythe-daemon.service 2>/dev/null; then
|
|||
sleep 0.5
|
||||
fi
|
||||
|
||||
echo "[2/6] Starting scythe-daemon with mock capture..."
|
||||
target/release/scythe-daemon --mock > daemon_test.log 2>&1 &
|
||||
echo "[2/6] Starting scythe daemon with mock capture..."
|
||||
target/release/scythe daemon --mock > daemon_test.log 2>&1 &
|
||||
DAEMON_PID=$!
|
||||
|
||||
cleanup() {
|
||||
if kill -0 $DAEMON_PID 2>/dev/null; then
|
||||
target/release/scythe-ui --quit 2>/dev/null || kill -9 $DAEMON_PID 2>/dev/null || true
|
||||
target/release/scythe --quit 2>/dev/null || kill -9 $DAEMON_PID 2>/dev/null || true
|
||||
fi
|
||||
rm -f daemon_test.log
|
||||
if [ "$WAS_SERVICE_ACTIVE" -eq 1 ]; then
|
||||
|
|
@ -56,23 +56,23 @@ echo "Daemon is live (PID: $DAEMON_PID, Socket: $SOCKET_PATH)"
|
|||
|
||||
# Test 1: Start Normal Recording
|
||||
echo "[3/6] Testing: Start Normal Recording..."
|
||||
target/release/scythe-ui --start
|
||||
target/release/scythe --start
|
||||
echo "Recording in progress for 3 seconds..."
|
||||
sleep 3
|
||||
|
||||
# Test 2: Stop Normal Recording
|
||||
echo "[4/6] Testing: Stop Normal Recording..."
|
||||
target/release/scythe-ui --stop
|
||||
target/release/scythe --stop
|
||||
sleep 1
|
||||
|
||||
# Test 3: Save Instant Replay
|
||||
echo "[5/6] Testing: Save Instant Replay buffer..."
|
||||
target/release/scythe-ui --save
|
||||
target/release/scythe --save
|
||||
sleep 2
|
||||
|
||||
# Test 4: Quit Daemon
|
||||
echo "[6/6] Testing: Clean Daemon Shutdown..."
|
||||
target/release/scythe-ui --quit
|
||||
target/release/scythe --quit
|
||||
wait $DAEMON_PID 2>/dev/null || true
|
||||
echo "Daemon shutdown cleanly."
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue