Skip to content

Instantly share code, notes, and snippets.

@mpottinger
Last active July 21, 2025 12:48
Show Gist options
  • Select an option

  • Save mpottinger/d8d993f298be22fdea5ff9ab11f9b7b3 to your computer and use it in GitHub Desktop.

Select an option

Save mpottinger/d8d993f298be22fdea5ff9ab11f9b7b3 to your computer and use it in GitHub Desktop.

Revisions

  1. mpottinger revised this gist Jul 21, 2025. 1 changed file with 67 additions and 0 deletions.
    67 changes: 67 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,67 @@
    # macOS Active Apps Library

    A Rust library for getting truly visible applications on macOS - those with windows currently on top of the screen.

    ## Features

    - **Get all visible windows** currently on screen (not just running apps)
    - **Get unique application names** with visible windows
    - **Get the frontmost (topmost) window** with details
    - **Detect JetBrains IDEs** specifically
    - **Get the frontmost JetBrains IDE** if one is focused

    ## Usage

    Add to your `Cargo.toml`:
    ```toml
    [dependencies]
    core-graphics = "0.25.0"
    core-foundation = "0.10.1"
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0"
    ```

    Basic usage:
    ```rust
    use macos_active_apps::*;

    // Get apps with visible windows
    let visible_apps = get_top_level_visible_apps();
    println!("Visible apps: {:?}", visible_apps);

    // Get frontmost window
    if let Some(frontmost) = get_frontmost_window() {
    println!("Frontmost: {} - {}", frontmost.app_name, frontmost.window_title);
    println!("Position: ({}, {})", frontmost.bounds.origin.x, frontmost.bounds.origin.y);
    println!("Size: {}x{}", frontmost.bounds.size.width, frontmost.bounds.size.height);
    }

    // Get visible JetBrains IDEs
    let ides = get_visible_jetbrains_ides();
    println!("JetBrains IDEs: {:?}", ides);

    // Check if a JetBrains IDE is frontmost
    if let Some(ide) = get_frontmost_jetbrains_ide() {
    println!("Frontmost IDE: {}", ide);
    }
    ```

    ## Run Test

    ```bash
    cargo run --bin test
    ```

    ## What Makes This Different

    Unlike other libraries that just list running applications, this one uses Core Graphics to get only applications that **actually have visible windows on screen**. This is useful for:

    - Window management tools
    - Screen recording apps
    - Productivity tools that need to know what's actually visible
    - IDE/editor detection for development tools

    ## Platform

    - **macOS only** (uses Core Graphics framework)
    - Requires macOS 10.6+ (for Core Graphics APIs)
  2. mpottinger created this gist Jul 21, 2025.
    19 changes: 19 additions & 0 deletions Cargo.toml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    [package]
    name = "macos_active_apps"
    version = "0.1.0"
    edition = "2021"

    [lib]
    name = "macos_active_apps"
    path = "src/lib.rs"

    # Optional test binary for direct testing
    [[bin]]
    name = "test"
    path = "src/bin/test.rs"

    [dependencies]
    core-graphics = "0.25.0"
    core-foundation = "0.10.1"
    serde = { version = "1.0", features = ["derive"] }
    serde_json = "1.0"
    239 changes: 239 additions & 0 deletions lib.rs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,239 @@
    /*!
    Get truly visible applications - those with windows currently on top of the screen.
    This uses the Core Graphics framework's CGWindowListCopyWindowInfo to get only windows
    that are actually visible on screen, not just running applications.
    */

    use core_graphics::window::{
    kCGWindowListExcludeDesktopElements, kCGWindowListOptionOnScreenOnly, CGWindowListCopyWindowInfo,
    };
    use core_graphics::geometry::{CGRect, CGPoint, CGSize};
    use core_foundation::array::CFArray;
    use core_foundation::dictionary::CFDictionary;
    use core_foundation::string::CFString;
    use core_foundation::base::{TCFType, CFTypeRef};
    use core_foundation::number::CFNumber;
    use std::collections::HashSet;

    #[derive(Debug, Clone)]
    pub struct WindowInfo {
    pub app_name: String,
    pub window_title: String,
    pub pid: i64,
    pub window_id: u32,
    pub bounds: CGRect,
    pub layer: i64,
    }

    impl WindowInfo {
    fn from_cf_dict(dict: &CFDictionary<CFString, CFTypeRef>) -> Option<Self> {
    // Helper function to safely extract string values
    let get_string = |key: &str| -> String {
    let cf_key = CFString::new(key);
    dict.find(cf_key)
    .and_then(|value_ref| {
    let cf_string = unsafe { CFString::wrap_under_get_rule(*value_ref as *const _) };
    Some(cf_string.to_string())
    })
    .unwrap_or_default()
    };
    // Helper function to safely extract number values
    let get_number = |key: &str| -> i64 {
    let cf_key = CFString::new(key);
    dict.find(cf_key)
    .and_then(|value_ref| {
    let cf_number = unsafe { CFNumber::wrap_under_get_rule(*value_ref as *const _) };
    cf_number.to_i64()
    })
    .unwrap_or(0)
    };

    // Helper function to safely extract float values
    let _get_float = |key: &str| -> f64 {
    let cf_key = CFString::new(key);
    dict.find(cf_key)
    .and_then(|value_ref| {
    let cf_number = unsafe { CFNumber::wrap_under_get_rule(*value_ref as *const _) };
    cf_number.to_f64()
    })
    .unwrap_or(0.0)
    };

    let app_name = get_string("kCGWindowOwnerName");
    let window_title = get_string("kCGWindowName");
    let pid = get_number("kCGWindowOwnerPID");
    let window_id = get_number("kCGWindowNumber") as u32;
    let layer = get_number("kCGWindowLayer");

    // Extract bounds dictionary
    let bounds = {
    let cf_key = CFString::new("kCGWindowBounds");
    dict.find(cf_key)
    .and_then(|value_ref| {
    let bounds_dict = unsafe {
    CFDictionary::<CFString, CFTypeRef>::wrap_under_get_rule(*value_ref as *const _)
    };

    let x = {
    let x_key = CFString::new("X");
    bounds_dict.find(x_key)
    .and_then(|v| {
    let cf_number = unsafe { CFNumber::wrap_under_get_rule(*v as *const _) };
    cf_number.to_f64()
    })
    .unwrap_or(0.0)
    };

    let y = {
    let y_key = CFString::new("Y");
    bounds_dict.find(y_key)
    .and_then(|v| {
    let cf_number = unsafe { CFNumber::wrap_under_get_rule(*v as *const _) };
    cf_number.to_f64()
    })
    .unwrap_or(0.0)
    };

    let width = {
    let width_key = CFString::new("Width");
    bounds_dict.find(width_key)
    .and_then(|v| {
    let cf_number = unsafe { CFNumber::wrap_under_get_rule(*v as *const _) };
    cf_number.to_f64()
    })
    .unwrap_or(0.0)
    };

    let height = {
    let height_key = CFString::new("Height");
    bounds_dict.find(height_key)
    .and_then(|v| {
    let cf_number = unsafe { CFNumber::wrap_under_get_rule(*v as *const _) };
    cf_number.to_f64()
    })
    .unwrap_or(0.0)
    };

    Some(CGRect::new(&CGPoint::new(x, y), &CGSize::new(width, height)))
    })
    .unwrap_or_else(|| CGRect::new(&CGPoint::new(0.0, 0.0), &CGSize::new(0.0, 0.0)))
    };

    // Skip windows with zero area
    if bounds.size.width > 0.0 && bounds.size.height > 0.0 {
    Some(WindowInfo {
    app_name,
    window_title,
    pid,
    window_id,
    bounds,
    layer,
    })
    } else {
    None
    }
    }
    }

    /// Get all visible windows that are currently on screen.
    pub fn get_visible_windows() -> Vec<WindowInfo> {
    let window_list_ref = unsafe {
    CGWindowListCopyWindowInfo(
    kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
    0,
    )
    };

    let window_list = unsafe { CFArray::<CFDictionary<CFString, CFTypeRef>>::wrap_under_create_rule(window_list_ref) };
    let mut visible_windows = Vec::new();

    for i in 0..window_list.len() {
    if let Some(window_dict) = window_list.get(i) {
    if let Some(window_info) = WindowInfo::from_cf_dict(&window_dict) {
    visible_windows.push(window_info);
    }
    }
    }

    visible_windows
    }

    /// Get unique application names that have visible windows on screen.
    pub fn get_top_level_visible_apps() -> Vec<String> {
    let windows = get_visible_windows();

    let mut app_names = HashSet::new();
    for window in windows {
    let app_name = &window.app_name;
    if !app_name.is_empty() && app_name != "Window Server" && app_name != "Dock" && app_name != "Control Center" {
    app_names.insert(app_name.clone());
    }
    }

    let mut sorted_apps: Vec<String> = app_names.into_iter().collect();
    sorted_apps.sort();
    sorted_apps
    }

    /// Get the frontmost (topmost) window.
    pub fn get_frontmost_window() -> Option<WindowInfo> {
    let windows = get_visible_windows();

    // Filter out system windows and find the one with the highest layer
    let user_windows: Vec<_> = windows
    .into_iter()
    .filter(|w| w.app_name != "Window Server" && w.app_name != "Dock" && w.app_name != "Control Center")
    .collect();

    user_windows
    .into_iter()
    .max_by_key(|w| w.layer)
    }

    /// Get JetBrains IDEs that are currently visible on screen.
    /// Returns a list of IDE names that have visible windows.
    pub fn get_visible_jetbrains_ides() -> Vec<String> {
    let jetbrains_ides = [
    "pycharm", "clion", "webstorm", "phpstorm", "rubymine", "datagrip",
    "rider", "goland", "appcode", "android-studio", "dataspell", "fleet",
    "gateway", "space", "rustrover", "intellij",
    ];

    let visible_apps = get_top_level_visible_apps();
    let mut found_ides = Vec::new();

    for app_name in visible_apps {
    let app_lower = app_name.to_lowercase();
    for &ide_name in &jetbrains_ides {
    if app_lower.contains(ide_name) {
    found_ides.push(ide_name.to_string());
    break; // Found match for this app, move to next
    }
    }
    }

    found_ides.sort();
    found_ides.dedup();
    found_ides
    }

    /// Get the frontmost JetBrains IDE if one is currently focused.
    /// Returns the IDE name if a JetBrains IDE is the frontmost window.
    pub fn get_frontmost_jetbrains_ide() -> Option<String> {
    let jetbrains_ides = [
    "pycharm", "clion", "webstorm", "phpstorm", "rubymine", "datagrip",
    "rider", "goland", "appcode", "android-studio", "dataspell", "fleet",
    "gateway", "space", "rustrover", "intellij",
    ];

    if let Some(frontmost) = get_frontmost_window() {
    let app_lower = frontmost.app_name.to_lowercase();
    for &ide_name in &jetbrains_ides {
    if app_lower.contains(ide_name) {
    return Some(ide_name.to_string());
    }
    }
    }

    None
    }
    47 changes: 47 additions & 0 deletions test.rs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    use macos_active_apps::*;

    fn main() {
    println!("=== Truly Visible Applications (with windows on screen) ===");
    let visible_apps = get_top_level_visible_apps();
    println!("Apps with visible windows: {:?}", visible_apps);
    println!("Total: {} applications", visible_apps.len());

    println!("\n=== JetBrains IDEs Detection ===");
    let jetbrains_ides = get_visible_jetbrains_ides();
    if jetbrains_ides.is_empty() {
    println!("No JetBrains IDEs currently visible");
    } else {
    println!("Visible JetBrains IDEs: {:?}", jetbrains_ides);
    }

    if let Some(frontmost_ide) = get_frontmost_jetbrains_ide() {
    println!("Frontmost JetBrains IDE: {}", frontmost_ide);
    } else {
    println!("No JetBrains IDE is currently frontmost");
    }

    println!("\n=== Frontmost Window ===");
    if let Some(frontmost) = get_frontmost_window() {
    println!("App: {} (PID: {})", frontmost.app_name, frontmost.pid);
    println!("Window: {}", if frontmost.window_title.is_empty() { "[No Title]" } else { &frontmost.window_title });
    println!("Position: ({}, {})", frontmost.bounds.origin.x, frontmost.bounds.origin.y);
    println!("Size: {} x {}", frontmost.bounds.size.width, frontmost.bounds.size.height);
    println!("Window ID: {}, Layer: {}", frontmost.window_id, frontmost.layer);
    } else {
    println!("No frontmost window found");
    }

    println!("\n=== All Visible Windows (first 10) ===");
    let all_windows = get_visible_windows();
    let mut count = 0;
    for window in all_windows {
    if window.app_name != "Window Server" && window.app_name != "Dock" && window.app_name != "Control Center" {
    count += 1;
    if count > 10 {
    break;
    }
    let title = if window.window_title.is_empty() { "[No Title]" } else { &window.window_title };
    println!("{:2}. {} (PID: {}): {}", count, window.app_name, window.pid, title);
    }
    }
    }