summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--waybar-record/Cargo.lock7
-rw-r--r--waybar-record/Cargo.toml11
-rw-r--r--waybar-record/src/main.rs66
3 files changed, 84 insertions, 0 deletions
diff --git a/waybar-record/Cargo.lock b/waybar-record/Cargo.lock
new file mode 100644
index 0000000..58bfb49
--- /dev/null
+++ b/waybar-record/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "waybar-record"
+version = "0.1.0"
diff --git a/waybar-record/Cargo.toml b/waybar-record/Cargo.toml
new file mode 100644
index 0000000..bff82f5
--- /dev/null
+++ b/waybar-record/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "waybar-record"
+version = "0.1.0"
+edition = "2024"
+
+# Make the binary small
+[profile.release]
+opt-level = "z"
+lto = true
+strip = true
+panic = "abort"
diff --git a/waybar-record/src/main.rs b/waybar-record/src/main.rs
new file mode 100644
index 0000000..8ceb6c7
--- /dev/null
+++ b/waybar-record/src/main.rs
@@ -0,0 +1,66 @@
+use std::fs;
+use std::io::{Read, Write};
+use std::time::Duration;
+
+const TARGET: &str = "gpu-screen-recorder";
+const POLL_INTERVAL: Duration = Duration::from_secs(2);
+
+/// Scans /proc for a process whose argv[0] basename matches the target.
+/// Reads cmdline (not the truncated `comm`) to avoid false positives
+/// from the 15-char comm truncation matching an unrelated binary.
+fn is_recording() -> bool {
+ let entries = match fs::read_dir("/proc") {
+ Ok(d) => d,
+ Err(_) => return false,
+ };
+
+ for entry in entries.flatten() {
+ let name = entry.file_name();
+ let Some(pid_str) = name.to_str() else {
+ continue;
+ };
+ if !pid_str.bytes().all(|b| b.is_ascii_digit()) {
+ continue;
+ }
+
+ let mut buf = Vec::with_capacity(64);
+ if fs::File::open(format!("/proc/{pid_str}/cmdline"))
+ .and_then(|mut f| f.read_to_end(&mut buf))
+ .is_err()
+ {
+ continue;
+ }
+
+ // cmdline is NUL-separated; argv[0] is everything before the first NUL
+ let argv0 = buf.split(|&b| b == 0).next().unwrap_or(&[]);
+ let argv0 = String::from_utf8_lossy(argv0);
+ let basename = argv0.rsplit('/').next().unwrap_or(&argv0);
+
+ if basename == TARGET {
+ return true;
+ }
+ }
+ false
+}
+
+fn emit(recording: bool) {
+ let json = if recording {
+ r#"{"text":"󰑊 <span color='#aaaaaa'>Recording</span>","class":"recording","alt":"recording","tooltip":"gpu-screen-recorder is running"}"#
+ } else {
+ r#"{"text":"","class":"idle","alt":"idle","tooltip":""}"#
+ };
+ println!("{json}");
+ let _ = std::io::stdout().flush();
+}
+
+fn main() {
+ let mut last: Option<bool> = None;
+ loop {
+ let now = is_recording();
+ if last != Some(now) {
+ emit(now);
+ last = Some(now);
+ }
+ std::thread::sleep(POLL_INTERVAL);
+ }
+}