console.zig
1 const std = @import("std"); 2 const console = @This(); 3 4 const VGA_WIDTH = 80; 5 const VGA_HEIGHT = 25; 6 const VGA_SIZE = VGA_WIDTH * VGA_HEIGHT; 7 8 var g_row: usize = 0; 9 var g_column: usize = 0; 10 var g_color: Color = .init(.light_gray, .black); 11 var g_buffer = @as([*]volatile u16, @ptrFromInt(0xB8000)); 12 13 pub const ColorType = enum(u4) { 14 black = 0, 15 blue = 1, 16 green = 2, 17 cyan = 3, 18 red = 4, 19 magenta = 5, 20 brown = 6, 21 light_gray = 7, 22 dark_gray = 8, 23 light_blue = 9, 24 light_green = 10, 25 light_cyan = 11, 26 light_red = 12, 27 light_magenta = 13, 28 light_brown = 14, 29 white = 15, 30 }; 31 32 const Color = packed struct(u8) { 33 fg: ColorType, 34 bg: ColorType, 35 36 pub fn init(fg: ColorType, bg: ColorType) Color { 37 return .{ .fg = fg, .bg = bg }; 38 } 39 40 /// Combine vga color and char. The upper byte will be color and lower byte will be character 41 pub fn getVgaChar(self: Color, char: u8) u16 { 42 return @as(u16, @as(u8, @bitCast(self))) << 8 | char; 43 } 44 }; 45 46 /// Initialize VGA 47 pub fn init() void { 48 clear(); 49 } 50 51 /// Set Color for VGA 52 pub fn setColor(fg: Color, bg: Color) void { 53 g_color = Color.init(fg, bg); 54 } 55 56 /// Clear the screen 57 pub fn clear() void { 58 @memset(g_buffer[0..VGA_SIZE], Color.getVgaChar(g_color, ' ')); 59 } 60 61 /// Print character with color at specific position 62 pub fn printCharAt(char: u8, color: Color, x: usize, y: usize) void { 63 const index = y * VGA_WIDTH + x; 64 g_buffer[index] = color.getVgaChar(char); 65 } 66 67 /// INFO: Scrolling is left as an exercise for the reader 68 fn checkAndScroll() void { 69 if (g_row == VGA_HEIGHT) { 70 g_row = 0; 71 } 72 } 73 74 /// Print character to the VGA 75 pub fn printChar(char: u8) void { 76 switch (char) { 77 '\n' => { 78 g_column = 0; 79 g_row += 1; 80 checkAndScroll(); 81 }, 82 else => { 83 printCharAt(char, g_color, g_column, g_row); 84 g_column += 1; 85 if (g_column == VGA_WIDTH) { 86 g_column = 0; 87 g_row += 1; 88 checkAndScroll(); 89 } 90 }, 91 } 92 } 93 94 /// Implementation of std.Io.Writer.vtable.drain function. 95 /// When flush is called or the writer buffer is full this function is called. 96 /// This function first writes all data of writer buffer after that it writes 97 /// the argument data in which the last element is written splat times. 98 fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { 99 // the length of data must not be zero 100 std.debug.assert(data.len != 0); 101 102 var consumed: usize = 0; 103 const pattern = data[data.len - 1]; 104 const splat_len = pattern.len * splat; 105 106 // If buffer is not empty write it first 107 if (w.end != 0) { 108 printString(w.buffered()); 109 w.end = 0; 110 } 111 112 // Now write all data except last element 113 for (data[0 .. data.len - 1]) |bytes| { 114 printString(bytes); 115 consumed += bytes.len; 116 } 117 118 // If out patter (i.e. last element of data) is non zero len then write splat times 119 switch (pattern.len) { 120 0 => {}, 121 else => { 122 for (0..splat) |_| { 123 printString(pattern); 124 } 125 }, 126 } 127 // Now we have to return how many bytes we consumed from data 128 consumed += splat_len; 129 return consumed; 130 } 131 132 /// Returns std.Io.Writer implementation for this console 133 pub fn writer(buffer: []u8) std.Io.Writer { 134 return .{ 135 .buffer = buffer, 136 .end = 0, 137 .vtable = &.{ 138 .drain = drain, 139 }, 140 }; 141 } 142 143 /// Print string to VGA 144 pub fn printString(str: []const u8) void { 145 for (str) |char| { 146 printChar(char); 147 } 148 } 149 150 /// Print with standard zig format to VGA 151 pub fn print(comptime fmt: []const u8, args: anytype) void { 152 var w = writer(&.{}); 153 w.print(fmt, args) catch 154 return; 155 }