Files
ghostty-rs/docs/rewrite/notes/macos-app-MA1.md
T
ddidderr 4861015105 feat(macos): foundation of the macOS app ported from Swift to Rust
Phase 9, MA1: crates/ghostty-macos (AppKit via objc2; empty stub on
non-Apple targets) ports the foundation of Ghostty 1.3.1's Swift macOS
app (ghostty/macos): main, AppDelegate (+Ghostty) with lifecycle, dock
menu, open-file, notifications, shortcut sync and quit/reopen paths;
MainMenu.xib as generated menu tables built into NSMenus; the
Ghostty.App/Config/Surface wrappers driving libghostty in-process via
ghostty-lib; the generic helpers; scripts/macos-bundle.sh to assemble
Ghostty.app (dry-run on Linux). Feature areas are stubbed for MA2-MA5.
Design and type mapping: docs/rewrite/notes/macos-app-design.md
(App Intents is a Swift-only framework and cannot be ported).

Compile-checked only (no macOS here): just check-macos passes for
aarch64/x86_64-apple-darwin with -D warnings; 125 platform-independent
tests run on Linux.

Test Plan:
- just check-macos
- cargo test -p ghostty-macos
2026-10-12 19:25:44 +02:00

3.8 KiB

macOS app, MA1: foundation

Design and work split: macos-app-design.md. Crate crates/ghostty-macos (lib ghostty_macos, bin ghostty-macos-app; empty stub off Apple). Compile-checked only (just check-macos, aarch64 + x86_64, clippy -D warnings, lib + tests + bin); never run on a Mac. Platform independent logic is unit-tested on Linux (125+ tests).

Ported (Swift -> Rust)

  • App/macOS/main.swift -> app/entry.rs; AppDelegate.swift -> app/app_delegate.rs (all delegate methods, lifecycle, dock menu, open-file with execute confirmation, quit confirmation incl. shutdown/restart AppleEvent check, SIGUSR2 reload, local key monitor, bell/dock badge, user notifications via objc2-user-notifications, appearance KVO, config-change handling, menu shortcut sync + performGhosttyBindingMenuKeyEquivalent, float-on-top, set-as-default-terminal, validateMenuItem, toggle visibility); AppDelegate+Ghostty.swift -> app/app_delegate_ghostty.rs.
  • MainMenu.xib -> app/menu_spec/ (tables generated by scripts/gen-macos-menu-spec.py, plus Outlet, SYNCED_SHORTCUTS, MENU_IMAGES, MenuShortcutKey, dock menu; tested) and app/main_menu.rs (builds the NSMenus, system menus, NSFontManager).
  • Ghostty.App/Config/ConfigTypes/Command/Action/Error/Event/Shell/Surface, GhosttyPackage, NSEvent+Extension, top of Ghostty.Input, SurfaceConfiguration, a model-only SurfaceView -> ghostty/* (by forks F1-F3 of MA1; see design doc).
  • Helpers/* generic ones -> helpers/*; unportable SwiftUI-only helpers listed in the design doc.
  • scripts/macos-bundle.sh (Ghostty.app assembly, dry-run off macOS), scripts/gen-macos-capi-consts.py, scripts/gen-macos-menu-spec.py, scripts/macos-crosscheck.sh now includes ghostty-macos (+ its bin).
  • Stubs with the surface AppDelegate/Ghostty.App use (owners replace them): features/terminal (MA3), quick_terminal::controller, settings, about, secure_input, global_keybinds, services, custom_app_icon (MA4), update (MA5).

Deviations

  • Menu outlets: the xib's menuSelectionForSearch outlet does not exist in Swift (and "Jump to Selection" is unconnected), so those two items never get a config shortcut -- kept as upstream (module docs of menu_spec).
  • NSApplicationMain + nib -> explicit NSApplication/delegate/menu setup in entry::run (no NSMainNibFile).
  • application(_:willEncode/didDecodeRestorableState:): quick terminal state not encoded yet (TODO(MA4)).
  • updateAppIcon: AppIcon persistence is MA4's; only the distributed iconDidChange notification is posted.
  • Fork-reported deviations: Ghostty.Config.backgroundColor falls back to a fixed light gray (Swift: dynamic windowBackgroundColor); window_fullscreen* getters return raw strings until MA3 (typed FullscreenMode); open_config passes the byte length of the URL (Swift String.count truncates non-ASCII paths); desktop notifications hop to the main queue before touching the view; CodableBridge archives are not byte-compatible with Swift's; ExpiringTarget drop only invalidates the timer; MIME->UTI lookups use CoreServices C functions; tab-add error is ObjCException{name,reason}; PasteboardHandle is an identity usize; user-visible duration format "1 hr, 2 min, 3 sec" matches Foundation en_US abbreviated style but the .hide fractional rounding is unverified off macOS.

Known gaps / TODO(integration)

  • Nothing here has run on a Mac; objc2 method signatures were only type-checked.
  • TODO(MA2) in ghostty/surface_view.rs/surface.rs, TODO(MA3) terminal stubs and fullscreen::set_is_tab_bar_hook, TODO(MA4) feature stubs, TODO(MA5) update.
  • SurfaceView::set_password_input does not yet drive SecureInput (MA4).
  • The bundle script does not embed Sparkle/the DockTilePlugin (MA5/MA4); no .lproj localizations exist in the Swift tree either.