mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
Merge pull request #2662 from ghostty-org/push-zzpqurznxmlq
pwd changed apprt action, macOS app sets proxy icon based on this
This commit is contained in:
@ -430,6 +430,11 @@ typedef struct {
|
|||||||
const char* title;
|
const char* title;
|
||||||
} ghostty_action_set_title_s;
|
} ghostty_action_set_title_s;
|
||||||
|
|
||||||
|
// apprt.action.Pwd.C
|
||||||
|
typedef struct {
|
||||||
|
const char* pwd;
|
||||||
|
} ghostty_action_pwd_s;
|
||||||
|
|
||||||
// terminal.MouseShape
|
// terminal.MouseShape
|
||||||
typedef enum {
|
typedef enum {
|
||||||
GHOSTTY_MOUSE_SHAPE_DEFAULT,
|
GHOSTTY_MOUSE_SHAPE_DEFAULT,
|
||||||
@ -552,6 +557,7 @@ typedef enum {
|
|||||||
GHOSTTY_ACTION_RENDER_INSPECTOR,
|
GHOSTTY_ACTION_RENDER_INSPECTOR,
|
||||||
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
|
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
|
||||||
GHOSTTY_ACTION_SET_TITLE,
|
GHOSTTY_ACTION_SET_TITLE,
|
||||||
|
GHOSTTY_ACTION_PWD,
|
||||||
GHOSTTY_ACTION_MOUSE_SHAPE,
|
GHOSTTY_ACTION_MOUSE_SHAPE,
|
||||||
GHOSTTY_ACTION_MOUSE_VISIBILITY,
|
GHOSTTY_ACTION_MOUSE_VISIBILITY,
|
||||||
GHOSTTY_ACTION_MOUSE_OVER_LINK,
|
GHOSTTY_ACTION_MOUSE_OVER_LINK,
|
||||||
@ -576,6 +582,7 @@ typedef union {
|
|||||||
ghostty_action_inspector_e inspector;
|
ghostty_action_inspector_e inspector;
|
||||||
ghostty_action_desktop_notification_s desktop_notification;
|
ghostty_action_desktop_notification_s desktop_notification;
|
||||||
ghostty_action_set_title_s set_title;
|
ghostty_action_set_title_s set_title;
|
||||||
|
ghostty_action_pwd_s pwd;
|
||||||
ghostty_action_mouse_shape_e mouse_shape;
|
ghostty_action_mouse_shape_e mouse_shape;
|
||||||
ghostty_action_mouse_visibility_e mouse_visibility;
|
ghostty_action_mouse_visibility_e mouse_visibility;
|
||||||
ghostty_action_mouse_over_link_s mouse_over_link;
|
ghostty_action_mouse_over_link_s mouse_over_link;
|
||||||
@ -705,7 +712,6 @@ void ghostty_surface_complete_clipboard_request(ghostty_surface_t,
|
|||||||
const char*,
|
const char*,
|
||||||
void*,
|
void*,
|
||||||
bool);
|
bool);
|
||||||
uintptr_t ghostty_surface_pwd(ghostty_surface_t, char*, uintptr_t);
|
|
||||||
bool ghostty_surface_has_selection(ghostty_surface_t);
|
bool ghostty_surface_has_selection(ghostty_surface_t);
|
||||||
uintptr_t ghostty_surface_selection(ghostty_surface_t, char*, uintptr_t);
|
uintptr_t ghostty_surface_selection(ghostty_surface_t, char*, uintptr_t);
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
// Various state values sent back up from the currently focused terminals.
|
// Various state values sent back up from the currently focused terminals.
|
||||||
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
|
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface
|
||||||
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
|
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle
|
||||||
|
@FocusedValue(\.ghosttySurfacePwd) private var surfacePwd
|
||||||
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
|
@FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit
|
||||||
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
|
@FocusedValue(\.ghosttySurfaceCellSize) private var cellSize
|
||||||
|
|
||||||
@ -65,14 +66,11 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
|
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
// The proxy icon URL for our window
|
// The pwd of the focused surface as a URL
|
||||||
private var proxyIconURL: URL? {
|
private var pwdURL: URL? {
|
||||||
guard let proxyURLString = focusedSurface?.pwd else {
|
guard let surfacePwd else { return nil }
|
||||||
return nil
|
return URL(fileURLWithPath: surfacePwd)
|
||||||
}
|
|
||||||
// Use fileURLWithPath initializer for file paths
|
|
||||||
return URL(fileURLWithPath: proxyURLString)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -88,7 +86,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE) {
|
if (Ghostty.info.mode == GHOSTTY_BUILD_MODE_DEBUG || Ghostty.info.mode == GHOSTTY_BUILD_MODE_RELEASE_SAFE) {
|
||||||
DebugBuildWarningView()
|
DebugBuildWarningView()
|
||||||
}
|
}
|
||||||
|
|
||||||
Ghostty.TerminalSplit(node: $viewModel.surfaceTree)
|
Ghostty.TerminalSplit(node: $viewModel.surfaceTree)
|
||||||
.environmentObject(ghostty)
|
.environmentObject(ghostty)
|
||||||
.focused($focused)
|
.focused($focused)
|
||||||
@ -99,7 +97,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||||||
.onChange(of: title) { newValue in
|
.onChange(of: title) { newValue in
|
||||||
self.delegate?.titleDidChange(to: newValue)
|
self.delegate?.titleDidChange(to: newValue)
|
||||||
}
|
}
|
||||||
.onChange(of: proxyIconURL) { newValue in
|
.onChange(of: pwdURL) { newValue in
|
||||||
self.delegate?.pwdDidChange(to: newValue)
|
self.delegate?.pwdDidChange(to: newValue)
|
||||||
}
|
}
|
||||||
.onChange(of: cellSize) { newValue in
|
.onChange(of: cellSize) { newValue in
|
||||||
|
@ -488,6 +488,9 @@ extension Ghostty {
|
|||||||
case GHOSTTY_ACTION_SET_TITLE:
|
case GHOSTTY_ACTION_SET_TITLE:
|
||||||
setTitle(app, target: target, v: action.action.set_title)
|
setTitle(app, target: target, v: action.action.set_title)
|
||||||
|
|
||||||
|
case GHOSTTY_ACTION_PWD:
|
||||||
|
pwdChanged(app, target: target, v: action.action.pwd)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_OPEN_CONFIG:
|
case GHOSTTY_ACTION_OPEN_CONFIG:
|
||||||
ghostty_config_open()
|
ghostty_config_open()
|
||||||
|
|
||||||
@ -943,6 +946,26 @@ extension Ghostty {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static func pwdChanged(
|
||||||
|
_ app: ghostty_app_t,
|
||||||
|
target: ghostty_target_s,
|
||||||
|
v: ghostty_action_pwd_s) {
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
Ghostty.logger.warning("pwd change does nothing with an app target")
|
||||||
|
return
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||||
|
guard let pwd = String(cString: v.pwd!, encoding: .utf8) else { return }
|
||||||
|
surfaceView.pwd = pwd
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static func setMouseShape(
|
private static func setMouseShape(
|
||||||
_ app: ghostty_app_t,
|
_ app: ghostty_app_t,
|
||||||
target: ghostty_target_s,
|
target: ghostty_target_s,
|
||||||
|
@ -92,6 +92,7 @@ extension Ghostty {
|
|||||||
Surface(view: surfaceView, size: geo.size)
|
Surface(view: surfaceView, size: geo.size)
|
||||||
.focused($surfaceFocus)
|
.focused($surfaceFocus)
|
||||||
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
|
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
|
||||||
|
.focusedValue(\.ghosttySurfacePwd, surfaceView.pwd)
|
||||||
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
||||||
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
|
.focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize)
|
||||||
#if canImport(AppKit)
|
#if canImport(AppKit)
|
||||||
@ -512,9 +513,7 @@ extension FocusedValues {
|
|||||||
struct FocusedGhosttySurface: FocusedValueKey {
|
struct FocusedGhosttySurface: FocusedValueKey {
|
||||||
typealias Value = Ghostty.SurfaceView
|
typealias Value = Ghostty.SurfaceView
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension FocusedValues {
|
|
||||||
var ghosttySurfaceTitle: String? {
|
var ghosttySurfaceTitle: String? {
|
||||||
get { self[FocusedGhosttySurfaceTitle.self] }
|
get { self[FocusedGhosttySurfaceTitle.self] }
|
||||||
set { self[FocusedGhosttySurfaceTitle.self] = newValue }
|
set { self[FocusedGhosttySurfaceTitle.self] = newValue }
|
||||||
@ -523,9 +522,16 @@ extension FocusedValues {
|
|||||||
struct FocusedGhosttySurfaceTitle: FocusedValueKey {
|
struct FocusedGhosttySurfaceTitle: FocusedValueKey {
|
||||||
typealias Value = String
|
typealias Value = String
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension FocusedValues {
|
var ghosttySurfacePwd: String? {
|
||||||
|
get { self[FocusedGhosttySurfacePwd.self] }
|
||||||
|
set { self[FocusedGhosttySurfacePwd.self] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FocusedGhosttySurfacePwd: FocusedValueKey {
|
||||||
|
typealias Value = String
|
||||||
|
}
|
||||||
|
|
||||||
var ghosttySurfaceZoomed: Bool? {
|
var ghosttySurfaceZoomed: Bool? {
|
||||||
get { self[FocusedGhosttySurfaceZoomed.self] }
|
get { self[FocusedGhosttySurfaceZoomed.self] }
|
||||||
set { self[FocusedGhosttySurfaceZoomed.self] = newValue }
|
set { self[FocusedGhosttySurfaceZoomed.self] = newValue }
|
||||||
@ -534,9 +540,7 @@ extension FocusedValues {
|
|||||||
struct FocusedGhosttySurfaceZoomed: FocusedValueKey {
|
struct FocusedGhosttySurfaceZoomed: FocusedValueKey {
|
||||||
typealias Value = Bool
|
typealias Value = Bool
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
extension FocusedValues {
|
|
||||||
var ghosttySurfaceCellSize: OSSize? {
|
var ghosttySurfaceCellSize: OSSize? {
|
||||||
get { self[FocusedGhosttySurfaceCellSize.self] }
|
get { self[FocusedGhosttySurfaceCellSize.self] }
|
||||||
set { self[FocusedGhosttySurfaceCellSize.self] = newValue }
|
set { self[FocusedGhosttySurfaceCellSize.self] = newValue }
|
||||||
|
@ -14,6 +14,10 @@ extension Ghostty {
|
|||||||
// to the app level and it is set from there.
|
// to the app level and it is set from there.
|
||||||
@Published var title: String = "👻"
|
@Published var title: String = "👻"
|
||||||
|
|
||||||
|
// The current pwd of the surface as defined by the pty. This can be
|
||||||
|
// changed with escape codes.
|
||||||
|
@Published var pwd: String? = nil
|
||||||
|
|
||||||
// The cell size of this surface. This is set by the core when the
|
// The cell size of this surface. This is set by the core when the
|
||||||
// surface is first created and any time the cell size changes (i.e.
|
// surface is first created and any time the cell size changes (i.e.
|
||||||
// when the font size changes). This is used to allow windows to be
|
// when the font size changes). This is used to allow windows to be
|
||||||
@ -71,17 +75,6 @@ extension Ghostty {
|
|||||||
return ghostty_surface_needs_confirm_quit(surface)
|
return ghostty_surface_needs_confirm_quit(surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the pwd of the surface if it has one.
|
|
||||||
var pwd: String? {
|
|
||||||
guard let surface = self.surface else { return nil }
|
|
||||||
let v = String(unsafeUninitializedCapacity: 1024) {
|
|
||||||
Int(ghostty_surface_pwd(surface, $0.baseAddress, UInt($0.count)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (v.count == 0) { return nil }
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the inspector instance for this surface, or nil if the
|
// Returns the inspector instance for this surface, or nil if the
|
||||||
// surface has been closed.
|
// surface has been closed.
|
||||||
var inspector: ghostty_inspector_t? {
|
var inspector: ghostty_inspector_t? {
|
||||||
|
@ -12,6 +12,9 @@ extension Ghostty {
|
|||||||
// to the app level and it is set from there.
|
// to the app level and it is set from there.
|
||||||
@Published var title: String = "👻"
|
@Published var title: String = "👻"
|
||||||
|
|
||||||
|
// The current pwd of the surface.
|
||||||
|
@Published var pwd: String? = nil
|
||||||
|
|
||||||
// The cell size of this surface. This is set by the core when the
|
// The cell size of this surface. This is set by the core when the
|
||||||
// surface is first created and any time the cell size changes (i.e.
|
// surface is first created and any time the cell size changes (i.e.
|
||||||
// when the font size changes). This is used to allow windows to be
|
// when the font size changes). This is used to allow windows to be
|
||||||
|
@ -856,6 +856,20 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.pwd_change => |w| {
|
||||||
|
defer w.deinit();
|
||||||
|
|
||||||
|
// We always allocate for this because we need to null-terminate.
|
||||||
|
const str = try self.alloc.dupeZ(u8, w.slice());
|
||||||
|
defer self.alloc.free(str);
|
||||||
|
|
||||||
|
try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.pwd,
|
||||||
|
.{ .pwd = str },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
.close => self.close(),
|
.close => self.close(),
|
||||||
|
|
||||||
// Close without confirmation.
|
// Close without confirmation.
|
||||||
|
@ -151,6 +151,9 @@ pub const Action = union(Key) {
|
|||||||
/// Set the title of the target.
|
/// Set the title of the target.
|
||||||
set_title: SetTitle,
|
set_title: SetTitle,
|
||||||
|
|
||||||
|
/// The current working directory has changed for the target terminal.
|
||||||
|
pwd: Pwd,
|
||||||
|
|
||||||
/// Set the mouse cursor shape.
|
/// Set the mouse cursor shape.
|
||||||
mouse_shape: terminal.MouseShape,
|
mouse_shape: terminal.MouseShape,
|
||||||
|
|
||||||
@ -215,6 +218,7 @@ pub const Action = union(Key) {
|
|||||||
render_inspector,
|
render_inspector,
|
||||||
desktop_notification,
|
desktop_notification,
|
||||||
set_title,
|
set_title,
|
||||||
|
pwd,
|
||||||
mouse_shape,
|
mouse_shape,
|
||||||
mouse_visibility,
|
mouse_visibility,
|
||||||
mouse_over_link,
|
mouse_over_link,
|
||||||
@ -417,6 +421,21 @@ pub const SetTitle = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Pwd = struct {
|
||||||
|
pwd: [:0]const u8,
|
||||||
|
|
||||||
|
// Sync with: ghostty_action_set_pwd_s
|
||||||
|
pub const C = extern struct {
|
||||||
|
pwd: [*:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn cval(self: Pwd) C {
|
||||||
|
return .{
|
||||||
|
.pwd = self.pwd.ptr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// The desktop notification to show.
|
/// The desktop notification to show.
|
||||||
pub const DesktopNotification = struct {
|
pub const DesktopNotification = struct {
|
||||||
title: [:0]const u8,
|
title: [:0]const u8,
|
||||||
|
@ -1428,25 +1428,6 @@ pub const CAPI = struct {
|
|||||||
return selection.len;
|
return selection.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies the surface working directory into the provided buffer and
|
|
||||||
/// returns the copied size. If the buffer is too small, there is no pwd,
|
|
||||||
/// or there is an error, then 0 is returned.
|
|
||||||
export fn ghostty_surface_pwd(surface: *Surface, buf: [*]u8, cap: usize) usize {
|
|
||||||
const pwd_ = surface.core_surface.pwd(global.alloc) catch |err| {
|
|
||||||
log.warn("error getting pwd err={}", .{err});
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
const pwd = pwd_ orelse return 0;
|
|
||||||
defer global.alloc.free(pwd);
|
|
||||||
|
|
||||||
// If the buffer is too small, return no pwd.
|
|
||||||
if (pwd.len > cap) return 0;
|
|
||||||
|
|
||||||
// Copy into the buffer and return the length
|
|
||||||
@memcpy(buf[0..pwd.len], pwd);
|
|
||||||
return pwd.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tell the surface that it needs to schedule a render
|
/// Tell the surface that it needs to schedule a render
|
||||||
export fn ghostty_surface_refresh(surface: *Surface) void {
|
export fn ghostty_surface_refresh(surface: *Surface) void {
|
||||||
surface.refresh();
|
surface.refresh();
|
||||||
|
@ -224,6 +224,7 @@ pub const App = struct {
|
|||||||
.cell_size,
|
.cell_size,
|
||||||
.renderer_health,
|
.renderer_health,
|
||||||
.color_change,
|
.color_change,
|
||||||
|
.pwd,
|
||||||
=> log.info("unimplemented action={}", .{action}),
|
=> log.info("unimplemented action={}", .{action}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -486,6 +486,7 @@ pub fn performAction(
|
|||||||
.render_inspector,
|
.render_inspector,
|
||||||
.renderer_health,
|
.renderer_health,
|
||||||
.color_change,
|
.color_change,
|
||||||
|
.pwd,
|
||||||
=> log.warn("unimplemented action={}", .{action}),
|
=> log.warn("unimplemented action={}", .{action}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,9 @@ pub const Message = union(enum) {
|
|||||||
color: terminal.color.RGB,
|
color: terminal.color.RGB,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// The terminal has reported a change in the working directory.
|
||||||
|
pwd_change: WriteReq,
|
||||||
|
|
||||||
pub const ReportTitleStyle = enum {
|
pub const ReportTitleStyle = enum {
|
||||||
csi_21_t,
|
csi_21_t,
|
||||||
|
|
||||||
|
@ -194,9 +194,9 @@ pub fn MessageData(comptime Elem: type, comptime small_size: comptime_int) type
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a const slice of the data pointed to by this request.
|
/// Returns a const slice of the data pointed to by this request.
|
||||||
pub fn slice(self: Self) []const Elem {
|
pub fn slice(self: *const Self) []const Elem {
|
||||||
return switch (self) {
|
return switch (self.*) {
|
||||||
.small => |v| v.data[0..v.len],
|
.small => |*v| v.data[0..v.len],
|
||||||
.stable => |v| v,
|
.stable => |v| v,
|
||||||
.alloc => |v| v.data,
|
.alloc => |v| v.data,
|
||||||
};
|
};
|
||||||
|
@ -1139,6 +1139,14 @@ pub const StreamHandler = struct {
|
|||||||
log.debug("terminal pwd: {s}", .{path});
|
log.debug("terminal pwd: {s}", .{path});
|
||||||
try self.terminal.setPwd(path);
|
try self.terminal.setPwd(path);
|
||||||
|
|
||||||
|
// Report it to the surface. If creating our write request fails
|
||||||
|
// then we just ignore it.
|
||||||
|
if (apprt.surface.Message.WriteReq.init(self.alloc, path)) |req| {
|
||||||
|
self.surfaceMessageWriter(.{ .pwd_change = req });
|
||||||
|
} else |err| {
|
||||||
|
log.warn("error notifying surface of pwd change err={}", .{err});
|
||||||
|
}
|
||||||
|
|
||||||
// If we haven't seen a title, use our pwd as the title.
|
// If we haven't seen a title, use our pwd as the title.
|
||||||
if (!self.seen_title) {
|
if (!self.seen_title) {
|
||||||
try self.changeWindowTitle(path);
|
try self.changeWindowTitle(path);
|
||||||
|
Reference in New Issue
Block a user