Last active
June 29, 2025 23:37
-
-
Save adamdroberts/1a0161a418fc503d80d330dd419edf43 to your computer and use it in GitHub Desktop.
MacOS swift code to detect NSProgressIndicator on screen in any application
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 characters
import Cocoa | |
import ApplicationServices | |
/// Traverses the view hierarchy of all windows associated with the current application | |
/// to find if any view is an NSProgressIndicator. | |
/// | |
/// - Returns: `true` if at least one NSProgressIndicator is found, `false` otherwise. | |
@MainActor func findNativeProgressBarsInAllWindows() -> Bool { | |
// Get the shared application instance | |
let app = NSApplication.shared | |
// Iterate through all windows managed by the application | |
for window in app.windows { | |
// Check the window's content view and its subviews | |
if let contentView = window.contentView { | |
if traverseViewHierarchy(startingFrom: contentView) { | |
// Found a progress bar in this window's hierarchy | |
return true | |
} | |
} | |
} | |
// No progress bar found in any window's view hierarchy | |
print("No NSProgressIndicator found in any window.") | |
return false | |
} | |
/// Recursively traverses a view hierarchy starting from a given view. | |
/// | |
/// - Parameter view: The root view to start the traversal from. | |
/// - Returns: `true` if an NSProgressIndicator is found within the hierarchy, `false` otherwise. | |
@MainActor private func traverseViewHierarchy(startingFrom view: NSView) -> Bool { | |
// Check if the current view itself is an NSProgressIndicator | |
if view is NSProgressIndicator { | |
print("Found NSProgressIndicator: \(view) in window: \(view.window?.title ?? "N/A")") | |
return true | |
} | |
// Recursively check each subview | |
for subview in view.subviews { | |
if traverseViewHierarchy(startingFrom: subview) { | |
// Found in a subview, propagate the result up | |
return true | |
} | |
} | |
// NSProgressIndicator not found in this view or its direct/indirect subviews | |
return false | |
} | |
/// Recursively traverses an AXUIElement hierarchy starting from a given element. | |
/// | |
/// - Parameter element: The root AXUIElement to start the traversal from. | |
/// - Returns: `true` if an element with the role of progress indicator is found, `false` otherwise. | |
func traverseAXHierarchy(startingFrom element: AXUIElement) -> Bool { | |
var role: CFTypeRef? | |
if AXUIElementCopyAttributeValue(element, kAXRoleAttribute as CFString, &role) == .success { | |
if let role = role as? String, role == (kAXProgressIndicatorRole as String) { | |
print("Found progress bar: \(element)") | |
return true | |
} | |
} | |
var children: CFTypeRef? | |
if AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &children) == .success, | |
let childrenArray = children as? [AXUIElement] { | |
for child in childrenArray { | |
if traverseAXHierarchy(startingFrom: child) { | |
return true | |
} | |
} | |
} | |
return false | |
} | |
/// Searches for native progress bars at the screen level using accessibility APIs. | |
/// | |
/// - Returns: `true` if at least one progress bar is found system-wide, `false` otherwise. | |
func findNativeProgressBarsOnScreen() -> Bool { | |
// Get information for all on-screen windows (excluding desktop elements) | |
let options: CGWindowListOption = [.optionOnScreenOnly, .excludeDesktopElements] | |
guard let windowInfoList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) as? [[String: Any]] else { | |
print("Failed to retrieve window information.") | |
return false | |
} | |
// Iterate through each window from the system list | |
for windowInfo in windowInfoList { | |
// Retrieve the process identifier (PID) for the window owner | |
if let pid = windowInfo[kCGWindowOwnerPID as String] as? pid_t { | |
let appElement = AXUIElementCreateApplication(pid) | |
var axWindows: CFTypeRef? | |
if AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &axWindows) == .success, | |
let axWindowsArray = axWindows as? [AXUIElement] { | |
for axWindow in axWindowsArray { | |
if traverseAXHierarchy(startingFrom: axWindow) { | |
return true | |
} | |
} | |
} | |
} | |
} | |
print("No system progress bar found.") | |
return false | |
} | |
// Example of how to call the new function: | |
DispatchQueue.main.async { // Ensure UI access is on the main thread | |
if findNativeProgressBarsOnScreen() { | |
print("Success: Found at least one native progress bar on screen.") | |
} else { | |
print("Result: No native progress bars were found on screen.") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment