266 lines
8.9 KiB
Python
266 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Minimize All Windows - Python Version (Fixed)
|
|
Requires: pip3 install pyobjc-framework-Cocoa pyobjc-framework-Quartz
|
|
|
|
This uses native macOS APIs which should be more reliable than AppleScript.
|
|
"""
|
|
|
|
import time
|
|
from Cocoa import NSWorkspace
|
|
from Quartz import (
|
|
CGWindowListCopyWindowInfo,
|
|
kCGWindowListOptionOnScreenOnly,
|
|
kCGNullWindowID,
|
|
kCGWindowListExcludeDesktopElements
|
|
)
|
|
import subprocess
|
|
|
|
|
|
def get_apps_with_visible_windows():
|
|
"""Get set of app names that have actual visible on-screen windows"""
|
|
window_list = CGWindowListCopyWindowInfo(
|
|
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
|
|
kCGNullWindowID
|
|
)
|
|
|
|
apps_with_windows = set()
|
|
|
|
for window in window_list:
|
|
# Get the owning application name
|
|
owner_name = window.get('kCGWindowOwnerName', '')
|
|
|
|
# Check if window is actually visible and has size
|
|
# Layer 0 = normal windows, skip menu bar items etc.
|
|
layer = window.get('kCGWindowLayer', 0)
|
|
bounds = window.get('kCGWindowBounds', {})
|
|
width = bounds.get('Width', 0)
|
|
height = bounds.get('Height', 0)
|
|
|
|
# Only count windows that are on the normal layer and have reasonable size
|
|
# (filters out menu bar, dock, status items, etc.)
|
|
if layer == 0 and width > 50 and height > 50 and owner_name:
|
|
apps_with_windows.add(owner_name)
|
|
|
|
return apps_with_windows
|
|
|
|
|
|
def minimize_window_via_applescript(app_name):
|
|
"""Use AppleScript to minimize the frontmost window of an app"""
|
|
script = f'''
|
|
tell application "{app_name}"
|
|
activate
|
|
end tell
|
|
delay 0.8
|
|
tell application "System Events"
|
|
tell process "{app_name}"
|
|
set frontmost to true
|
|
delay 0.4
|
|
keystroke "m" using command down
|
|
end tell
|
|
end tell
|
|
'''
|
|
try:
|
|
subprocess.run(['osascript', '-e', script], timeout=5, check=False)
|
|
return True
|
|
except Exception as e:
|
|
print(f"Failed to minimize {app_name}: {e}")
|
|
return False
|
|
|
|
|
|
# Apps that don't respond to Cmd+M and need special handling
|
|
# Maps app display name -> process name (as seen by System Events)
|
|
EXCEPTION_APPS = {
|
|
'Spyder 6': 'python',
|
|
'MacVim': 'MacVim',
|
|
# Add more exception apps here as needed, e.g.:
|
|
# 'SomeApp': 'some_process_name',
|
|
}
|
|
|
|
|
|
def minimize_exception_app(app_name, process_name):
|
|
"""
|
|
Minimize windows for apps that don't respond to Cmd+M.
|
|
Uses the AXMinimized accessibility attribute directly.
|
|
"""
|
|
script = f'''
|
|
tell application "System Events"
|
|
tell process "{process_name}"
|
|
set windowCount to count of windows
|
|
if windowCount > 0 then
|
|
repeat with w in windows
|
|
try
|
|
-- Only minimize if not already minimized
|
|
if value of attribute "AXMinimized" of w is false then
|
|
set value of attribute "AXMinimized" of w to true
|
|
end if
|
|
end try
|
|
end repeat
|
|
end if
|
|
return windowCount
|
|
end tell
|
|
end tell
|
|
'''
|
|
try:
|
|
result = subprocess.run(
|
|
['osascript', '-e', script],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10
|
|
)
|
|
window_count = result.stdout.strip()
|
|
print(f"{app_name}: Minimized via AXMinimized (windows: {window_count})")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Failed to minimize {app_name} via AXMinimized: {e}")
|
|
return False
|
|
|
|
|
|
def minimize_finder_windows():
|
|
"""Minimize all Finder windows using Finder's native scripting"""
|
|
# First, get count of non-collapsed Finder windows
|
|
count_script = '''
|
|
tell application "Finder"
|
|
set visibleWindows to (every Finder window whose collapsed is false)
|
|
return count of visibleWindows
|
|
end tell
|
|
'''
|
|
try:
|
|
result = subprocess.run(
|
|
['osascript', '-e', count_script],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5
|
|
)
|
|
window_count = int(result.stdout.strip()) if result.stdout.strip() else 0
|
|
|
|
if window_count == 0:
|
|
print("Finder: No visible windows to minimize")
|
|
return
|
|
|
|
print(f"Finder: Minimizing {window_count} window(s)")
|
|
|
|
# Finder uses "collapsed" not "miniaturized" for minimizing windows
|
|
minimize_script = '''
|
|
tell application "Finder"
|
|
set allWindows to every Finder window
|
|
repeat with w in allWindows
|
|
set collapsed of w to true
|
|
end repeat
|
|
end tell
|
|
'''
|
|
subprocess.run(['osascript', '-e', minimize_script], timeout=10, check=False)
|
|
|
|
except Exception as e:
|
|
print(f"Failed to minimize Finder windows: {e}")
|
|
|
|
|
|
def main():
|
|
# Apps to skip - these either don't have minimizable windows or cause issues
|
|
skip_apps = {'SystemUIServer', 'Dock', 'Electron', 'MSTeams', 'nxdock',
|
|
'Control Center', 'Notification Center', 'Spotlight',
|
|
# User-specified apps to never minimize
|
|
'Teams', 'Microsoft Teams', 'Ferdium', 'Messages', 'iMessage',
|
|
'Grasshopper', 'Outlook', 'Microsoft Outlook', 'Wavebox',
|
|
'iTerm2'}
|
|
# 'Claude', 'Copilot', 'GitHub', 'GitHub Desktop',
|
|
skip_keywords = ['Helper', 'Agent']
|
|
|
|
# FIX #1: Get only apps that actually have visible windows on screen
|
|
apps_with_windows = get_apps_with_visible_windows()
|
|
print(f"Apps with visible windows: {apps_with_windows}")
|
|
|
|
# FIX #2: Handle Finder separately and skip it in main loop
|
|
# Remove Finder from the set - we'll handle it specially
|
|
apps_with_windows.discard('Finder')
|
|
|
|
# Also remove exception apps from the main loop - we'll handle them separately
|
|
for exception_app in EXCEPTION_APPS.keys():
|
|
apps_with_windows.discard(exception_app)
|
|
|
|
# Get all running applications (to get proper app objects)
|
|
workspace = NSWorkspace.sharedWorkspace()
|
|
running_apps = workspace.runningApplications()
|
|
|
|
processed_apps = set()
|
|
|
|
for app in running_apps:
|
|
app_name = app.localizedName()
|
|
|
|
# Skip if already processed
|
|
if app_name in processed_apps:
|
|
continue
|
|
|
|
# FIX #1: Skip if this app doesn't have visible windows
|
|
if app_name not in apps_with_windows:
|
|
continue
|
|
|
|
# Skip exception apps - they're handled separately
|
|
if app_name in EXCEPTION_APPS:
|
|
continue
|
|
|
|
# Skip system apps and problematic apps
|
|
if app_name in skip_apps:
|
|
continue
|
|
|
|
# Skip apps with certain keywords
|
|
if any(keyword in app_name for keyword in skip_keywords):
|
|
continue
|
|
|
|
# Skip if not a regular app
|
|
if not app.activationPolicy() == 0: # NSApplicationActivationPolicyRegular
|
|
continue
|
|
|
|
print(f"Processing: {app_name}")
|
|
minimize_window_via_applescript(app_name)
|
|
processed_apps.add(app_name)
|
|
time.sleep(0.3)
|
|
|
|
# Handle exception apps that don't respond to Cmd+M
|
|
for app_name, process_name in EXCEPTION_APPS.items():
|
|
# Check if this app actually has visible windows
|
|
# We need to re-check since we removed it from apps_with_windows earlier
|
|
check_script = f'''
|
|
tell application "System Events"
|
|
if exists process "{process_name}" then
|
|
tell process "{process_name}"
|
|
set visibleCount to 0
|
|
repeat with w in windows
|
|
try
|
|
if value of attribute "AXMinimized" of w is false then
|
|
set visibleCount to visibleCount + 1
|
|
end if
|
|
end try
|
|
end repeat
|
|
return visibleCount
|
|
end tell
|
|
else
|
|
return 0
|
|
end if
|
|
end tell
|
|
'''
|
|
try:
|
|
result = subprocess.run(
|
|
['osascript', '-e', check_script],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5
|
|
)
|
|
visible_count = int(result.stdout.strip()) if result.stdout.strip() else 0
|
|
if visible_count > 0:
|
|
print(f"Processing exception app: {app_name}")
|
|
minimize_exception_app(app_name, process_name)
|
|
time.sleep(0.3)
|
|
except Exception as e:
|
|
print(f"Error checking {app_name}: {e}")
|
|
|
|
# FIX #2: Handle Finder windows separately using Finder's native scripting
|
|
# This is more reliable than using Cmd+M
|
|
minimize_finder_windows()
|
|
|
|
print("Done minimizing all windows")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|