Microkernel thing OS experiment (Zig ⚡)
1const limine = @import("limine");
2const builtin = @import("builtin");
3const psf2 = @import("psf2.zig");
4pub const Font = psf2.Font;
5const std = @import("std");
6const fontdata = @embedFile("fonts/spleen-12x24.psf");
7const are_we_le = builtin.cpu.arch.endian() == .little;
8
9pub const DefaultFont = Font.new(fontdata) catch unreachable;
10
11pub const Framebuffer = struct {
12 const Self = @This();
13 address: [*]u8,
14 width: u64,
15 height: u64,
16 pitch: u64,
17 bypp: u16,
18 red_mask_size: u8,
19 red_mask_shift: u8,
20 green_mask_size: u8,
21 green_mask_shift: u8,
22 blue_mask_size: u8,
23 blue_mask_shift: u8,
24
25 pub fn from_limine(fb: *const limine.Framebuffer) Self {
26 return .{
27 .address = @ptrCast(fb.address),
28 .width = fb.width,
29 .height = fb.height,
30 .pitch = fb.pitch,
31 .red_mask_size = fb.red_mask_size,
32 .red_mask_shift = fb.red_mask_shift,
33 .green_mask_size = fb.green_mask_size,
34 .green_mask_shift = fb.green_mask_shift,
35 .blue_mask_size = fb.blue_mask_size,
36 .blue_mask_shift = fb.blue_mask_shift,
37 .bypp = fb.bpp / 8,
38 };
39 }
40};
41
42pub const Console = struct {
43 const Self = @This();
44 const Writer = std.io.GenericWriter(*Self, error{}, write);
45 // framebuffer data
46 fb: Framebuffer,
47 // font
48 font: psf2.Font,
49 // state data
50 current_x: u64 = 0,
51 current_y: u64 = 0,
52 fg_color: u32 = 0xFFFFFFFF,
53 bg_color: u32 = 0,
54
55 pub fn from_font(fb: Framebuffer, font: psf2.Font) Self {
56 return .{
57 .fb = fb,
58 .font = font,
59 };
60 }
61
62 // places a character at the given position
63 pub fn putchar(self: *const Self, ch: u8, cx: u64, cy: u64, fg_val: u32, bg_val: u32) void {
64 // convert colors to bytes
65 const fg_bytes: [4]u8 = @bitCast(if (are_we_le) fg_val else @byteSwap(fg_val));
66 const bg_bytes: [4]u8 = @bitCast(if (are_we_le) bg_val else @byteSwap(bg_val));
67 // initial calculations
68 const bytes_per_line = self.font.hdr.bytesPerLine();
69 const mask_shamt: u5 = @intCast(bytes_per_line * 8 - 1);
70 const mask_initial: u32 = @as(u32, 1) << mask_shamt;
71 const glyph = self.font.getGlyph(ch) catch return;
72
73 // find the screen offset for the beignning of the character
74 // add pitch to go to next line...
75 var offset: u64 = (cy * self.font.hdr.height * self.fb.pitch) + (cx * (self.font.hdr.width + 0) * self.fb.bypp);
76 // run for every line
77 var y: u32 = 0;
78 var mask: u32 = 0;
79 while (y < self.font.hdr.height) : (y += 1) {
80 // initialize the mask and the current line
81 mask = mask_initial;
82
83 // get the current line
84 const line_value: u32 = psf2.readIntTo32(glyph[y * bytes_per_line ..][0..bytes_per_line]);
85 var line_offset: u64 = offset;
86 var x: u32 = 0;
87 while (x < self.font.hdr.width) : (x += 1) {
88 // write the pixel value to the correct position of the screen...
89 if (line_value & mask != 0) {
90 @memcpy(self.fb.address[line_offset..][0..self.fb.bypp], fg_bytes[0..]);
91 } else {
92 @memcpy(self.fb.address[line_offset..][0..self.fb.bypp], bg_bytes[0..]);
93 }
94 line_offset += self.fb.bypp;
95 mask >>= 1;
96 }
97 offset += self.fb.pitch;
98 }
99 }
100
101 pub fn putc(self: *Self, ch: u8) void {
102 // input can be \r, \n, or printable.
103 // ignore \r, move down for \n, and print char normally
104 // if \n, check to see if we overrun then scroll
105 // if normal, see if we overrun the end and do newline
106 if (ch == '\r') return;
107 if (ch == '\n') {
108 self.current_x = 0;
109 self.current_y += 1;
110 // go to top if we went below the bottom of the screen
111 if (self.current_y >= self.maxCharsHeight()) {
112 self.scrollUp(1);
113 self.current_y = self.maxCharsHeight() - 1;
114 }
115 return;
116 }
117 self.putchar(ch, self.current_x, self.current_y, self.fg_color, self.bg_color);
118 self.current_x += 1;
119
120 if (self.current_x < self.maxCharsWidth()) return;
121 self.current_x = 0;
122 self.current_y += 1;
123 if (self.current_y >= self.maxCharsHeight()) {
124 self.scrollUp(1);
125 self.current_y = self.maxCharsHeight() - 1;
126 }
127 }
128
129 pub fn puts(self: *Self, msg: []const u8) void {
130 for (msg) |ch| {
131 self.putc(ch);
132 }
133 }
134
135 fn convertColor(self: *const Self, color: u32) u32 {
136 const mult: u32 = blk: {
137 const width: u4 = @truncate(self.fb.red_mask_size);
138 break :blk (@as(u32, 1) << width) - 1;
139 };
140 const div = 255;
141 const red: u32 = (color >> 16) & 0xFF;
142 const green: u32 = (color >> 8) & 0xFF;
143 const blue: u32 = color & 0xFF;
144
145 const red_shift: u5 = @truncate(self.fb.red_mask_shift);
146 const green_shift: u5 = @truncate(self.fb.green_mask_shift);
147 const blue_shift: u5 = @truncate(self.fb.blue_mask_shift);
148
149 return (((red * mult) / div) << red_shift) | (((green * mult) / div) << green_shift) | (((blue * mult) / div) << blue_shift);
150 }
151
152 pub fn setColor(self: *Self, fg: u32, bg: u32) void {
153 self.fg_color = self.convertColor(fg);
154 self.bg_color = self.convertColor(bg);
155 }
156
157 pub fn writer(self: *Self) Writer {
158 return .{ .context = self };
159 }
160
161 pub fn write(self: *Self, buffer: []const u8) !usize {
162 self.puts(buffer);
163 return buffer.len;
164 }
165
166 // scroll the lines of text, without doing anything else.
167 // erase the first line of text, and memcpy the second line and on up to the first
168 pub fn scrollUp(self: *Self, amount: u64) void {
169 const num_lines = self.maxCharsHeight();
170 const h = self.font.hdr.height;
171 if (amount > num_lines) return; // later just clear the entire screen
172 var i: u64 = amount;
173 while (i < num_lines) : (i += 1) {
174 // for each run, erase the previous line and copy the current line up a line.
175 // const curr_line = self.fb.address[i * h * self.fb.pitch .. (i + 1) * h * self.fb.pitch];
176 const curr_line = self.fb.address[i * h * self.fb.pitch ..][0 .. h * self.fb.pitch];
177 const prev_line = self.fb.address[(i - amount) * h * self.fb.pitch ..][0 .. h * self.fb.pitch];
178
179 @memset(prev_line, 0);
180 @memcpy(prev_line, curr_line);
181 }
182 // finally, delete the last line (s)
183 // const last_line = self.fb.address[(num_lines - amount) * h * self.fb.pitch .. (num_lines) * h * self.fb.pitch];
184 const last_line = self.fb.address[(num_lines - amount) * h * self.fb.pitch ..][0 .. amount * h * self.fb.pitch];
185 @memset(last_line, 0);
186 }
187
188 fn maxCharsWidth(self: *const Self) u64 {
189 return self.fb.width / (self.font.hdr.width + 0);
190 }
191
192 fn maxCharsHeight(self: *const Self) u64 {
193 return self.fb.height / self.font.hdr.height;
194 }
195};