focal 0.2.8

Terminal focus library - focus terminal windows and multiplexer panes
Documentation
//! iTerm2-specific focus handler (macOS).
//!
//! Uses JavaScript for Automation (JXA) to find and focus the specific
//! iTerm2 session matching a TTY device.

use crate::FocusMode;

/// Try to focus the iTerm2 session with matching TTY.
///
/// # Focus Modes
///
/// - **SingleWindow**: Uses JXA + System Events AXRaise to bring only the
///   specific window to the front. Requires accessibility permissions.
///
/// - **ActivateApp**: Uses JXA + Application.activate() which brings ALL
///   iTerm2 windows to the front. No special permissions required.
///
/// # Arguments
/// * `tty_device` - TTY device name (e.g., "ttys003")
/// * `mode` - The focus mode to use
///
/// # Returns
/// `true` if focusing succeeded, `false` otherwise.
pub fn try_focus(tty_device: &str, mode: FocusMode) -> bool {
    match mode {
        FocusMode::SingleWindow => try_focus_single_window(tty_device),
        FocusMode::ActivateApp => try_focus_activate_app(tty_device),
    }
}

/// Focus using SingleWindow mode (AXRaise - only brings one window to front).
///
/// Uses System Events to perform AXRaise on the specific window, which
/// brings only that window to the front without affecting other iTerm2 windows.
#[cfg(target_os = "macos")]
fn try_focus_single_window(tty_device: &str) -> bool {
    let script = format!(
        r#"
(function() {{
    try {{
        var iTerm = Application("iTerm2");
        if (!iTerm.running()) return false;

        var windows = iTerm.windows();
        for (var i = 0; i < windows.length; i++) {{
            var tabs = windows[i].tabs();
            for (var j = 0; j < tabs.length; j++) {{
                var sessions = tabs[j].sessions();
                for (var k = 0; k < sessions.length; k++) {{
                    var tty = sessions[k].tty();
                    if (tty && tty.includes("{tty_device}")) {{
                        // Select the session and tab within iTerm
                        sessions[k].select();
                        tabs[j].select();

                        // Make this window frontmost among iTerm windows
                        // This reorders iTerm's window list so our target is first
                        windows[i].index = 1;

                        // Use System Events to raise ONLY this specific window
                        // After setting index=1, the first window in System Events
                        // should be our target window
                        var se = Application("System Events");
                        var iTermProcess = se.processes["iTerm2"];

                        // Bring iTerm2 to front (required for AXRaise to work across apps)
                        iTermProcess.frontmost = true;

                        var seWindows = iTermProcess.windows();
                        if (seWindows.length > 0) {{
                            // AXRaise brings just this window to front
                            seWindows[0].actions["AXRaise"].perform();
                        }}
                        return true;
                    }}
                }}
            }}
        }}
        return false;
    }} catch(e) {{
        return false;
    }}
}})()
"#
    );

    run_jxa(&script)
}

#[cfg(not(target_os = "macos"))]
fn try_focus_single_window(_tty_device: &str) -> bool {
    false
}

/// Focus using ActivateApp mode (brings ALL iTerm2 windows to front).
#[cfg(target_os = "macos")]
fn try_focus_activate_app(tty_device: &str) -> bool {
    let script = format!(
        r#"
(function() {{
    try {{
        var iTerm = Application("iTerm2");
        if (!iTerm.running()) return false;

        var windows = iTerm.windows();
        for (var i = 0; i < windows.length; i++) {{
            var tabs = windows[i].tabs();
            for (var j = 0; j < tabs.length; j++) {{
                var sessions = tabs[j].sessions();
                for (var k = 0; k < sessions.length; k++) {{
                    var tty = sessions[k].tty();
                    if (tty && tty.includes("{tty_device}")) {{
                        // Select the session and tab within iTerm
                        sessions[k].select();
                        tabs[j].select();

                        // Make this window frontmost among iTerm windows
                        windows[i].index = 1;

                        // Activate the entire app (brings ALL windows to front)
                        iTerm.activate();
                        return true;
                    }}
                }}
            }}
        }}
        return false;
    }} catch(e) {{
        return false;
    }}
}})()
"#
    );

    run_jxa(&script)
}

#[cfg(not(target_os = "macos"))]
fn try_focus_activate_app(_tty_device: &str) -> bool {
    false
}

/// Run a JavaScript for Automation (JXA) script and return true if it returned "true".
#[cfg(target_os = "macos")]
fn run_jxa(script: &str) -> bool {
    super::jxa::run_with_timeout(script)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_try_focus_nonexistent_tty_single_window() {
        let result = try_focus("ttys999999", FocusMode::SingleWindow);
        assert!(!result);
    }

    #[test]
    fn test_try_focus_nonexistent_tty_activate_app() {
        let result = try_focus("ttys999999", FocusMode::ActivateApp);
        assert!(!result);
    }
}