#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MacPermissions {
pub screen_recording: bool,
pub accessibility: bool,
}
pub fn permission_warnings(p: &MacPermissions) -> Vec<String> {
let mut warnings = Vec::new();
if !p.screen_recording {
warnings.push(
"macOS Screen Recording is NOT effective for this binary — window/screen \
capture will fail (hang ~30s then `Failed to copy data`, falling back to a \
wrong-region grab). Re-grant in System Settings → Privacy & Security → \
Screen Recording. NOTE: an ad-hoc (`codesign --sign -`) signature pins the \
grant to the build's cdhash, so EVERY rebuild breaks it — sign with a stable \
identity (see scripts/dev-build.sh) so the grant survives."
.to_string(),
);
}
if !p.accessibility {
warnings.push(
"macOS Accessibility is NOT effective for this binary — synthetic mouse/keyboard \
input will silently no-op. Re-grant in System Settings → Privacy & Security → \
Accessibility. (Same cdhash-pinning caveat as Screen Recording: an ad-hoc \
signature breaks the grant on every rebuild — use a stable signing identity.)"
.to_string(),
);
}
warnings
}
#[cfg(target_os = "macos")]
pub fn query() -> MacPermissions {
MacPermissions {
screen_recording: unsafe { ffi::CGPreflightScreenCaptureAccess() },
accessibility: unsafe { ffi::AXIsProcessTrusted() != 0 },
}
}
#[cfg(not(target_os = "macos"))]
pub fn query() -> MacPermissions {
MacPermissions {
screen_recording: true,
accessibility: true,
}
}
pub fn preflight_warnings() -> Vec<String> {
permission_warnings(&query())
}
#[cfg(target_os = "macos")]
mod ffi {
#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
pub fn CGPreflightScreenCaptureAccess() -> bool;
}
#[link(name = "ApplicationServices", kind = "framework")]
unsafe extern "C" {
pub fn AXIsProcessTrusted() -> u8;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_warnings_when_all_granted() {
let p = MacPermissions {
screen_recording: true,
accessibility: true,
};
assert!(permission_warnings(&p).is_empty());
}
#[test]
fn warns_about_screen_recording_when_denied() {
let p = MacPermissions {
screen_recording: false,
accessibility: true,
};
let w = permission_warnings(&p);
assert_eq!(w.len(), 1);
assert!(
w[0].contains("Screen Recording"),
"expected Screen Recording warning, got: {w:?}"
);
}
#[test]
fn warns_about_accessibility_when_denied() {
let p = MacPermissions {
screen_recording: true,
accessibility: false,
};
let w = permission_warnings(&p);
assert_eq!(w.len(), 1);
assert!(
w[0].contains("Accessibility"),
"expected Accessibility warning, got: {w:?}"
);
}
#[test]
fn warns_about_both_when_both_denied() {
let p = MacPermissions {
screen_recording: false,
accessibility: false,
};
assert_eq!(permission_warnings(&p).len(), 2);
}
}