Last active
July 21, 2025 12:48
-
-
Save mpottinger/d8d993f298be22fdea5ff9ab11f9b7b3 to your computer and use it in GitHub Desktop.
Revisions
-
mpottinger revised this gist
Jul 21, 2025 . 1 changed file with 67 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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) -
mpottinger created this gist
Jul 21, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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" This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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 } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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); } } }