A fast, keyboard-first macOS app launcher — a hybrid of Launchpad and Spotlight with pinyin search, archive support, and 0% CPU when idle. Pure Swift / SwiftUI / AppKit. No Electron, no dependencies.
A free, open-source alternative to Spotlight / Alfred / Raycast for users who just want a classic Launchpad-style paged grid with great keyboard support and Chinese pinyin search built in.
- Download
- Quick Start
- Features
- Keyboard & Mouse
- Build from Source
- Project Structure
- Architectural Decisions
- FAQ
- Known Limitations
- Roadmap
- Uninstall
- Contributing
- License
Grab the latest aLaunchpad.zip or aLaunchpad.dmg from the Releases page. Both contain the same universal binary (Apple Silicon + Intel).
aLaunchpad is ad-hoc signed (not notarized by Apple), so the first time you open it macOS will refuse with "Apple cannot verify aLaunchpad is free of malware". To allow it:
- Try to open the app once. It gets blocked — that's expected.
- Open System Settings → Privacy & Security.
- Scroll to the Security section and click Open Anyway next to the aLaunchpad notice.
- Confirm in the next dialog. From then on the app opens normally.
Or, from Terminal:
xattr -dr com.apple.quarantine /Applications/aLaunchpad.app- Download
aLaunchpad.zipfrom Releases, unzip, dragaLaunchpad.appinto/Applications. - Open it once (see Gatekeeper note above).
- Press ⌥ + Space anywhere to toggle the launcher.
- Type to filter — try
wxto find 微信,chrometo find Chrome. - Enter to launch, Esc to dismiss.
That's it. No accounts, no permissions prompts, no background daemon.
- macOS 13 Ventura or later
- Apple Silicon (arm64) or Intel (x86_64) Mac
- Scans all macOS app sources —
/Applications,/System/Applications,~/Applications. Dedupes bybundleIdentifier, falls back to path. - Auto-refresh via FSEvents — installing or uninstalling an app triggers a rescan automatically within ~1 s (the FSEventStream's coalescing latency). No need to relaunch or hit "Rescan Applications" manually. Selection is preserved across rescans if the previously-selected app still exists.
- Floating translucent panel — 90% × 90% centered HUD-blur panel with 10pt rounded corners and proper window shadow.
- Classic Launchpad-style paged grid — fixed 8 columns × 4 rows per page (32 apps) with a dot indicator at the bottom. Page count is computed from the visible app list; only one page → no dots. 88pt icons, fixed 2-line label, row spacing computed dynamically from available height so the grid always fills evenly between the search bar and the dot row.
- Async scanning — runs on a detached background priority task; never blocks the main thread.
- Icon cache — in-memory
NSImagecache keyed by app path; recycled across scroll passes.
- Live filtering as you type, auto-focused on open.
- Pinyin matching —
微信is found by typingweixinorwx. Powered byCFStringTransform(kCFStringTransformMandarinLatin); tokens are precomputed at scan time so every keystroke is just a fewString.containschecks. - Substring + initials — both full pinyin (
wei xin) and initials (wx) match.
- Favorites are pushed to the front of the paged list, so they always occupy the first page. Under Custom sort, favorites and non-favorites each respect the drag order within their own group, so dragging a non-favorite around never reshuffles which apps are pinned to page 1.
- Archive (v1.2) — hover any app icon and click the small
archiveboxbutton in its top-right corner to move it out of the main grid. The 📦 icon next to the sort button opens the archived-apps view (in-place switch, same panel); click the small button again in archive view to restore. Archived apps still appear in main-view search results, rendered dimmed with a small corner badge, and Enter launches them normally. - v1.1 users: the old
Hidemechanism is replaced. ExistingaLaunchpad.hiddendata is migrated automatically toaLaunchpad.archivedon first launch. - All preferences persist to
UserDefaultskeyed underaLaunchpad.*.
- Custom (drag to rearrange) — default for fresh installs (v1.3). Drag any icon to a new slot on the page; the icons currently in the way animate aside to preview the drop. Hold the dragged icon in the leftmost or rightmost 30 pt gutter for ~0.6 s to flip the page so you can drop across pages. On release the icon snaps into the hovered slot and the new order is persisted to
UserDefaultsunderaLaunchpad.customOrder. Newly-detected apps are appended to the global end on every rescan, so a fresh install never reshuffles your layout. - Name (A → Z)
- Name (Z → A)
- Date Added (Newest) — uses filesystem creation date via
URLResourceKey.creationDateKey - Date Added (Oldest)
- The sort toolbar button uses one of five bespoke PNG glyphs (
sort_custom,sort_nameAsc,sort_nameDesc,sort_dateNewest,sort_dateOldest) cropped from a single sprite sheet; tooltip and dropdown labels are locale-aware (Chinese onzh*systems, English otherwise). - Sort selection persists across launches.
- Borderless, non-activating
NSPanelsubclass that still accepts key focus - HUD material (
.hudWindow) backdrop blur + 25% dark overlay - Floats over all spaces and full-screen apps
- 10pt rounded corners matching macOS standard zoomed-window radius
- 90% × 90% of the visible workspace, centered (respects menu bar + Dock)
- Click-through prevention via a non-hit-testing visual layer with a click-eater below
- Outside-click dismissal via
NSEvent.addGlobalMonitorForEvents - Trackpad swipe paging via accumulated horizontal
.scrollWheeldeltas
A persistent menu bar item (▦ SF Symbol) with:
- Open aLaunchpad
- Rescan Applications
- Quit aLaunchpad
A pastel pink-and-blue "a" with scattered rounded squares, packed into the bundle from aLaunchpad/Resources/AppIcon.png at all 10 macOS-required sizes via set_icon.sh. If AppIcon.png is absent, build.sh falls back to a programmatic icon drawn by Scripts/MakeIcon.swift (brushed silver gradient + 3×3 grid) so the project still builds with zero external assets.
- Fresh launch (Dock / Finder / Spotlight) opens the launcher panel automatically.
- Already running: clicking the Dock icon re-opens the panel (
applicationShouldHandleReopen). - Closing the panel (Esc / outside click / app launch) does NOT quit the app — it stays in the menu bar.
- 0.0% CPU when the panel is hidden (verified)
- No timers, no polling, no FSEvents watcher (active only while visible), no network
- Global event monitors are installed only while the panel is visible and removed on hide
- ~150 MB resident memory (typical SwiftUI app + cached icons)
| Key | Action |
|---|---|
| ⌥ + Space | Toggle the launcher panel (global hotkey) |
| ← / → | Move selection by one column; auto-flips the page when crossing the boundary |
| ↑ / ↓ | Move selection by one row |
| ⌘ + ← / ⌘ + → | Jump to the previous / next page |
| Enter | Launch the currently-selected app |
| Esc | Hide the launcher |
| Gesture | Action |
|---|---|
| Click app icon | Launch the app, hide the launcher |
| Right-click app icon | Context menu — Add/Remove Favorites · Archive / Unarchive · Reveal in Finder |
| Hover or keyboard-select an app icon | Small archive / unarchive button floats in its top-right corner |
| Drag app icon (Custom sort only) | Rearrange icons on the page; hold near the left/right edge for ~0.6 s to flip pages and drop across pages |
| Two-finger swipe (trackpad / Magic Mouse) | Flip to the previous / next page |
| Click a dot in the page indicator | Jump to that page |
| Click blank area in launcher | Hide the launcher |
| Click any other app / desktop / Dock | Hide the launcher |
| 📦 icon at right of search row | Toggle the archived-apps view |
| Sort icon at right of search row | Sort menu — Custom (drag) · Name (A→Z / Z→A) · Date Added (Newest / Oldest) |
- macOS 13+
- Xcode Command Line Tools (
xcode-select --install)
git clone https://github.com/mu0072/aLaunchpad.git
cd aLaunchpad
./build.shProduces build/aLaunchpad.app for your host architecture. The script auto-generates the app icon if aLaunchpad/Resources/AppIcon.icns is missing.
UNIVERSAL=1 ./build.shThis is how the official Releases are built.
# Re-pack the bundled AppIcon.png (or your own custom PNG)
./set_icon.sh aLaunchpad/Resources/AppIcon.png
# Or fall back to the programmatic icon
./make_icon.shcp -R build/aLaunchpad.app /Applications/
open /Applications/aLaunchpad.app- Open the app (Dock icon will appear)
- Right-click the Dock icon → Options → Keep in Dock
macOS gives no visual feedback for "Keep in Dock" — verify by quitting the app and checking the icon stays in the Dock.
aLaunchpad/ # project root
├── README.md # this file (English)
├── README.zh-CN.md # Chinese version
├── LICENSE # MIT
├── CONTRIBUTING.md # how to contribute
├── build.sh # one-shot build → build/aLaunchpad.app
├── make_icon.sh # generates AppIcon.icns
├── set_icon.sh # sets the .app's Finder icon
├── Scripts/
│ └── MakeIcon.swift # programmatic icon drawing
├── .github/workflows/
│ └── release.yml # CI: build + publish on `v*` tag
└── aLaunchpad/ # Swift sources
├── Info.plist # bundle config
├── OpenPadApp.swift # @main entry
├── AppDelegate.swift # @MainActor — menu bar + lifecycle
├── Resources/
│ ├── AppIcon.png # 1254×1254 source PNG packed by set_icon.sh
│ ├── AppIcon.icns # generated at build time (gitignored)
│ ├── sort_custom.png # toolbar glyph — Custom (drag)
│ ├── sort_nameAsc.png # toolbar glyph — Name A→Z
│ ├── sort_nameDesc.png # toolbar glyph — Name Z→A
│ ├── sort_dateNewest.png # toolbar glyph — Date Added (Newest)
│ └── sort_dateOldest.png # toolbar glyph — Date Added (Oldest)
├── Models/
│ ├── AppItem.swift # value-type app model + dateAdded
│ └── Pinyin.swift # CFStringTransform + SearchTokens
├── Services/
│ ├── AppScanner.swift # async filesystem scan + dedup
│ ├── AppLauncher.swift # NSWorkspace async/await launch
│ ├── AppFolderWatcher.swift # FSEventStream → auto-rescan
│ ├── HotkeyManager.swift # Carbon ⌥Space global hotkey
│ └── IconCache.swift # @MainActor NSImage cache
├── ViewModels/
│ ├── LauncherViewModel.swift # @MainActor MVVM center
│ └── DragController.swift # drag/drop state for Custom sort (v1.3)
├── Views/
│ ├── ContentView.swift # root view + click-eater layers
│ ├── AppGridView.swift # paged 8×4 grid + dynamic row spacing
│ ├── AppIconView.swift # icon + label + selection ring + ctx menu
│ ├── SearchBar.swift # top-centered search field
│ ├── SortMenuButton.swift # ↕ icon menu
│ ├── ArchiveToggleButton.swift # 📦 icon, toggles archive view
│ └── VisualEffectView.swift # NSVisualEffectView bridge
└── Window/
└── WindowManager.swift # LauncherPanel + event monitors
Architecture is strict MVVM:
- Models — immutable value types, no side effects, testable in isolation
- Services — system boundaries (filesystem, NSWorkspace, Carbon), individually mockable
- ViewModels —
@MainActorObservableObject, the only mutable state holder - Views — pure SwiftUI rendering; all events route up through closures
SwiftUI's Window and WindowGroup scenes don't expose enough panel configuration on macOS 13. We need:
- Non-activating (clicks don't tear focus from the underlying app's editor)
- Can become key (so the search field accepts input)
- Floats above full-screen apps
- Borderless with custom-clipped corners
A NSPanel subclass (LauncherPanel) with NSHostingView<ContentView> is the only reliable path.
NSEvent.addGlobalMonitorForEvents fires only for events that happen outside the receiving app's process. That's exactly the semantics we want for outside-click dismissal — no risk of false-positives from in-app menus, popovers, or right-clicks. windowDidResignKey would over-trigger on any focus change including transient menus.
┌─ VisualEffectView (allowsHitTesting=false) — visuals only, never eats clicks
├─ Color (opacity 0.0001 + onTapGesture) — click-eater, dismisses on tap
└─ VStack { searchRow, AppGridView, ... } — real controls
SwiftUI gesture priority puts child gestures (icon .onTapGesture, search field, sort menu, dot indicator) above the parent click-eater, so clicks that land on a real control go to that control, and only blank-area clicks fall through to the dismiss handler.
Earlier versions wrapped the grid in a ScrollView. Once we matched classic Launchpad with a fixed-grid paged layout, scrolling became redundant — every visible row is a real row, and the user can flip pages with arrow keys, ⌘←/→, two-finger swipe, or the dot indicator. Removing the scroll view also removed a layer of click swallowing inside the panel.
LazyVGrid only takes a static spacing: parameter, so a fixed value leaves an obvious gap below the last row when the grid is shorter than the available area. The grid now reads geo.size.height from a GeometryReader, knows the rendered cell height (icon 88 + label 30 + paddings = 138pt), and computes spacing = (height − rows × cellHeight) / (rows − 1) clamped to [8, 48]. The cell uses a fixed-height label frame so every row really is the same height.
NSEvent.addGlobalMonitorForEvents only fires when the app is not key — useless for "press ⌥Space anytime, even when focused on Safari, to open the launcher." Carbon's RegisterEventHotKey is the only sanctioned macOS API for true global hotkeys without accessibility-permission prompts.
The shipped icon lives at aLaunchpad/Resources/AppIcon.png and is packed into the bundle at build time. It's the only binary asset in the repo. The reason Scripts/MakeIcon.swift still exists is so the project always builds — if the PNG is removed or replaced and you forget to commit it, build.sh automatically generates a placeholder programmatic icon (brushed silver + 3×3 grid) so the bundle is never broken.
Q: How is this different from Spotlight / Alfred / Raycast? A: aLaunchpad does one thing — launching installed apps — with a classic Launchpad-style paged grid. No file search, no calculator, no web search, no plugins. If you only ever used Spotlight to open apps, this replaces that with a faster keyboard flow and pinyin support, at zero idle CPU.
Q: Does it support pinyin search?
A: Yes. Type wx to find 微信, chrome to find Chrome. Tokens are precomputed at scan time via CFStringTransform, so search is instant regardless of how many apps you have.
Q: Does it run on Intel Macs?
A: Yes. Release builds are universal (arm64 + x86_64). Local ./build.sh builds for your host architecture only by default; use UNIVERSAL=1 ./build.sh for both.
Q: Why 0% CPU when hidden? A: No timers, no polling, no background scanning. The FSEvents watcher and global event monitors are installed only while the panel is visible and removed on hide. Idle cost is essentially the menu bar item.
Q: Will it ask for Accessibility / Full Disk Access permissions?
A: No. The Carbon global hotkey API doesn't require Accessibility, and app scanning uses public Bundle / filesystem APIs on standard /Applications paths.
Q: How do I remove the "Apple cannot verify" warning? A: See First launch (macOS Gatekeeper). Short version: open System Settings → Privacy & Security → Open Anyway.
Q: Can I change the global hotkey from ⌥Space? A: Not yet — see Roadmap. PRs welcome.
| Limitation | Workaround |
|---|---|
| Not notarized by Apple | Allow the first launch in System Settings (see Gatekeeper) |
| No hotkey conflict UI | If ⌥Space is taken by another app, registration silently fails; menu bar still works |
| Page size hard-coded 8×4 | No preference panel yet to change columns / rows per page |
| No usage-frequency ranking | All apps weighted equally; no MRU sort |
| Single screen | Uses NSScreen.main; multi-monitor layouts always show on the primary |
- Custom hotkey UI — Preferences pane to rebind from ⌥Space
- Multi-monitor — show on the screen containing the cursor
- Icon LRU + disk cache — bound memory on machines with hundreds of apps
- Usage-based ranking — surface frequently launched apps first
- Configurable page grid — preference to change columns/rows per page
- Developer ID signing + notarization — drop the Gatekeeper prompt
- Tests — unit tests for
AppScanner,SearchTokens.matches,LauncherViewModel - Localization — extract hard-coded Chinese UI strings into
Localizable.strings
v1.3 shipped drag-to-reorder + drag-between-pages (Custom sort). v1.2 shipped Archive. See Releases for the full history.
pkill -x aLaunchpad
rm -rf /Applications/aLaunchpad.app
# Remove preferences (favorites, archive, sort, etc.)
defaults delete local.alaunchpad.app 2>/dev/null
# Remove from Launch Services database
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister \
-u /Applications/aLaunchpad.app 2>/dev/nullPRs and issues welcome. See CONTRIBUTING.md for setup, style, and PR conventions.
Built with macOS native frameworks only:
- SwiftUI (UI)
- AppKit (
NSPanel,NSVisualEffectView,NSWorkspace,NSStatusItem,NSHostingView) - Foundation (
Bundle,URLResourceKey,UserDefaults) - Carbon HIToolbox (
RegisterEventHotKey) - Core Foundation (
CFStringTransformfor pinyin)
No third-party dependencies.
Released under the MIT License.