Technical Documentation: lwouis/alt-tab-macos
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
Overview (`src/windowserver/README.md`)
windowserver — the physical window plane
This layer tracks the facts the WindowServer (SkyLight/CGS) owns. It does not track attention. The split
is deliberate: the WindowServer remains available when an app is wedged and is authoritative for physical
window state, while AppKit is the only component that knows which window inside a process is key. The
attention plane is documented in ../window-tracking/AttentionOrderSpecs.md.
The boundary (what comes from where)
- Physical plane → WindowServer. Existence, geometry, z-order/level, Space membership, ordered-in,
minimize, fullscreen, and a fallback title come from SLS events plus batched queries. Notification 808 is
an order change, not focus, and cannot move MRU. - Semantic plane → one app-level AX observer per process. Focused/main-window notifications and title
changes are push signals. There is no observer per window. AX reads remain for discovery-time subrole,
user-visible title, tab detection, the initial per-app focus seed, and the one factless activation. AX
actions (raise/minimize/close/fullscreen) also remain. Focused/main selects a known group's representative;
it does not prove full tab membership, which remains a composite of AXTabGroup reads and physical fallback. - Window order → the attention model.
NSWorkspacenames the front process; AX, the type-13 cross-app
click, and AltTab's own intent name a window inside a process. Only a committed attention decision or a
structural repair may write MRU. - Accessibility permission is still required — focusing another app's window is permission-gated
regardless of AX-vs-SLS. The win here is reliability and no observer churn, not fewer permissions.
The pieces
| file | kind | role |
|---|---|---|
../events/WindowServerEvents.swift |
impure | installs the SLS notify-proc tap; the physical-state source |
WsEventRouting (triad) |
pure | WindowServer notification id → the model action it implies |
WsWindowState (triad) |
pure | decode raw SLS fields (attrs/level/spaceMask) → on-screen/fullscreen/app-level |
WindowAcquisitionPolicy.swift |
pure | names the two AX-element acquisition routes (current-Space vs other-Space) |
WindowServerQuery.swift |
impure | the "one big SLS call": batch-query the WindowServer → [WsRawWindow] |
Why there is no wid → AXUIElement shortcut (and what we do instead)
The AX↔wid bridge is strictly one-directional (_AXUIElementGetWindow is element→wid, a Mach MIG call
resolved by the target app; there is no reverse routine, no window-by-id parameterized attribute, and the
remote token carries an opaque app-internal id, not a wid — all RE-confirmed). So an AX element for an
other-Space window can generally only be obtained by enumerate-and-match (the_AXUIElementCreateWithRemoteToken brute-force). Elements are therefore acquired lazily and cached. The
periodic inventory groups every missing wid by process: one batched attribute read resolves its current-Space
subset plus the app's key/main window — kAXFocusedWindow and kAXMainWindow are the two window attributes
AppKit does NOT put behind the Space filter, so they name an other-Space root for free — then one targeted
brute-force shares a 250ms budget across whatever is left. Exact event-driven discovery still asks for one
wid. A window backed only by an exact attention signal plus its WindowServer row can still be shown and
focused; actions needing an AX element self-heal after acquisition succeeds.
There is one route that is not a lookup at all. Every AX notification arrives holding the element of the
window it is about, and nothing in AppKit's posting path consults a Space, so a window kAXWindows hides
still hands its element over when the app announces it (measured cross-process).Applications.applyObservedElement adopts that element for a window
that has none, after a role check — a notification may name a descendant, whose wid is its window's. It is a
push, so it only reaches windows that speak: it shrinks the brute-force population rather than replacing it.
Pure vs impure
Pure kernels are co-located triads (Foo.swift + FooSpecs.md + FooTests.swift), compiled into both the
app and unit-tests targets, holding no state and doing no IPC. Impure executors (the tap, the query, the
acquisition) do the IPC and are verified at runtime. Document design in the Specs.md, not code comments.
Acknowledgments (`docs/acknowledgments.md`)
Acknowledgments
These third-party libraries are used:
ShortcutRecorder
Website - CC 4.0 License
Contributing (`docs/contributing.md`)
You can suggest an enhancement or discuss an issue on github, or use the feedback form in the app.
Technical overview
This document gives a technical overview of the project, for newcomers who want to contribute.
Building the project
This project has minimal dependency on Xcode-only features (e.g. InterfaceBuilder, Playgrounds). You can build it by doing:
scripts/codesign/setup_local.shto generate a local self-signed certificate, to avoid having to re-check theSystem Preferences > Security & Privacypermissions on every build- Either open
alt-tab-macos.xcodeprojwith Xcode, or use the CLI:xcodebuild -project alt-tab-macos.xcodeproj -scheme Debugto build the .app with theDebugbuild configuration
Mac development
Mac development ecosystem is pretty terrible in general. They keep piling on the tech stacks on top of each other, so you have C APIs, ObjC APIs, Swift APIs, Interface builder, Playgrounds, Swift UI, Mac Catalyst. All these are bridging with each other with a bunch of macros, SDKs glue, compiler flags, compatibility mode, XCode legacy build system, etc. For alt-tab, we are on Swift 5.0. Note that swift just recently started being stable, but overall any change of version breaks a lot of stuff. Swift itself is the mainstream language with the worst governance I’ve seen in modern times.
Regarding SDKs, it’s very different from other (better) ecosystems like Java. Here the SDK is bundled with XCode, and XCode is bundled with the OS. This means that from a machine running let’s say macOS 10.10, you have access to only a specific range of XCode versions (you can’t run the latest for instance), and these give you access to a specific range of SDKs (i.e. Swift + objc + c + bridges + compiler + toolchain + etc)
Documentation is abysmal. Very simple things are not documented at all, and good information is hard to find. Compared to other ecosystem I’ve worked on in the past like Android, nodejs, Java, rust, this is really a bad spot. You can truly tell Apple doesn’t care about supporting third-parties. They are in such a good position that people will struggle and just push through to deliver on their ecosystem because it is so valuable, and because they don’t have to care, they don’t. They could pay an intern to update the docs over the summer for instance, just to give you context of the lack of care we are talking about here.
Dependencies in this project are vendored under vendor/ and consumed as local Swift Package Manager packages. Each dependency has an update_*.sh script under vendor/scripts/ that re-fetches it from upstream, strips unused files, and regenerates its Package.swift. There is no remote dependency resolution at build time — git clone && xcodebuild is the full bootstrap.
OS APIs are quite limited for the kind of low-level, system-wide app AltTab is. This means often we just don’t have an API to do something. For instance, there is no API to ask the OS “how many Spaces does the user have?” or “Can you focus the window on Space 2?”. There are however, retro-engineered private APIs which you can call. These are not documented at all, not guaranteed to be there in future macOS releases, and prevent us from releasing AltTab on the Mac AppStore. We have tried our best to document the ones we are using, next to the code that calls them.
Project architecture
To mitigate the issues listed above, we took some measures.
We minimize reliance on XCode, InterfaceBuilder, Playground, and other GUI tools. You can’t cut the dependency completely though as only XCode can build macOS apps. The whole UI, including the menubar, is built in code (no xib). Currently, the project has these files:
alt-tab-macos.xcodeprojfile describing AltTab itself. It contains some settings for the appalt_tab_macos.entitlementsandInfo.plistwhich are static files describing some app config for XCodevendor/holds vendored open-source libraries (Sparkle, ShortcutRecorder, AppCenter) as local SPM packages.vendor/scripts/update_*.shrefresh them from upstream- Some
.xcconfigfiles inconfig/which contain XCode settings that people typically change using XCode UI, but that I want to be version controlled
The project directory is organized in the following way:
| Path | Role |
|---|---|
config/ |
XCode build settings (.xcconfig files) |
docs/ |
supporting material to document the project |
resources/ |
files that are shipped inside the final .app (e.g. icons) |
scripts/ |
bash scripts useful for CI and local workflows |
vendor/ |
vendored open-source libraries, consumed as local SPM packages |
src/ |
Swift source code |
The src/ folder groups files by feature, so files that change together live together. Each folder typically contains the implementation, and for the parts we want to pin down, a triad of Foo.swift + FooSpecs.md (specs) + FooTests.swift (unit tests):
| Path | Role |
|---|---|
src/switcher/ |
the switcher itself: its window, tiles, state, appearance, shortcuts (the core feature) |
src/windowserver/ |
the WindowServer event pipeline feeding the list of windows |
src/events/ |
incoming events we listen to (keyboard, mouse, cursor, Dock, AX queries, etc.) |
src/macos/ |
wrappers around macOS APIs (AX/CGS call schedulers, permissions, login item); src/macos/api-wrappers/ holds the retro-engineered private-API signatures |
src/preferences/ |
user settings: storage, migrations, and the Settings window |
src/pro/ |
the paid "Pro" features (license, scheduling, related UI) |
src/secondary-windows/ |
windows other than the switcher (feedback, permission, debug) |
src/kit/ |
reusable AppKit building blocks (custom buttons, views, controls) |
src/util/ |
generic helpers (background work, throttling, scheduling policy) |
src/api/ |
client for our backend (license and feedback endpoints) |
src/vendors/ |
glue code for vendored libraries (AppCenter, Sparkle, ObjC exception catcher) |
src/debug/ |
benchmarking and QA tooling |
src/_test-support/ |
mocks and helpers shared by the unit tests |
Other folders/files are either tooling or auto-generated (e.g. DerivedData/ is generated by Xcode builds).
QA
alt-tab-macos is deeply integrated with the OS and other apps. Thus doing end-to-end automated QA would be a nightmare. For the time being QA is done manually.
In an attempt to not have too many regressions, this documents will list OS interactions. This should be useful as some of them are very exotic and not many people know about them.
List of use-cases
Which windows to list, and be able to focus
- Minimized windows
- Windows from hidden apps
- Windows of fullscreen apps
- Windows of fullscreen apps with split-screen
- Windows merged into 1 as tabs (e.g. Finder "Merge All Windows", drag-and-drop a window onto an existing window, etc)
- Windows on multiple monitors
- Windows on multiple Spaces
- Should not show: dialogs, pop-overs, context menus (e.g. Outlook meeting reminder, iStats Pro menus)
App is summoned during an OS animation
- The UI should only appear after the animation completes for:
- Space transition
- an app going fullscreen
- The UI should not show at all (i.e. ignore the shortcut) if Mission Control is open
- The UI should show instantly during:
- Window minimizing/de-minimizing
- Window maximizing (i.e. double-click the titlebar)
- An app is launching/quitting
Thumbnail layout corner-cases
- Very small windows (i.e. smaller than the thumbnail min size)
- Very wide/tall windows
- Should show the app name for windows without a title
- Long titles should be truncated
- Many windows are opened
- There is no open window
- AltTab should appear on top of all windows, dialogs, pop-overs, the Dock, etc
OS events to handle while AltTab’s UI is shown
- An app is launching/quitting
- A new window opens
- An existing window is closed
Drag-and-drop on top of the thumbnails
- Drag-and-dropping a URL onto a window thumbnail should open it with that window’s app
- Drag-and-dropping a file onto a window thumbnail should open it with that window’s app
System Preferences
- General > Appearance > "Dark": switches to Dark Mode
- General > Accent color > "Graphite": traffic lights on thumbnails should be gray
- Accessibility > Display > Reduce transparency: AltTab background should be a solid color
- General > Show scroll bars > "Always": regenerates all scrollbars
- Display > Resolution > Scaled: changes DPI and rescale AltTab
- Mission Control > "Displays have separate Spaces": changes Spaces behavior on multi-displays setups
Spaces
- Spaces get created/destroyed
- A window is moved to another space by drag-and-dropping on the Spaces thumbnails at the top of the Mission Control UI
- A window is moved to another space by dragging it on the side of the current Space, and waiting for a Space transition, then dropping it
- A window is moved to another space by destroying the Space it is in
- An app is assigned to a specific space or all spaces by clicking its Dock icon > Options > Assign to
Shortcuts
- The hold "key" can be multiple modifiers (e.g.
⌥⇧) - Shortcuts should have priority over system shortcuts such as
cmd+tab, so the user can replace these - The "select next window" shortcut can be modifiers, modifiers+key, or just key; it can also contain the same modifiers as the hold "key"
- All shortcuts, except the hold key, can be disabled by the user
- Shortcuts can include the
escapeanddeletekey; these should not stop recording shortcuts - Secure Input can prevent AltTab from listening to the keyboard
- Some shortcuts should only work when AltTab is open
- These shortcuts should active whether the hold shortcut is held or not
- Shortcuts should work with capslock active or inactive
- Shortcuts should repeat if kept pressed
- Repeat rate and initial delay should match the values set in
System Preference>Keyboard - when navigating left/right/up/down, the repeating behavior should stop when hitting the last window in the list. The user can then manually do the shortcut once more to cycle to the other side; it then repeats again
- Repeat rate and initial delay should match the values set in
- The shortcut sets 1 and 2 should not interact with each other (e.g. opening AltTab with one, then using the other to navigate)
- Shortcuts can focus the window on release, or be pressing a key or using the mouse
- Keyboards from other countries have different layout which impact shortcuts
- e.g. the default
⌥`shortcut should become⌥<on a Spanish ISO keyboard
- e.g. the default
Localization
- Right-to-left languages (e.g. arabic) have the whole layout reversed
- For the main window, even navigation is reversed
- For preferences and feedback windows, all layout is reversed
- Text length can vary per languages which can create layout issues
Misc
- AltTab is launched after some apps/windows are already opened
- Displays/mouses/trackpads/keyboards get connected/disconnected while AltTab is used
- Sudden Termination
Contributors (`docs/contributors.md`)
[Developed the app](https://github.com/lwouis/alt-tab-macos/graphs/contributors)
adamnemecek, AfzalivE, akx, Allsochen, ayroblu, britalmeida, Calinou, cdbattags, damonpam, decodism, delaneyb, gingerr, GrzegorzKazana, hughlilly, itayelgard, JakkuSakura, johnsontsai11, karbassi, L1cardo, lwouis, metacodes, mnin, MrPaschenko, nella17, Rjevski, ShlomoCode, SimplyYourAverageDev, stopdesign, zacharee
Localized the app
73, A, a774 nn, Aamirsuleman, Aarni Koskela, Abdulelah, Abdur-Rahman Bilal, Aden Aziz, Admin, Aindriú Mac Giolla Eoin, Ajaykumarmkaju, Aku, Albert Abdilim, Aleksander Bang-Larsen, Alex, Alexander Kilian, Alexey Eremeev, Ali, Ali Gokmen, Ali. tas103, Allen Guan, Alonso Chen, Ameng, Anders, Andreas Wagner, Andrej Koman, Andrew Vader, antoon334, Anurag Roy, anushree b, Arman, Artem Stankov, Arthur, Ash, blanorama, bluefirex, BO the Man, bovirus, Bufo, BV Joshi, caduellery, Caner İlhan, catcore, changanmoon, Christian Keilmann, Chun Fei Lung, codecantata, codingalone, Cong Tri, Dan, Dan84, Darius Rosendahl, Darko, David R, Denis, Dékány Martin, dibas-np, Didier Deschrijver, Dzhuneyt, edu_sombra, EDUARDO, Eimantas, Eliezer Shpigelman, Emir H. KAYNAK, EP45, Eric WANTZ, Ersagun Kuruca, EUKA, Eukarya, Eyelly wu, fil, Filipe, Frangarciasalomon, Frank, Friendship1, Fukuda, Ganesh Sawant, Gezimos, Giacomo Volpi, Giang, Github, Gkostov, Grzegorz Kazana, Guillaume, Gumpanath Puttarassu, hann-solo, Haoshuai Xu, Hao豪, Heramb Lonkar, Hjörtur Hjartarson, Hokuto Kato, Huandngoc, Huynh Sang, Ialiendeg, Ida Bomholt Dyrholm Jacobsen, Igor Aradski, igorpocta, Indexerrowaty, Inês Almeida, isametry, Ismatulla, Isthereanybody, Jaeyong Sung, Jakob, Jakob Licina, Jakub Bartušek, Jesus Angel Villafuerte, jiho4, Jisan, Joao Vitor Casarin, Jord Nijhuis, Jui Yuan Liu, Jules Beckers, Julian Nowaczyk, Julio, justcontributor, Kagurazaka Tsuki, kal, kant, Kawarimidoll+git, kazuki sakamoto, Kevinsevinche, KillianG, Kinyagulovrr, Klara, Kostiantyn Ko, Kristófer Fannar Björnsson, Kushnee5, Kuuchi, Lakshman Kolappan, Lars, Lasse Mattila, Lcwhhh, Leo, Lester, Logan Boissy, Loic. peron, Loïc, LostInCompilation, Lumaxis, lwouis, M, Malo, Maplevantablack, Marc Pla, Marcus Billman, Marekscholle, Marko McLion, Martin Mitka, Martin. mitka, Masayoshi Motojima, Masih, Matheus, Max, Maximilian Falk, MaximilianFreitag, Mehmet Ali Ertorer, Meriç Bağlayan, Michael, Milos M, Mohammad Al Zouabi, mohma, Mohsin Rahman, Mr. axel. bock, mrtbts, MuDraconis, Muzhenstudent, Mwolfinspace, Nathancodes, NIKHIL S, Nikola Rajić, Nils Fahldieck, Nilton Souza, Nmolham, Nuriddin Islamov, Ori, Paul Pichaureau, Paul Przybyszewski, Paul. pichaureau, Pehovorka, Pendolino, Petar Shomov, Peterkim0620, Petr Kolář, Petter Johan Olsen, Piotr Budny, Quentin. douarre, Rajesh Kaswala, rajuneon, Rami Elsawy, Raphaël, Rasmus, Raymonf, raz, Razvan, rbnis, rei fujise, Roccobot, Roccobot, Rodrigo Schneider, Ron Nuss, rPaasch, Ryan Nardi, sa-ha, Sai Khant Zay Lynn Yaung, Sami Suleiman, Samimkel00, Saransh, sawtooth, Sebastian, Selcuk Dursun, Sergey, Serhii Alekseev, Seyedparsa Mirtaheri, Shameem Reza, SheNeVmerla, Shivam Bansal, shlomo, Sippawit Thammawiset, Sjur Moshagen, Sjur.n. moshagen, Stan Smits, star-affinity, Stefan, Stefan Lange, Svetoslav Stefanov, Tamilselvan0x0, Thebennue, Thomas Wölk, Thorsten, Thorsten, Tomoa Nozawa, Trevor, Tsilimpotis Dimitrios, Umutakkaya1996, Vadym, Vijayant Saini, Vincent Orback, Vlad, Webmaster, Wesley Matos, Whatsmine-HaoshuaiXu, White Vogel, Wilhelm Wolfgang Gärtner, Yongsung Yoon, Yorben, Yossi Zahn, ysaito, Yukai, yumechan, Yusuf Caliskan, Zamaialexander, Zuhaib Syed, Τσιλιμπότης Δημ., Андрій Кирбаба, Ивайло, Пустой, 林清, 橙梓
--- METRICS ---
- Files Extracted: 5
- Estimated Token Budget: ~5390 tokens
- Canonical Reference: https://codewiki.google/github.com/lwouis/alt-tab-macos