mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Merge pull request #114 from mitchellh/process-exit
Detect command exit and close surface
This commit is contained in:
@ -80,6 +80,7 @@ pub fn open(size: winsize) !Pty {
|
|||||||
|
|
||||||
pub fn deinit(self: *Pty) void {
|
pub fn deinit(self: *Pty) void {
|
||||||
_ = std.os.system.close(self.master);
|
_ = std.os.system.close(self.master);
|
||||||
|
_ = std.os.system.close(self.slave);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,6 +479,12 @@ pub fn deinit(self: *Surface) void {
|
|||||||
log.info("surface closed addr={x}", .{@ptrToInt(self)});
|
log.info("surface closed addr={x}", .{@ptrToInt(self)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close this surface. This will trigger the runtime to start the
|
||||||
|
/// close process, which should ultimately deinitialize this surface.
|
||||||
|
pub fn close(self: *Surface) void {
|
||||||
|
self.rt_surface.close();
|
||||||
|
}
|
||||||
|
|
||||||
/// Called from the app thread to handle mailbox messages to our specific
|
/// Called from the app thread to handle mailbox messages to our specific
|
||||||
/// surface.
|
/// surface.
|
||||||
pub fn handleMessage(self: *Surface, msg: Message) !void {
|
pub fn handleMessage(self: *Surface, msg: Message) !void {
|
||||||
@ -503,6 +509,8 @@ pub fn handleMessage(self: *Surface, msg: Message) !void {
|
|||||||
try self.clipboardWrite(v.data);
|
try self.clipboardWrite(v.data);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.close => self.close(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -953,11 +961,7 @@ pub fn keyCallback(
|
|||||||
} else log.warn("runtime doesn't implement gotoSplit", .{});
|
} else log.warn("runtime doesn't implement gotoSplit", .{});
|
||||||
},
|
},
|
||||||
|
|
||||||
.close_surface => {
|
.close_surface => self.close(),
|
||||||
if (@hasDecl(apprt.Surface, "closeSurface")) {
|
|
||||||
try self.rt_surface.closeSurface();
|
|
||||||
} else log.warn("runtime doesn't implement closeSurface", .{});
|
|
||||||
},
|
|
||||||
|
|
||||||
.close_window => {
|
.close_window => {
|
||||||
_ = self.app_mailbox.push(.{ .close = self }, .{ .instant = {} });
|
_ = self.app_mailbox.push(.{ .close = self }, .{ .instant = {} });
|
||||||
|
@ -167,7 +167,7 @@ pub const Surface = struct {
|
|||||||
func(self.opts.userdata, direction);
|
func(self.opts.userdata, direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn closeSurface(self: *const Surface) !void {
|
pub fn close(self: *const Surface) void {
|
||||||
const func = self.app.opts.close_surface orelse {
|
const func = self.app.opts.close_surface orelse {
|
||||||
log.info("runtime embedder does not support closing a surface", .{});
|
log.info("runtime embedder does not support closing a surface", .{});
|
||||||
return;
|
return;
|
||||||
@ -486,7 +486,7 @@ pub const CAPI = struct {
|
|||||||
/// Request that the surface become closed. This will go through the
|
/// Request that the surface become closed. This will go through the
|
||||||
/// normal trigger process that a close surface input binding would.
|
/// normal trigger process that a close surface input binding would.
|
||||||
export fn ghostty_surface_request_close(ptr: *Surface) void {
|
export fn ghostty_surface_request_close(ptr: *Surface) void {
|
||||||
ptr.closeSurface() catch {};
|
ptr.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request that the surface split in the given direction.
|
/// Request that the surface split in the given direction.
|
||||||
|
@ -378,6 +378,11 @@ pub const Surface = struct {
|
|||||||
try self.app.newTab(&self.core_surface);
|
try self.app.newTab(&self.core_surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close this surface.
|
||||||
|
pub fn close(self: *const Surface) void {
|
||||||
|
self.window.setShouldClose(true);
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the size limits of the window.
|
/// Set the size limits of the window.
|
||||||
/// Note: this interface is not good, we should redo it if we plan
|
/// Note: this interface is not good, we should redo it if we plan
|
||||||
/// to use this more. i.e. you can't set max width but no max height,
|
/// to use this more. i.e. you can't set max width but no max height,
|
||||||
|
@ -609,7 +609,7 @@ pub const Surface = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Close this surface.
|
/// Close this surface.
|
||||||
fn close(self: *Surface) void {
|
pub fn close(self: *Surface) void {
|
||||||
self.window.closeSurface(self);
|
self.window.closeSurface(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,10 @@ pub const Message = union(enum) {
|
|||||||
|
|
||||||
/// Write the clipboard contents.
|
/// Write the clipboard contents.
|
||||||
clipboard_write: WriteReq,
|
clipboard_write: WriteReq,
|
||||||
|
|
||||||
|
/// Close the surface. This will only close the current surface that
|
||||||
|
/// receives this, not the full application.
|
||||||
|
close: void,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A surface mailbox.
|
/// A surface mailbox.
|
||||||
|
@ -105,6 +105,10 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
// Start our subprocess
|
// Start our subprocess
|
||||||
const master_fd = try self.subprocess.start(alloc);
|
const master_fd = try self.subprocess.start(alloc);
|
||||||
errdefer self.subprocess.stop();
|
errdefer self.subprocess.stop();
|
||||||
|
const pid = pid: {
|
||||||
|
const command = self.subprocess.command orelse return error.ProcessNotStarted;
|
||||||
|
break :pid command.pid orelse return error.ProcessNoPid;
|
||||||
|
};
|
||||||
|
|
||||||
// Setup our data that is used for callbacks
|
// Setup our data that is used for callbacks
|
||||||
var ev_data_ptr = try alloc.create(EventData);
|
var ev_data_ptr = try alloc.create(EventData);
|
||||||
@ -118,13 +122,19 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
var wakeup = try xev.Async.init();
|
var wakeup = try xev.Async.init();
|
||||||
errdefer wakeup.deinit();
|
errdefer wakeup.deinit();
|
||||||
|
|
||||||
|
// Watcher to detect subprocess exit
|
||||||
|
var process = try xev.Process.init(pid);
|
||||||
|
errdefer process.deinit();
|
||||||
|
|
||||||
// Setup our event data before we start
|
// Setup our event data before we start
|
||||||
ev_data_ptr.* = .{
|
ev_data_ptr.* = .{
|
||||||
.writer_mailbox = thread.mailbox,
|
.writer_mailbox = thread.mailbox,
|
||||||
.writer_wakeup = thread.wakeup,
|
.writer_wakeup = thread.wakeup,
|
||||||
|
.surface_mailbox = self.surface_mailbox,
|
||||||
.renderer_state = self.renderer_state,
|
.renderer_state = self.renderer_state,
|
||||||
.renderer_wakeup = self.renderer_wakeup,
|
.renderer_wakeup = self.renderer_wakeup,
|
||||||
.renderer_mailbox = self.renderer_mailbox,
|
.renderer_mailbox = self.renderer_mailbox,
|
||||||
|
.process = process,
|
||||||
.data_stream = stream,
|
.data_stream = stream,
|
||||||
.loop = &thread.loop,
|
.loop = &thread.loop,
|
||||||
.terminal_stream = .{
|
.terminal_stream = .{
|
||||||
@ -133,7 +143,6 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
.ev = ev_data_ptr,
|
.ev = ev_data_ptr,
|
||||||
.terminal = &self.terminal,
|
.terminal = &self.terminal,
|
||||||
.grid_size = &self.grid_size,
|
.grid_size = &self.grid_size,
|
||||||
.surface_mailbox = self.surface_mailbox,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -143,6 +152,15 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
self.data = ev_data_ptr;
|
self.data = ev_data_ptr;
|
||||||
errdefer self.data = null;
|
errdefer self.data = null;
|
||||||
|
|
||||||
|
// Start our process watcher
|
||||||
|
process.wait(
|
||||||
|
ev_data_ptr.loop,
|
||||||
|
&ev_data_ptr.process_wait_c,
|
||||||
|
EventData,
|
||||||
|
ev_data_ptr,
|
||||||
|
processExit,
|
||||||
|
);
|
||||||
|
|
||||||
// Start our reader thread
|
// Start our reader thread
|
||||||
const read_thread = try std.Thread.spawn(
|
const read_thread = try std.Thread.spawn(
|
||||||
.{},
|
.{},
|
||||||
@ -164,6 +182,7 @@ pub fn threadExit(self: *Exec, data: ThreadData) void {
|
|||||||
self.data = null;
|
self.data = null;
|
||||||
|
|
||||||
// Stop our subprocess
|
// Stop our subprocess
|
||||||
|
if (data.ev.process_exited) self.subprocess.externalExit();
|
||||||
self.subprocess.stop();
|
self.subprocess.stop();
|
||||||
|
|
||||||
// Wait for our reader thread to end
|
// Wait for our reader thread to end
|
||||||
@ -258,6 +277,9 @@ const EventData = struct {
|
|||||||
writer_mailbox: *termio.Mailbox,
|
writer_mailbox: *termio.Mailbox,
|
||||||
writer_wakeup: xev.Async,
|
writer_wakeup: xev.Async,
|
||||||
|
|
||||||
|
/// Mailbox for the surface.
|
||||||
|
surface_mailbox: apprt.surface.Mailbox,
|
||||||
|
|
||||||
/// The stream parser. This parses the stream of escape codes and so on
|
/// The stream parser. This parses the stream of escape codes and so on
|
||||||
/// from the child process and calls callbacks in the stream handler.
|
/// from the child process and calls callbacks in the stream handler.
|
||||||
terminal_stream: terminal.Stream(StreamHandler),
|
terminal_stream: terminal.Stream(StreamHandler),
|
||||||
@ -272,6 +294,14 @@ const EventData = struct {
|
|||||||
/// The mailbox for notifying the renderer of things.
|
/// The mailbox for notifying the renderer of things.
|
||||||
renderer_mailbox: *renderer.Thread.Mailbox,
|
renderer_mailbox: *renderer.Thread.Mailbox,
|
||||||
|
|
||||||
|
/// The process watcher
|
||||||
|
process: xev.Process,
|
||||||
|
process_exited: bool = false,
|
||||||
|
|
||||||
|
/// This is used for both waiting for the process to exit and then
|
||||||
|
/// subsequently to wait for the data_stream to close.
|
||||||
|
process_wait_c: xev.Completion = .{},
|
||||||
|
|
||||||
/// The data stream is the main IO for the pty.
|
/// The data stream is the main IO for the pty.
|
||||||
data_stream: xev.Stream,
|
data_stream: xev.Stream,
|
||||||
|
|
||||||
@ -301,6 +331,9 @@ const EventData = struct {
|
|||||||
|
|
||||||
// Stop our data stream
|
// Stop our data stream
|
||||||
self.data_stream.deinit();
|
self.data_stream.deinit();
|
||||||
|
|
||||||
|
// Stop our process watcher
|
||||||
|
self.process.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This queues a render operation with the renderer thread. The render
|
/// This queues a render operation with the renderer thread. The render
|
||||||
@ -311,6 +344,26 @@ const EventData = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn processExit(
|
||||||
|
ev_: ?*EventData,
|
||||||
|
_: *xev.Loop,
|
||||||
|
_: *xev.Completion,
|
||||||
|
r: xev.Process.WaitError!u32,
|
||||||
|
) xev.CallbackAction {
|
||||||
|
const code = r catch unreachable;
|
||||||
|
log.debug("child process exited status={}", .{code});
|
||||||
|
|
||||||
|
const ev = ev_.?;
|
||||||
|
ev.process_exited = true;
|
||||||
|
|
||||||
|
// Notify our surface we want to close
|
||||||
|
_ = ev.surface_mailbox.push(.{
|
||||||
|
.close = {},
|
||||||
|
}, .{ .forever = {} });
|
||||||
|
|
||||||
|
return .disarm;
|
||||||
|
}
|
||||||
|
|
||||||
fn ttyWrite(
|
fn ttyWrite(
|
||||||
ev_: ?*EventData,
|
ev_: ?*EventData,
|
||||||
_: *xev.Loop,
|
_: *xev.Loop,
|
||||||
@ -537,6 +590,12 @@ const Subprocess = struct {
|
|||||||
return pty.master;
|
return pty.master;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called to notify that we exited externally so we can unset our
|
||||||
|
/// running state.
|
||||||
|
pub fn externalExit(self: *Subprocess) void {
|
||||||
|
self.command = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Stop the subprocess. This is safe to call anytime. This will wait
|
/// Stop the subprocess. This is safe to call anytime. This will wait
|
||||||
/// for the subprocess to end so it will block.
|
/// for the subprocess to end so it will block.
|
||||||
pub fn stop(self: *Subprocess) void {
|
pub fn stop(self: *Subprocess) void {
|
||||||
@ -752,7 +811,6 @@ const StreamHandler = struct {
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
grid_size: *renderer.GridSize,
|
grid_size: *renderer.GridSize,
|
||||||
terminal: *terminal.Terminal,
|
terminal: *terminal.Terminal,
|
||||||
surface_mailbox: apprt.surface.Mailbox,
|
|
||||||
|
|
||||||
/// This is set to true when a message was written to the writer
|
/// This is set to true when a message was written to the writer
|
||||||
/// mailbox. This can be used by callers to determine if they need
|
/// mailbox. This can be used by callers to determine if they need
|
||||||
@ -1099,7 +1157,7 @@ const StreamHandler = struct {
|
|||||||
std.mem.copy(u8, &buf, title);
|
std.mem.copy(u8, &buf, title);
|
||||||
buf[title.len] = 0;
|
buf[title.len] = 0;
|
||||||
|
|
||||||
_ = self.surface_mailbox.push(.{
|
_ = self.ev.surface_mailbox.push(.{
|
||||||
.set_title = buf,
|
.set_title = buf,
|
||||||
}, .{ .forever = {} });
|
}, .{ .forever = {} });
|
||||||
}
|
}
|
||||||
@ -1111,14 +1169,14 @@ const StreamHandler = struct {
|
|||||||
|
|
||||||
// Get clipboard contents
|
// Get clipboard contents
|
||||||
if (data.len == 1 and data[0] == '?') {
|
if (data.len == 1 and data[0] == '?') {
|
||||||
_ = self.surface_mailbox.push(.{
|
_ = self.ev.surface_mailbox.push(.{
|
||||||
.clipboard_read = kind,
|
.clipboard_read = kind,
|
||||||
}, .{ .forever = {} });
|
}, .{ .forever = {} });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write clipboard contents
|
// Write clipboard contents
|
||||||
_ = self.surface_mailbox.push(.{
|
_ = self.ev.surface_mailbox.push(.{
|
||||||
.clipboard_write = try apprt.surface.Message.WriteReq.init(
|
.clipboard_write = try apprt.surface.Message.WriteReq.init(
|
||||||
self.alloc,
|
self.alloc,
|
||||||
data,
|
data,
|
||||||
|
2
vendor/libxev
vendored
2
vendor/libxev
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 46e3dafa477a9a92abe7b425cafdbcbe7ba3574d
|
Subproject commit 9024a57ef9133382f95927eaf4b6c2b1662598e0
|
Reference in New Issue
Block a user