agentmux_common/
xauthority.rs1use std::path::{Path, PathBuf};
18
19pub fn ensure_xauthority() -> Option<PathBuf> {
25 if let Some(p) = std::env::var_os("XAUTHORITY").map(PathBuf::from).filter(|p| p.is_file()) {
26 return Some(p);
27 }
28 let found = systemd_user_xauthority()
29 .or_else(|| {
30 std::env::var_os("XDG_RUNTIME_DIR")
31 .and_then(|d| newest_mutter_cookie(Path::new(&d)))
32 })
33 .or_else(|| {
34 std::env::var_os("HOME")
35 .map(|h| PathBuf::from(h).join(".Xauthority"))
36 .filter(|p| p.is_file())
37 })?;
38 std::env::set_var("XAUTHORITY", &found);
39 tracing::info!("XAUTHORITY was unset; using {}", found.display());
40 Some(found)
41}
42
43fn systemd_user_xauthority() -> Option<PathBuf> {
44 let out = std::process::Command::new("systemctl")
45 .args(["--user", "show-environment"])
46 .output()
47 .ok()?;
48 if !out.status.success() {
49 return None;
50 }
51 parse_show_environment(&String::from_utf8_lossy(&out.stdout)).filter(|p| p.is_file())
52}
53
54fn parse_show_environment(out: &str) -> Option<PathBuf> {
56 out.lines()
57 .find_map(|l| l.strip_prefix("XAUTHORITY="))
58 .map(|v| v.trim().trim_matches(|c| c == '\'' || c == '"'))
59 .filter(|v| !v.is_empty())
60 .map(PathBuf::from)
61}
62
63fn newest_mutter_cookie(dir: &Path) -> Option<PathBuf> {
65 std::fs::read_dir(dir)
66 .ok()?
67 .filter_map(Result::ok)
68 .filter(|e| e.file_name().to_string_lossy().starts_with(".mutter-Xwaylandauth."))
69 .filter_map(|e| {
70 let meta = e.metadata().ok()?;
71 if !meta.is_file() {
72 return None;
73 }
74 Some((meta.modified().ok()?, e.path()))
75 })
76 .max_by_key(|(mtime, _)| *mtime)
77 .map(|(_, path)| path)
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn parses_xauthority_from_show_environment() {
86 let out = "DISPLAY=:0\nXAUTHORITY=/run/user/1000/.mutter-Xwaylandauth.EVK3V3\nWAYLAND_DISPLAY=wayland-0\n";
87 assert_eq!(
88 parse_show_environment(out),
89 Some(PathBuf::from("/run/user/1000/.mutter-Xwaylandauth.EVK3V3"))
90 );
91 }
92
93 #[test]
94 fn strips_quotes_systemd_adds_to_values() {
95 assert_eq!(parse_show_environment("XAUTHORITY='/tmp/a b'\n"), Some(PathBuf::from("/tmp/a b")));
96 }
97
98 #[test]
99 fn missing_or_empty_xauthority_is_none() {
100 assert_eq!(parse_show_environment("DISPLAY=:0\n"), None);
101 assert_eq!(parse_show_environment("XAUTHORITY=\n"), None);
102 }
103
104 #[test]
105 fn picks_the_newest_mutter_cookie_and_ignores_other_files() {
106 let dir = tempfile::tempdir().unwrap();
107 let old = dir.path().join(".mutter-Xwaylandauth.OLD111");
108 let new = dir.path().join(".mutter-Xwaylandauth.NEW222");
109 std::fs::write(&old, b"x").unwrap();
110 std::fs::write(dir.path().join("pulse-cookie"), b"x").unwrap();
111 std::thread::sleep(std::time::Duration::from_millis(20));
112 std::fs::write(&new, b"x").unwrap();
113 assert_eq!(newest_mutter_cookie(dir.path()), Some(new));
114 }
115
116 #[test]
117 fn no_cookie_in_dir_is_none() {
118 let dir = tempfile::tempdir().unwrap();
119 std::fs::write(dir.path().join("bus"), b"x").unwrap();
120 assert_eq!(newest_mutter_cookie(dir.path()), None);
121 }
122}