feat(apprt): port the core/runtime contract types
Ports ZIG/src/apprt/{action,structs,surface,ipc,runtime}.zig into
ghostty-apprt: the Action enum performed by the core on the app runtime
(all 65 variants with docs), Target<S> generic over the runtime's
surface handle, clipboard/size/scale/selection structs, the core->surface
Message enum with a Mailbox over an AppMailbox trait, and IPC action
types for +new-window.
Owned Strings and Arc<Config> replace Zig's borrowed slices/pointers.
Scrollbar and RendererHealth are local stand-ins until core integration.
The embedded C API and browser runtime are out of scope.
Test Plan:
- cargo test -p ghostty-apprt (16 tests)
This commit is contained in:
1 parent
fe37bde1fa
commit
5a5fa7893b
9 files changed
+1529
-1
No files matched your search
Generated
+25
@@ -321,6 +321,20 @@ name = "bytemuck"
|
|||||||
version = "1.25.2"
|
version = "1.25.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck_derive"
|
||||||
|
version = "1.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "castaway"
|
name = "castaway"
|
||||||
@@ -876,6 +890,17 @@ dependencies = [
|
|||||||
"temp-dir",
|
"temp-dir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ghostty-apprt"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ghostty-config",
|
||||||
|
"ghostty-datastruct",
|
||||||
|
"ghostty-input",
|
||||||
|
"ghostty-vt",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ghostty-apprt-gtk"
|
name = "ghostty-apprt-gtk"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ ghostty-vt.workspace = true
|
|||||||
ghostty-input.workspace = true
|
ghostty-input.workspace = true
|
||||||
ghostty-config.workspace = true
|
ghostty-config.workspace = true
|
||||||
ghostty-datastruct.workspace = true
|
ghostty-datastruct.workspace = true
|
||||||
|
thiserror = "2.0.21"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
@@ -0,0 +1,883 @@
|
|||||||
|
//! Port of `action.zig`: the actions an apprt has to react to.
|
||||||
|
//!
|
||||||
|
//! C-ABI plumbing (`C`, `CValue`, `cval`, `ghostty.h` sync tests) is not
|
||||||
|
//! ported; the enums keep their C integer discriminants via `as i32`.
|
||||||
|
|
||||||
|
use crate::surface::{ChildExited, RendererHealth, Scrollbar};
|
||||||
|
use ghostty_config::config::Config;
|
||||||
|
use ghostty_config::types_b::Duration;
|
||||||
|
use ghostty_input::binding::Trigger;
|
||||||
|
use ghostty_vt::mouse_shape::MouseShape;
|
||||||
|
use ghostty_vt::osc::ProgressReport;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// The target for an action. This is generally the thing that had focus
|
||||||
|
/// while the action was made but the concept of "focus" is not guaranteed
|
||||||
|
/// since actions can also be triggered by timers, scripts, etc.
|
||||||
|
///
|
||||||
|
/// Generic over the surface handle `S` (Zig: `*CoreSurface`).
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Target<S> {
|
||||||
|
App,
|
||||||
|
Surface(S),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mirrors `Target.Key`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum TargetKey {
|
||||||
|
App,
|
||||||
|
Surface,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Target<S> {
|
||||||
|
#[must_use]
|
||||||
|
pub fn key(&self) -> TargetKey {
|
||||||
|
match self {
|
||||||
|
Target::App => TargetKey::App,
|
||||||
|
Target::Surface(_) => TargetKey::Surface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The possible actions an apprt has to react to. Actions are one-way
|
||||||
|
/// messages that are sent to the app runtime to trigger some behavior.
|
||||||
|
///
|
||||||
|
/// Actions are very often key binding actions but can also be triggered
|
||||||
|
/// by lifecycle events. For example, the `quit_timer` action is not bindable.
|
||||||
|
///
|
||||||
|
/// Importantly, actions are generally OPTIONAL to implement by an apprt.
|
||||||
|
/// Required functionality is called directly on the runtime structure so
|
||||||
|
/// there is a compiler error if an action is not implemented.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Action {
|
||||||
|
/// Quit the application.
|
||||||
|
Quit,
|
||||||
|
|
||||||
|
/// Open a new window. The target determines whether properties such
|
||||||
|
/// as font size should be inherited.
|
||||||
|
NewWindow,
|
||||||
|
|
||||||
|
/// Open a new tab. If the target is a surface it should be opened in
|
||||||
|
/// the same window as the surface. If the target is the app then
|
||||||
|
/// the tab should be opened in a new window.
|
||||||
|
NewTab,
|
||||||
|
|
||||||
|
/// Closes the tab belonging to the currently focused split, or all other
|
||||||
|
/// tabs, depending on the mode.
|
||||||
|
CloseTab(CloseTabMode),
|
||||||
|
|
||||||
|
/// Create a new split. The value determines the location of the split
|
||||||
|
/// relative to the target.
|
||||||
|
NewSplit(SplitDirection),
|
||||||
|
|
||||||
|
/// Close all open windows.
|
||||||
|
CloseAllWindows,
|
||||||
|
|
||||||
|
/// Toggle maximized window state.
|
||||||
|
ToggleMaximize,
|
||||||
|
|
||||||
|
/// Toggle fullscreen mode.
|
||||||
|
ToggleFullscreen(Fullscreen),
|
||||||
|
|
||||||
|
/// Toggle tab overview.
|
||||||
|
ToggleTabOverview,
|
||||||
|
|
||||||
|
/// Toggle whether window directions are shown.
|
||||||
|
ToggleWindowDecorations,
|
||||||
|
|
||||||
|
/// Toggle the quick terminal in or out.
|
||||||
|
ToggleQuickTerminal,
|
||||||
|
|
||||||
|
/// Toggle the command palette.
|
||||||
|
ToggleCommandPalette,
|
||||||
|
|
||||||
|
/// Toggle the visibility of all Ghostty terminal windows.
|
||||||
|
ToggleVisibility,
|
||||||
|
|
||||||
|
/// Toggle the window background opacity. This only has an effect
|
||||||
|
/// if the window started as transparent (non-opaque), and toggles
|
||||||
|
/// it between fully opaque and the configured background opacity.
|
||||||
|
ToggleBackgroundOpacity,
|
||||||
|
|
||||||
|
/// Moves a tab by a relative offset.
|
||||||
|
///
|
||||||
|
/// Adjusts the tab position based on `offset` (e.g., -1 for left, +1
|
||||||
|
/// for right). If the new position is out of bounds, it wraps around
|
||||||
|
/// cyclically within the tab range.
|
||||||
|
MoveTab(MoveTab),
|
||||||
|
|
||||||
|
/// Jump to a specific tab. Must handle the scenario that the tab
|
||||||
|
/// value is invalid.
|
||||||
|
GotoTab(GotoTab),
|
||||||
|
|
||||||
|
/// Jump to a specific split.
|
||||||
|
GotoSplit(GotoSplit),
|
||||||
|
|
||||||
|
/// Jump to next/previous window.
|
||||||
|
GotoWindow(GotoWindow),
|
||||||
|
|
||||||
|
/// Resize the split in the given direction.
|
||||||
|
ResizeSplit(ResizeSplit),
|
||||||
|
|
||||||
|
/// Equalize all the splits in the target window.
|
||||||
|
EqualizeSplits,
|
||||||
|
|
||||||
|
/// Toggle whether a split is zoomed or not. A zoomed split is resized
|
||||||
|
/// to take up the entire window.
|
||||||
|
ToggleSplitZoom,
|
||||||
|
|
||||||
|
/// Present the target terminal whether its a tab, split, or window.
|
||||||
|
PresentTerminal,
|
||||||
|
|
||||||
|
/// Sets a size limit (in pixels) for the target terminal.
|
||||||
|
SizeLimit(SizeLimit),
|
||||||
|
|
||||||
|
/// Resets the window size to the default size. See the
|
||||||
|
/// `reset_window_size` keybinding for more information.
|
||||||
|
ResetWindowSize,
|
||||||
|
|
||||||
|
/// Specifies the initial size of the target terminal.
|
||||||
|
///
|
||||||
|
/// This may be sent once during the initialization of a surface
|
||||||
|
/// (as part of the init call) to indicate the initial size requested
|
||||||
|
/// for the window if it is not maximized or fullscreen.
|
||||||
|
///
|
||||||
|
/// This may also be sent at any time after the surface is initialized
|
||||||
|
/// to note the new "default size" of the window. This should in general
|
||||||
|
/// be ignored, but may be useful if the apprt wants to support
|
||||||
|
/// a "return to default size" action.
|
||||||
|
InitialSize(InitialSize),
|
||||||
|
|
||||||
|
/// The cell size has changed to the given dimensions in pixels.
|
||||||
|
CellSize(CellSize),
|
||||||
|
|
||||||
|
/// The scrollbar is updating.
|
||||||
|
Scrollbar(Scrollbar),
|
||||||
|
|
||||||
|
/// The target should be re-rendered. This usually has a specific
|
||||||
|
/// surface target but if the app is targeted then all active
|
||||||
|
/// surfaces should be redrawn.
|
||||||
|
Render,
|
||||||
|
|
||||||
|
/// Control whether the inspector is shown or hidden.
|
||||||
|
Inspector(Inspector),
|
||||||
|
|
||||||
|
/// Show the GTK inspector.
|
||||||
|
ShowGtkInspector,
|
||||||
|
|
||||||
|
/// The inspector for the given target has changes and should be
|
||||||
|
/// rendered at the next opportunity.
|
||||||
|
RenderInspector,
|
||||||
|
|
||||||
|
/// Show a desktop notification.
|
||||||
|
DesktopNotification(DesktopNotification),
|
||||||
|
|
||||||
|
/// Set the title of the target to the requested value.
|
||||||
|
SetTitle(SetTitle),
|
||||||
|
|
||||||
|
/// Set the tab title override for the target's tab.
|
||||||
|
SetTabTitle(SetTitle),
|
||||||
|
|
||||||
|
/// Set the title of the target to a prompted value. It is up to
|
||||||
|
/// the apprt to prompt. The value specifies whether to prompt for the
|
||||||
|
/// surface title or the tab title.
|
||||||
|
PromptTitle(PromptTitle),
|
||||||
|
|
||||||
|
/// The current working directory has changed for the target terminal.
|
||||||
|
Pwd(Pwd),
|
||||||
|
|
||||||
|
/// Set the mouse cursor shape.
|
||||||
|
MouseShape(MouseShape),
|
||||||
|
|
||||||
|
/// Set whether the mouse cursor is visible or not.
|
||||||
|
MouseVisibility(MouseVisibility),
|
||||||
|
|
||||||
|
/// Called when the mouse is over or recently left a link.
|
||||||
|
MouseOverLink(MouseOverLink),
|
||||||
|
|
||||||
|
/// The health of the renderer has changed.
|
||||||
|
RendererHealth(RendererHealth),
|
||||||
|
|
||||||
|
/// Open the Ghostty configuration. This is platform-specific about
|
||||||
|
/// what it means; it can mean opening a dedicated UI or just opening
|
||||||
|
/// a file in a text editor.
|
||||||
|
OpenConfig,
|
||||||
|
|
||||||
|
/// Called when there are no more surfaces and the app should quit
|
||||||
|
/// after the configured delay.
|
||||||
|
///
|
||||||
|
/// Despite the name, this is the notification that libghostty sends
|
||||||
|
/// when there are no more surfaces regardless of if the configuration
|
||||||
|
/// wants to quit after close, has any delay set, etc. It's up to the
|
||||||
|
/// apprt to implement the proper logic based on the config.
|
||||||
|
///
|
||||||
|
/// This can be cancelled by sending another quit_timer action with "stop".
|
||||||
|
/// Multiple "starts" shouldn't happen and can be ignored or cause a
|
||||||
|
/// restart it isn't that important.
|
||||||
|
QuitTimer(QuitTimer),
|
||||||
|
|
||||||
|
/// Set the window floating state. A floating window is one that is
|
||||||
|
/// always on top of other windows even when not focused.
|
||||||
|
FloatWindow(FloatWindow),
|
||||||
|
|
||||||
|
/// Set the secure input functionality on or off. "Secure input" means
|
||||||
|
/// that the user is currently at some sort of prompt where they may be
|
||||||
|
/// entering a password or other sensitive information. This can be used
|
||||||
|
/// by the app runtime to change the appearance of the cursor, setup
|
||||||
|
/// system APIs to not log the input, etc.
|
||||||
|
SecureInput(SecureInput),
|
||||||
|
|
||||||
|
/// A sequenced key binding has started, continued, or stopped.
|
||||||
|
/// The UI should show some indication that the user is in a sequenced
|
||||||
|
/// key mode because other input may be ignored.
|
||||||
|
KeySequence(KeySequence),
|
||||||
|
|
||||||
|
/// A key table has been activated or deactivated.
|
||||||
|
KeyTable(KeyTable),
|
||||||
|
|
||||||
|
/// A terminal color was changed programmatically through things
|
||||||
|
/// such as OSC 10/11.
|
||||||
|
ColorChange(ColorChange),
|
||||||
|
|
||||||
|
/// A request to reload the configuration. The reload request can be
|
||||||
|
/// from a user or for some internal reason. The reload request may
|
||||||
|
/// request it is a soft reload or a full reload. See the struct for
|
||||||
|
/// more documentation.
|
||||||
|
///
|
||||||
|
/// The configuration should be passed to updateConfig either at the
|
||||||
|
/// app or surface level depending on the target.
|
||||||
|
ReloadConfig(ReloadConfig),
|
||||||
|
|
||||||
|
/// The configuration has changed. The value is the new configuration
|
||||||
|
/// (shared via `Arc`; in Zig a pointer valid only for the duration of
|
||||||
|
/// the action).
|
||||||
|
///
|
||||||
|
/// This should be used by apprts to update any internal state that
|
||||||
|
/// depends on configuration for the given target (i.e. headerbar colors).
|
||||||
|
///
|
||||||
|
/// This allows an apprt to have config-dependent state reactively
|
||||||
|
/// change without having to store the entire configuration or poll
|
||||||
|
/// for changes.
|
||||||
|
ConfigChange(ConfigChange),
|
||||||
|
|
||||||
|
/// Closes the currently focused window.
|
||||||
|
CloseWindow,
|
||||||
|
|
||||||
|
/// Called when the bell character is seen. The apprt should do whatever
|
||||||
|
/// it needs to ring the bell. This is usually a sound or visual effect.
|
||||||
|
RingBell,
|
||||||
|
|
||||||
|
/// Undo the last action. See the "undo" keybinding for more
|
||||||
|
/// details on what can and cannot be undone.
|
||||||
|
Undo,
|
||||||
|
|
||||||
|
/// Redo the last undone action.
|
||||||
|
Redo,
|
||||||
|
|
||||||
|
CheckForUpdates,
|
||||||
|
|
||||||
|
/// Open a URL using the native OS mechanisms. On macOS this might be `open`
|
||||||
|
/// or on Linux this might be `xdg-open`. The exact mechanism is up to the
|
||||||
|
/// apprt.
|
||||||
|
OpenUrl(OpenUrl),
|
||||||
|
|
||||||
|
/// Show a native GUI notification that the child process has exited.
|
||||||
|
ShowChildExited(ChildExited),
|
||||||
|
|
||||||
|
/// Show a native GUI notification about the progress of some TUI operation.
|
||||||
|
ProgressReport(ProgressReport),
|
||||||
|
|
||||||
|
/// Show the on-screen keyboard.
|
||||||
|
ShowOnScreenKeyboard,
|
||||||
|
|
||||||
|
/// A command has finished,
|
||||||
|
CommandFinished(CommandFinished),
|
||||||
|
|
||||||
|
/// Start the search overlay with an optional initial needle. If the
|
||||||
|
/// search is already active and the needle is non-empty, update the
|
||||||
|
/// current search needle and focus the search input.
|
||||||
|
StartSearch(StartSearch),
|
||||||
|
|
||||||
|
/// End the search overlay, clearing the search state and hiding it.
|
||||||
|
EndSearch,
|
||||||
|
|
||||||
|
/// The total number of matches found by the search.
|
||||||
|
SearchTotal(SearchTotal),
|
||||||
|
|
||||||
|
/// The currently selected search match index (1-based).
|
||||||
|
SearchSelected(SearchSelected),
|
||||||
|
|
||||||
|
/// The readonly state of the surface has changed.
|
||||||
|
Readonly(Readonly),
|
||||||
|
|
||||||
|
/// Copy the effective title of the surface to the clipboard.
|
||||||
|
/// The effective title is the user-overridden title if set,
|
||||||
|
/// otherwise the terminal-set title.
|
||||||
|
CopyTitleToClipboard,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mirrors `Action.Key`. The order matches the libghostty C enum.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Key {
|
||||||
|
Quit,
|
||||||
|
NewWindow,
|
||||||
|
NewTab,
|
||||||
|
CloseTab,
|
||||||
|
NewSplit,
|
||||||
|
CloseAllWindows,
|
||||||
|
ToggleMaximize,
|
||||||
|
ToggleFullscreen,
|
||||||
|
ToggleTabOverview,
|
||||||
|
ToggleWindowDecorations,
|
||||||
|
ToggleQuickTerminal,
|
||||||
|
ToggleCommandPalette,
|
||||||
|
ToggleVisibility,
|
||||||
|
ToggleBackgroundOpacity,
|
||||||
|
MoveTab,
|
||||||
|
GotoTab,
|
||||||
|
GotoSplit,
|
||||||
|
GotoWindow,
|
||||||
|
ResizeSplit,
|
||||||
|
EqualizeSplits,
|
||||||
|
ToggleSplitZoom,
|
||||||
|
PresentTerminal,
|
||||||
|
SizeLimit,
|
||||||
|
ResetWindowSize,
|
||||||
|
InitialSize,
|
||||||
|
CellSize,
|
||||||
|
Scrollbar,
|
||||||
|
Render,
|
||||||
|
Inspector,
|
||||||
|
ShowGtkInspector,
|
||||||
|
RenderInspector,
|
||||||
|
DesktopNotification,
|
||||||
|
SetTitle,
|
||||||
|
SetTabTitle,
|
||||||
|
PromptTitle,
|
||||||
|
Pwd,
|
||||||
|
MouseShape,
|
||||||
|
MouseVisibility,
|
||||||
|
MouseOverLink,
|
||||||
|
RendererHealth,
|
||||||
|
OpenConfig,
|
||||||
|
QuitTimer,
|
||||||
|
FloatWindow,
|
||||||
|
SecureInput,
|
||||||
|
KeySequence,
|
||||||
|
KeyTable,
|
||||||
|
ColorChange,
|
||||||
|
ReloadConfig,
|
||||||
|
ConfigChange,
|
||||||
|
CloseWindow,
|
||||||
|
RingBell,
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
CheckForUpdates,
|
||||||
|
OpenUrl,
|
||||||
|
ShowChildExited,
|
||||||
|
ProgressReport,
|
||||||
|
ShowOnScreenKeyboard,
|
||||||
|
CommandFinished,
|
||||||
|
StartSearch,
|
||||||
|
EndSearch,
|
||||||
|
SearchTotal,
|
||||||
|
SearchSelected,
|
||||||
|
Readonly,
|
||||||
|
CopyTitleToClipboard,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
/// The tag of this action.
|
||||||
|
#[must_use]
|
||||||
|
pub fn key(&self) -> Key {
|
||||||
|
match self {
|
||||||
|
Action::Quit => Key::Quit,
|
||||||
|
Action::NewWindow => Key::NewWindow,
|
||||||
|
Action::NewTab => Key::NewTab,
|
||||||
|
Action::CloseTab(_) => Key::CloseTab,
|
||||||
|
Action::NewSplit(_) => Key::NewSplit,
|
||||||
|
Action::CloseAllWindows => Key::CloseAllWindows,
|
||||||
|
Action::ToggleMaximize => Key::ToggleMaximize,
|
||||||
|
Action::ToggleFullscreen(_) => Key::ToggleFullscreen,
|
||||||
|
Action::ToggleTabOverview => Key::ToggleTabOverview,
|
||||||
|
Action::ToggleWindowDecorations => Key::ToggleWindowDecorations,
|
||||||
|
Action::ToggleQuickTerminal => Key::ToggleQuickTerminal,
|
||||||
|
Action::ToggleCommandPalette => Key::ToggleCommandPalette,
|
||||||
|
Action::ToggleVisibility => Key::ToggleVisibility,
|
||||||
|
Action::ToggleBackgroundOpacity => Key::ToggleBackgroundOpacity,
|
||||||
|
Action::MoveTab(_) => Key::MoveTab,
|
||||||
|
Action::GotoTab(_) => Key::GotoTab,
|
||||||
|
Action::GotoSplit(_) => Key::GotoSplit,
|
||||||
|
Action::GotoWindow(_) => Key::GotoWindow,
|
||||||
|
Action::ResizeSplit(_) => Key::ResizeSplit,
|
||||||
|
Action::EqualizeSplits => Key::EqualizeSplits,
|
||||||
|
Action::ToggleSplitZoom => Key::ToggleSplitZoom,
|
||||||
|
Action::PresentTerminal => Key::PresentTerminal,
|
||||||
|
Action::SizeLimit(_) => Key::SizeLimit,
|
||||||
|
Action::ResetWindowSize => Key::ResetWindowSize,
|
||||||
|
Action::InitialSize(_) => Key::InitialSize,
|
||||||
|
Action::CellSize(_) => Key::CellSize,
|
||||||
|
Action::Scrollbar(_) => Key::Scrollbar,
|
||||||
|
Action::Render => Key::Render,
|
||||||
|
Action::Inspector(_) => Key::Inspector,
|
||||||
|
Action::ShowGtkInspector => Key::ShowGtkInspector,
|
||||||
|
Action::RenderInspector => Key::RenderInspector,
|
||||||
|
Action::DesktopNotification(_) => Key::DesktopNotification,
|
||||||
|
Action::SetTitle(_) => Key::SetTitle,
|
||||||
|
Action::SetTabTitle(_) => Key::SetTabTitle,
|
||||||
|
Action::PromptTitle(_) => Key::PromptTitle,
|
||||||
|
Action::Pwd(_) => Key::Pwd,
|
||||||
|
Action::MouseShape(_) => Key::MouseShape,
|
||||||
|
Action::MouseVisibility(_) => Key::MouseVisibility,
|
||||||
|
Action::MouseOverLink(_) => Key::MouseOverLink,
|
||||||
|
Action::RendererHealth(_) => Key::RendererHealth,
|
||||||
|
Action::OpenConfig => Key::OpenConfig,
|
||||||
|
Action::QuitTimer(_) => Key::QuitTimer,
|
||||||
|
Action::FloatWindow(_) => Key::FloatWindow,
|
||||||
|
Action::SecureInput(_) => Key::SecureInput,
|
||||||
|
Action::KeySequence(_) => Key::KeySequence,
|
||||||
|
Action::KeyTable(_) => Key::KeyTable,
|
||||||
|
Action::ColorChange(_) => Key::ColorChange,
|
||||||
|
Action::ReloadConfig(_) => Key::ReloadConfig,
|
||||||
|
Action::ConfigChange(_) => Key::ConfigChange,
|
||||||
|
Action::CloseWindow => Key::CloseWindow,
|
||||||
|
Action::RingBell => Key::RingBell,
|
||||||
|
Action::Undo => Key::Undo,
|
||||||
|
Action::Redo => Key::Redo,
|
||||||
|
Action::CheckForUpdates => Key::CheckForUpdates,
|
||||||
|
Action::OpenUrl(_) => Key::OpenUrl,
|
||||||
|
Action::ShowChildExited(_) => Key::ShowChildExited,
|
||||||
|
Action::ProgressReport(_) => Key::ProgressReport,
|
||||||
|
Action::ShowOnScreenKeyboard => Key::ShowOnScreenKeyboard,
|
||||||
|
Action::CommandFinished(_) => Key::CommandFinished,
|
||||||
|
Action::StartSearch(_) => Key::StartSearch,
|
||||||
|
Action::EndSearch => Key::EndSearch,
|
||||||
|
Action::SearchTotal(_) => Key::SearchTotal,
|
||||||
|
Action::SearchSelected(_) => Key::SearchSelected,
|
||||||
|
Action::Readonly(_) => Key::Readonly,
|
||||||
|
Action::CopyTitleToClipboard => Key::CopyTitleToClipboard,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum SplitDirection {
|
||||||
|
Right,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Up,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum GotoSplit {
|
||||||
|
Previous,
|
||||||
|
Next,
|
||||||
|
Up,
|
||||||
|
Left,
|
||||||
|
Down,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum GotoWindow {
|
||||||
|
Previous,
|
||||||
|
Next,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount to resize the split by and the direction to resize it in.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ResizeSplit {
|
||||||
|
pub amount: u16,
|
||||||
|
pub direction: ResizeSplitDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum ResizeSplitDirection {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct MoveTab {
|
||||||
|
pub amount: isize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The tab to jump to. Non-negative integer values represent the index
|
||||||
|
/// (zero-based) of the tab to jump to. Negative values are special values
|
||||||
|
/// (Zig: non-exhaustive enum; unknown negatives are `Index(n)` with n < -3
|
||||||
|
/// and must be treated as invalid by the apprt).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum GotoTab {
|
||||||
|
Previous,
|
||||||
|
Next,
|
||||||
|
Last,
|
||||||
|
Index(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GotoTab {
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_int(v: i32) -> Self {
|
||||||
|
match v {
|
||||||
|
-1 => Self::Previous,
|
||||||
|
-2 => Self::Next,
|
||||||
|
-3 => Self::Last,
|
||||||
|
n => Self::Index(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn int(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::Previous => -1,
|
||||||
|
Self::Next => -2,
|
||||||
|
Self::Last => -3,
|
||||||
|
Self::Index(n) => n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The fullscreen mode to toggle to if we're moving to fullscreen.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Fullscreen {
|
||||||
|
Native,
|
||||||
|
|
||||||
|
/// macOS has a non-native fullscreen mode that is more like a maximized
|
||||||
|
/// window. This is much faster to enter and exit than the native mode.
|
||||||
|
MacosNonNative,
|
||||||
|
MacosNonNativeVisibleMenu,
|
||||||
|
MacosNonNativePaddedNotch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum FloatWindow {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum SecureInput {
|
||||||
|
On,
|
||||||
|
Off,
|
||||||
|
Toggle,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The inspector mode to toggle to if we're toggling the inspector.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Inspector {
|
||||||
|
Toggle,
|
||||||
|
Show,
|
||||||
|
Hide,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum QuitTimer {
|
||||||
|
Start,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum Readonly {
|
||||||
|
Off,
|
||||||
|
On,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum MouseVisibility {
|
||||||
|
Visible,
|
||||||
|
Hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether to prompt for the surface title or tab title.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum PromptTitle {
|
||||||
|
Surface,
|
||||||
|
Tab,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct MouseOverLink {
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct SizeLimit {
|
||||||
|
pub min_width: u32,
|
||||||
|
pub min_height: u32,
|
||||||
|
pub max_width: u32,
|
||||||
|
pub max_height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct InitialSize {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct CellSize {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct SetTitle {
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SetTitle {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "SetTitle{{ {} }}", self.title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Pwd {
|
||||||
|
pub pwd: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Pwd {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Pwd{{ {} }}", self.pwd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The desktop notification to show.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct DesktopNotification {
|
||||||
|
pub title: String,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DesktopNotification {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"DesktopNotification{{ title: {}, body: {} }}",
|
||||||
|
self.title, self.body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum KeySequence {
|
||||||
|
Trigger(Trigger),
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum KeyTable {
|
||||||
|
Activate(String),
|
||||||
|
Deactivate,
|
||||||
|
DeactivateAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ColorChange {
|
||||||
|
pub kind: ColorKind,
|
||||||
|
pub r: u8,
|
||||||
|
pub g: u8,
|
||||||
|
pub b: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Negative numbers indicate some named kind; 0+ values indicate a palette
|
||||||
|
/// index (Zig: non-exhaustive enum).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ColorKind {
|
||||||
|
Foreground,
|
||||||
|
Background,
|
||||||
|
Cursor,
|
||||||
|
Palette(i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorKind {
|
||||||
|
#[must_use]
|
||||||
|
pub fn from_int(v: i32) -> Self {
|
||||||
|
match v {
|
||||||
|
-1 => Self::Foreground,
|
||||||
|
-2 => Self::Background,
|
||||||
|
-3 => Self::Cursor,
|
||||||
|
n => Self::Palette(n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn int(self) -> i32 {
|
||||||
|
match self {
|
||||||
|
Self::Foreground => -1,
|
||||||
|
Self::Background => -2,
|
||||||
|
Self::Cursor => -3,
|
||||||
|
Self::Palette(n) => n,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct ReloadConfig {
|
||||||
|
/// A soft reload means that the configuration doesn't need to be
|
||||||
|
/// read off disk, but libghostty needs the full config again so call
|
||||||
|
/// updateConfig with it.
|
||||||
|
pub soft: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ConfigChange {
|
||||||
|
pub config: Arc<Config>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open a URL
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct OpenUrl {
|
||||||
|
/// The type of data that the URL refers to.
|
||||||
|
pub kind: OpenUrlKind,
|
||||||
|
|
||||||
|
/// The URL.
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The type of the data at the URL to open. This is used as a hint to
|
||||||
|
/// potentially open the URL in a different way.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum OpenUrlKind {
|
||||||
|
/// The type is unknown. This is the default and apprts should
|
||||||
|
/// open the URL in the most generic way possible. For example,
|
||||||
|
/// on macOS this would be the equivalent of `open` or on Linux
|
||||||
|
/// this would be `xdg-open`.
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
|
||||||
|
/// The URL is known to be a text file. In this case, the apprt
|
||||||
|
/// should try to open the URL in a text editor or viewer or
|
||||||
|
/// some equivalent, if possible.
|
||||||
|
Text,
|
||||||
|
|
||||||
|
/// The URL is known to contain HTML content.
|
||||||
|
Html,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum CloseTabMode {
|
||||||
|
/// Close the current tab.
|
||||||
|
This,
|
||||||
|
/// Close all other tabs.
|
||||||
|
Other,
|
||||||
|
/// Close all tabs to the right of the current tab.
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct CommandFinished {
|
||||||
|
pub exit_code: Option<u8>,
|
||||||
|
pub duration: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct StartSearch {
|
||||||
|
pub needle: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct SearchTotal {
|
||||||
|
pub total: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct SearchSelected {
|
||||||
|
pub selected: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn target_key() {
|
||||||
|
assert_eq!(Target::<u32>::App.key(), TargetKey::App);
|
||||||
|
assert_eq!(Target::Surface(1u32).key(), TargetKey::Surface);
|
||||||
|
assert_eq!(TargetKey::Surface as i32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn action_key_order_matches_c_enum() {
|
||||||
|
assert_eq!(Key::Quit as i32, 0);
|
||||||
|
assert_eq!(Key::CopyTitleToClipboard as i32, 64);
|
||||||
|
assert_eq!(Action::Quit.key(), Key::Quit);
|
||||||
|
assert_eq!(Action::EndSearch.key(), Key::EndSearch);
|
||||||
|
assert_eq!(
|
||||||
|
Action::ResizeSplit(ResizeSplit {
|
||||||
|
amount: 1,
|
||||||
|
direction: ResizeSplitDirection::Up
|
||||||
|
})
|
||||||
|
.key(),
|
||||||
|
Key::ResizeSplit
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Action::QuitTimer(QuitTimer::Start).key() as i32,
|
||||||
|
Key::QuitTimer as i32
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn goto_tab_roundtrip() {
|
||||||
|
assert_eq!(GotoTab::from_int(-1), GotoTab::Previous);
|
||||||
|
assert_eq!(GotoTab::from_int(-2), GotoTab::Next);
|
||||||
|
assert_eq!(GotoTab::from_int(-3), GotoTab::Last);
|
||||||
|
assert_eq!(GotoTab::from_int(4), GotoTab::Index(4));
|
||||||
|
assert_eq!(GotoTab::Index(4).int(), 4);
|
||||||
|
assert_eq!(GotoTab::Last.int(), -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn color_kind_roundtrip() {
|
||||||
|
assert_eq!(ColorKind::from_int(-2), ColorKind::Background);
|
||||||
|
assert_eq!(ColorKind::from_int(7), ColorKind::Palette(7));
|
||||||
|
assert_eq!(ColorKind::Cursor.int(), -3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_discriminants() {
|
||||||
|
assert_eq!(SplitDirection::Up as i32, 3);
|
||||||
|
assert_eq!(GotoSplit::Right as i32, 5);
|
||||||
|
assert_eq!(Fullscreen::MacosNonNativePaddedNotch as i32, 3);
|
||||||
|
assert_eq!(OpenUrlKind::default(), OpenUrlKind::Unknown);
|
||||||
|
assert_eq!(CloseTabMode::Right as i32, 2);
|
||||||
|
assert_eq!(Readonly::On as i32, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_formats() {
|
||||||
|
assert_eq!(SetTitle { title: "x".into() }.to_string(), "SetTitle{ x }");
|
||||||
|
assert_eq!(Pwd { pwd: "/a".into() }.to_string(), "Pwd{ /a }");
|
||||||
|
assert_eq!(
|
||||||
|
DesktopNotification {
|
||||||
|
title: "t".into(),
|
||||||
|
body: "b".into()
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
"DesktopNotification{ title: t, body: b }"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reload_config_default_hard() {
|
||||||
|
assert!(!ReloadConfig::default().soft);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//! Inter-process Communication to a running Ghostty instance from a separate
|
||||||
|
//! process (port of `ipc.zig`). The transport (D-Bus) lives in apprt-gtk.
|
||||||
|
|
||||||
|
/// Errors from IPC.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
/// The IPC failed. If a function returns this error, it's expected that
|
||||||
|
/// an a more specific error message will have been written to stderr (or
|
||||||
|
/// otherwise shown to the user in an appropriate way).
|
||||||
|
#[error("IPC failed")]
|
||||||
|
IpcFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which instance to talk to.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Target {
|
||||||
|
/// Open up a new window in a custom instance of Ghostty.
|
||||||
|
Class(String),
|
||||||
|
|
||||||
|
/// Detect which instance to open a new window in.
|
||||||
|
Detect,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The arguments to pass to Ghostty as the command.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct NewWindow {
|
||||||
|
/// A list of command arguments to launch in the new window. If this is
|
||||||
|
/// `None` the command configured in the config or the user's default
|
||||||
|
/// shell should be launched.
|
||||||
|
///
|
||||||
|
/// It is an error for this to be `Some`, but zero length.
|
||||||
|
pub arguments: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// IPC actions.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Action {
|
||||||
|
/// The arguments to pass to Ghostty as the command.
|
||||||
|
NewWindow(NewWindow),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mirrors `Action.Key`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ActionKey {
|
||||||
|
NewWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Action {
|
||||||
|
#[must_use]
|
||||||
|
pub fn key(&self) -> ActionKey {
|
||||||
|
match self {
|
||||||
|
Action::NewWindow(_) => ActionKey::NewWindow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Target {
|
||||||
|
#[must_use]
|
||||||
|
pub fn is_detect(&self) -> bool {
|
||||||
|
matches!(self, Target::Detect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn action_key() {
|
||||||
|
let a = Action::NewWindow(NewWindow {
|
||||||
|
arguments: Some(vec!["a".into()]),
|
||||||
|
});
|
||||||
|
assert_eq!(a.key(), ActionKey::NewWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn target_variants() {
|
||||||
|
assert!(Target::Detect.is_detect());
|
||||||
|
assert!(!Target::Class("x".into()).is_detect());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,17 @@
|
|||||||
//! ghostty-apprt: the contract between the core and app runtimes (ZIG/src/apprt/{action,structs,surface,ipc}.zig).
|
//! ghostty-apprt: the contract between the core and app runtimes
|
||||||
|
//! (ZIG/src/apprt/{action,structs,surface,ipc,runtime,none}.zig).
|
||||||
|
//!
|
||||||
|
//! The C-ABI parts of the Zig sources (`C` structs, `cval`, `CValue`,
|
||||||
|
//! `ghostty.h` sync tests) belong to the embedded runtime and are not ported.
|
||||||
|
|
||||||
|
pub mod action;
|
||||||
|
pub mod ipc;
|
||||||
|
pub mod runtime;
|
||||||
|
pub mod structs;
|
||||||
|
pub mod surface;
|
||||||
|
|
||||||
|
pub use action::{Action, Target};
|
||||||
|
pub use structs::{
|
||||||
|
Clipboard, ClipboardContent, ClipboardRequest, ClipboardRequestType, ColorScheme, ContentScale,
|
||||||
|
CursorPos, ImePos, Selection, SurfaceSize,
|
||||||
|
};
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//! Port of `runtime.zig` (and `none.zig`).
|
||||||
|
|
||||||
|
/// Runtime is the runtime to use for Ghostty. All runtimes do not provide
|
||||||
|
/// equivalent feature sets.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Runtime {
|
||||||
|
/// Will not produce an executable at all (lib only). Matches `none.zig`:
|
||||||
|
/// there is no apprt to communicate with, so IPC always returns `false`.
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// GTK4. Rich windowed application. This uses a full GObject-based
|
||||||
|
/// approach to building the application.
|
||||||
|
Gtk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Runtime {
|
||||||
|
/// The default runtime for the current target. The Linux and FreeBSD
|
||||||
|
/// default is GTK because it is a full featured application; otherwise
|
||||||
|
/// `None`.
|
||||||
|
#[must_use]
|
||||||
|
pub fn default_for_target() -> Self {
|
||||||
|
if cfg!(any(target_os = "linux", target_os = "freebsd")) {
|
||||||
|
Runtime::Gtk
|
||||||
|
} else {
|
||||||
|
Runtime::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `none.zig` `App::performIpc`: always false as there is no apprt to
|
||||||
|
/// communicate with.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// Never fails.
|
||||||
|
pub fn none_perform_ipc(
|
||||||
|
_target: &crate::ipc::Target,
|
||||||
|
_action: &crate::ipc::Action,
|
||||||
|
) -> Result<bool, crate::ipc::Error> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_runtime_is_gtk_on_linux() {
|
||||||
|
if cfg!(target_os = "linux") {
|
||||||
|
assert_eq!(Runtime::default_for_target(), Runtime::Gtk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn none_ipc_returns_false() {
|
||||||
|
let r = none_perform_ipc(
|
||||||
|
&crate::ipc::Target::Detect,
|
||||||
|
&crate::ipc::Action::NewWindow(crate::ipc::NewWindow { arguments: None }),
|
||||||
|
);
|
||||||
|
assert!(matches!(r, Ok(false)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
//! Port of `structs.zig`.
|
||||||
|
|
||||||
|
/// ContentScale is the ratio between the current DPI and the platform's
|
||||||
|
/// default DPI. This is used to determine how much certain rendered elements
|
||||||
|
/// need to be scaled up or down.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ContentScale {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The size of the surface in pixels.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct SurfaceSize {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The position of the cursor in pixels.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct CursorPos {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Input Method Editor (IME) position.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct ImePos {
|
||||||
|
pub x: f64,
|
||||||
|
pub y: f64,
|
||||||
|
pub width: f64,
|
||||||
|
pub height: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The clipboard type.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum Clipboard {
|
||||||
|
/// ctrl+c/v
|
||||||
|
Standard = 0,
|
||||||
|
Selection = 1,
|
||||||
|
Primary = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ClipboardContent {
|
||||||
|
pub mime: String,
|
||||||
|
pub data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ClipboardRequestType {
|
||||||
|
Paste,
|
||||||
|
Osc52Read,
|
||||||
|
Osc52Write,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clipboard request. This is used to request clipboard contents and must
|
||||||
|
/// be sent as a response to a ClipboardRequest event.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ClipboardRequest {
|
||||||
|
/// A direct paste of clipboard contents.
|
||||||
|
Paste,
|
||||||
|
|
||||||
|
/// A request to read clipboard contents via OSC 52.
|
||||||
|
Osc52Read(Clipboard),
|
||||||
|
|
||||||
|
/// A request to write clipboard contents via OSC 52.
|
||||||
|
Osc52Write(Clipboard),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClipboardRequest {
|
||||||
|
#[must_use]
|
||||||
|
pub fn request_type(&self) -> ClipboardRequestType {
|
||||||
|
match self {
|
||||||
|
Self::Paste => ClipboardRequestType::Paste,
|
||||||
|
Self::Osc52Read(_) => ClipboardRequestType::Osc52Read,
|
||||||
|
Self::Osc52Write(_) => ClipboardRequestType::Osc52Write,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The color scheme in use (light vs dark).
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum ColorScheme {
|
||||||
|
Light = 0,
|
||||||
|
Dark = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selection information
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Selection {
|
||||||
|
/// Top-left point of the selection in the viewport in scaled
|
||||||
|
/// window pixels. (0,0) is the top-left of the window.
|
||||||
|
pub tl_x_px: f64,
|
||||||
|
pub tl_y_px: f64,
|
||||||
|
|
||||||
|
/// The offset of the selection start in cells from the top-left
|
||||||
|
/// of the viewport.
|
||||||
|
///
|
||||||
|
/// This is a strange metric but its used by macOS.
|
||||||
|
pub offset_start: u32,
|
||||||
|
pub offset_len: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_values() {
|
||||||
|
assert_eq!(Clipboard::Standard as u8, 0);
|
||||||
|
assert_eq!(Clipboard::Selection as u8, 1);
|
||||||
|
assert_eq!(Clipboard::Primary as u8, 2);
|
||||||
|
assert_eq!(ColorScheme::Light as u8, 0);
|
||||||
|
assert_eq!(ColorScheme::Dark as u8, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clipboard_request_type() {
|
||||||
|
assert_eq!(
|
||||||
|
ClipboardRequest::Osc52Read(Clipboard::Primary).request_type(),
|
||||||
|
ClipboardRequestType::Osc52Read
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ClipboardRequest::Paste.request_type(),
|
||||||
|
ClipboardRequestType::Paste
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
//! Port of `surface.zig`: messages sent from the core to a surface's apprt.
|
||||||
|
|
||||||
|
use crate::structs::Clipboard;
|
||||||
|
use ghostty_config::config::Config;
|
||||||
|
use ghostty_config::types_a::WorkingDirectory;
|
||||||
|
use ghostty_datastruct::MessageData;
|
||||||
|
use ghostty_datastruct::blocking_queue::Timeout;
|
||||||
|
use ghostty_vt::mouse_shape::MouseShape;
|
||||||
|
use ghostty_vt::osc::ProgressReport;
|
||||||
|
use ghostty_vt::osc::color::ColoredTarget;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Represents a write request. Magic number comes from the max size
|
||||||
|
/// we want this union to be.
|
||||||
|
pub type WriteReq = MessageData<u8, 255>;
|
||||||
|
|
||||||
|
/// Maximum title size (including NUL sentinel space) of `set_title`.
|
||||||
|
pub const SET_TITLE_LEN: usize = 256;
|
||||||
|
/// `[63:0]u8` => 64 bytes including the sentinel.
|
||||||
|
pub const NOTIFICATION_TITLE_LEN: usize = 64;
|
||||||
|
/// `[255:0]u8` => 256 bytes including the sentinel.
|
||||||
|
pub const NOTIFICATION_BODY_LEN: usize = 256;
|
||||||
|
|
||||||
|
// TODO(integration): `terminal.Scrollbar` lives in ghostty-terminal
|
||||||
|
// (page_list::Scrollbar); apprt doesn't depend on it. Local stand-in with
|
||||||
|
// identical fields.
|
||||||
|
/// Scrollbar geometry in rows.
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Scrollbar {
|
||||||
|
/// Total scrollable rows.
|
||||||
|
pub total: usize,
|
||||||
|
/// Viewport's offset from the oldest row.
|
||||||
|
pub offset: usize,
|
||||||
|
/// Visible row count.
|
||||||
|
pub len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(integration): `renderer.Health` lives in ghostty-renderer
|
||||||
|
// (backend::Health); apprt doesn't depend on it.
|
||||||
|
/// The health status of a renderer.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum RendererHealth {
|
||||||
|
Healthy,
|
||||||
|
Unhealthy,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ReportTitleStyle {
|
||||||
|
Csi21T,
|
||||||
|
// This enum is a placeholder for future title styles.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct ChildExited {
|
||||||
|
pub exit_code: u32,
|
||||||
|
pub runtime_ms: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fixed-size desktop notification (title `[63:0]u8`, body `[255:0]u8`).
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct DesktopNotification {
|
||||||
|
/// NUL-terminated.
|
||||||
|
pub title: [u8; NOTIFICATION_TITLE_LEN],
|
||||||
|
/// NUL-terminated.
|
||||||
|
pub body: [u8; NOTIFICATION_BODY_LEN],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cstr(buf: &[u8]) -> &[u8] {
|
||||||
|
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
|
||||||
|
&buf[..end]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fill<const N: usize>(src: &[u8]) -> [u8; N] {
|
||||||
|
let mut out = [0u8; N];
|
||||||
|
let n = src.len().min(N - 1);
|
||||||
|
out[..n].copy_from_slice(&src[..n]);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopNotification {
|
||||||
|
/// Build from byte strings, truncating to fit (keeping the sentinel).
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(title: &[u8], body: &[u8]) -> Self {
|
||||||
|
Self {
|
||||||
|
title: fill(title),
|
||||||
|
body: fill(body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn title(&self) -> &[u8] {
|
||||||
|
cstr(&self.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn body(&self) -> &[u8] {
|
||||||
|
cstr(&self.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The message types that can be sent to a single surface.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Message {
|
||||||
|
/// Set the title of the surface (NUL-terminated within 256 bytes).
|
||||||
|
/// TODO(upstream): should become a `WriteReq`.
|
||||||
|
SetTitle([u8; SET_TITLE_LEN]),
|
||||||
|
|
||||||
|
/// Report the window title back to the terminal
|
||||||
|
ReportTitle(ReportTitleStyle),
|
||||||
|
|
||||||
|
/// Set the mouse shape.
|
||||||
|
SetMouseShape(MouseShape),
|
||||||
|
|
||||||
|
/// Read the clipboard and write to the pty.
|
||||||
|
ClipboardRead(Clipboard),
|
||||||
|
|
||||||
|
/// Write the clipboard contents.
|
||||||
|
ClipboardWrite {
|
||||||
|
clipboard_type: Clipboard,
|
||||||
|
req: WriteReq,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Change the configuration to the given configuration. In Zig the
|
||||||
|
/// pointer is not valid after receiving; here it is shared via `Arc`.
|
||||||
|
ChangeConfig(Arc<Config>),
|
||||||
|
|
||||||
|
/// Close the surface. This will only close the current surface that
|
||||||
|
/// receives this, not the full application.
|
||||||
|
Close,
|
||||||
|
|
||||||
|
/// The child process running in the surface has exited. This may trigger
|
||||||
|
/// a surface close, it may not. Additional details about the child
|
||||||
|
/// command are given in the `ChildExited` struct.
|
||||||
|
ChildExited(ChildExited),
|
||||||
|
|
||||||
|
/// Show a desktop notification.
|
||||||
|
DesktopNotification(DesktopNotification),
|
||||||
|
|
||||||
|
/// Health status change for the renderer.
|
||||||
|
RendererHealth(RendererHealth),
|
||||||
|
|
||||||
|
/// Tell the surface to present itself to the user. This may require raising
|
||||||
|
/// a window and switching tabs.
|
||||||
|
PresentSurface,
|
||||||
|
|
||||||
|
/// Notifies the surface that password input has started within
|
||||||
|
/// the terminal. This should always be followed by a false value
|
||||||
|
/// unless the surface exits.
|
||||||
|
PasswordInput(bool),
|
||||||
|
|
||||||
|
/// A terminal color was changed using OSC sequences.
|
||||||
|
ColorChange(ColoredTarget),
|
||||||
|
|
||||||
|
/// Notifies the surface that a tick of the timer that is timing
|
||||||
|
/// out selection scrolling has occurred. "selection scrolling"
|
||||||
|
/// is when the user has clicked and dragged the mouse outside
|
||||||
|
/// the viewport of the terminal and the terminal is scrolling
|
||||||
|
/// the viewport to follow the mouse cursor.
|
||||||
|
SelectionScrollTick(bool),
|
||||||
|
|
||||||
|
/// The terminal has reported a change in the working directory.
|
||||||
|
PwdChange(WriteReq),
|
||||||
|
|
||||||
|
/// The terminal encountered a bell character.
|
||||||
|
RingBell,
|
||||||
|
|
||||||
|
/// Report the progress of an action using a GUI element
|
||||||
|
ProgressReport(ProgressReport),
|
||||||
|
|
||||||
|
/// A command has started in the shell, start a timer.
|
||||||
|
StartCommand,
|
||||||
|
|
||||||
|
/// A command has finished in the shell, stop the timer and send out
|
||||||
|
/// notifications as appropriate. The optional u8 is the exit code
|
||||||
|
/// of the command.
|
||||||
|
StopCommand(Option<u8>),
|
||||||
|
|
||||||
|
/// The scrollbar state changed for the surface.
|
||||||
|
Scrollbar(Scrollbar),
|
||||||
|
|
||||||
|
/// Search progress update
|
||||||
|
SearchTotal(Option<usize>),
|
||||||
|
|
||||||
|
/// Selected search index change
|
||||||
|
SearchSelected(Option<usize>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The app-level mailbox a surface mailbox forwards into.
|
||||||
|
///
|
||||||
|
/// TODO(integration): in Zig this is `App.Mailbox` taking an
|
||||||
|
/// `App.Message::surface_message { surface, message }`. ghostty-core
|
||||||
|
/// implements this trait for its app mailbox (the `S` handle being the core
|
||||||
|
/// surface pointer/id).
|
||||||
|
pub trait AppMailbox<S> {
|
||||||
|
/// Push a surface-directed message to the app thread. Returns the queue
|
||||||
|
/// size after the push; zero means the push failed.
|
||||||
|
fn push_surface_message(&self, surface: S, message: Message, timeout: Timeout) -> usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A surface mailbox.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Mailbox<S, A> {
|
||||||
|
pub surface: S,
|
||||||
|
pub app: A,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Clone, A: AppMailbox<S>> Mailbox<S, A> {
|
||||||
|
/// Send a message to the surface. Surface message sending is actually
|
||||||
|
/// implemented on the app thread, so we rewrap the message with our
|
||||||
|
/// surface handle and send it to the app thread.
|
||||||
|
pub fn push(&self, msg: Message, timeout: Timeout) -> usize {
|
||||||
|
self.app
|
||||||
|
.push_surface_message(self.surface.clone(), msg, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Context for new surface creation to determine inheritance behavior
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum NewSurfaceContext {
|
||||||
|
Window = 0,
|
||||||
|
Tab = 1,
|
||||||
|
Split = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn should_inherit_working_directory(context: NewSurfaceContext, config: &Config) -> bool {
|
||||||
|
match context {
|
||||||
|
NewSurfaceContext::Window => config.window_inherit_working_directory,
|
||||||
|
NewSurfaceContext::Tab => config.tab_inherit_working_directory,
|
||||||
|
NewSurfaceContext::Split => config.split_inherit_working_directory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new config for a surface that should be used for any new
|
||||||
|
/// surfaces.
|
||||||
|
///
|
||||||
|
/// `prev_pwd` is the pwd of the previously focused surface (Zig:
|
||||||
|
/// `app.focusedSurface()?.pwd()`), which the caller (core) must look up.
|
||||||
|
// TODO(integration): core could wrap this to resolve the focused surface.
|
||||||
|
#[must_use]
|
||||||
|
pub fn new_config(config: &Config, context: NewSurfaceContext, prev_pwd: Option<String>) -> Config {
|
||||||
|
let mut copy = config.clone();
|
||||||
|
if let Some(pwd) = prev_pwd
|
||||||
|
&& should_inherit_working_directory(context, config)
|
||||||
|
{
|
||||||
|
copy.working_directory = Some(WorkingDirectory::Path(pwd));
|
||||||
|
}
|
||||||
|
copy
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
struct Rec(RefCell<Vec<(u32, bool)>>);
|
||||||
|
impl AppMailbox<u32> for Rec {
|
||||||
|
fn push_surface_message(&self, s: u32, m: Message, _t: Timeout) -> usize {
|
||||||
|
self.0.borrow_mut().push((s, matches!(m, Message::Close)));
|
||||||
|
self.0.borrow().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mailbox_wraps_surface() {
|
||||||
|
let mb = Mailbox {
|
||||||
|
surface: 7u32,
|
||||||
|
app: Rec(RefCell::new(vec![])),
|
||||||
|
};
|
||||||
|
assert_eq!(mb.push(Message::Close, Timeout::Instant), 1);
|
||||||
|
assert_eq!(mb.app.0.borrow()[0], (7, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn notification_truncates() {
|
||||||
|
let n = DesktopNotification::new(&[b'a'; 100], b"hi");
|
||||||
|
assert_eq!(n.title().len(), 63);
|
||||||
|
assert_eq!(n.body(), b"hi");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inherit_and_new_config() {
|
||||||
|
let c = Config {
|
||||||
|
tab_inherit_working_directory: false,
|
||||||
|
..Config::default()
|
||||||
|
};
|
||||||
|
assert!(should_inherit_working_directory(
|
||||||
|
NewSurfaceContext::Window,
|
||||||
|
&c
|
||||||
|
));
|
||||||
|
assert!(!should_inherit_working_directory(
|
||||||
|
NewSurfaceContext::Tab,
|
||||||
|
&c
|
||||||
|
));
|
||||||
|
let n = new_config(&c, NewSurfaceContext::Window, Some("/tmp".into()));
|
||||||
|
assert_eq!(
|
||||||
|
n.working_directory,
|
||||||
|
Some(WorkingDirectory::Path("/tmp".into()))
|
||||||
|
);
|
||||||
|
let n = new_config(&c, NewSurfaceContext::Tab, Some("/tmp".into()));
|
||||||
|
assert_eq!(n.working_directory, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ghostty-apprt notes
|
||||||
|
|
||||||
|
## Mapping (ZIG/src/apprt -> crates/ghostty-apprt/src)
|
||||||
|
- action.zig -> action.rs: `Target<S>` (+`TargetKey`), `Action` enum (all 65 variants, docs ported), `Key` (C-enum order, `Action::key()`), helper enums/structs (SplitDirection, GotoSplit, GotoWindow, GotoTab, ResizeSplit, MoveTab, Fullscreen, FloatWindow, SecureInput, Inspector, QuitTimer, Readonly, MouseVisibility, PromptTitle, SizeLimit, InitialSize, CellSize, SetTitle, Pwd, DesktopNotification, KeySequence, KeyTable, ColorChange/ColorKind, ReloadConfig, ConfigChange, OpenUrl/OpenUrlKind, CloseTabMode, CommandFinished, StartSearch, SearchTotal, SearchSelected, MouseOverLink).
|
||||||
|
- structs.zig -> structs.rs: ContentScale, SurfaceSize, CursorPos, ImePos (Zig `IMEPos`), Clipboard, ClipboardContent, ClipboardRequest(+Type), ColorScheme, Selection.
|
||||||
|
- surface.zig -> surface.rs: `Message`, `Mailbox<S, A>`, `AppMailbox<S>` trait, `NewSurfaceContext`, `should_inherit_working_directory`, `new_config`.
|
||||||
|
- ipc.zig -> ipc.rs: `Target` (Class/Detect), `Action::NewWindow(NewWindow{arguments})`, `ActionKey`, `Error::IpcFailed`.
|
||||||
|
- runtime.zig/none.zig -> runtime.rs: `Runtime{None,Gtk}`, `none_perform_ipc` (always false).
|
||||||
|
- Skipped: embedded.zig, browser.zig, gtk.zig (apprt-gtk), all C ABI (`C`, `CValue`, `cval`, `getGObjectType`).
|
||||||
|
|
||||||
|
## Reused types
|
||||||
|
ghostty_input::binding::Trigger, ghostty_vt::mouse_shape::MouseShape, ghostty_vt::osc::{ProgressReport, color::ColoredTarget},
|
||||||
|
ghostty_config::{config::Config, types_b::Duration, types_a::WorkingDirectory}, ghostty_datastruct::{MessageData, blocking_queue::Timeout}.
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
- Strings are `String` (not NUL-terminated); `surface::Message::SetTitle` and `DesktopNotification` keep fixed arrays (256 / 64 / 256 bytes, NUL-terminated) with `title()`/`body()` accessors.
|
||||||
|
- `ConfigChange.config` and `Message::ChangeConfig` hold `Arc<Config>` instead of a borrowed pointer.
|
||||||
|
- `GotoTab`/`ColorKind` (non-exhaustive Zig enums) are Rust enums with an `Index(i32)`/`Palette(i32)` catch-all and `from_int`/`int`.
|
||||||
|
- `Mailbox::push` forwards through the `AppMailbox<S>::push_surface_message(surface, msg, timeout)` trait (Zig wraps in `App.Message.surface_message`).
|
||||||
|
- `new_config` takes the previous focused surface's pwd as `Option<String>` rather than an `App`.
|
||||||
|
- Tests of kind `ghostty.h ...` (checkGhosttyHEnum) skipped (C ABI); `refAllDecls` skipped. Replaced by 16 Rust tests (discriminants, key order, roundtrips, mailbox, config inheritance).
|
||||||
|
|
||||||
|
## TODO(integration)
|
||||||
|
- `surface::Scrollbar` stands in for ghostty_terminal `page_list::Scrollbar`; `surface::RendererHealth` for ghostty_renderer `backend::Health`. Core should convert or (if dep order permits) re-export the real ones.
|
||||||
|
- ghostty-core implements `AppMailbox<S>` for its app mailbox (S = core surface handle) and instantiates `Target<S>`.
|
||||||
|
- Selection (structs) is the pixel-based apprt struct; terminal::Selection is not used by any payload here.
|
||||||
Reference in new issue
Block a user