2022-11-20 17:55:22 +01:00
|
|
|
#include "video/TextConsole.h"
|
|
|
|
#include "boot/bootboot.h"
|
|
|
|
#include "video/Framebuffer.h"
|
2022-12-16 20:40:04 +01:00
|
|
|
#include <luna/CString.h>
|
2023-04-26 20:42:08 +02:00
|
|
|
#include <luna/CType.h>
|
2023-09-02 19:35:42 +02:00
|
|
|
#include <luna/EscapeSequence.h>
|
2022-12-04 12:42:43 +01:00
|
|
|
#include <luna/Format.h>
|
2022-12-07 11:40:02 +01:00
|
|
|
#include <luna/Result.h>
|
2022-12-21 17:38:19 +01:00
|
|
|
#include <luna/ScopeGuard.h>
|
2022-12-18 13:09:37 +01:00
|
|
|
#include <luna/Utf8.h>
|
2022-12-07 11:40:02 +01:00
|
|
|
#include <stdarg.h>
|
2022-11-20 17:55:22 +01:00
|
|
|
|
2022-12-05 13:04:01 +01:00
|
|
|
extern const BOOTBOOT bootboot;
|
2022-11-20 17:55:22 +01:00
|
|
|
|
|
|
|
#include "video/BuiltinFont.h"
|
|
|
|
|
2023-09-02 19:35:42 +02:00
|
|
|
// Default text color.
|
2022-11-20 17:55:22 +01:00
|
|
|
static constexpr u32 WHITE = 0xffffffff;
|
|
|
|
|
2023-09-02 19:35:42 +02:00
|
|
|
// xterm color palette.
|
|
|
|
static constexpr u32 BLACK = 0xff000000;
|
|
|
|
static constexpr u32 RED = 0xffcd0000;
|
|
|
|
static constexpr u32 GREEN = 0xff00cd00;
|
|
|
|
static constexpr u32 YELLOW = 0xffcdcd00;
|
|
|
|
static constexpr u32 BLUE = 0xff0000ee;
|
|
|
|
static constexpr u32 MAGENTA = 0xffcd00cd;
|
|
|
|
static constexpr u32 CYAN = 0xff00cdcd;
|
|
|
|
static constexpr u32 GRAY = 0xffe5e5e5;
|
|
|
|
|
|
|
|
static constexpr u32 BRIGHT_BLACK = 0xff7f7f7f;
|
|
|
|
static constexpr u32 BRIGHT_RED = 0xffff0000;
|
|
|
|
static constexpr u32 BRIGHT_GREEN = 0xff00ff00;
|
|
|
|
static constexpr u32 BRIGHT_YELLOW = 0xffffff00;
|
|
|
|
static constexpr u32 BRIGHT_BLUE = 0xff5c5cff;
|
|
|
|
static constexpr u32 BRIGHT_MAGENTA = 0xffff00ff;
|
|
|
|
static constexpr u32 BRIGHT_CYAN = 0xff00ffff;
|
|
|
|
static constexpr u32 BRIGHT_GRAY = 0xffffffff;
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
static u32 g_background_color = BLACK;
|
|
|
|
static u32 g_foreground_color = WHITE;
|
|
|
|
|
|
|
|
static constexpr u32 FONT_HEIGHT = 16;
|
|
|
|
static constexpr u32 FONT_WIDTH = 8;
|
|
|
|
|
2023-09-02 19:35:42 +02:00
|
|
|
static bool bold = false;
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
static u32 g_x_position = 0;
|
|
|
|
static u32 g_y_position = 0;
|
|
|
|
|
2023-09-04 11:44:35 +02:00
|
|
|
static constexpr int CURSOR_TIMEOUT = 500;
|
|
|
|
static int current_cursor_timeout = CURSOR_TIMEOUT;
|
|
|
|
static bool cursor_activated = true;
|
|
|
|
static bool cursor_enabled = true;
|
|
|
|
|
2022-12-18 14:42:53 +01:00
|
|
|
static Utf8StateDecoder utf8_decoder;
|
2022-12-18 13:09:37 +01:00
|
|
|
|
2023-09-02 19:35:42 +02:00
|
|
|
static Option<EscapeSequenceParser> escape_sequence_parser;
|
|
|
|
|
2022-12-18 13:09:37 +01:00
|
|
|
static void putwchar_at(wchar_t c, u32 x, u32 y)
|
2022-11-20 17:55:22 +01:00
|
|
|
{
|
2022-12-05 13:06:12 +01:00
|
|
|
const u8* glyph = &font[c * 16];
|
2022-11-20 17:55:22 +01:00
|
|
|
for (u32 i = 0; i < FONT_HEIGHT; i++)
|
|
|
|
{
|
|
|
|
for (u32 j = 0; j < FONT_WIDTH; j++)
|
|
|
|
{
|
|
|
|
volatile u8 mask = *glyph;
|
|
|
|
if (mask & (0b10000000 >> j)) Framebuffer::pixel(x + j, y + i, g_foreground_color);
|
|
|
|
else
|
|
|
|
Framebuffer::pixel(x + j, y + i, g_background_color);
|
|
|
|
}
|
|
|
|
glyph++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void erase_current_line()
|
|
|
|
{
|
|
|
|
Framebuffer::rect(0, g_y_position, Framebuffer::width(), FONT_HEIGHT, BLACK);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void scroll()
|
|
|
|
{
|
|
|
|
memcpy(Framebuffer::ptr(), Framebuffer::ptr() + (Framebuffer::scanline() * FONT_HEIGHT),
|
|
|
|
Framebuffer::size() - (Framebuffer::scanline() * FONT_HEIGHT));
|
|
|
|
g_y_position -= FONT_HEIGHT;
|
|
|
|
erase_current_line();
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool should_scroll()
|
|
|
|
{
|
2023-09-04 11:44:35 +02:00
|
|
|
return g_y_position >= Framebuffer::height();
|
2022-11-20 17:55:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void next_line()
|
|
|
|
{
|
|
|
|
g_x_position = 0;
|
|
|
|
g_y_position += FONT_HEIGHT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void next_char()
|
|
|
|
{
|
|
|
|
g_x_position += FONT_WIDTH;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void prev_char()
|
|
|
|
{
|
|
|
|
g_x_position -= FONT_WIDTH;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void erase_current_char()
|
|
|
|
{
|
|
|
|
Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, BLACK);
|
|
|
|
}
|
|
|
|
|
2023-09-04 11:44:35 +02:00
|
|
|
static void draw_cursor()
|
|
|
|
{
|
|
|
|
Framebuffer::rect(g_x_position, g_y_position, FONT_WIDTH, FONT_HEIGHT, WHITE);
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
static bool at_end_of_screen()
|
|
|
|
{
|
|
|
|
return (g_x_position + FONT_WIDTH) > Framebuffer::width();
|
|
|
|
}
|
|
|
|
|
2023-09-02 19:35:42 +02:00
|
|
|
static bool handle_escape_sequence(wchar_t c)
|
|
|
|
{
|
|
|
|
auto rc = escape_sequence_parser->advance(static_cast<u8>(c));
|
|
|
|
if (rc.has_error())
|
|
|
|
{
|
|
|
|
escape_sequence_parser = Option<EscapeSequenceParser> {};
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!rc.value()) return true;
|
|
|
|
if (!escape_sequence_parser->valid())
|
|
|
|
{
|
|
|
|
escape_sequence_parser = Option<EscapeSequenceParser> {};
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto& params = escape_sequence_parser->parameters();
|
|
|
|
switch (escape_sequence_parser->code())
|
|
|
|
{
|
|
|
|
case EscapeCode::CursorUp: {
|
|
|
|
int lines = params.size() ? params[0] : 1;
|
|
|
|
int pixels = lines * FONT_HEIGHT;
|
|
|
|
if ((u32)pixels > g_y_position) g_y_position = 0;
|
|
|
|
else
|
|
|
|
g_y_position -= pixels;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::CursorDown: {
|
|
|
|
int lines = params.size() ? params[0] : 1;
|
|
|
|
int pixels = lines * FONT_HEIGHT;
|
|
|
|
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
|
|
|
|
else
|
|
|
|
g_y_position += pixels;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::CursorBack: {
|
|
|
|
int chars = params.size() ? params[0] : 1;
|
|
|
|
int pixels = chars * FONT_WIDTH;
|
|
|
|
if ((u32)pixels > g_x_position) g_x_position = 0;
|
|
|
|
else
|
|
|
|
g_x_position -= pixels;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::CursorForward: {
|
|
|
|
int chars = params.size() ? params[0] : 1;
|
|
|
|
int pixels = chars * FONT_WIDTH;
|
|
|
|
if (pixels + g_x_position >= Framebuffer::width()) g_x_position = Framebuffer::width() - FONT_WIDTH;
|
|
|
|
else
|
|
|
|
g_x_position += pixels;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::CursorNextLine: {
|
|
|
|
int lines = params.size() ? params[0] : 1;
|
|
|
|
int pixels = lines * FONT_HEIGHT;
|
|
|
|
if ((u32)pixels > g_y_position) g_y_position = 0;
|
|
|
|
else
|
|
|
|
g_y_position -= pixels;
|
|
|
|
g_x_position = 0;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::CursorPreviousLine: {
|
|
|
|
int lines = params.size() ? params[0] : 1;
|
|
|
|
int pixels = lines * FONT_HEIGHT;
|
|
|
|
if (pixels + g_y_position >= Framebuffer::height()) g_y_position = Framebuffer::height() - FONT_HEIGHT;
|
|
|
|
else
|
|
|
|
g_y_position += pixels;
|
|
|
|
g_x_position = 0;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::CursorHorizontalAbsolute: {
|
|
|
|
int line = (params.size() ? params[0] : 1) - 1;
|
|
|
|
if (line < 0) break;
|
|
|
|
u32 position = line * FONT_HEIGHT;
|
|
|
|
if (position >= Framebuffer::height()) position = Framebuffer::height() - FONT_HEIGHT;
|
|
|
|
g_y_position = position;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::SetCursorPosition: {
|
|
|
|
int x = (params.size() ? params[0] : 1) - 1;
|
|
|
|
int y = (params.size() > 1 ? params[1] : 1) - 1;
|
|
|
|
if (x < 0 || y < 0) break;
|
|
|
|
u32 x_position = x * FONT_WIDTH;
|
|
|
|
if (x_position >= Framebuffer::width()) x_position = Framebuffer::width() - FONT_HEIGHT;
|
|
|
|
g_x_position = x_position;
|
|
|
|
u32 y_position = y * FONT_HEIGHT;
|
|
|
|
if (y_position >= Framebuffer::height()) y_position = Framebuffer::height() - FONT_HEIGHT;
|
|
|
|
g_y_position = y_position;
|
|
|
|
};
|
|
|
|
break;
|
|
|
|
case EscapeCode::SelectGraphicRendition: {
|
|
|
|
if (!params.size())
|
|
|
|
{
|
|
|
|
g_foreground_color = WHITE;
|
|
|
|
g_background_color = BLACK;
|
|
|
|
bold = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (usize i = 0; i < params.size(); i++)
|
|
|
|
{
|
|
|
|
int arg = params[i];
|
|
|
|
switch (arg)
|
|
|
|
{
|
|
|
|
case 0: {
|
|
|
|
g_foreground_color = BLACK;
|
|
|
|
g_background_color = WHITE;
|
|
|
|
bold = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
|
|
bold = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 22: {
|
|
|
|
bold = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 30: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_BLACK : BLACK;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 31: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_RED : RED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 32: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_GREEN : GREEN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 33: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_YELLOW : YELLOW;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 34: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_BLUE : BLUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 35: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_MAGENTA : MAGENTA;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 36: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_CYAN : CYAN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 37: {
|
|
|
|
g_foreground_color = bold ? BRIGHT_GRAY : GRAY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 39: {
|
|
|
|
g_foreground_color = WHITE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 40: {
|
|
|
|
g_background_color = bold ? BRIGHT_BLACK : BLACK;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 41: {
|
|
|
|
g_background_color = bold ? BRIGHT_RED : RED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 42: {
|
|
|
|
g_background_color = bold ? BRIGHT_GREEN : GREEN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 43: {
|
|
|
|
g_background_color = bold ? BRIGHT_YELLOW : YELLOW;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 44: {
|
|
|
|
g_background_color = bold ? BRIGHT_BLUE : BLUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 45: {
|
|
|
|
g_background_color = bold ? BRIGHT_MAGENTA : MAGENTA;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 46: {
|
|
|
|
g_background_color = bold ? BRIGHT_CYAN : CYAN;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 47: {
|
|
|
|
g_background_color = bold ? BRIGHT_GRAY : GRAY;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 49: {
|
|
|
|
g_background_color = BLACK;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
|
|
|
|
escape_sequence_parser = Option<EscapeSequenceParser> {};
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
namespace TextConsole
|
|
|
|
{
|
2022-12-18 13:09:37 +01:00
|
|
|
void putwchar(wchar_t c)
|
2022-11-20 17:55:22 +01:00
|
|
|
{
|
2022-12-18 13:09:37 +01:00
|
|
|
// Unprintable (not in the built-in font) characters get represented as a box
|
|
|
|
if (c > (wchar_t)255) c = (wchar_t)256;
|
|
|
|
|
2023-09-02 19:35:42 +02:00
|
|
|
if (escape_sequence_parser.has_value())
|
|
|
|
{
|
|
|
|
if (handle_escape_sequence(c)) return;
|
|
|
|
}
|
|
|
|
|
2023-09-04 11:44:35 +02:00
|
|
|
// Erase the current cursor.
|
|
|
|
if (cursor_enabled) erase_current_char();
|
|
|
|
|
|
|
|
bool should_draw_cursor = cursor_enabled;
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
switch (c)
|
|
|
|
{
|
2022-12-18 13:09:37 +01:00
|
|
|
case L'\n': {
|
2022-11-20 17:55:22 +01:00
|
|
|
next_line();
|
|
|
|
if (should_scroll()) scroll();
|
|
|
|
break;
|
|
|
|
}
|
2023-07-13 20:33:20 +02:00
|
|
|
case L'\t': {
|
|
|
|
wprint(L" ");
|
|
|
|
break;
|
|
|
|
}
|
2022-12-18 13:09:37 +01:00
|
|
|
case L'\r': g_x_position = 0; break;
|
|
|
|
case L'\b':
|
2022-11-20 17:55:22 +01:00
|
|
|
if (g_x_position != 0)
|
|
|
|
{
|
|
|
|
prev_char();
|
|
|
|
erase_current_char();
|
|
|
|
}
|
|
|
|
break;
|
2023-09-02 19:35:42 +02:00
|
|
|
case L'\x1b':
|
|
|
|
case L'\x9b':
|
|
|
|
case L'\x90':
|
2023-09-04 11:44:35 +02:00
|
|
|
case L'\x9d':
|
|
|
|
escape_sequence_parser = EscapeSequenceParser { (u8)c };
|
|
|
|
should_draw_cursor = false;
|
|
|
|
break;
|
2022-11-20 17:55:22 +01:00
|
|
|
default: {
|
2023-04-26 20:42:08 +02:00
|
|
|
if (_iscntrl(c)) return;
|
2022-12-18 13:09:37 +01:00
|
|
|
putwchar_at(c, g_x_position, g_y_position);
|
2022-11-20 17:55:22 +01:00
|
|
|
next_char();
|
|
|
|
if (at_end_of_screen())
|
|
|
|
{
|
|
|
|
next_line();
|
|
|
|
if (should_scroll()) scroll();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-09-04 11:44:35 +02:00
|
|
|
|
|
|
|
if (should_draw_cursor)
|
|
|
|
{
|
|
|
|
current_cursor_timeout = CURSOR_TIMEOUT;
|
|
|
|
cursor_activated = true;
|
|
|
|
draw_cursor();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void tick_cursor()
|
|
|
|
{
|
|
|
|
if (!cursor_enabled) return;
|
|
|
|
|
|
|
|
current_cursor_timeout--;
|
|
|
|
if (current_cursor_timeout == 0)
|
|
|
|
{
|
|
|
|
current_cursor_timeout = CURSOR_TIMEOUT;
|
|
|
|
cursor_activated = !cursor_activated;
|
|
|
|
|
|
|
|
if (cursor_activated) draw_cursor();
|
|
|
|
else
|
|
|
|
erase_current_char();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void disable_cursor()
|
|
|
|
{
|
|
|
|
cursor_enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void enable_cursor()
|
|
|
|
{
|
|
|
|
cursor_enabled = true;
|
|
|
|
cursor_activated = true;
|
|
|
|
current_cursor_timeout = CURSOR_TIMEOUT;
|
|
|
|
draw_cursor();
|
2022-11-20 17:55:22 +01:00
|
|
|
}
|
|
|
|
|
2022-12-21 17:38:19 +01:00
|
|
|
Result<void> putchar(char c)
|
2022-12-18 13:09:37 +01:00
|
|
|
{
|
2022-12-21 17:38:19 +01:00
|
|
|
auto guard = make_scope_guard([] { utf8_decoder.reset(); });
|
|
|
|
|
2023-06-18 18:38:01 +02:00
|
|
|
bool is_ready = TRY(utf8_decoder.feed(c));
|
2022-12-21 17:38:19 +01:00
|
|
|
|
2023-06-18 18:38:01 +02:00
|
|
|
if (is_ready) putwchar(TRY(utf8_decoder.extract()));
|
2022-12-18 13:09:37 +01:00
|
|
|
|
2023-06-18 18:38:01 +02:00
|
|
|
guard.deactivate();
|
2022-12-21 17:38:19 +01:00
|
|
|
|
|
|
|
return {};
|
2022-12-18 13:09:37 +01:00
|
|
|
}
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
void set_foreground(u32 color)
|
|
|
|
{
|
|
|
|
g_foreground_color = color;
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_background(u32 color)
|
|
|
|
{
|
|
|
|
g_background_color = color;
|
|
|
|
}
|
|
|
|
|
2022-11-30 13:29:28 +01:00
|
|
|
u32 foreground()
|
|
|
|
{
|
|
|
|
return g_foreground_color;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 background()
|
|
|
|
{
|
|
|
|
return g_background_color;
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:55:22 +01:00
|
|
|
void move_to(u32 x, u32 y)
|
|
|
|
{
|
|
|
|
g_x_position = x;
|
|
|
|
g_y_position = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clear()
|
|
|
|
{
|
|
|
|
move_to(0, 0);
|
|
|
|
Framebuffer::rect(0, 0, Framebuffer::width(), Framebuffer::height(), BLACK);
|
|
|
|
}
|
|
|
|
|
2022-12-21 17:38:19 +01:00
|
|
|
Result<void> print(const char* str)
|
2022-11-20 17:55:22 +01:00
|
|
|
{
|
2022-12-21 17:38:19 +01:00
|
|
|
while (*str) TRY(putchar(*str++));
|
|
|
|
return {};
|
2022-11-20 17:55:22 +01:00
|
|
|
}
|
|
|
|
|
2023-03-18 20:09:10 +01:00
|
|
|
Result<void> write(const char* str, usize len)
|
|
|
|
{
|
|
|
|
while (len--) TRY(putchar(*str++));
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-12-21 19:41:13 +01:00
|
|
|
void wprint(const wchar_t* str)
|
|
|
|
{
|
|
|
|
while (*str) putwchar(*str++);
|
|
|
|
}
|
|
|
|
|
2022-12-21 17:38:19 +01:00
|
|
|
Result<void> println(const char* str)
|
2022-11-20 17:55:22 +01:00
|
|
|
{
|
2022-12-21 17:38:19 +01:00
|
|
|
TRY(print(str));
|
|
|
|
putwchar(L'\n');
|
|
|
|
return {};
|
2022-11-20 17:55:22 +01:00
|
|
|
}
|
|
|
|
|
2022-12-21 19:41:13 +01:00
|
|
|
void wprintln(const wchar_t* str)
|
|
|
|
{
|
|
|
|
wprint(str);
|
|
|
|
putwchar(L'\n');
|
|
|
|
}
|
|
|
|
|
2022-12-21 17:38:19 +01:00
|
|
|
Result<usize> printf(const char* format, ...)
|
2022-11-20 17:55:22 +01:00
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
2023-01-10 19:31:41 +01:00
|
|
|
const usize rc = TRY(cstyle_format(
|
2022-12-21 17:38:19 +01:00
|
|
|
format, [](char c, void*) -> Result<void> { return putchar(c); }, nullptr, ap));
|
2022-11-20 17:55:22 +01:00
|
|
|
va_end(ap);
|
|
|
|
return rc;
|
|
|
|
}
|
2023-07-12 22:09:28 +02:00
|
|
|
|
|
|
|
u16 rows()
|
|
|
|
{
|
2023-07-21 15:14:05 +02:00
|
|
|
return (u16)Framebuffer::height() / (u16)FONT_HEIGHT;
|
2023-07-12 22:09:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
u16 cols()
|
|
|
|
{
|
2023-07-21 15:14:05 +02:00
|
|
|
return (u16)Framebuffer::width() / (u16)FONT_WIDTH;
|
2023-07-12 22:09:28 +02:00
|
|
|
}
|
2023-01-02 13:07:29 +01:00
|
|
|
}
|