diff --git a/src/App.zig b/src/App.zig index fd3df4804..4f3b4cc6a 100644 --- a/src/App.zig +++ b/src/App.zig @@ -7,10 +7,11 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const Window = @import("Window.zig"); -const log = std.log; - +/// General purpose allocator alloc: Allocator, +/// The primary window for the application. We currently support only +/// single window operations. window: *Window, /// Initialize the main app instance. This creates the main window, sets diff --git a/src/Pty.zig b/src/Pty.zig new file mode 100644 index 000000000..18fdb6158 --- /dev/null +++ b/src/Pty.zig @@ -0,0 +1,76 @@ +//! Linux PTY creation and management. This is just a thin layer on top +//! of Linux syscalls. The caller is responsible for detail-oriented handling +//! of the returned file handles. +const Pty = @This(); + +const std = @import("std"); +const testing = std.testing; +const linux = std.os.linux; +const fd_t = std.os.fd_t; +const winsize = linux.winsize; +const c = @cImport({ + @cInclude("pty.h"); +}); + +/// The file descriptors for the master and slave side of the pty. +master: fd_t, +slave: fd_t, + +/// Open a new PTY with the given initial size. +pub fn open(size: winsize) !Pty { + var master_fd: fd_t = undefined; + var slave_fd: fd_t = undefined; + if (c.openpty( + &master_fd, + &slave_fd, + null, + null, + @ptrCast([*c]const c.struct_winsize, &size), + ) < 0) + return error.OpenptyFailed; + + return Pty{ + .master = master_fd, + .slave = slave_fd, + }; +} + +pub fn deinit(self: *Pty) void { + std.os.close(self.master); + self.* = undefined; +} + +/// Return the size of the pty. +pub fn getSize(self: Pty) !winsize { + var ws: winsize = undefined; + if (linux.ioctl(self.master, linux.T.IOCGWINSZ, @ptrToInt(&ws)) < 0) + return error.IoctlFailed; + + return ws; +} + +/// Set the size of the pty. +pub fn setSize(self: Pty, size: winsize) !void { + if (linux.ioctl(self.master, linux.T.IOCSWINSZ, @ptrToInt(&size)) < 0) + return error.IoctlFailed; +} + +test { + var ws: winsize = .{ + .ws_row = 50, + .ws_col = 80, + .ws_xpixel = 1, + .ws_ypixel = 1, + }; + + var pty = try open(ws); + defer pty.deinit(); + + // Initialize size should match what we gave it + try testing.expectEqual(ws, try pty.getSize()); + + // Can set and read new sizes + ws.ws_row *= 2; + try pty.setSize(ws); + try testing.expectEqual(ws, try pty.getSize()); +} diff --git a/src/Window.zig b/src/Window.zig index d3827512d..4a6e733f7 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -11,6 +11,7 @@ const Allocator = std.mem.Allocator; const Grid = @import("Grid.zig"); const glfw = @import("glfw"); const gl = @import("opengl.zig"); +const Pty = @import("Pty.zig"); const log = std.log.scoped(.window); @@ -20,6 +21,9 @@ window: glfw.Window, /// The terminal grid attached to this window. grid: Grid, +/// The underlying pty for this window. +pty: Pty, + /// Create a new window. This allocates and returns a pointer because we /// need a stable pointer for user data callbacks. Therefore, a stack-only /// initialization is not currently possible. @@ -61,13 +65,24 @@ pub fn create(alloc: Allocator) !*Window { gl.c.glEnable(gl.c.GL_BLEND); gl.c.glBlendFunc(gl.c.GL_SRC_ALPHA, gl.c.GL_ONE_MINUS_SRC_ALPHA); - // Create our terminal grid with a bogus initial size. + // Create our terminal grid with the initial window size + const window_size = try window.getSize(); var grid = try Grid.init(alloc); - try grid.setScreenSize(.{ .width = 640, .height = 480 }); + try grid.setScreenSize(.{ .width = window_size.width, .height = window_size.height }); + + // Create our pty + var pty = try Pty.open(.{ + .ws_row = @intCast(u16, grid.size.rows), + .ws_col = @intCast(u16, grid.size.columns), + .ws_xpixel = @intCast(u16, window_size.width), + .ws_ypixel = @intCast(u16, window_size.height), + }); + errdefer pty.deinit(); self.* = .{ .window = window, .grid = grid, + .pty = pty, }; // Setup our callbacks and user data @@ -78,6 +93,7 @@ pub fn create(alloc: Allocator) !*Window { } pub fn destroy(self: *Window, alloc: Allocator) void { + self.pty.deinit(); self.grid.deinit(); self.window.destroy(); alloc.destroy(self); @@ -114,6 +130,14 @@ fn sizeCallback(window: glfw.Window, width: i32, height: i32) void { // TODO: temp win.grid.demoCells() catch unreachable; + // Update the size of our pty + win.pty.setSize(.{ + .ws_row = @intCast(u16, win.grid.size.rows), + .ws_col = @intCast(u16, win.grid.size.columns), + .ws_xpixel = @intCast(u16, width), + .ws_ypixel = @intCast(u16, height), + }) catch |err| log.err("error updating pty screen size err={}", .{err}); + // Update our viewport for this context to be the entire window gl.viewport(0, 0, width, height) catch |err| log.err("error updating OpenGL viewport err={}", .{err}); diff --git a/src/main.zig b/src/main.zig index 432033cd4..4e65b8230 100644 --- a/src/main.zig +++ b/src/main.zig @@ -22,4 +22,5 @@ test { _ = @import("Atlas.zig"); _ = @import("FontAtlas.zig"); _ = @import("Grid.zig"); + _ = @import("Pty.zig"); }