Skip to main content

screen_app/recp/
settings_deep_link.rs

1//! M-RECP.0 / AUT-261 — System Settings deep-link helpers.
2//!
3//! Maps a [`SettingsPane`] enum to the OS-specific URL / shell-command
4//! the user's system honours. Today: macOS + Windows return real URLs;
5//! Linux returns `None` (no universal deep-link).
6
7/// Which System Settings / Control Panel pane to open.
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
9pub enum SettingsPane {
10    /// Camera privacy pane (Privacy & Security → Camera on macOS,
11    /// Privacy → Camera on Windows 11).
12    Camera,
13    /// Microphone privacy pane.
14    Microphone,
15    /// Screen recording privacy pane (macOS only — Windows doesn't
16    /// have this as a system-level Settings pane).
17    ScreenRecording,
18}
19
20/// The shell argument list that opens the requested pane on the
21/// current OS, OR `None` if no deep-link is known. Linux returns
22/// `None` because the desktop environment determines the right
23/// command (GNOME Control Center vs KDE System Settings vs …).
24#[must_use]
25pub fn open_command(pane: SettingsPane) -> Option<Vec<String>> {
26    let url = url_for_current_os(pane)?;
27    Some(open_args_for_current_os(&url))
28}
29
30#[cfg(target_os = "macos")]
31#[must_use]
32#[allow(
33    clippy::unnecessary_wraps,
34    reason = "the macOS / Windows / other-OS variants share one Option<String> signature; macos always returns Some but the cross-OS callers branch on None"
35)]
36fn url_for_current_os(pane: SettingsPane) -> Option<String> {
37    Some(
38        match pane {
39            SettingsPane::Camera => {
40                "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera"
41            }
42            SettingsPane::Microphone => {
43                "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"
44            }
45            SettingsPane::ScreenRecording => {
46                "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
47            }
48        }
49        .to_string(),
50    )
51}
52
53#[cfg(target_os = "windows")]
54fn url_for_current_os(pane: SettingsPane) -> Option<String> {
55    Some(
56        match pane {
57            SettingsPane::Camera => "ms-settings:privacy-webcam",
58            SettingsPane::Microphone => "ms-settings:privacy-microphone",
59            SettingsPane::ScreenRecording => return None,
60        }
61        .to_string(),
62    )
63}
64
65#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
66fn url_for_current_os(_pane: SettingsPane) -> Option<String> {
67    None
68}
69
70#[cfg(target_os = "macos")]
71fn open_args_for_current_os(url: &str) -> Vec<String> {
72    vec!["open".into(), url.into()]
73}
74
75#[cfg(target_os = "windows")]
76fn open_args_for_current_os(url: &str) -> Vec<String> {
77    vec!["cmd".into(), "/c".into(), "start".into(), url.into()]
78}
79
80#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
81fn open_args_for_current_os(_url: &str) -> Vec<String> {
82    Vec::new()
83}
84
85// All tests in this module are cfg-gated to specific OSes (macOS or
86// Windows). On Linux neither test compiles, leaving `use super::*`
87// dead — which trips `-D unused-imports` in CI's `just lint` run on
88// the ubuntu runner. Gate the imports to match.
89#[cfg(all(test, any(target_os = "macos", target_os = "windows")))]
90mod tests {
91    use super::*;
92
93    #[test]
94    #[cfg(target_os = "macos")]
95    fn macos_returns_real_camera_url() {
96        let cmd = open_command(SettingsPane::Camera).unwrap();
97        assert_eq!(cmd[0], "open");
98        assert!(cmd[1].contains("Privacy_Camera"));
99    }
100
101    #[test]
102    #[cfg(target_os = "macos")]
103    fn macos_supports_all_three_panes() {
104        for pane in [
105            SettingsPane::Camera,
106            SettingsPane::Microphone,
107            SettingsPane::ScreenRecording,
108        ] {
109            assert!(open_command(pane).is_some(), "missing url for {pane:?}");
110        }
111    }
112
113    #[test]
114    #[cfg(target_os = "windows")]
115    fn windows_supports_camera_and_microphone() {
116        assert!(open_command(SettingsPane::Camera).is_some());
117        assert!(open_command(SettingsPane::Microphone).is_some());
118        // Windows has no system-level screen-recording pane.
119        assert!(open_command(SettingsPane::ScreenRecording).is_none());
120    }
121}