mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
execute the child command
This commit is contained in:
@ -49,7 +49,11 @@ stderr: ?File = null,
|
|||||||
/// If set, this will be executed /in the child process/ after fork but
|
/// If set, this will be executed /in the child process/ after fork but
|
||||||
/// before exec. This is useful to setup some state in the child before the
|
/// before exec. This is useful to setup some state in the child before the
|
||||||
/// exec process takes over, such as signal handlers, setsid, setuid, etc.
|
/// exec process takes over, such as signal handlers, setsid, setuid, etc.
|
||||||
pre_exec: ?fn () void = null,
|
pre_exec: ?fn (*Command) void = null,
|
||||||
|
|
||||||
|
/// User data that is sent to the callback. Set with setData and getData
|
||||||
|
/// for a more user-friendly API.
|
||||||
|
data: ?*anyopaque = null,
|
||||||
|
|
||||||
/// Process ID is set after start is called.
|
/// Process ID is set after start is called.
|
||||||
pid: ?i32 = null,
|
pid: ?i32 = null,
|
||||||
@ -121,7 +125,7 @@ pub fn start(self: *Command, alloc: Allocator) !void {
|
|||||||
if (self.stderr) |f| try setupFd(f.handle, os.STDERR_FILENO);
|
if (self.stderr) |f| try setupFd(f.handle, os.STDERR_FILENO);
|
||||||
|
|
||||||
// If the user requested a pre exec callback, call it now.
|
// If the user requested a pre exec callback, call it now.
|
||||||
if (self.pre_exec) |f| f();
|
if (self.pre_exec) |f| f(self);
|
||||||
|
|
||||||
// Finally, replace our process.
|
// Finally, replace our process.
|
||||||
_ = std.os.execveZ(pathZ, argsZ, envp) catch null;
|
_ = std.os.execveZ(pathZ, argsZ, envp) catch null;
|
||||||
@ -140,6 +144,19 @@ pub fn wait(self: Command) !Exit {
|
|||||||
return Exit.init(res.status);
|
return Exit.init(res.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets command->data to data.
|
||||||
|
pub fn setData(self: *Command, pointer: ?*anyopaque) void {
|
||||||
|
self.data = pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns command->data.
|
||||||
|
pub fn getData(self: Command, comptime DT: type) ?*DT {
|
||||||
|
return if (self.data) |ptr|
|
||||||
|
@ptrCast(?*DT, @alignCast(@alignOf(DT), ptr))
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for "cmd" in the PATH and return the absolute path. This will
|
/// Search for "cmd" in the PATH and return the absolute path. This will
|
||||||
/// always allocate if there is a non-null result. The caller must free the
|
/// always allocate if there is a non-null result. The caller must free the
|
||||||
/// resulting value.
|
/// resulting value.
|
||||||
|
20
src/Pty.zig
20
src/Pty.zig
@ -14,6 +14,7 @@ const c = switch (builtin.os.tag) {
|
|||||||
@cInclude("util.h"); // openpty()
|
@cInclude("util.h"); // openpty()
|
||||||
}),
|
}),
|
||||||
else => @cImport({
|
else => @cImport({
|
||||||
|
@cInclude("sys/ioctl.h"); // ioctl and constants
|
||||||
@cInclude("pty.h");
|
@cInclude("pty.h");
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -27,6 +28,8 @@ const winsize = extern struct {
|
|||||||
ws_ypixel: u16,
|
ws_ypixel: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub extern "c" fn setsid() std.c.pid_t;
|
||||||
|
|
||||||
/// The file descriptors for the master and slave side of the pty.
|
/// The file descriptors for the master and slave side of the pty.
|
||||||
master: fd_t,
|
master: fd_t,
|
||||||
slave: fd_t,
|
slave: fd_t,
|
||||||
@ -73,6 +76,23 @@ pub fn setSize(self: Pty, size: winsize) !void {
|
|||||||
return error.IoctlFailed;
|
return error.IoctlFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This should be called prior to exec in the forked child process
|
||||||
|
/// in order to setup the tty properly.
|
||||||
|
pub fn childPreExec(self: Pty) !void {
|
||||||
|
// Create a new process group
|
||||||
|
if (setsid() < 0) return error.ProcessGroupFailed;
|
||||||
|
|
||||||
|
// Set controlling terminal
|
||||||
|
if (std.os.linux.ioctl(self.slave, c.TIOCSCTTY, 0) < 0)
|
||||||
|
return error.SetControllingTerminalFailed;
|
||||||
|
|
||||||
|
// Can close master/slave pair now
|
||||||
|
std.os.close(self.slave);
|
||||||
|
std.os.close(self.master);
|
||||||
|
|
||||||
|
// TODO: reset signals
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
var ws: winsize = .{
|
var ws: winsize = .{
|
||||||
.ws_row = 50,
|
.ws_row = 50,
|
||||||
|
@ -14,6 +14,7 @@ const glfw = @import("glfw");
|
|||||||
const gl = @import("opengl.zig");
|
const gl = @import("opengl.zig");
|
||||||
const libuv = @import("libuv/main.zig");
|
const libuv = @import("libuv/main.zig");
|
||||||
const Pty = @import("Pty.zig");
|
const Pty = @import("Pty.zig");
|
||||||
|
const Command = @import("Command.zig");
|
||||||
const Terminal = @import("terminal/Terminal.zig");
|
const Terminal = @import("terminal/Terminal.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.window);
|
const log = std.log.scoped(.window);
|
||||||
@ -30,6 +31,9 @@ grid: Grid,
|
|||||||
/// The underlying pty for this window.
|
/// The underlying pty for this window.
|
||||||
pty: Pty,
|
pty: Pty,
|
||||||
|
|
||||||
|
/// The command we're running for our tty.
|
||||||
|
command: Command,
|
||||||
|
|
||||||
/// The terminal emulator internal state. This is the abstract "terminal"
|
/// The terminal emulator internal state. This is the abstract "terminal"
|
||||||
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
||||||
/// just stores internal state about a grid. This is connected back to
|
/// just stores internal state about a grid. This is connected back to
|
||||||
@ -104,6 +108,36 @@ pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window {
|
|||||||
});
|
});
|
||||||
errdefer pty.deinit();
|
errdefer pty.deinit();
|
||||||
|
|
||||||
|
// Create our child process
|
||||||
|
const path = (try Command.expandPath(alloc, "sh")) orelse
|
||||||
|
return error.CommandNotFound;
|
||||||
|
defer alloc.free(path);
|
||||||
|
|
||||||
|
var env = std.BufMap.init(alloc);
|
||||||
|
defer env.deinit();
|
||||||
|
try env.put("TERM", "dumb");
|
||||||
|
|
||||||
|
var cmd: Command = .{
|
||||||
|
.path = path,
|
||||||
|
.args = &[_][]const u8{path},
|
||||||
|
.env = &env,
|
||||||
|
.pre_exec = (struct {
|
||||||
|
fn callback(c: *Command) void {
|
||||||
|
const p = c.getData(Pty) orelse unreachable;
|
||||||
|
p.childPreExec() catch |err|
|
||||||
|
log.err("error initializing child: {}", .{err});
|
||||||
|
}
|
||||||
|
}).callback,
|
||||||
|
.data = &pty,
|
||||||
|
};
|
||||||
|
// note: can't set these in the struct initializer because it
|
||||||
|
// sets the handle to "0". Probably a stage1 zig bug.
|
||||||
|
cmd.stdin = std.fs.File{ .handle = pty.slave };
|
||||||
|
cmd.stdout = cmd.stdin;
|
||||||
|
cmd.stderr = cmd.stdin;
|
||||||
|
try cmd.start(alloc);
|
||||||
|
log.debug("started subcommand path={s} pid={}", .{ path, cmd.pid });
|
||||||
|
|
||||||
// Create our terminal
|
// Create our terminal
|
||||||
var term = Terminal.init(grid.size.columns, grid.size.rows);
|
var term = Terminal.init(grid.size.columns, grid.size.rows);
|
||||||
errdefer term.deinit(alloc);
|
errdefer term.deinit(alloc);
|
||||||
@ -121,6 +155,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window {
|
|||||||
.window = window,
|
.window = window,
|
||||||
.grid = grid,
|
.grid = grid,
|
||||||
.pty = pty,
|
.pty = pty,
|
||||||
|
.command = cmd,
|
||||||
.terminal = term,
|
.terminal = term,
|
||||||
.cursor_timer = timer,
|
.cursor_timer = timer,
|
||||||
};
|
};
|
||||||
@ -142,8 +177,14 @@ pub fn destroy(self: *Window) void {
|
|||||||
t.deinit(alloc);
|
t.deinit(alloc);
|
||||||
}
|
}
|
||||||
}).callback);
|
}).callback);
|
||||||
self.terminal.deinit(self.alloc);
|
|
||||||
|
// Deinitialize the pty. This closes the pty handles. This should
|
||||||
|
// cause a close in the our subprocess so just wait for that.
|
||||||
self.pty.deinit();
|
self.pty.deinit();
|
||||||
|
_ = self.command.wait() catch |err|
|
||||||
|
log.err("error waiting for command to exit: {}", .{err});
|
||||||
|
|
||||||
|
self.terminal.deinit(self.alloc);
|
||||||
self.grid.deinit();
|
self.grid.deinit();
|
||||||
self.window.destroy();
|
self.window.destroy();
|
||||||
self.alloc.destroy(self);
|
self.alloc.destroy(self);
|
||||||
|
Reference in New Issue
Block a user