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
179 lines
5.5 KiB
Rust
179 lines
5.5 KiB
Rust
//! `Sources/Features/Terminal`: terminal windows and their controllers.
|
|
//!
|
|
//! TODO(MA3): this file is the *foundation stub* written by MA1 so that
|
|
//! `Ghostty.App`'s action glue and the app delegate compile. It declares the
|
|
//! classes with the API surface they use, logs `TODO(MA3)` and returns
|
|
//! conservative values. MA3 replaces every body with the real port of
|
|
//! `BaseTerminalController.swift`, `TerminalController.swift`,
|
|
//! `Window Styles/TerminalWindow.swift` etc., keeping these signatures.
|
|
#![cfg(target_vendor = "apple")]
|
|
// Stubs: bodies do not use `self` yet.
|
|
#![allow(clippy::unused_self)]
|
|
|
|
use std::{
|
|
cell::{Cell, RefCell},
|
|
rc::Rc,
|
|
};
|
|
|
|
use log::warn;
|
|
use objc2::{DefinedClass, MainThreadOnly, define_class, rc::Retained};
|
|
use objc2_app_kit::{NSResponder, NSWindow, NSWindowController};
|
|
use objc2_foundation::NSObject;
|
|
|
|
use crate::ghostty::{
|
|
app::App,
|
|
package::SplitFocusDirection,
|
|
surface_configuration::SurfaceConfiguration,
|
|
surface_view::SurfaceView,
|
|
};
|
|
|
|
/// Instance variables of [`BaseTerminalController`].
|
|
#[derive(Default)]
|
|
pub struct BaseTerminalControllerIvars {
|
|
/// `bell` (TODO(MA3): published, drives the title bell indicator).
|
|
pub bell: Cell<bool>,
|
|
/// `titleOverride`.
|
|
pub title_override: RefCell<Option<String>>,
|
|
}
|
|
|
|
define_class!(
|
|
/// `BaseTerminalController`: the base class of all terminal window controllers.
|
|
// SAFETY: NSWindowController has no subclassing requirements we violate;
|
|
// no Drop impl.
|
|
#[unsafe(super(NSWindowController, NSResponder, NSObject))]
|
|
#[thread_kind = MainThreadOnly]
|
|
#[name = "GhosttyBaseTerminalController"]
|
|
#[ivars = BaseTerminalControllerIvars]
|
|
pub struct BaseTerminalController;
|
|
);
|
|
|
|
impl BaseTerminalController {
|
|
/// `surfaceTree.isSplit`.
|
|
#[must_use]
|
|
pub fn surface_tree_is_split(&self) -> bool {
|
|
warn!("TODO(MA3): BaseTerminalController::surface_tree_is_split");
|
|
false
|
|
}
|
|
|
|
/// `surfaceTree.focusTarget(for:from:) != nil` for the node holding `view`
|
|
/// (App.gotoSplit's performability check).
|
|
#[must_use]
|
|
pub fn focus_target_exists(
|
|
&self,
|
|
_direction: SplitFocusDirection,
|
|
_view: &SurfaceView,
|
|
) -> bool {
|
|
warn!("TODO(MA3): BaseTerminalController::focus_target_exists");
|
|
false
|
|
}
|
|
|
|
/// `surfaceTree` as a flat list of its surface views (iterating the tree).
|
|
#[must_use]
|
|
pub fn surface_tree(&self) -> Vec<Retained<SurfaceView>> {
|
|
warn!("TODO(MA3): BaseTerminalController::surface_tree");
|
|
Vec::new()
|
|
}
|
|
|
|
/// `focusedSurface`.
|
|
#[must_use]
|
|
pub fn focused_surface(&self) -> Option<Retained<SurfaceView>> {
|
|
warn!("TODO(MA3): BaseTerminalController::focused_surface");
|
|
None
|
|
}
|
|
|
|
/// `toggleBackgroundOpacity()`.
|
|
pub fn toggle_background_opacity(&self) {
|
|
warn!("TODO(MA3): BaseTerminalController::toggle_background_opacity");
|
|
}
|
|
|
|
/// `promptTabTitle()`.
|
|
pub fn prompt_tab_title(&self) {
|
|
warn!("TODO(MA3): BaseTerminalController::prompt_tab_title");
|
|
}
|
|
|
|
/// `titleOverride = ...`.
|
|
pub fn set_title_override(&self, title: Option<String>) {
|
|
*self.ivars().title_override.borrow_mut() = title;
|
|
warn!("TODO(MA3): BaseTerminalController::set_title_override (title not applied)");
|
|
}
|
|
|
|
/// `bell`.
|
|
#[must_use]
|
|
pub fn bell(&self) -> bool {
|
|
self.ivars().bell.get()
|
|
}
|
|
|
|
/// `relabelTabs()`.
|
|
pub fn relabel_tabs(&self) {
|
|
warn!("TODO(MA3): BaseTerminalController::relabel_tabs");
|
|
}
|
|
}
|
|
|
|
define_class!(
|
|
/// `TerminalController`: the controller of a regular terminal window.
|
|
// SAFETY: as above.
|
|
#[unsafe(super(BaseTerminalController, NSWindowController, NSResponder, NSObject))]
|
|
#[thread_kind = MainThreadOnly]
|
|
#[name = "GhosttyTerminalController"]
|
|
#[ivars = ()]
|
|
pub struct TerminalController;
|
|
);
|
|
|
|
impl TerminalController {
|
|
/// `TerminalController.all`: every terminal controller, in creation order.
|
|
#[must_use]
|
|
pub fn all() -> Vec<Retained<TerminalController>> {
|
|
Vec::new()
|
|
}
|
|
|
|
/// `TerminalController.newWindow(_:withBaseConfig:)`.
|
|
#[must_use]
|
|
pub fn new_window(
|
|
_ghostty: &Rc<App>,
|
|
_base_config: Option<&SurfaceConfiguration>,
|
|
) -> Option<Retained<TerminalController>> {
|
|
warn!("TODO(MA3): TerminalController::new_window");
|
|
None
|
|
}
|
|
|
|
/// `TerminalController.newTab(_:from:withBaseConfig:)`.
|
|
#[must_use]
|
|
pub fn new_tab(
|
|
_ghostty: &Rc<App>,
|
|
_from: Option<&NSWindow>,
|
|
_base_config: Option<&SurfaceConfiguration>,
|
|
) -> Option<Retained<TerminalController>> {
|
|
warn!("TODO(MA3): TerminalController::new_tab");
|
|
None
|
|
}
|
|
|
|
/// `TerminalController.preferredParent`.
|
|
#[must_use]
|
|
pub fn preferred_parent() -> Option<Retained<TerminalController>> {
|
|
None
|
|
}
|
|
|
|
/// `TerminalController.closeAllWindows()`.
|
|
pub fn close_all_windows() {
|
|
warn!("TODO(MA3): TerminalController::close_all_windows");
|
|
}
|
|
}
|
|
|
|
/// `TerminalWindow.defaultLevelKey`.
|
|
pub const DEFAULT_LEVEL_KEY: &str = "TerminalDefaultLevel";
|
|
|
|
define_class!(
|
|
/// `TerminalWindow`: the NSWindow subclass of terminal windows.
|
|
// SAFETY: as above.
|
|
#[unsafe(super(NSWindow, NSResponder, NSObject))]
|
|
#[thread_kind = MainThreadOnly]
|
|
#[name = "GhosttyTerminalWindow"]
|
|
#[ivars = ()]
|
|
pub struct TerminalWindow;
|
|
);
|
|
|
|
impl TerminalWindow {
|
|
/// `TerminalWindow.defaultLevelKey`.
|
|
pub const DEFAULT_LEVEL_KEY: &'static str = DEFAULT_LEVEL_KEY;
|
|
}
|