diff --git a/include/ghostty.h b/include/ghostty.h
index afc20bb3f..5ec1885b0 100644
--- a/include/ghostty.h
+++ b/include/ghostty.h
@@ -1115,6 +1115,8 @@ GHOSTTY_API void ghostty_surface_set_focus(ghostty_surface_t, bool);
GHOSTTY_API void ghostty_surface_set_occlusion(ghostty_surface_t, bool);
GHOSTTY_API void ghostty_surface_set_size(ghostty_surface_t, uint32_t, uint32_t);
GHOSTTY_API ghostty_surface_size_s ghostty_surface_size(ghostty_surface_t);
+GHOSTTY_API uint64_t ghostty_surface_foreground_pid(ghostty_surface_t);
+GHOSTTY_API ghostty_string_s ghostty_surface_tty_name(ghostty_surface_t);
GHOSTTY_API void ghostty_surface_set_color_scheme(ghostty_surface_t,
ghostty_color_scheme_e);
GHOSTTY_API ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t,
diff --git a/macos/Ghostty.sdef b/macos/Ghostty.sdef
index bdfc501fb..c84cf2f6c 100644
--- a/macos/Ghostty.sdef
+++ b/macos/Ghostty.sdef
@@ -90,6 +90,8 @@
+
+
diff --git a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift
index a2c4abea0..1a0a0acb5 100644
--- a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift
+++ b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift
@@ -11,6 +11,12 @@ struct TerminalEntity: AppEntity {
@Property(title: "Working Directory")
var workingDirectory: String?
+ @Property(title: "PID")
+ var pid: Int?
+
+ @Property(title: "TTY")
+ var tty: String?
+
@Property(title: "Kind")
var kind: Kind
@@ -49,6 +55,8 @@ struct TerminalEntity: AppEntity {
self.id = view.id
self.title = view.title
self.workingDirectory = view.pwd
+ self.pid = view.surfaceModel?.foregroundPID
+ self.tty = view.surfaceModel?.ttyName
if let nsImage = ImageRenderer(content: view.screenshot()).nsImage {
self.screenshot = nsImage
}
diff --git a/macos/Sources/Features/AppleScript/ScriptTerminal.swift b/macos/Sources/Features/AppleScript/ScriptTerminal.swift
index 2cdde382e..202eb662b 100644
--- a/macos/Sources/Features/AppleScript/ScriptTerminal.swift
+++ b/macos/Sources/Features/AppleScript/ScriptTerminal.swift
@@ -11,6 +11,8 @@ import AppKit
/// - `property id` -> `@objc(id)` getter below.
/// - `property title` -> `@objc(title)` getter below.
/// - `property working directory` -> `@objc(workingDirectory)` getter below.
+/// - `property pid` -> `@objc(pid)` getter below.
+/// - `property tty` -> `@objc(tty)` getter below.
///
/// We keep only a weak reference to the underlying `SurfaceView` so this
/// wrapper never extends the terminal's lifetime.
@@ -53,6 +55,20 @@ final class ScriptTerminal: NSObject {
return surfaceView?.pwd ?? ""
}
+ /// Exposed as the AppleScript `pid` property.
+ @objc(pid)
+ var pid: Int {
+ guard NSApp.isAppleScriptEnabled else { return 0 }
+ return surfaceView?.surfaceModel?.foregroundPID ?? 0
+ }
+
+ /// Exposed as the AppleScript `tty` property.
+ @objc(tty)
+ var tty: String {
+ guard NSApp.isAppleScriptEnabled else { return "" }
+ return surfaceView?.surfaceModel?.ttyName ?? ""
+ }
+
/// Used by command handling (`perform action ... on `).
func perform(action: String) -> Bool {
guard NSApp.isAppleScriptEnabled else { return false }
diff --git a/macos/Sources/Ghostty/Ghostty.Surface.swift b/macos/Sources/Ghostty/Ghostty.Surface.swift
index b072db15e..440b1b373 100644
--- a/macos/Sources/Ghostty/Ghostty.Surface.swift
+++ b/macos/Sources/Ghostty/Ghostty.Surface.swift
@@ -92,6 +92,21 @@ extension Ghostty {
ghostty_surface_mouse_captured(surface)
}
+ /// The PID of the foreground process group attached to the PTY.
+ @MainActor
+ var foregroundPID: Int? {
+ let pid = ghostty_surface_foreground_pid(surface)
+ guard pid != 0 else { return nil }
+ return Int(exactly: pid)
+ }
+
+ /// The PTY device name for this surface.
+ @MainActor
+ var ttyName: String? {
+ let ttyName = AllocatedString(ghostty_surface_tty_name(surface)).string
+ return ttyName.isEmpty ? nil : ttyName
+ }
+
/// Send a mouse button event to the terminal.
///
/// This sends a complete mouse button event including the button state (press/release),
diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig
index 519a35f2b..ad20cf465 100644
--- a/src/apprt/embedded.zig
+++ b/src/apprt/embedded.zig
@@ -20,6 +20,7 @@ const CoreInspector = @import("../inspector/main.zig").Inspector;
const CoreSurface = @import("../Surface.zig");
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
+const String = @import("../main_c.zig").String;
const log = std.log.scoped(.embedded_window);
@@ -1709,6 +1710,23 @@ pub const CAPI = struct {
};
}
+ /// Returns the PID of the foreground process for the surface PTY.
+ export fn ghostty_surface_foreground_pid(surface: *Surface) u64 {
+ return surface.core_surface.getProcessInfo(.foreground_pid) orelse 0;
+ }
+
+ /// Returns the PTY name for the surface. The returned string must be
+ /// freed by the caller via ghostty_string_free.
+ export fn ghostty_surface_tty_name(surface: *Surface) String {
+ const tty_name = surface.core_surface.getProcessInfo(.tty_name) orelse return .empty;
+ const copy = surface.app.core_app.alloc.dupeZ(u8, tty_name) catch |err| {
+ log.err("error allocating tty name err={}", .{err});
+ return .empty;
+ };
+
+ return .fromSlice(copy);
+ }
+
/// Update the color scheme of the surface.
export fn ghostty_surface_set_color_scheme(surface: *Surface, scheme_raw: c_int) void {
const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch {
diff --git a/src/config/CApi.zig b/src/config/CApi.zig
index 6e835b4a4..7d3366c93 100644
--- a/src/config/CApi.zig
+++ b/src/config/CApi.zig
@@ -2,7 +2,7 @@ const builtin = @import("builtin");
const std = @import("std");
const inputpkg = @import("../input.zig");
const state = &@import("../global.zig").state;
-const c = @import("../main_c.zig");
+const String = @import("../main_c.zig").String;
const Config = @import("Config.zig");
const c_get = @import("c_get.zig");
@@ -132,7 +132,7 @@ export fn ghostty_config_get_diagnostic(self: *Config, idx: u32) Diagnostic {
return .{ .message = message.ptr };
}
-export fn ghostty_config_open_path() c.String {
+export fn ghostty_config_open_path() String {
const path = edit.openPath(state.alloc) catch |err| {
log.err("error opening config in editor err={}", .{err});
return .empty;