scythe/src/ipc.rs

197 lines
6.2 KiB
Rust

use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum Command {
SaveReplay,
StartRecording,
StopRecording,
ToggleRecording,
ToggleAudio,
CycleAudioMode,
ToggleCursor,
ReloadConfig,
StopDaemon,
ShowOverlay,
GetStatus,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct DaemonStatus {
pub is_recording: bool,
pub recording_duration_sec: u64,
pub is_replay_active: bool,
pub audio_muted: bool,
#[serde(default = "default_audio_mode_str")]
pub audio_mode: String,
#[serde(default = "default_true")]
pub show_cursor: bool,
#[serde(default = "default_mic_volume")]
pub mic_volume: f32,
#[serde(default = "default_system_volume")]
pub system_volume: f32,
#[serde(default)]
pub mic_level_peak: f32,
#[serde(default)]
pub system_level_peak: f32,
}
fn default_true() -> bool {
true
}
fn default_mic_volume() -> f32 {
0.60
}
fn default_system_volume() -> f32 {
1.00
}
fn default_audio_mode_str() -> String {
"system".to_string()
}
#[cfg(unix)]
pub fn get_socket_path() -> String {
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
.unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() }));
format!("{}/scythe.sock", runtime_dir)
}
#[cfg(unix)]
pub fn get_legacy_socket_path() -> String {
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
.unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() }));
format!("{}/vrec.sock", runtime_dir)
}
pub fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use std::io::Write;
use std::time::Duration;
let payload = serde_json::to_vec(&cmd)?;
let len_buf = (payload.len() as u32).to_le_bytes();
let mut last_err = None;
for attempt in 0..3 {
if attempt > 0 {
std::thread::sleep(Duration::from_millis(60));
}
#[cfg(unix)]
{
use std::os::unix::net::UnixStream;
let socket_path = get_socket_path();
let connect_res = UnixStream::connect(&socket_path)
.or_else(|_| UnixStream::connect(get_legacy_socket_path()));
match connect_res {
Ok(mut stream) => {
let _ = stream.set_write_timeout(Some(Duration::from_millis(1000)));
let _ = stream.set_read_timeout(Some(Duration::from_millis(1000)));
if stream.write_all(&len_buf).is_ok() && stream.write_all(&payload).is_ok() {
return Ok(());
}
}
Err(e) => {
last_err = Some(Box::new(e) as Box<dyn std::error::Error + Send + Sync>);
}
}
}
#[cfg(windows)]
{
use std::net::{SocketAddr, TcpStream};
let addr: SocketAddr = "127.0.0.1:42069".parse().unwrap();
match TcpStream::connect_timeout(&addr, Duration::from_millis(300)) {
Ok(mut stream) => {
let _ = stream.set_write_timeout(Some(Duration::from_millis(600)));
let _ = stream.set_read_timeout(Some(Duration::from_millis(600)));
if stream.write_all(&len_buf).is_ok() && stream.write_all(&payload).is_ok() {
return Ok(());
}
}
Err(e) => {
last_err = Some(Box::new(e) as Box<dyn std::error::Error + Send + Sync>);
}
}
}
}
Err(last_err.unwrap_or_else(|| "Failed to communicate with scythe-daemon".into()))
}
pub fn query_status() -> Result<DaemonStatus, Box<dyn std::error::Error + Send + Sync>> {
use std::io::{Read, Write};
use std::time::Duration;
let payload = serde_json::to_vec(&Command::GetStatus)?;
let len_buf = (payload.len() as u32).to_le_bytes();
let mut last_err = None;
for attempt in 0..3 {
if attempt > 0 {
std::thread::sleep(Duration::from_millis(60));
}
#[cfg(unix)]
let stream_res = {
use std::os::unix::net::UnixStream;
let socket_path = get_socket_path();
UnixStream::connect(&socket_path)
.or_else(|_| UnixStream::connect(get_legacy_socket_path()))
};
#[cfg(windows)]
let stream_res = {
use std::net::{SocketAddr, TcpStream};
let addr: SocketAddr = "127.0.0.1:42069".parse().unwrap();
TcpStream::connect_timeout(&addr, Duration::from_millis(300))
};
match stream_res {
Ok(mut stream) => {
let _ = stream.set_write_timeout(Some(Duration::from_millis(600)));
let _ = stream.set_read_timeout(Some(Duration::from_millis(600)));
if stream.write_all(&len_buf).is_err() || stream.write_all(&payload).is_err() {
continue;
}
let mut resp_len_buf = [0u8; 4];
if stream.read_exact(&mut resp_len_buf).is_err() {
continue;
}
let resp_len = u32::from_le_bytes(resp_len_buf) as usize;
let mut resp_payload = vec![0u8; resp_len];
if stream.read_exact(&mut resp_payload).is_err() {
continue;
}
if let Ok(status) = serde_json::from_slice::<DaemonStatus>(&resp_payload) {
return Ok(status);
}
}
Err(e) => {
last_err = Some(Box::new(e) as Box<dyn std::error::Error + Send + Sync>);
}
}
}
Err(last_err.unwrap_or_else(|| "Failed to query status from scythe-daemon".into()))
}
pub fn get_overlay_pid_path() -> std::path::PathBuf {
#[cfg(unix)]
{
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
.unwrap_or_else(|_| format!("/run/user/{}", unsafe { libc::getuid() }));
std::path::PathBuf::from(runtime_dir).join("scythe-overlay.pid")
}
#[cfg(not(unix))]
{
std::env::temp_dir().join("scythe-overlay.pid")
}
}
pub fn clean_overlay_pid() {
let _ = std::fs::remove_file(get_overlay_pid_path());
}