#![cfg_attr(target_os = "windows", windows_subsystem = "windows")] #![allow(clippy::chunks_exact_to_as_chunks)] use scythe::ipc::{Command, send_command, query_status}; use scythe::overlay::{show_shadowplay_toast, ToastIcon}; use std::env; use global_hotkey::{GlobalHotKeyManager, hotkey::{HotKey, Modifiers, Code}, GlobalHotKeyEvent, HotKeyState}; fn ensure_wayland_env() { scythe::overlay::ensure_wayland_env(); } fn get_daemon_cmd() -> std::process::Command { #[allow(unused_mut)] 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(target_os = "windows")] let fallback = path.join("vrec-daemon.exe"); #[cfg(not(target_os = "windows"))] let primary = path.join("scythe-daemon"); #[cfg(not(target_os = "windows"))] let fallback = path.join("vrec-daemon"); if primary.exists() { let mut c = std::process::Command::new(primary); c.current_dir(&path); c } else if fallback.exists() { let mut c = std::process::Command::new(fallback); c.current_dir(&path); c } else { #[cfg(target_os = "windows")] { std::process::Command::new("scythe-daemon.exe") } #[cfg(not(target_os = "windows"))] { std::process::Command::new("scythe-daemon") } } } else { #[cfg(target_os = "windows")] { std::process::Command::new("scythe-daemon.exe") } #[cfg(not(target_os = "windows"))] { std::process::Command::new("scythe-daemon") } }; #[cfg(target_os = "windows")] { use std::os::windows::process::CommandExt; cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW } cmd } fn get_ui_cmd() -> std::process::Command { #[allow(unused_mut)] let mut cmd = if let Ok(path) = std::env::current_exe() { let mut c = std::process::Command::new(&path); if let Some(parent) = path.parent() { c.current_dir(parent); } c } else { #[cfg(target_os = "windows")] { std::process::Command::new("scythe-ui.exe") } #[cfg(not(target_os = "windows"))] { std::process::Command::new("scythe-ui") } }; #[cfg(target_os = "windows")] { use std::os::windows::process::CommandExt; cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW } cmd } fn check_and_toggle_overlay() -> bool { let pid_path = scythe::ipc::get_overlay_pid_path(); if pid_path.exists() { if let Ok(metadata) = std::fs::metadata(&pid_path) && let Ok(modified) = metadata.modified() && let Ok(elapsed) = modified.elapsed() { // Debounce: if the overlay was spawned less than 400ms ago, this is a // duplicate event from concurrent triggers. Do not kill it; simply exit. if elapsed < std::time::Duration::from_millis(400) { return true; } } if let Ok(content) = std::fs::read_to_string(&pid_path) { #[cfg(unix)] if let Ok(pid) = content.trim().parse::() { let exists = unsafe { libc::kill(pid, 0) == 0 }; if exists && pid != std::process::id() as i32 { let proc_cmd = format!("/proc/{}/cmdline", pid); let is_scythe = std::fs::read_to_string(&proc_cmd) .map(|cmd| cmd.contains("scythe") || cmd.contains("vrec")) .unwrap_or(false); if is_scythe { // Overlay already open: toggle it closed instantly! unsafe { libc::kill(pid, libc::SIGTERM) }; let _ = std::fs::remove_file(&pid_path); return true; } } } #[cfg(windows)] if let Ok(pid) = content.trim().parse::() { if pid != std::process::id() { unsafe { use windows::Win32::UI::WindowsAndMessaging::{ EnumWindows, GetWindowThreadProcessId, PostMessageW, WM_CLOSE, }; use windows::Win32::Foundation::{BOOL, HWND, LPARAM, CloseHandle}; use windows::Win32::System::Threading::{ OpenProcess, TerminateProcess, PROCESS_TERMINATE, PROCESS_SYNCHRONIZE, WaitForSingleObject, }; unsafe extern "system" fn close_proc(hwnd: HWND, lparam: LPARAM) -> BOOL { let target = lparam.0 as u32; let mut pid = 0u32; GetWindowThreadProcessId(hwnd, Some(&mut pid)); if pid == target { let _ = PostMessageW(hwnd, WM_CLOSE, None, None); } BOOL(1) } let _ = EnumWindows(Some(close_proc), LPARAM(pid as isize)); // Wait up to 150ms for graceful shutdown before fallback termination if let Ok(h) = OpenProcess(PROCESS_SYNCHRONIZE | PROCESS_TERMINATE, false, pid) { let wait_res = WaitForSingleObject(h, 150); if wait_res.0 != 0 { let _ = TerminateProcess(h, 0); } let _ = CloseHandle(h); } let _ = std::fs::remove_file(&pid_path); return true; } } } } let _ = std::fs::remove_file(&pid_path); } let my_pid = std::process::id().to_string(); let _ = std::fs::write(&pid_path, my_pid); false } fn ensure_daemon_running_async() { #[cfg(unix)] { use std::os::unix::net::UnixStream; if UnixStream::connect(scythe::ipc::get_socket_path()).is_ok() || UnixStream::connect(scythe::ipc::get_legacy_socket_path()).is_ok() { return; } if scythe::hyprland_binds::is_hyprland() { let _ = std::process::Command::new("systemctl") .args(["--user", "start", "xdg-desktop-portal-hyprland"]) .status(); } } #[cfg(target_os = "windows")] { if scythe::ipc::is_daemon_running() { return; } } let _ = get_daemon_cmd() .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .spawn(); } fn ensure_daemon_running() { if query_status().is_err() { #[cfg(target_os = "linux")] { if scythe::hyprland_binds::is_hyprland() { let _ = std::process::Command::new("systemctl") .args(["--user", "start", "xdg-desktop-portal-hyprland"]) .status(); } } let _ = get_daemon_cmd() .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .spawn(); for _ in 0..25 { std::thread::sleep(std::time::Duration::from_millis(100)); if query_status().is_ok() { break; } } } } fn ensure_hotkeys_running() { #[cfg(target_os = "windows")] { unsafe { use windows::Win32::Foundation::CloseHandle; use windows::Win32::System::Threading::{OpenMutexW, SYNCHRONIZATION_ACCESS_RIGHTS}; if let Ok(handle) = OpenMutexW(SYNCHRONIZATION_ACCESS_RIGHTS(0x00100000), false, windows::core::w!("Local\\scythe_hotkeys_single_instance")) { let _ = CloseHandle(handle); return; } } let _ = get_ui_cmd() .arg("--hotkeys") .stdin(std::process::Stdio::null()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .spawn(); } } fn send_with_notification(cmd: Command, title: &str, subtitle: &str, icon: ToastIcon) -> Result<(), Box> { ensure_daemon_running(); match send_command(cmd) { Ok(()) => { show_shadowplay_toast(title, subtitle, icon); Ok(()) } Err(e) => { show_shadowplay_toast("SCYTHE", "Error: failed to connect to daemon", ToastIcon::Error); Err(e) } } } fn handle_toggle_recording() -> Result<(), Box> { ensure_daemon_running(); let status = query_status().ok(); let is_rec = status.as_ref().map(|s| s.is_recording).unwrap_or(false); match send_command(Command::ToggleRecording) { Ok(()) => { if is_rec { show_shadowplay_toast("RECORDING", "Recording saved", ToastIcon::Save); } else { show_shadowplay_toast("RECORDING", "Recording started", ToastIcon::Record); } Ok(()) } Err(e) => { show_shadowplay_toast("SCYTHE", "Error: failed to connect to daemon", ToastIcon::Error); Err(e) } } } fn handle_toggle_cursor() -> Result<(), Box> { ensure_daemon_running(); let current_cursor = query_status().ok().map(|s| s.show_cursor).unwrap_or(true); let next_cursor = !current_cursor; match send_command(Command::ToggleCursor) { Ok(()) => { if next_cursor { show_shadowplay_toast("MOUSE CURSOR", "Visible in recording", ToastIcon::Cursor); } else { show_shadowplay_toast("MOUSE CURSOR", "Hidden from recording", ToastIcon::Cursor); } Ok(()) } Err(e) => { show_shadowplay_toast("SCYTHE", "Error: failed to connect to daemon", ToastIcon::Error); Err(e) } } } fn handle_save_replay() -> Result<(), Box> { ensure_daemon_running(); if let Ok(st) = query_status() && !st.is_replay_active { show_shadowplay_toast("INSTANT REPLAY", "Replay is turned off", ToastIcon::Error); return Ok(()); } send_with_notification(Command::SaveReplay, "INSTANT REPLAY", "Saved to Videos", ToastIcon::Replay) } fn handle_screenshot() -> Result<(), Box> { let cfg = scythe::config::ScytheConfig::load(); let dir = scythe::config::ScytheConfig::expand_tilde(&cfg.screenshot_directory); let _ = std::fs::create_dir_all(&dir); let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_secs(); let ext = if cfg.screenshot_format.to_lowercase() == "jpg" || cfg.screenshot_format.to_lowercase() == "jpeg" { "jpg" } else { "png" }; let filename = format!("Screenshot_{}.{}", now, ext); let out_path = dir.join(&filename); #[cfg(target_os = "linux")] { let mut grim = std::process::Command::new("grim"); if cfg.screenshot_cursor { grim.arg("-c"); } if ext == "jpg" { grim.args(["-t", "jpeg", "-q", "90"]); } grim.arg(&out_path); let res = grim.status(); if res.is_ok() && out_path.exists() { show_shadowplay_toast("SCREENSHOT", &format!("Saved to {}", filename), ToastIcon::Screenshot); return Ok(()); } let _ = std::process::Command::new("import").args(["-window", "root"]).arg(&out_path).status(); } #[cfg(target_os = "windows")] { let ps_cmd = format!( "Add-Type -AssemblyName System.Windows.Forms,System.Drawing; \ $b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; \ $bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height; \ $g = [System.Drawing.Graphics]::FromImage($bmp); \ $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size); \ $bmp.Save('{}'); $g.Dispose(); $bmp.Dispose()", out_path.to_string_lossy().replace('/', "\\") ); let _ = std::process::Command::new("powershell") .args(["-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", &ps_cmd]) .status(); } show_shadowplay_toast("SCREENSHOT", &format!("Saved to {}", filename), ToastIcon::Screenshot); Ok(()) } fn handle_mute_mic() -> Result<(), Box> { let mut cfg = scythe::config::ScytheConfig::load(); let is_muted = cfg.mic_volume < 0.05; cfg.mic_volume = if is_muted { 0.60 } else { 0.0 }; let _ = cfg.save(); scythe::config::ScytheConfig::notify_daemon_reload(); show_shadowplay_toast("MICROPHONE", if is_muted { "Unmuted" } else { "Muted" }, ToastIcon::Info); Ok(()) } fn handle_mute_sys() -> Result<(), Box> { let mut cfg = scythe::config::ScytheConfig::load(); let is_muted = cfg.system_volume < 0.05; cfg.system_volume = if is_muted { 1.00 } else { 0.0 }; let _ = cfg.save(); scythe::config::ScytheConfig::notify_daemon_reload(); show_shadowplay_toast("SYSTEM AUDIO", if is_muted { "Unmuted" } else { "Muted" }, ToastIcon::Info); Ok(()) } fn handle_toggle_streaming() -> Result<(), Box> { ensure_daemon_running(); let cfg = scythe::config::ScytheConfig::load(); if cfg.stream_key.trim().is_empty() { show_shadowplay_toast("LIVESTREAM", "Stream key is empty. Configure in Settings.", ToastIcon::Error); return Ok(()); } let status = query_status().ok(); let is_streaming = status.as_ref().map(|s| s.is_streaming).unwrap_or(false); match send_command(Command::ToggleStreaming) { Ok(()) => { if is_streaming { show_shadowplay_toast("LIVESTREAM", "Stream stopped", ToastIcon::Info); } else { show_shadowplay_toast("LIVESTREAM", "Livestream started", ToastIcon::Record); } Ok(()) } Err(e) => { show_shadowplay_toast("SCYTHE", "Error: failed to connect to daemon", ToastIcon::Error); Err(e) } } } fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); #[cfg(target_os = "windows")] unsafe { let is_cli = args.iter().any(|a| a == "--status" || a == "--help" || a == "-h"); if !is_cli { use windows::Win32::System::Console::FreeConsole; let _ = FreeConsole(); } } ensure_wayland_env(); if args.len() > 1 { match args[1].as_str() { "--save" | "--save-replay" => { return handle_save_replay(); } "--notify-save" => { show_shadowplay_toast("INSTANT REPLAY", "Saved to Videos", ToastIcon::Replay); return Ok(()); } "--notify-start" => { show_shadowplay_toast("RECORDING", "Recording started", ToastIcon::Record); return Ok(()); } "--notify-stop" => { show_shadowplay_toast("RECORDING", "Recording saved", ToastIcon::Save); return Ok(()); } "--screenshot" => { return handle_screenshot(); } "--mute-mic" => { return handle_mute_mic(); } "--mute-sys" => { return handle_mute_sys(); } "--pause" => { return handle_toggle_recording(); } "--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(""); let icon_name = args.get(4).map(|s| s.as_str()).unwrap_or("info"); scythe::overlay_egui::run_egui_toast(title, subtitle, ToastIcon::from_name(icon_name)); return Ok(()); } "--menu" => { ensure_daemon_running_async(); ensure_hotkeys_running(); if check_and_toggle_overlay() { return Ok(()); } scythe::overlay_egui::run_egui_overlay(); return Ok(()); } "--egui" => { ensure_daemon_running_async(); ensure_hotkeys_running(); if check_and_toggle_overlay() { return Ok(()); } scythe::overlay_egui::run_egui_overlay(); return Ok(()); } "--gtk" => { ensure_daemon_running(); ensure_hotkeys_running(); #[cfg(target_os = "linux")] scythe::overlay::show_menu_overlay(); #[cfg(not(target_os = "linux"))] scythe::overlay_egui::run_egui_overlay(); return Ok(()); } "--hotkeys" | "--background" => { // fall through to hotkey loop } "--record" | "--toggle" => { return handle_toggle_recording(); } "--stream" | "--toggle-stream" => { return handle_toggle_streaming(); } "--cursor" | "--toggle-cursor" => { return handle_toggle_cursor(); } "--start" => { return send_with_notification(Command::StartRecording, "RECORDING", "Recording started", ToastIcon::Record); } "--stop" => { return send_with_notification(Command::StopRecording, "RECORDING", "Recording saved", ToastIcon::Save); } "--reload" => { ensure_daemon_running(); return send_command(Command::ReloadConfig); } "--status" => { ensure_daemon_running(); match query_status() { Ok(st) => { println!("Daemon Status:"); println!(" State: {}", if st.is_recording { format!("RECORDING ({}s)", st.recording_duration_sec) } else { "IDLE".to_string() }); println!(" Livestream: {}", if st.is_streaming { format!("STREAMING ({}s)", st.streaming_duration_sec) } else { "OFF".to_string() }); println!(" Instant Replay: {}", if st.is_replay_active { "ACTIVE" } else { "OFF" }); println!(" Audio Mode: {}", st.audio_mode); println!(" Mic Volume: {:.0}%", st.mic_volume * 100.0); println!(" System Volume: {:.0}%", st.system_volume * 100.0); println!(" Show Cursor: {}", st.show_cursor); return Ok(()); } Err(e) => { eprintln!("Failed to connect to scythe daemon: {}", e); std::process::exit(1); } } } "--quit" => { return send_command(Command::StopDaemon); } "--help" | "-h" => { println!("scythe-ui - UI Overlay and Hotkey listener for scythe"); println!("Usage:"); println!(" scythe-ui Open the interactive overlay UI menu (default)"); println!(" scythe-ui --menu Open the interactive overlay UI menu"); println!(" scythe-ui --gtk Open legacy GTK layer-shell overlay (Linux)"); println!(" scythe-ui --hotkeys Run global hotkey manager in background"); println!(" scythe-ui --status Query current daemon status"); println!(" scythe-ui --save Save instant replay and show notification"); println!(" scythe-ui --save-replay Alias for --save"); println!(" scythe-ui --notify-save Show instant replay saved notification toast"); println!(" scythe-ui --notify-start Show recording started notification toast"); println!(" scythe-ui --notify-stop Show recording saved notification toast"); println!(" scythe-ui --toast Show custom ShadowPlay notification toast"); println!(" scythe-ui --record Toggle normal recording 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 --start Start normal recording"); println!(" scythe-ui --stop Stop normal recording"); println!(" scythe-ui --reload Reload daemon configuration"); println!(" scythe-ui --quit Stop background daemon"); return Ok(()); } _ => {} } } else { ensure_daemon_running_async(); ensure_hotkeys_running(); if check_and_toggle_overlay() { return Ok(()); } scythe::overlay_egui::run_egui_overlay(); return Ok(()); } #[cfg(target_os = "windows")] let _mutex = unsafe { use windows::Win32::Foundation::{GetLastError, WIN32_ERROR}; use windows::Win32::System::Threading::CreateMutexW; let handle = CreateMutexW(None, true, windows::core::w!("Local\\scythe_hotkeys_single_instance")); if GetLastError() == WIN32_ERROR(183) { return Ok(()); } handle.ok() }; println!("Starting scythe UI/Hotkey process..."); 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(); let is_gnome = desktop.contains("gnome") || desktop.contains("ubuntu"); let is_kde = desktop.contains("kde"); if session_type.to_lowercase() == "wayland" { if is_gnome || is_kde { println!("Note: Wayland native layer shell used. Ensure your compositor allows it."); println!("Tip: Global hotkeys may be restricted by Wayland compositors."); println!(" You can bind `scythe-ui --menu` or `scythe-ui --save` in your system shortcut settings."); } else { println!("wlroots/Hyprland compositor detected."); scythe::hyprland_binds::register_hyprland_binds(&scythe::config::ScytheConfig::load()); scythe::hyprland_binds::spawn_hyprland_reload_watcher(); } } else { println!("X11 session detected."); } let manager = match GlobalHotKeyManager::new() { Ok(m) => m, Err(e) => { eprintln!("Failed to create global hotkey manager: {}", e); return Ok(()); } }; #[derive(Default)] struct RegisteredIds { menu: Option, save: Option, rec: Option, pause: Option, cur: Option, shot: Option, mute_mic: Option, mute_sys: Option, } let mut current_config = scythe::config::ScytheConfig::load(); let mut registered_hotkeys: Vec = Vec::new(); let register_all = |mgr: &GlobalHotKeyManager, cfg: &scythe::config::ScytheConfig, reg: &mut Vec| -> RegisteredIds { for hk in reg.drain(..) { let _ = mgr.unregister(hk); } let is_on_hyprland = scythe::hyprland_binds::is_hyprland(); let m_menu = scythe::hotkey::parse_hotkey(&cfg.menu_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::ALT), Code::KeyZ))); let m_save = scythe::hotkey::parse_hotkey(&cfg.save_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR))); let m_rec = scythe::hotkey::parse_hotkey(&cfg.record_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::F9))); let m_pause = scythe::hotkey::parse_hotkey(&cfg.pause_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::F8))); let m_cur = scythe::hotkey::parse_hotkey(&cfg.cursor_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::F10))); let m_shot = scythe::hotkey::parse_hotkey(&cfg.screenshot_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyS))); let m_mute_mic = scythe::hotkey::parse_hotkey(&cfg.mute_mic_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyM))); let m_mute_sys = scythe::hotkey::parse_hotkey(&cfg.mute_sys_hotkey).or_else(|| Some(HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyN))); let mut ids = RegisteredIds::default(); if !is_on_hyprland && let Some(hk) = m_menu { ids.menu = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_save { ids.save = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_rec { ids.rec = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_pause { ids.pause = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_cur { ids.cur = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_shot { ids.shot = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_mute_mic { ids.mute_mic = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } if let Some(hk) = m_mute_sys { ids.mute_sys = Some(hk.id()); let _ = mgr.register(hk); reg.push(hk); } ids }; let mut current_ids = register_all(&manager, ¤t_config, &mut registered_hotkeys); println!("Listening for global hotkeys (Overlay: {}, Replay: {}, Record: {}, Screenshot: {})...", current_config.menu_hotkey, current_config.save_hotkey, current_config.record_hotkey, current_config.screenshot_hotkey); let receiver = GlobalHotKeyEvent::receiver(); let mut last_config_check = std::time::Instant::now(); loop { #[cfg(target_os = "windows")] unsafe { use windows::Win32::UI::WindowsAndMessaging::{ PeekMessageW, TranslateMessage, DispatchMessageW, MSG, PM_REMOVE, }; let mut msg = MSG::default(); while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() { let _ = TranslateMessage(&msg); DispatchMessageW(&msg); } } if last_config_check.elapsed() > std::time::Duration::from_millis(800) { last_config_check = std::time::Instant::now(); let new_cfg = scythe::config::ScytheConfig::load(); if new_cfg.menu_hotkey != current_config.menu_hotkey || new_cfg.save_hotkey != current_config.save_hotkey || new_cfg.record_hotkey != current_config.record_hotkey || new_cfg.pause_hotkey != current_config.pause_hotkey || new_cfg.cursor_hotkey != current_config.cursor_hotkey || new_cfg.screenshot_hotkey != current_config.screenshot_hotkey || new_cfg.mute_mic_hotkey != current_config.mute_mic_hotkey || new_cfg.mute_sys_hotkey != current_config.mute_sys_hotkey { current_config = new_cfg; current_ids = register_all(&manager, ¤t_config, &mut registered_hotkeys); } } while let Ok(event) = receiver.try_recv() { if event.state != HotKeyState::Pressed { continue; } if Some(event.id) == current_ids.menu { std::thread::spawn(|| { ensure_daemon_running(); let _ = get_ui_cmd().arg("--menu").spawn(); }); } else if Some(event.id) == current_ids.save { std::thread::spawn(|| { let _ = handle_save_replay(); }); } else if Some(event.id) == current_ids.rec { std::thread::spawn(|| { let _ = handle_toggle_recording(); }); } else if Some(event.id) == current_ids.pause { std::thread::spawn(|| { let _ = handle_toggle_recording(); }); } else if Some(event.id) == current_ids.cur { std::thread::spawn(|| { let _ = handle_toggle_cursor(); }); } else if Some(event.id) == current_ids.shot { std::thread::spawn(|| { let _ = handle_screenshot(); }); } else if Some(event.id) == current_ids.mute_mic { std::thread::spawn(|| { let _ = handle_mute_mic(); }); } else if Some(event.id) == current_ids.mute_sys { std::thread::spawn(|| { let _ = handle_mute_sys(); }); } } #[cfg(target_os = "windows")] unsafe { use windows::Win32::UI::WindowsAndMessaging::{ MsgWaitForMultipleObjectsEx, QS_ALLINPUT, MWMO_INPUTAVAILABLE, }; let _ = MsgWaitForMultipleObjectsEx(None, 200, QS_ALLINPUT.0, MWMO_INPUTAVAILABLE); } #[cfg(not(target_os = "windows"))] std::thread::sleep(std::time::Duration::from_millis(30)); } }