const std = @import("std"); const os = std.os; const io = std.io; const fs = std.fs; const mem = std.mem; const heap = std.heap; const fmt = std.fmt; const posix = std.posix; pub fn main() !void { var gpa = heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var term = try Terminal.init(allocator); defer term.deinit(); try term.clearScreen(); // var box1 = Terminal.Box.init(allocator); // defer box1.deinit(); // box1.content = "hi"; // box1.top = 0; // box1.bottom = 0; // box1.left = 0; // box1.right = 0; // var box2 = Terminal.Box.init(allocator); // defer box2.deinit(); // box2.content = "hi"; // box2.left = 80; // try term.box.addChild(&box1); // try term.box.addChild(&box2); while (true) { const events = try term.getEvents(); _ = events; // for (events) |ev| if (ev.system == .winch) std.debug.print("hii\n", .{}); try term.draw(); std.time.sleep(1000); } // var box = Terminal.Box{ // .x = 0, // .y = 0, // .width = 13, // .height = 1, // .content = "hello world", // .border = true, // }; // try box.draw(&term); // try term.clearScreen(); // try term.print("fooboo", .{}); // try term.cursorLeft(); // try term.cursorLeft(); // try term.print("ar", .{}); // // try term.cursorSet(12, 3); // try term.blinkOn(); // try term.boldOn(); // try term.underlineOn(); // try term.italicsOn(); // try term.print("ABCDEFGHIJKLMNOPQRSTUVWXYZ", .{}); // try term.blinkOff(); // try term.boldOff(); // try term.underlineOff(); // try term.italicsOff(); // try term.print("eeheeveehee\n", .{}); } pub const Terminal = struct { tty: fs.File, original_termios: os.linux.termios, info: Info, allocator: mem.Allocator, box: Box, events: std.ArrayList(Event), pub fn init(allocator: mem.Allocator) !Terminal { var term = Terminal{ .tty = try fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }), .original_termios = undefined, .info = try Info.init(allocator), .allocator = allocator, .box = Box.init(allocator), .events = std.ArrayList(Event).init(allocator), }; errdefer term.tty.close(); errdefer term.info.deinit(); term.box.position = .{ .x = 0, .y = 0, .width = 50, .height = 20 }; try term.uncook(); try attachSignalHandlers(); try term.updateWinSize(); return term; } pub fn deinit(self: *Terminal) void { self.events.deinit(); self.box.deinit(); self.cook() catch @panic("failed to restore termios"); self.tty.close(); self.info.deinit(); } fn uncook(self: *Terminal) !void { self.original_termios = try posix.tcgetattr(self.tty.handle); var raw = self.original_termios; raw.lflag.ECHO = false; raw.lflag.ICANON = false; // raw.lflag.ISIG = false; raw.lflag.ISIG = true; raw.lflag.IEXTEN = false; raw.iflag.IXON = false; raw.iflag.ICRNL = false; raw.iflag.BRKINT = false; raw.iflag.INPCK = false; raw.iflag.ISTRIP = false; raw.oflag.OPOST = false; raw.cc[@intFromEnum(os.linux.V.TIME)] = 0; raw.cc[@intFromEnum(os.linux.V.MIN)] = 1; try posix.tcsetattr(self.tty.handle, .FLUSH, raw); } fn cook(self: *Terminal) !void { try posix.tcsetattr(self.tty.handle, .FLUSH, self.original_termios); } fn attachSignalHandlers() !void { var act = posix.Sigaction{ .handler = .{ .handler = &S.handlerFn }, .mask = posix.empty_sigset, .flags = 0, }; // try posix.sigaction(posix.SIG.INT, &act, null); try posix.sigaction(posix.SIG.USR1, &act, null); try posix.sigaction(posix.SIG.USR2, &act, null); try posix.sigaction(posix.SIG.WINCH, &act, null); } pub fn getEvents(self: *Terminal) ![]Event { if (S.ev) |ev| { switch (ev) { Event.system => { switch (ev.system) { .winch => try self.updateWinSize(), else => try self.events.append(ev), } }, // else => try self.events.append(ev); } S.ev = null; } return try self.events.toOwnedSlice(); } const S = struct { var ev: ?Event = null; fn handlerFn(sig: i32) callconv(.C) void { switch (sig) { // posix.SIG.INT => ev = Event{ .system = .int }, posix.SIG.USR1 => ev = Event{ .system = .usr1 }, posix.SIG.USR2 => ev = Event{ .system = .usr2 }, posix.SIG.WINCH => ev = Event{ .system = .winch }, else => {}, } } }; pub const Event = union(enum) { system: enum { // int, usr1, usr2, winch, }, }; fn updateWinSize(self: *Terminal) !void { var sz: os.linux.winsize = undefined; const ret = os.linux.ioctl(0, os.linux.T.IOCGWINSZ, @intFromPtr(&sz)); // std.debug.print("ret: {d}, {any}\r\n", .{ ret, sz }); if (ret == 0) { self.box.position = .{ .x = 0, .y = 0, .height = @intCast(sz.ws_row), .width = @intCast(sz.ws_col), }; } else unreachable; // TODO: handle else case } pub fn draw(self: *Terminal) !void { try self.box.draw(self); } pub fn print(self: *Terminal, comptime format: []const u8, args: anytype) !void { const formatted = try fmt.allocPrint(self.allocator, format, args); defer self.allocator.free(formatted); try self.tty.writeAll(formatted); } pub fn cursorUp(self: *Terminal) !void { try self.info.writeString(.cursor_up, self.tty.writer(), &[_]u32{}); } pub fn cursorDown(self: *Terminal) !void { try self.info.writeString(.cursor_down, self.tty.writer(), &[_]u32{}); } pub fn cursorLeft(self: *Terminal) !void { try self.info.writeString(.cursor_left, self.tty.writer(), &[_]u32{}); } pub fn cursorRight(self: *Terminal) !void { try self.info.writeString(.cursor_right, self.tty.writer(), &[_]u32{}); } pub fn cursorSet(self: *Terminal, x: u32, y: u32) !void { try self.info.writeString(.cursor_address, self.tty.writer(), &[_]u32{ y, x }); } pub fn blinkOn(self: *Terminal) !void { try self.info.writeString(.enter_blink_mode, self.tty.writer(), &[_]u32{}); } pub fn blinkOff(self: *Terminal) !void { try self.info.writeString(.exit_blink_mode, self.tty.writer(), &[_]u32{}); } pub fn boldOn(self: *Terminal) !void { try self.info.writeString(.enter_bold_mode, self.tty.writer(), &[_]u32{}); } pub fn boldOff(self: *Terminal) !void { try self.info.writeString(.exit_bold_mode, self.tty.writer(), &[_]u32{}); } pub fn italicsOn(self: *Terminal) !void { try self.info.writeString(.enter_italics_mode, self.tty.writer(), &[_]u32{}); } pub fn italicsOff(self: *Terminal) !void { try self.info.writeString(.exit_italics_mode, self.tty.writer(), &[_]u32{}); } pub fn underlineOn(self: *Terminal) !void { try self.info.writeString(.enter_underline_mode, self.tty.writer(), &[_]u32{}); } pub fn underlineOff(self: *Terminal) !void { try self.info.writeString(.exit_underline_mode, self.tty.writer(), &[_]u32{}); } pub fn clearScreen(self: *Terminal) !void { try self.info.writeString(.clear_screen, self.tty.writer(), &[_]u32{}); } // pub fn clearRegion(self: *Terminal, x: u32, y: u32, width: u32, height: u32) !void { // var row = y; // var i: u32 = 0; // while (i < height) { // i += 1; // row += 1; // } // } pub const Info = @import("terminfo.zig"); pub const Box = struct { parent: ?*Box, children: std.ArrayList(*Box), position: ?struct { x: u32, y: u32, width: u32, height: u32, } = null, dirty: bool = true, left: u32, right: u32, top: u32, bottom: u32, border_type: enum { line, bg } = .line, border_char: u8 = ' ', border_bg: i16 = -1, border_fg: i16 = -1, content: []const u8 = "", pub fn init(allocator: mem.Allocator) Box { return .{ .parent = null, .children = std.ArrayList(*Box).init(allocator), .left = 2, .right = 2, .top = 1, .bottom = 1, }; } pub fn deinit(self: *Box) void { for (self.children.items) |child| child.deinit(); self.children.deinit(); } pub fn addChild(self: *Box, child: *Box) !void { std.debug.assert(child.parent == null); child.parent = self; std.debug.assert(self.position != null); child.position = if (self.border_type == .line) .{ .x = self.position.?.x + child.left, .y = self.position.?.y + child.top, .width = self.position.?.width - child.right - child.left, .height = self.position.?.height - 2, } else .{ .x = self.position.?.x + child.left, .y = self.position.?.y + child.top, .width = self.position.?.width, .height = self.position.?.height, }; try self.children.append(child); } pub fn removeChild(self: *Box, child: *Box) !void { _ = self; _ = child; } pub fn removeChildByIndex(self: *Box, child: usize) !void { _ = self; _ = child; } pub fn moveChild(self: *Box, child: *Box, idx: usize) !void { _ = self; _ = child; _ = idx; } pub fn moveChildByIndex(self: *Box, child: usize, idx: usize) !void { _ = self; _ = child; _ = idx; } pub fn draw(self: *Box, term: *Terminal) !void { if (self.dirty) { const rect = self.getRect(); switch (self.border_type) { .line => { var x = rect.x; var y = rect.y; try term.cursorSet(x, y); try term.print("┌", .{}); while (x < rect.x + rect.w - 2) : (x += 1) try term.print("─", .{}); try term.print("┐", .{}); y += 1; while (y < rect.h - 1) : (y += 1) { try term.cursorSet(rect.x, rect.y + y); try term.print("│", .{}); try term.cursorSet(rect.x + rect.w + 1, rect.y + y); try term.print("│", .{}); } x = rect.x; try term.cursorSet(rect.x, rect.h); try term.print("└", .{}); while (x < rect.x + rect.w - 2) : (x += 1) try term.print("─", .{}); try term.print("┘", .{}); }, .bg => {}, } self.dirty = false; } for (self.children.items) |child| try child.draw(term); } fn getRect(self: *Box) Rect { if (self.parent) |parent| { if (parent.border_type == .line) return .{ .x = self.position.?.x + 1, .y = self.position.?.y + 1, .w = self.position.?.width - 2, .h = self.position.?.height - 2, }; } return .{ .x = self.position.?.x, .y = self.position.?.y, .w = self.position.?.width, .h = self.position.?.height, }; } const Rect = struct { x: u32, y: u32, w: u32, h: u32 }; }; };