main.rs (1941B)
1 use std::fs; 2 use std::io::{Read, Write}; 3 use std::time::Duration; 4 5 const TARGET: &str = "gpu-screen-recorder"; 6 const POLL_INTERVAL: Duration = Duration::from_secs(2); 7 8 /// Scans /proc for a process whose argv[0] basename matches the target. 9 /// Reads cmdline (not the truncated `comm`) to avoid false positives 10 /// from the 15-char comm truncation matching an unrelated binary. 11 fn is_recording() -> bool { 12 let entries = match fs::read_dir("/proc") { 13 Ok(d) => d, 14 Err(_) => return false, 15 }; 16 17 for entry in entries.flatten() { 18 let name = entry.file_name(); 19 let Some(pid_str) = name.to_str() else { 20 continue; 21 }; 22 if !pid_str.bytes().all(|b| b.is_ascii_digit()) { 23 continue; 24 } 25 26 let mut buf = Vec::with_capacity(64); 27 if fs::File::open(format!("/proc/{pid_str}/cmdline")) 28 .and_then(|mut f| f.read_to_end(&mut buf)) 29 .is_err() 30 { 31 continue; 32 } 33 34 // cmdline is NUL-separated; argv[0] is everything before the first NUL 35 let argv0 = buf.split(|&b| b == 0).next().unwrap_or(&[]); 36 let argv0 = String::from_utf8_lossy(argv0); 37 let basename = argv0.rsplit('/').next().unwrap_or(&argv0); 38 39 if basename == TARGET { 40 return true; 41 } 42 } 43 false 44 } 45 46 fn emit(recording: bool) { 47 let json = if recording { 48 r#"{"text":" <span color='#aaaaaa'>Recording</span>","class":"recording","alt":"recording","tooltip":"gpu-screen-recorder is running"}"# 49 } else { 50 r#"{"text":"","class":"idle","alt":"idle","tooltip":""}"# 51 }; 52 println!("{json}"); 53 let _ = std::io::stdout().flush(); 54 } 55 56 fn main() { 57 let mut last: Option<bool> = None; 58 loop { 59 let now = is_recording(); 60 if last != Some(now) { 61 emit(now); 62 last = Some(now); 63 } 64 std::thread::sleep(POLL_INTERVAL); 65 } 66 }