Skip to content

Commit afdae72

Browse files
authored
macos: add pid and tty properties to AppleScript terminal and App Intents TerminalEntity (#11922)
2 parents 4446dba + 9a90022 commit afdae72

7 files changed

Lines changed: 63 additions & 2 deletions

File tree

include/ghostty.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,8 @@ GHOSTTY_API void ghostty_surface_set_focus(ghostty_surface_t, bool);
11151115
GHOSTTY_API void ghostty_surface_set_occlusion(ghostty_surface_t, bool);
11161116
GHOSTTY_API void ghostty_surface_set_size(ghostty_surface_t, uint32_t, uint32_t);
11171117
GHOSTTY_API ghostty_surface_size_s ghostty_surface_size(ghostty_surface_t);
1118+
GHOSTTY_API uint64_t ghostty_surface_foreground_pid(ghostty_surface_t);
1119+
GHOSTTY_API ghostty_string_s ghostty_surface_tty_name(ghostty_surface_t);
11181120
GHOSTTY_API void ghostty_surface_set_color_scheme(ghostty_surface_t,
11191121
ghostty_color_scheme_e);
11201122
GHOSTTY_API ghostty_input_mods_e ghostty_surface_key_translation_mods(ghostty_surface_t,

macos/Ghostty.sdef

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@
9090
<cocoa key="title"/>
9191
</property>
9292
<property name="working directory" code="Gwdr" type="text" access="r" description="Current working directory for the terminal process."/>
93+
<property name="pid" code="Gpid" type="integer" access="r" description="PID of the foreground process in this terminal."/>
94+
<property name="tty" code="Gtty" type="text" access="r" description="TTY device path for this terminal (e.g. /dev/ttys016)."/>
9395
<responds-to command="split">
9496
<cocoa method="handleSplitCommand:"/>
9597
</responds-to>

macos/Sources/Features/App Intents/Entities/TerminalEntity.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ struct TerminalEntity: AppEntity {
1111
@Property(title: "Working Directory")
1212
var workingDirectory: String?
1313

14+
@Property(title: "PID")
15+
var pid: Int?
16+
17+
@Property(title: "TTY")
18+
var tty: String?
19+
1420
@Property(title: "Kind")
1521
var kind: Kind
1622

@@ -49,6 +55,8 @@ struct TerminalEntity: AppEntity {
4955
self.id = view.id
5056
self.title = view.title
5157
self.workingDirectory = view.pwd
58+
self.pid = view.surfaceModel?.foregroundPID
59+
self.tty = view.surfaceModel?.ttyName
5260
if let nsImage = ImageRenderer(content: view.screenshot()).nsImage {
5361
self.screenshot = nsImage
5462
}

macos/Sources/Features/AppleScript/ScriptTerminal.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import AppKit
1111
/// - `property id` -> `@objc(id)` getter below.
1212
/// - `property title` -> `@objc(title)` getter below.
1313
/// - `property working directory` -> `@objc(workingDirectory)` getter below.
14+
/// - `property pid` -> `@objc(pid)` getter below.
15+
/// - `property tty` -> `@objc(tty)` getter below.
1416
///
1517
/// We keep only a weak reference to the underlying `SurfaceView` so this
1618
/// wrapper never extends the terminal's lifetime.
@@ -53,6 +55,20 @@ final class ScriptTerminal: NSObject {
5355
return surfaceView?.pwd ?? ""
5456
}
5557

58+
/// Exposed as the AppleScript `pid` property.
59+
@objc(pid)
60+
var pid: Int {
61+
guard NSApp.isAppleScriptEnabled else { return 0 }
62+
return surfaceView?.surfaceModel?.foregroundPID ?? 0
63+
}
64+
65+
/// Exposed as the AppleScript `tty` property.
66+
@objc(tty)
67+
var tty: String {
68+
guard NSApp.isAppleScriptEnabled else { return "" }
69+
return surfaceView?.surfaceModel?.ttyName ?? ""
70+
}
71+
5672
/// Used by command handling (`perform action ... on <terminal>`).
5773
func perform(action: String) -> Bool {
5874
guard NSApp.isAppleScriptEnabled else { return false }

macos/Sources/Ghostty/Ghostty.Surface.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,21 @@ extension Ghostty {
9292
ghostty_surface_mouse_captured(surface)
9393
}
9494

95+
/// The PID of the foreground process group attached to the PTY.
96+
@MainActor
97+
var foregroundPID: Int? {
98+
let pid = ghostty_surface_foreground_pid(surface)
99+
guard pid != 0 else { return nil }
100+
return Int(exactly: pid)
101+
}
102+
103+
/// The PTY device name for this surface.
104+
@MainActor
105+
var ttyName: String? {
106+
let ttyName = AllocatedString(ghostty_surface_tty_name(surface)).string
107+
return ttyName.isEmpty ? nil : ttyName
108+
}
109+
95110
/// Send a mouse button event to the terminal.
96111
///
97112
/// This sends a complete mouse button event including the button state (press/release),

src/apprt/embedded.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const CoreInspector = @import("../inspector/main.zig").Inspector;
2020
const CoreSurface = @import("../Surface.zig");
2121
const configpkg = @import("../config.zig");
2222
const Config = configpkg.Config;
23+
const String = @import("../main_c.zig").String;
2324

2425
const log = std.log.scoped(.embedded_window);
2526

@@ -1709,6 +1710,23 @@ pub const CAPI = struct {
17091710
};
17101711
}
17111712

1713+
/// Returns the PID of the foreground process for the surface PTY.
1714+
export fn ghostty_surface_foreground_pid(surface: *Surface) u64 {
1715+
return surface.core_surface.getProcessInfo(.foreground_pid) orelse 0;
1716+
}
1717+
1718+
/// Returns the PTY name for the surface. The returned string must be
1719+
/// freed by the caller via ghostty_string_free.
1720+
export fn ghostty_surface_tty_name(surface: *Surface) String {
1721+
const tty_name = surface.core_surface.getProcessInfo(.tty_name) orelse return .empty;
1722+
const copy = surface.app.core_app.alloc.dupeZ(u8, tty_name) catch |err| {
1723+
log.err("error allocating tty name err={}", .{err});
1724+
return .empty;
1725+
};
1726+
1727+
return .fromSlice(copy);
1728+
}
1729+
17121730
/// Update the color scheme of the surface.
17131731
export fn ghostty_surface_set_color_scheme(surface: *Surface, scheme_raw: c_int) void {
17141732
const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch {

src/config/CApi.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const builtin = @import("builtin");
22
const std = @import("std");
33
const inputpkg = @import("../input.zig");
44
const state = &@import("../global.zig").state;
5-
const c = @import("../main_c.zig");
5+
const String = @import("../main_c.zig").String;
66

77
const Config = @import("Config.zig");
88
const c_get = @import("c_get.zig");
@@ -132,7 +132,7 @@ export fn ghostty_config_get_diagnostic(self: *Config, idx: u32) Diagnostic {
132132
return .{ .message = message.ptr };
133133
}
134134

135-
export fn ghostty_config_open_path() c.String {
135+
export fn ghostty_config_open_path() String {
136136
const path = edit.openPath(state.alloc) catch |err| {
137137
log.err("error opening config in editor err={}", .{err});
138138
return .empty;

0 commit comments

Comments
 (0)