
One of my biggest frustrations after spending years on Windows was the loss of a proper window switcher when I moved to macOS. Cmd-Tab switches between applications, not windows. If you have four Terminal windows open, you cannot directly jump to a specific one β you have to Cmd-Tab to Terminal, then use Cmd-` to cycle through its windows. It is a two-step dance every time.
So I built AltTab β a lightweight, zero-dependency macOS utility that brings back the Windows Alt-Tab experience using Option-Tab.
What It Does
Hold Option and tap Tab β a panel appears showing thumbnails of every open window across all your apps. Cycle through them with Tab / Shift-Tab or the arrow keys, then release Option to jump straight to the selected window. Click any thumbnail to switch instantly.
| Shortcut | Action |
|---|---|
Option-Tab |
Open switcher / next window |
Tab |
Cycle forward |
Shift-Tab |
Cycle backward |
β / β |
Navigate left / right |
Escape |
Cancel |
Enter |
Confirm and switch |
| Click | Select and switch immediately |
Minimized windows are included and will automatically unminimize when selected. No Dock icon, no clutter β just a menu bar icon.
Installation
Requirements: macOS 13 Ventura or later Β· Full Xcode installation
One-liner install
bash
git clone https://github.com/sergio-farfan/alttab-macos.git
cd alttab-macos
./build.sh install
open ~/Applications/AltTab.app
Then open System Settings β Privacy & Security β Accessibility and grant permission to AltTab. That is all β press Option-Tab and it just works.
Build options
bash
./build.sh build # Build Release binary only
./build.sh install # Build and install to ~/Applications
./build.sh install --system # Build and install to /Applications (requires sudo)
./build.sh run # Build and launch immediately
./build.sh uninstall # Remove the installed app
Permissions
-
Accessibility (required) β needed to intercept the
Option-Tabhotkey and raise windows - Screen Recording (optional) β enables live window thumbnails; gracefully falls back to app icons if denied
How It Works Under the Hood
Building this taught me a lot about low-level macOS APIs. Here are the most interesting technical pieces.
1. Global Hotkey Detection via CGEvent Tap
The most fundamental challenge: how do you intercept Option-Tab system-wide, in every app, without stealing keyboard focus?
The answer is a CGEvent tap at the session level:
swift
let tap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(
(1 << CGEventType.keyDown.rawValue) |
(1 << CGEventType.flagsChanged.rawValue)
),
callback: eventTapCallback,
userInfo: Unmanaged.passRetained(self).toOpaque()
)
This sits in front of every application and intercepts keyboard events before they are delivered. A simple 3-state machine (idle β active β idle) tracks whether the switcher is open. When the event is one we want to handle (Tab, arrows, Escape), we swallow it by returning nil. Everything else passes through untouched.
One subtle detail: we never swallow flagsChanged events (modifier key state). If we did, the system would get confused about which modifiers are held down.
Re-enable polling: macOS can disable your event tap if it is too slow or if the user is typing quickly. A 2-second timer watches for this and re-enables the tap. If the switcher was open when the tap was disabled, we force-cancel it β this prevents the panel from sticking on screen.
2. Discovering Every Window (Including Minimized Ones)
This was surprisingly tricky. No single API gives you a complete list.
On-screen windows come from CGWindowList:
swift
let list = CGWindowListCopyWindowInfo(
[.optionOnScreenOnly, .excludeDesktopElements],
kCGNullWindowID
)
But minimized windows are not in this list. For those, you have to query each application via AXUIElement:
swift
let appElement = AXUIElementCreateApplication(pid)
var windowsRef: CFTypeRef?
AXUIElementCopyAttributeValue(appElement, kAXWindowsAttribute as CFString, &windowsRef)
// Then check kAXMinimizedAttribute on each window
Bridging AXUIElement back to a CGWindowID requires a private SPI:
swift
@_silgen_name("_AXUIElementGetWindow")
func _AXUIElementGetWindow(_ element: AXUIElement, _ windowID: inout CGWindowID) -> AXError
Yes, it is undocumented. But it is the same approach used by Alfred, Raycast, and other window managers β well-established in practice. The combined result is a complete, deduplicated window list.
3. Most-Recently-Used Window Ordering
Windows are shown most-recently-used first. Tracking this correctly is harder than it sounds.
NSWorkspace.didActivateApplicationNotification tells you which app is in front β but not which window within that app. To track intra-app focus changes (like Cmd-` between Terminal windows), I install a per-app AXObserver:
`swift
AXObserverCreate(pid, axObserverCallback, &observer)
AXObserverAddNotification(
observer!, appElement,
kAXFocusedWindowChangedNotification as CFString,
Unmanaged.passRetained(self).toOpaque()
)
CFRunLoopAddSource(CFRunLoopGetMain(),
AXObserverGetRunLoopSource(observer!), .defaultMode)
`
Each callback fires when the focused window changes within an app and promotes that window to the front of the MRU list.
4. The Non-Activating Panel Trick
The switcher UI is an NSPanel with .nonactivatingPanel in its style mask. This is critical.
A regular NSWindow would steal keyboard focus when shown, making Option-Tab act on AltTab itself rather than the target window. With .nonactivatingPanel, the panel appears on screen but the previously active app retains focus β so when you release Option, the system activates the window you selected, not AltTab.
`swift
let panel = NSPanel(
contentRect: .zero,
styleMask: [.borderless, .nonactivatingPanel],
backing: .buffered,
defer: false
)
panel.level = .floating
panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
`
5. Live Thumbnails with ScreenCaptureKit
On macOS 14+, I use SCScreenshotManager for high-quality async captures:
`swift
let image = try await SCScreenshotManager.captureImage(
contentFilter,
configuration: config
)
`
On macOS 13, it falls back to the synchronous CGWindowListCreateImage on a background queue. If Screen Recording permission is denied, it silently shows the app icon instead. No crash, no prompt β just graceful degradation.
Project Stats
- ~2,000 lines of Swift across 9 source files
- Zero external dependencies β pure Swift + AppKit
- MIT licensed β fork it, modify it, ship it
- macOS 13+ (Ventura, Sonoma, Sequoia)
Try It Out
The code is on GitHub: github.com/sergio-farfan/alttab-macos
If you have been frustrated by macOS window switching, give it a try. And if you are curious about the low-level macOS APIs β CGEvent taps, AXUIElement, ScreenCaptureKit β the codebase is small enough to read in an afternoon.
Feedback, issues, and PRs are welcome!
United States
NORTH AMERICA
Related News
CBS News Shutters Radio Service After Nearly a Century
3h ago
Officer Leaks Location of French Aircraft Carrier With Strava Run
3h ago
White House Unveils National AI Policy Framework To Limit State Power
3h ago
Microsoft Says It Is Fixing Windows 11
3h ago
Can Private Space Companies Replace the ISS Before 2030?
3h ago
