feat: keep Clipbin recording in the tray

Add native Linux SNI and Windows notification-area trays, single-instance reopening, background startup and login entries. Preserve recording and discovery after close, with clean Quit and reachable no-tray fallbacks.

Track capture-source audio delivery independently of silent output tracks and warn without stopping video. Give clip metadata its own row and shape labels at final bounds to avoid GPUI 0.2.2 truncation cache and text-run bugs.

Validated with strict workspace checks, 57 tests, Windows core/OBS/probe cross-clippy, all ten capture scenarios, tray/session/clips/playback rigs and narrow/wide screenshots. Document runtime validation gaps.
This commit is contained in:
2026-10-13 18:29:37 +02:00
parent 178c400eeb
commit f1c31d6c90
21 changed files with 1413 additions and 133 deletions
+60
View File
@@ -0,0 +1,60 @@
//! Reconcile the OS login entry with the saved setting.
use std::{io, path::Path};
pub fn reconcile(enabled: bool) -> io::Result<()> {
apply(enabled, &std::env::current_exe()?)
}
#[cfg(target_os = "linux")]
fn apply(enabled: bool, executable: &Path) -> io::Result<()> {
let path = dirs::config_dir()
.ok_or_else(|| io::Error::other("No config directory"))?
.join("autostart/clipbin.desktop");
if enabled {
std::fs::create_dir_all(path.parent().unwrap())?;
let executable = executable
.to_str()
.ok_or_else(|| io::Error::other("Executable path is not UTF-8"))?;
// Desktop Entry Exec quoting includes reserved characters and doubles %.
let quoted = executable
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('`', "\\`")
.replace('$', "\\$")
.replace('%', "%%");
let escaped = quoted
.replace('\\', "\\\\")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t");
std::fs::write(
path,
format!(
"[Desktop Entry]\nType=Application\nName=Clipbin\nExec=\"{escaped}\" --background\nTerminal=false\n"
),
)
} else {
match std::fs::remove_file(path) {
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
result => result,
}
}
}
#[cfg(windows)]
fn apply(enabled: bool, executable: &Path) -> io::Result<()> {
use winreg::{RegKey, enums::HKEY_CURRENT_USER};
let (key, _) = RegKey::predef(HKEY_CURRENT_USER)
.create_subkey(r"Software\Microsoft\Windows\CurrentVersion\Run")?;
if enabled {
key.set_value(
"Clipbin",
&format!("\"{}\" --background", executable.display()),
)
} else {
match key.delete_value("Clipbin") {
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
result => result,
}
}
}