Merge pull request #114 from mitchellh/process-exit

Detect command exit and close surface
This commit is contained in:
Mitchell Hashimoto
2023-03-18 19:59:55 -07:00
committed by GitHub
8 changed files with 86 additions and 14 deletions

View File

@ -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;
} }

View File

@ -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 = {} });

View File

@ -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.

View File

@ -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,

View File

@ -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);
} }

View File

@ -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.

View File

@ -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

@ -1 +1 @@
Subproject commit 46e3dafa477a9a92abe7b425cafdbcbe7ba3574d Subproject commit 9024a57ef9133382f95927eaf4b6c2b1662598e0