From e7e6e06df25f5d2edebb577cd33c5c1e317c3039 Mon Sep 17 00:00:00 2001 From: ocean6954 Date: Mon, 30 Mar 2026 10:57:22 +0900 Subject: [PATCH] appintents: expose PID and TTY on TerminalEntity Expose the foreground process PID and slave TTY name on TerminalEntity via the getProcessInfo() API added in #11639. This allows Shortcuts/Siri to query process information for a given terminal session. Closes #10756 Co-Authored-By: Claude Opus 4.6 (1M context) --- include/ghostty.h | 2 ++ .../App Intents/Entities/TerminalEntity.swift | 8 ++++++++ .../App Intents/GetTerminalDetailsIntent.swift | 6 ++++++ macos/Sources/Ghostty/Ghostty.Surface.swift | 14 ++++++++++++++ src/apprt/embedded.zig | 13 +++++++++++++ 5 files changed, 43 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index 3c4002abc01..90c39608a40 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -1085,6 +1085,8 @@ ghostty_surface_config_s ghostty_surface_inherited_config(ghostty_surface_t, gho void ghostty_surface_update_config(ghostty_surface_t, ghostty_config_t); bool ghostty_surface_needs_confirm_quit(ghostty_surface_t); bool ghostty_surface_process_exited(ghostty_surface_t); +uint64_t ghostty_surface_child_pid(ghostty_surface_t); +const char* ghostty_surface_tty_name(ghostty_surface_t); void ghostty_surface_refresh(ghostty_surface_t); void ghostty_surface_draw(ghostty_surface_t); void ghostty_surface_set_content_scale(ghostty_surface_t, double, double); diff --git a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift index a2c4abea05a..462b1b9b6dd 100644 --- a/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift +++ b/macos/Sources/Features/App Intents/Entities/TerminalEntity.swift @@ -14,6 +14,12 @@ struct TerminalEntity: AppEntity { @Property(title: "Kind") var kind: Kind + @Property(title: "PID") + var pid: Int? + + @Property(title: "TTY") + var tty: String? + var screenshot: NSImage? static var typeDisplayRepresentation: TypeDisplayRepresentation { @@ -49,6 +55,8 @@ struct TerminalEntity: AppEntity { self.id = view.id self.title = view.title self.workingDirectory = view.pwd + self.pid = view.surfaceModel?.childPID + self.tty = view.surfaceModel?.ttyName if let nsImage = ImageRenderer(content: view.screenshot()).nsImage { self.screenshot = nsImage } diff --git a/macos/Sources/Features/App Intents/GetTerminalDetailsIntent.swift b/macos/Sources/Features/App Intents/GetTerminalDetailsIntent.swift index 99d6e39ba4b..e406295a810 100644 --- a/macos/Sources/Features/App Intents/GetTerminalDetailsIntent.swift +++ b/macos/Sources/Features/App Intents/GetTerminalDetailsIntent.swift @@ -35,6 +35,8 @@ struct GetTerminalDetailsIntent: AppIntent { switch detail { case .title: return .result(value: terminal.title) case .workingDirectory: return .result(value: terminal.workingDirectory) + case .pid: return .result(value: terminal.pid.map { String($0) }) + case .tty: return .result(value: terminal.tty) case .allContents: guard let view = terminal.surfaceView else { throw GhosttyIntentError.surfaceNotFound } return .result(value: view.cachedScreenContents.get()) @@ -53,6 +55,8 @@ struct GetTerminalDetailsIntent: AppIntent { enum TerminalDetail: String { case title case workingDirectory + case pid + case tty case allContents case selectedText case visibleText @@ -64,6 +68,8 @@ extension TerminalDetail: AppEnum { static var caseDisplayRepresentations: [Self: DisplayRepresentation] = [ .title: .init(title: "Title"), .workingDirectory: .init(title: "Working Directory"), + .pid: .init(title: "PID"), + .tty: .init(title: "TTY"), .allContents: .init(title: "Full Contents"), .selectedText: .init(title: "Selected Text"), .visibleText: .init(title: "Visible Text"), diff --git a/macos/Sources/Ghostty/Ghostty.Surface.swift b/macos/Sources/Ghostty/Ghostty.Surface.swift index b072db15e36..71ec8f35e61 100644 --- a/macos/Sources/Ghostty/Ghostty.Surface.swift +++ b/macos/Sources/Ghostty/Ghostty.Surface.swift @@ -82,6 +82,20 @@ extension Ghostty { event.withCValue { keyIsBinding($0) } } + /// The PID of the foreground process, or nil if not available. + @MainActor + var childPID: Int? { + let pid = ghostty_surface_child_pid(surface) + return pid != 0 ? Int(pid) : nil + } + + /// The TTY device name (e.g. "/dev/ttys003"), or nil if not available. + @MainActor + var ttyName: String? { + guard let ptr = ghostty_surface_tty_name(surface) else { return nil } + return String(cString: ptr) + } + /// Whether the terminal has captured mouse input. /// /// When the mouse is captured, the terminal application is receiving mouse events diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 0d5a4f8da20..d3b1f0f8e80 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -1598,6 +1598,19 @@ pub const CAPI = struct { return surface.core_surface.child_exited; } + /// Returns the PID of the foreground process running in the surface, + /// or 0 if the process is not available. + export fn ghostty_surface_child_pid(surface: *Surface) u64 { + return surface.core_surface.getProcessInfo(.foreground_pid) orelse 0; + } + + /// Returns the TTY device name of the surface (e.g. "/dev/ttys003"), + /// or null if not available. + export fn ghostty_surface_tty_name(surface: *Surface) ?[*:0]const u8 { + const name = surface.core_surface.getProcessInfo(.tty_name) orelse return null; + return name.ptr; + } + /// Returns true if the surface has a selection. export fn ghostty_surface_has_selection(surface: *Surface) bool { return surface.core_surface.hasSelection();