feat: make terminal and klog Window Server apps

This commit is contained in:
2026-03-28 09:51:32 +01:00
parent 27b0fef416
commit 48378e3bea
15 changed files with 857 additions and 549 deletions
+14 -4
View File
@@ -59,7 +59,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth desktop login shell rpgdemo paint tcc lua screenshot texteditor
CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog desktop login shell rpgdemo paint tcc lua screenshot texteditor
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
@@ -81,9 +81,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
# Common shared assets (wallpapers, etc.)
COMMONKEEP := $(BINDIR)/common/.keep
.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth login desktop shell rpgdemo paint tcc lua screenshot texteditor icons fonts bearssl libc tls libjpeg libjpegwrite install-apps
.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog login desktop shell rpgdemo paint tcc lua screenshot texteditor icons fonts bearssl libc tls libjpeg libjpegwrite install-apps
all: bearssl libc libjpeg libjpegwrite tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth doom rpgdemo paint tcc lua screenshot texteditor login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP)
all: bearssl libc libjpeg libjpegwrite tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog doom rpgdemo paint tcc lua screenshot texteditor login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP)
# Build BearSSL static library (cross-compiled for freestanding x86_64).
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
@@ -172,6 +172,14 @@ music: libc
bluetooth: libc
$(MAKE) -C src/bluetooth
# Build terminal standalone GUI tool (depends on libc).
terminal: libc
$(MAKE) -C src/terminal
# Build kernel log standalone GUI tool (depends on libc).
klog: libc
$(MAKE) -C src/klog
# Build login screen via its own Makefile (depends on bearssl and libc).
login: bearssl libc
$(MAKE) -C src/login
@@ -226,7 +234,7 @@ lua: libc
$(MAKE) -C src/lua
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music bluetooth screenshot texteditor
install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music bluetooth terminal klog screenshot texteditor
../scripts/install_apps.sh
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
@@ -280,6 +288,8 @@ clean:
$(MAKE) -C src/volume clean
$(MAKE) -C src/music clean
$(MAKE) -C src/bluetooth clean
$(MAKE) -C src/terminal clean
$(MAKE) -C src/klog clean
$(MAKE) -C src/paint clean
$(MAKE) -C src/rpgdemo clean
$(MAKE) -C src/screenshot clean
+4 -192
View File
@@ -1,201 +1,13 @@
/*
* app_klog.cpp
* MontaukOS Desktop - Kernel Log viewer (tails the kernel ring buffer)
* MontaukOS Desktop - Kernel Log launcher
* Spawns standalone klog.elf Window Server app
* Copyright (c) 2026 Daniel Hammer
*/
#include "apps_common.hpp"
// ============================================================================
// Kernel Log state
// ============================================================================
static constexpr int KLOG_READ_SIZE = 65536; // matches kernel ring buffer size
static constexpr int KLOG_POLL_MS = 250;
struct KlogState {
TerminalState term;
char* klog_buf;
int last_len;
char last_tail_byte;
uint64_t last_poll_ms;
};
// ============================================================================
// Re-feed log into the terminal (fills scrollback + visible screen)
// ============================================================================
static void klog_refeed(KlogState* klog, int n) {
// Find enough lines to fill scrollback + visible screen
int lines_needed = klog->term.rows + klog->term.max_scrollback;
int start = 0;
int line_count = 0;
for (int i = n - 1; i >= 0; i--) {
if (klog->klog_buf[i] == '\n') {
line_count++;
if (line_count >= lines_needed) { start = i + 1; break; }
}
}
// Reset terminal state
klog->term.scrollback_lines = 0;
klog->term.view_offset = 0;
klog->term.cursor_x = 0;
klog->term.cursor_y = 0;
klog->term.current_fg = colors::TERM_FG;
klog->term.current_bg = colors::TERM_BG;
// Clear visible screen
TermCell* screen = term_screen_row(&klog->term, 0);
int total = klog->term.cols * klog->term.rows;
for (int i = 0; i < total; i++)
screen[i] = {' ', colors::TERM_FG, colors::TERM_BG};
terminal_feed(&klog->term, klog->klog_buf + start, n - start);
}
// ============================================================================
// Callbacks
// ============================================================================
static void klog_on_draw(Window* win, Framebuffer& fb) {
KlogState* klog = (KlogState*)win->app_data;
if (!klog) return;
Rect cr = win->content_rect();
// Handle resize
int new_cols = cr.w / mono_cell_width();
int new_rows = cr.h / mono_cell_height();
if (new_cols != klog->term.cols || new_rows != klog->term.rows) {
terminal_resize(&klog->term, new_cols, new_rows);
// Refeed to fill the new grid with log content
if (klog->last_len > 0) {
klog_refeed(klog, klog->last_len);
}
}
bool was_dirty = klog->term.dirty;
terminal_render(&klog->term, win->content, cr.w, cr.h);
// Draw scrollbar overlay when scrollback exists
if (was_dirty && klog->term.scrollback_lines > 0) {
int cell_h = mono_cell_height();
int total_rows = klog->term.scrollback_lines + klog->term.rows;
int total_h = total_rows * cell_h;
int view_h = cr.h;
if (total_h > view_h) {
Canvas c(win->content, cr.w, cr.h);
int sb_w = 4;
int sb_x = cr.w - sb_w - 2;
int thumb_h = (view_h * view_h) / total_h;
if (thumb_h < 20) thumb_h = 20;
int scroll_from_top = klog->term.scrollback_lines - klog->term.view_offset;
int max_scroll = klog->term.scrollback_lines;
int thumb_y = (max_scroll > 0)
? (scroll_from_top * (view_h - thumb_h)) / max_scroll : 0;
c.fill_rect(sb_x, 0, sb_w, view_h, Color::from_rgb(0x33, 0x33, 0x33));
c.fill_rect(sb_x, thumb_y, sb_w, thumb_h, Color::from_rgb(0x88, 0x88, 0x88));
}
}
}
static void klog_on_mouse(Window* win, MouseEvent& ev) {
KlogState* klog = (KlogState*)win->app_data;
if (!klog) return;
if (ev.scroll != 0) {
klog->term.view_offset += (ev.scroll < 0) ? 3 : -3;
if (klog->term.view_offset < 0) klog->term.view_offset = 0;
if (klog->term.view_offset > klog->term.scrollback_lines)
klog->term.view_offset = klog->term.scrollback_lines;
klog->term.dirty = true;
}
}
static void klog_on_key(Window* win, const Montauk::KeyEvent& key) {
// Read-only viewer — no keyboard input
}
static void klog_on_poll(Window* win) {
KlogState* klog = (KlogState*)win->app_data;
if (!klog) return;
uint64_t now = montauk::get_milliseconds();
if (now - klog->last_poll_ms < KLOG_POLL_MS) return;
klog->last_poll_ms = now;
int n = (int)montauk::read_klog(klog->klog_buf, KLOG_READ_SIZE);
if (n <= 0 && klog->last_len <= 0) return;
if (n > klog->last_len) {
// Buffer grew — feed only the new portion
terminal_feed(&klog->term, klog->klog_buf + klog->last_len, n - klog->last_len);
} else if (n == klog->last_len && n > 0) {
// Same size — check if the tail changed (ring buffer wrapped)
if (klog->klog_buf[n - 1] != klog->last_tail_byte) {
klog_refeed(klog, n);
} else {
return; // nothing changed
}
} else if (n < klog->last_len) {
// Unexpected shrink — full re-render
klog_refeed(klog, n);
}
klog->last_len = n;
if (n > 0) klog->last_tail_byte = klog->klog_buf[n - 1];
win->dirty = true;
}
static void klog_on_close(Window* win) {
KlogState* klog = (KlogState*)win->app_data;
if (klog) {
if (klog->term.cells) montauk::free(klog->term.cells);
if (klog->term.alt_cells) montauk::free(klog->term.alt_cells);
if (klog->klog_buf) montauk::mfree(klog->klog_buf);
montauk::mfree(klog);
win->app_data = nullptr;
}
}
// ============================================================================
// Kernel Log launcher
// ============================================================================
void open_klog(DesktopState* ds) {
int idx = desktop_create_window(ds, "Kernel Log", 160, 60, 720, 480);
if (idx < 0) return;
Window* win = &ds->windows[idx];
Rect cr = win->content_rect();
int cols = cr.w / mono_cell_width();
int rows = cr.h / mono_cell_height();
KlogState* klog = (KlogState*)montauk::malloc(sizeof(KlogState));
montauk::memset(klog, 0, sizeof(KlogState));
// Initialize with scrollback enabled
terminal_init_cells(&klog->term, cols, rows, TERM_MAX_SCROLLBACK);
// Allocate klog read buffer
klog->klog_buf = (char*)montauk::malloc(KLOG_READ_SIZE);
klog->last_len = 0;
klog->last_tail_byte = 0;
klog->last_poll_ms = 0;
// Do an initial read to show existing log content
int n = (int)montauk::read_klog(klog->klog_buf, KLOG_READ_SIZE);
if (n > 0) {
klog_refeed(klog, n);
klog->last_len = n;
klog->last_tail_byte = klog->klog_buf[n - 1];
}
win->app_data = klog;
win->on_draw = klog_on_draw;
win->on_mouse = klog_on_mouse;
win->on_key = klog_on_key;
win->on_close = klog_on_close;
win->on_poll = klog_on_poll;
(void)ds;
montauk::spawn("0:/apps/klog/klog.elf");
}
+7 -349
View File
@@ -1,358 +1,16 @@
/*
* app_terminal.cpp
* MontaukOS Desktop
* MontaukOS Desktop - Terminal launcher
* Spawns standalone terminal.elf Window Server app
* Copyright (c) 2026 Daniel Hammer
*/
#include "apps_common.hpp"
// ============================================================================
// Tab data model
// ============================================================================
static constexpr int TERM_TAB_BAR_H = 34;
static constexpr int TERM_MAX_TABS = 8;
static constexpr int TERM_TAB_W = 130;
static constexpr int TERM_TAB_GAP = 4;
static constexpr int TERM_PLUS_W = 28;
static constexpr int TERM_PLUS_PAD = 8;
static constexpr int TERM_TAB_PAD = 8;
struct TermTabState {
TerminalState* tabs[TERM_MAX_TABS];
int tab_count;
int active_tab;
int prev_active_tab; // track tab switches to force redraw
uint32_t* last_content; // detect content buffer reallocation
DesktopState* desktop;
};
// ============================================================================
// Tab helpers
// ============================================================================
static void term_free_tab(TerminalState* ts) {
if (!ts) return;
if (ts->child_pid > 0) montauk::kill(ts->child_pid);
if (ts->cells) montauk::free(ts->cells);
if (ts->alt_cells) montauk::free(ts->alt_cells);
montauk::mfree(ts);
}
static TerminalState* term_create_tab(DesktopState* ds, int cols, int rows) {
TerminalState* ts = (TerminalState*)montauk::malloc(sizeof(TerminalState));
montauk::memset(ts, 0, sizeof(TerminalState));
terminal_init(ts, cols, rows);
ts->desktop = ds;
return ts;
}
static void term_close_tab(TermTabState* tts, Window* win, int idx) {
if (idx < 0 || idx >= tts->tab_count) return;
term_free_tab(tts->tabs[idx]);
// Shift remaining tabs left
for (int i = idx; i < tts->tab_count - 1; i++)
tts->tabs[i] = tts->tabs[i + 1];
tts->tab_count--;
tts->tabs[tts->tab_count] = nullptr;
if (tts->tab_count == 0) {
// Last tab closed — close the window
DesktopState* ds = tts->desktop;
if (ds) {
for (int i = 0; i < ds->window_count; i++) {
if (&ds->windows[i] == win) {
desktop_close_window(ds, i);
return;
}
}
}
return;
}
// Adjust active tab index
if (idx == tts->active_tab) {
// Closed the active tab: prefer left neighbor, fall back to right
if (tts->active_tab > 0)
tts->active_tab--;
// else stays at 0 (right neighbor shifted here)
} else if (tts->active_tab > idx) {
tts->active_tab--;
}
if (tts->active_tab >= tts->tab_count)
tts->active_tab = tts->tab_count - 1;
// Mark new active tab dirty so it redraws
tts->tabs[tts->active_tab]->dirty = true;
}
static void term_add_tab(TermTabState* tts, int cols, int rows) {
if (tts->tab_count >= TERM_MAX_TABS) return;
TerminalState* ts = term_create_tab(tts->desktop, cols, rows);
tts->tabs[tts->tab_count] = ts;
tts->active_tab = tts->tab_count;
tts->tab_count++;
}
// ============================================================================
// Terminal callbacks
// ============================================================================
static void terminal_on_draw(Window* win, Framebuffer& fb) {
TermTabState* tts = (TermTabState*)win->app_data;
if (!tts || tts->tab_count == 0) return;
if (tts->active_tab < 0 || tts->active_tab >= tts->tab_count)
tts->active_tab = 0;
Rect cr = win->content_rect();
int term_h = cr.h - TERM_TAB_BAR_H;
if (term_h < 1) term_h = 1;
// Force full redraw when active tab changed
if (tts->active_tab != tts->prev_active_tab) {
tts->tabs[tts->active_tab]->dirty = true;
tts->prev_active_tab = tts->active_tab;
}
// Force full redraw when content buffer was reallocated (resize/maximize)
if (win->content != tts->last_content) {
tts->tabs[tts->active_tab]->dirty = true;
tts->last_content = win->content;
}
TerminalState* ts = tts->tabs[tts->active_tab];
// Check if window was resized and update terminal grid
int new_cols = cr.w / mono_cell_width();
int new_rows = term_h / mono_cell_height();
if (new_cols < 1) new_cols = 1;
if (new_rows < 1) new_rows = 1;
if (new_cols != ts->cols || new_rows != ts->rows) {
for (int i = 0; i < tts->tab_count; i++)
terminal_resize(tts->tabs[i], new_cols, new_rows);
}
// Only redraw when terminal content changed
if (!ts->dirty) return;
// ---- Draw tab bar ----
// Bar is darker than TERM_BG; active tab matches TERM_BG to merge with content
Canvas c(win->content, cr.w, cr.h);
Color bar_bg = Color::from_hex(0x1C1C1C);
c.fill_rect(0, 0, cr.w, TERM_TAB_BAR_H, bar_bg);
int fh = system_font_height();
int tab_x = TERM_TAB_PAD;
for (int i = 0; i < tts->tab_count; i++) {
bool active = (i == tts->active_tab);
char label[16];
snprintf(label, sizeof(label), "Tab %d", i + 1);
if (active) {
// Active tab: TERM_BG pill with squared-off bottom → merges into content
int ty = 5;
int th = TERM_TAB_BAR_H - ty;
c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 6, colors::TERM_BG);
c.fill_rect(tab_x, TERM_TAB_BAR_H - 6, TERM_TAB_W, 6, colors::TERM_BG);
int text_y = ty + (th - fh) / 2;
c.text(tab_x + 12, text_y, label, Color::from_hex(0xE0E0E0));
c.text(tab_x + TERM_TAB_W - 20, text_y, "x", Color::from_hex(0x707070));
} else {
// Inactive tab: subtle rounded pill, sits above the bottom edge
int ty = 7;
int th = 22;
c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 5, Color::from_hex(0x262626));
int text_y = ty + (th - fh) / 2;
c.text(tab_x + 12, text_y, label, Color::from_hex(0x6E6E6E));
c.text(tab_x + TERM_TAB_W - 20, text_y, "x", Color::from_hex(0x444444));
}
tab_x += TERM_TAB_W + TERM_TAB_GAP;
}
// "+" button pinned to right edge
if (tts->tab_count < TERM_MAX_TABS) {
int plus_h = 22;
int py = 7;
int px = cr.w - TERM_PLUS_PAD - TERM_PLUS_W;
c.fill_rounded_rect(px, py, TERM_PLUS_W, plus_h, 5, Color::from_hex(0x262626));
int pw_text = text_width("+");
c.text(px + (TERM_PLUS_W - pw_text) / 2, py + (plus_h - fh) / 2, "+",
Color::from_hex(0x6E6E6E));
}
// ---- Render active terminal into offset buffer ----
uint32_t* term_pixels = win->content + TERM_TAB_BAR_H * cr.w;
terminal_render(ts, term_pixels, cr.w, term_h);
// Draw scrollbar overlay when scrollback exists
if (ts->scrollback_lines > 0 && !ts->alt_screen_active) {
int cell_h = mono_cell_height();
int total_rows = ts->scrollback_lines + ts->rows;
int total_h = total_rows * cell_h;
int view_h = term_h;
if (total_h > view_h) {
Canvas sc(term_pixels, cr.w, term_h);
int sb_w = 4;
int sb_x = cr.w - sb_w - 2;
int thumb_h = (view_h * view_h) / total_h;
if (thumb_h < 20) thumb_h = 20;
int scroll_from_top = ts->scrollback_lines - ts->view_offset;
int max_scroll = ts->scrollback_lines;
int thumb_y = (max_scroll > 0)
? (scroll_from_top * (view_h - thumb_h)) / max_scroll : 0;
sc.fill_rect(sb_x, 0, sb_w, view_h, Color::from_rgb(0x33, 0x33, 0x33));
sc.fill_rect(sb_x, thumb_y, sb_w, thumb_h, Color::from_rgb(0x88, 0x88, 0x88));
}
}
}
static void terminal_on_mouse(Window* win, MouseEvent& ev) {
TermTabState* tts = (TermTabState*)win->app_data;
if (!tts || tts->tab_count == 0) return;
// Scroll wheel: forward to active tab regardless of mouse position
TerminalState* ts = tts->tabs[tts->active_tab];
if (ev.scroll != 0 && !ts->alt_screen_active) {
ts->view_offset += (ev.scroll < 0) ? 3 : -3;
if (ts->view_offset < 0) ts->view_offset = 0;
if (ts->view_offset > ts->scrollback_lines)
ts->view_offset = ts->scrollback_lines;
ts->dirty = true;
}
// Tab bar click handling
if (!ev.left_pressed()) return;
Rect cr = win->content_rect();
int lx = ev.x - cr.x;
int ly = ev.y - cr.y;
if (ly >= TERM_TAB_BAR_H) return; // click in terminal area, ignore
int tab_x = TERM_TAB_PAD;
for (int i = 0; i < tts->tab_count; i++) {
if (lx >= tab_x && lx < tab_x + TERM_TAB_W) {
// Check if click is on the close "x" (last 24px of tab)
if (lx >= tab_x + TERM_TAB_W - 24) {
term_close_tab(tts, win, i);
} else if (i != tts->active_tab) {
tts->active_tab = i;
tts->tabs[i]->dirty = true;
}
return;
}
tab_x += TERM_TAB_W + TERM_TAB_GAP;
}
// "+" button pinned to right edge
int px = cr.w - TERM_PLUS_PAD - TERM_PLUS_W;
if (tts->tab_count < TERM_MAX_TABS && lx >= px && lx < px + TERM_PLUS_W) {
int term_h = cr.h - TERM_TAB_BAR_H;
int cols = cr.w / mono_cell_width();
int rows = term_h / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
term_add_tab(tts, cols, rows);
}
}
static void terminal_on_key(Window* win, const Montauk::KeyEvent& key) {
TermTabState* tts = (TermTabState*)win->app_data;
if (!tts || tts->tab_count == 0) return;
// Ctrl+T: new tab (scancode 0x14 = 'T')
if (key.ctrl && key.pressed && key.scancode == 0x14) {
if (tts->tab_count < TERM_MAX_TABS) {
Rect cr = win->content_rect();
int term_h = cr.h - TERM_TAB_BAR_H;
int cols = cr.w / mono_cell_width();
int rows = term_h / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
term_add_tab(tts, cols, rows);
}
return;
}
// Ctrl+W: close current tab (scancode 0x11 = 'W')
if (key.ctrl && key.pressed && key.scancode == 0x11) {
term_close_tab(tts, win, tts->active_tab);
return;
}
// Forward all other keys to active tab
TerminalState* ts = tts->tabs[tts->active_tab];
terminal_handle_key(ts, key);
}
static void terminal_on_close(Window* win) {
TermTabState* tts = (TermTabState*)win->app_data;
if (tts) {
for (int i = 0; i < tts->tab_count; i++)
term_free_tab(tts->tabs[i]);
montauk::mfree(tts);
win->app_data = nullptr;
}
}
static void terminal_on_poll(Window* win) {
TermTabState* tts = (TermTabState*)win->app_data;
if (!tts) return;
// Poll ALL tabs — background shells still produce output
for (int i = tts->tab_count - 1; i >= 0; i--) {
if (!terminal_poll(tts->tabs[i])) {
// This tab's shell exited
term_close_tab(tts, win, i);
win->dirty = true;
// term_close_tab may have closed the window (last tab).
// desktop_close_window calls on_close which nulls app_data
// before the window slot is recycled, so win is still valid here.
if (!win->app_data) return;
}
}
if (tts->tab_count > 0 && tts->tabs[tts->active_tab]->dirty) {
win->dirty = true;
}
}
// ============================================================================
// Terminal launcher
// ============================================================================
void open_terminal(DesktopState* ds) {
int idx = desktop_create_window(ds, "Terminal", 200, 80, 648, 480);
if (idx < 0) return;
Window* win = &ds->windows[idx];
Rect cr = win->content_rect();
int term_h = cr.h - TERM_TAB_BAR_H;
int cols = cr.w / mono_cell_width();
int rows = term_h / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
TermTabState* tts = (TermTabState*)montauk::malloc(sizeof(TermTabState));
montauk::memset(tts, 0, sizeof(TermTabState));
tts->desktop = ds;
tts->tabs[0] = term_create_tab(ds, cols, rows);
tts->tab_count = 1;
tts->active_tab = 0;
tts->prev_active_tab = 0;
tts->last_content = win->content;
win->app_data = tts;
win->on_draw = terminal_on_draw;
win->on_mouse = terminal_on_mouse;
win->on_key = terminal_on_key;
win->on_close = terminal_on_close;
win->on_poll = terminal_on_poll;
if (ds && ds->home_dir[0] != '\0') {
montauk::spawn("0:/apps/terminal/terminal.elf", ds->home_dir);
} else {
montauk::spawn("0:/apps/terminal/terminal.elf");
}
}
-4
View File
@@ -116,11 +116,9 @@ struct EmbeddedAppDef {
};
static const EmbeddedAppDef embedded_apps[] = {
{ "Terminal", 0, 0 },
{ "Files", 1, 0 },
{ "Calculator", 3, 0 },
{ "System Info", 2, 2 },
{ "Kernel Log", 5, 2 },
{ "Processes", 6, 2 },
{ "Mandelbrot", 7, 3 },
};
@@ -130,11 +128,9 @@ static constexpr int NUM_EMBEDDED = sizeof(embedded_apps) / sizeof(embedded_apps
// Resolve embedded app_id to an icon pointer in DesktopState
static SvgIcon* icon_for_embedded(DesktopState* ds, int app_id) {
switch (app_id) {
case 0: return &ds->icon_terminal;
case 1: return &ds->icon_filemanager;
case 2: return &ds->icon_sysinfo;
case 3: return &ds->icon_calculator;
case 5: return &ds->icon_terminal; // Kernel Log uses terminal icon
case 6: return &ds->icon_procmgr;
case 7: return &ds->icon_mandelbrot;
default: return nullptr;
+74
View File
@@ -0,0 +1,74 @@
# Makefile for klog (standalone Window Server app) on MontaukOS
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
LIBDIR := ../../lib
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-msse \
-msse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp font_data.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
TARGET := $(BINDIR)/apps/klog/klog.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/apps/klog
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
+1
View File
@@ -0,0 +1 @@
#include "../desktop/font_data.cpp"
+221
View File
@@ -0,0 +1,221 @@
/*
* main.cpp
* MontaukOS Kernel Log - standalone Window Server app
* Preserves the old desktop-integrated log viewer layout
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/canvas.hpp>
#include <gui/standalone.hpp>
#include <gui/terminal.hpp>
#include <gui/truetype.hpp>
using namespace gui;
static constexpr int INIT_W = 718;
static constexpr int INIT_H = 449;
static constexpr int KLOG_READ_SIZE = 65536;
static constexpr int KLOG_POLL_MS = 250;
struct KlogState {
TerminalState term;
char* klog_buf;
int last_len;
char last_tail_byte;
uint64_t last_poll_ms;
uint32_t* last_pixels;
};
static WsWindow g_win;
static KlogState g_klog = {};
static void klog_refeed(int n) {
int lines_needed = g_klog.term.rows + g_klog.term.max_scrollback;
int start = 0;
int line_count = 0;
for (int i = n - 1; i >= 0; i--) {
if (g_klog.klog_buf[i] == '\n') {
line_count++;
if (line_count >= lines_needed) {
start = i + 1;
break;
}
}
}
g_klog.term.scrollback_lines = 0;
g_klog.term.view_offset = 0;
g_klog.term.cursor_x = 0;
g_klog.term.cursor_y = 0;
g_klog.term.current_fg = colors::TERM_FG;
g_klog.term.current_bg = colors::TERM_BG;
TermCell* screen = term_screen_row(&g_klog.term, 0);
int total = g_klog.term.cols * g_klog.term.rows;
for (int i = 0; i < total; i++)
screen[i] = {' ', colors::TERM_FG, colors::TERM_BG};
terminal_feed(&g_klog.term, g_klog.klog_buf + start, n - start);
}
static bool klog_poll() {
uint64_t now = montauk::get_milliseconds();
if (now - g_klog.last_poll_ms < KLOG_POLL_MS) return false;
g_klog.last_poll_ms = now;
int n = (int)montauk::read_klog(g_klog.klog_buf, KLOG_READ_SIZE);
if (n <= 0 && g_klog.last_len <= 0) return false;
if (n > g_klog.last_len) {
terminal_feed(&g_klog.term, g_klog.klog_buf + g_klog.last_len, n - g_klog.last_len);
} else if (n == g_klog.last_len && n > 0) {
if (g_klog.klog_buf[n - 1] != g_klog.last_tail_byte) {
klog_refeed(n);
} else {
return false;
}
} else if (n < g_klog.last_len) {
klog_refeed(n);
}
g_klog.last_len = n;
if (n > 0) g_klog.last_tail_byte = g_klog.klog_buf[n - 1];
return true;
}
static bool klog_render() {
if (!g_win.pixels || !g_klog.term.cells) return false;
int new_cols = g_win.width / mono_cell_width();
int new_rows = g_win.height / mono_cell_height();
if (new_cols < 1) new_cols = 1;
if (new_rows < 1) new_rows = 1;
if (new_cols != g_klog.term.cols || new_rows != g_klog.term.rows) {
terminal_resize(&g_klog.term, new_cols, new_rows);
if (g_klog.last_len > 0)
klog_refeed(g_klog.last_len);
}
if (g_win.pixels != g_klog.last_pixels) {
g_klog.last_pixels = g_win.pixels;
g_klog.term.dirty = true;
}
if (!g_klog.term.dirty) return false;
bool was_dirty = g_klog.term.dirty;
terminal_render(&g_klog.term, g_win.pixels, g_win.width, g_win.height);
if (was_dirty && g_klog.term.scrollback_lines > 0) {
int cell_h = mono_cell_height();
int total_rows = g_klog.term.scrollback_lines + g_klog.term.rows;
int total_h = total_rows * cell_h;
int view_h = g_win.height;
if (total_h > view_h) {
Canvas c = g_win.canvas();
int sb_w = 4;
int sb_x = g_win.width - sb_w - 2;
int thumb_h = (view_h * view_h) / total_h;
if (thumb_h < 20) thumb_h = 20;
int scroll_from_top = g_klog.term.scrollback_lines - g_klog.term.view_offset;
int max_scroll = g_klog.term.scrollback_lines;
int thumb_y = (max_scroll > 0)
? (scroll_from_top * (view_h - thumb_h)) / max_scroll : 0;
c.fill_rect(sb_x, 0, sb_w, view_h, Color::from_rgb(0x33, 0x33, 0x33));
c.fill_rect(sb_x, thumb_y, sb_w, thumb_h, Color::from_rgb(0x88, 0x88, 0x88));
}
}
return true;
}
static void klog_handle_mouse(const Montauk::WinEvent& ev) {
if (ev.mouse.scroll == 0) return;
g_klog.term.view_offset += (ev.mouse.scroll < 0) ? 3 : -3;
if (g_klog.term.view_offset < 0) g_klog.term.view_offset = 0;
if (g_klog.term.view_offset > g_klog.term.scrollback_lines)
g_klog.term.view_offset = g_klog.term.scrollback_lines;
g_klog.term.dirty = true;
}
static void klog_cleanup() {
if (g_klog.term.cells) montauk::free(g_klog.term.cells);
if (g_klog.term.alt_cells) montauk::free(g_klog.term.alt_cells);
if (g_klog.klog_buf) montauk::mfree(g_klog.klog_buf);
montauk::memset(&g_klog, 0, sizeof(g_klog));
}
extern "C" void _start() {
if (!fonts::init())
montauk::exit(1);
if (!g_win.create("Kernel Log", INIT_W, INIT_H))
montauk::exit(1);
int cols = g_win.width / mono_cell_width();
int rows = g_win.height / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
terminal_init_cells(&g_klog.term, cols, rows, TERM_MAX_SCROLLBACK);
if (!g_klog.term.cells || !g_klog.term.alt_cells) {
g_win.destroy();
montauk::exit(1);
}
g_klog.klog_buf = (char*)montauk::malloc(KLOG_READ_SIZE);
if (!g_klog.klog_buf) {
klog_cleanup();
g_win.destroy();
montauk::exit(1);
}
g_klog.last_pixels = g_win.pixels;
int n = (int)montauk::read_klog(g_klog.klog_buf, KLOG_READ_SIZE);
if (n > 0) {
klog_refeed(n);
g_klog.last_len = n;
g_klog.last_tail_byte = g_klog.klog_buf[n - 1];
}
if (klog_render())
g_win.present();
while (true) {
bool redraw = klog_poll();
Montauk::WinEvent ev;
int r = g_win.poll(&ev);
if (r < 0) break;
if (r == 0) {
if (redraw && klog_render())
g_win.present();
montauk::sleep_ms(16);
continue;
}
if (ev.type == 3) break;
if (ev.type == 1) {
klog_handle_mouse(ev);
} else if (ev.type == 2 || ev.type == 4) {
g_klog.term.dirty = true;
}
if (klog_render())
g_win.present();
}
klog_cleanup();
g_win.destroy();
montauk::exit(0);
}
+11
View File
@@ -0,0 +1,11 @@
[app]
name = "Kernel Log"
binary = "klog.elf"
icon = "utilities-terminal.svg"
[menu]
category = "System"
visible = true
[launch]
pass_home_dir = false
+33
View File
@@ -0,0 +1,33 @@
/*
* stb_truetype_impl.cpp
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <cstddef>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/stb_math.h>
#define STBTT_ifloor(x) ((int) stb_floor(x))
#define STBTT_iceil(x) ((int) stb_ceil(x))
#define STBTT_sqrt(x) stb_sqrt(x)
#define STBTT_pow(x,y) stb_pow(x,y)
#define STBTT_fmod(x,y) stb_fmod(x,y)
#define STBTT_cos(x) stb_cos(x)
#define STBTT_acos(x) stb_acos(x)
#define STBTT_fabs(x) stb_fabs(x)
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
#define STBTT_strlen(x) montauk::slen(x)
#define STBTT_assert(x) ((void)(x))
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>
+74
View File
@@ -0,0 +1,74 @@
# Makefile for terminal (standalone Window Server app) on MontaukOS
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
LIBDIR := ../../lib
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-msse \
-msse2 \
-mno-red-zone \
-mcmodel=small \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp font_data.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
TARGET := $(BINDIR)/apps/terminal/terminal.elf
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile
mkdir -p $(BINDIR)/apps/terminal
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -rf $(OBJDIR) $(TARGET)
+1
View File
@@ -0,0 +1 @@
#include "../desktop/font_data.cpp"
+371
View File
@@ -0,0 +1,371 @@
/*
* main.cpp
* MontaukOS Terminal - standalone Window Server app
* Preserves the old desktop-integrated terminal layout and tab behavior
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/canvas.hpp>
#include <gui/standalone.hpp>
#include <gui/terminal.hpp>
#include <gui/truetype.hpp>
extern "C" {
#include <stdio.h>
}
using namespace gui;
static constexpr int INIT_W = 646;
static constexpr int INIT_H = 449;
static constexpr int TERM_TAB_BAR_H = 34;
static constexpr int TERM_MAX_TABS = 8;
static constexpr int TERM_TAB_W = 130;
static constexpr int TERM_TAB_GAP = 4;
static constexpr int TERM_PLUS_W = 28;
static constexpr int TERM_PLUS_PAD = 8;
static constexpr int TERM_TAB_PAD = 8;
struct TermTabs {
TerminalState* tabs[TERM_MAX_TABS];
int tab_count;
int active_tab;
int prev_active_tab;
uint32_t* last_pixels;
};
static WsWindow g_win;
static TermTabs g_tabs = {};
static bool g_should_exit = false;
static bool g_force_redraw = true;
static bool left_pressed(const Montauk::WinEvent& ev) {
return (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
}
static void term_request_redraw() {
g_force_redraw = true;
}
static void term_free_tab(TerminalState* ts) {
if (!ts) return;
if (ts->child_pid > 0) montauk::kill(ts->child_pid);
if (ts->cells) montauk::free(ts->cells);
if (ts->alt_cells) montauk::free(ts->alt_cells);
montauk::mfree(ts);
}
static TerminalState* term_create_tab(int cols, int rows) {
TerminalState* ts = (TerminalState*)montauk::malloc(sizeof(TerminalState));
if (!ts) return nullptr;
montauk::memset(ts, 0, sizeof(TerminalState));
terminal_init(ts, cols, rows);
if (!ts->cells || !ts->alt_cells) {
montauk::mfree(ts);
return nullptr;
}
return ts;
}
static void term_close_tab(int idx) {
if (idx < 0 || idx >= g_tabs.tab_count) return;
term_free_tab(g_tabs.tabs[idx]);
for (int i = idx; i < g_tabs.tab_count - 1; i++)
g_tabs.tabs[i] = g_tabs.tabs[i + 1];
g_tabs.tab_count--;
g_tabs.tabs[g_tabs.tab_count] = nullptr;
if (g_tabs.tab_count == 0) {
g_should_exit = true;
return;
}
if (idx == g_tabs.active_tab) {
if (g_tabs.active_tab > 0)
g_tabs.active_tab--;
} else if (g_tabs.active_tab > idx) {
g_tabs.active_tab--;
}
if (g_tabs.active_tab >= g_tabs.tab_count)
g_tabs.active_tab = g_tabs.tab_count - 1;
g_tabs.tabs[g_tabs.active_tab]->dirty = true;
term_request_redraw();
}
static bool term_add_tab(int cols, int rows) {
if (g_tabs.tab_count >= TERM_MAX_TABS) return false;
TerminalState* ts = term_create_tab(cols, rows);
if (!ts) return false;
g_tabs.tabs[g_tabs.tab_count] = ts;
g_tabs.active_tab = g_tabs.tab_count;
g_tabs.tab_count++;
term_request_redraw();
return true;
}
static bool term_poll_tabs() {
bool changed = false;
for (int i = g_tabs.tab_count - 1; i >= 0; i--) {
if (!terminal_poll(g_tabs.tabs[i])) {
term_close_tab(i);
changed = true;
if (g_should_exit) return true;
}
}
if (g_tabs.tab_count > 0 && g_tabs.tabs[g_tabs.active_tab]->dirty)
changed = true;
return changed || g_force_redraw;
}
static bool term_render() {
if (!g_win.pixels || g_tabs.tab_count <= 0) return false;
if (g_tabs.active_tab < 0 || g_tabs.active_tab >= g_tabs.tab_count)
g_tabs.active_tab = 0;
int term_h = g_win.height - TERM_TAB_BAR_H;
if (term_h < 1) term_h = 1;
int new_cols = g_win.width / mono_cell_width();
int new_rows = term_h / mono_cell_height();
if (new_cols < 1) new_cols = 1;
if (new_rows < 1) new_rows = 1;
TerminalState* ts = g_tabs.tabs[g_tabs.active_tab];
if (new_cols != ts->cols || new_rows != ts->rows) {
for (int i = 0; i < g_tabs.tab_count; i++)
terminal_resize(g_tabs.tabs[i], new_cols, new_rows);
}
if (g_tabs.active_tab != g_tabs.prev_active_tab) {
g_tabs.tabs[g_tabs.active_tab]->dirty = true;
g_tabs.prev_active_tab = g_tabs.active_tab;
}
if (g_win.pixels != g_tabs.last_pixels) {
g_tabs.tabs[g_tabs.active_tab]->dirty = true;
g_tabs.last_pixels = g_win.pixels;
}
ts = g_tabs.tabs[g_tabs.active_tab];
if (!g_force_redraw && !ts->dirty) return false;
Canvas c = g_win.canvas();
Color bar_bg = Color::from_hex(0x1C1C1C);
c.fill_rect(0, 0, g_win.width, TERM_TAB_BAR_H, bar_bg);
int fh = system_font_height();
int tab_x = TERM_TAB_PAD;
for (int i = 0; i < g_tabs.tab_count; i++) {
bool active = (i == g_tabs.active_tab);
char label[16];
snprintf(label, sizeof(label), "Tab %d", i + 1);
if (active) {
int ty = 5;
int th = TERM_TAB_BAR_H - ty;
c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 6, colors::TERM_BG);
c.fill_rect(tab_x, TERM_TAB_BAR_H - 6, TERM_TAB_W, 6, colors::TERM_BG);
int text_y = ty + (th - fh) / 2;
c.text(tab_x + 12, text_y, label, Color::from_hex(0xE0E0E0));
c.text(tab_x + TERM_TAB_W - 20, text_y, "x", Color::from_hex(0x707070));
} else {
int ty = 7;
int th = 22;
c.fill_rounded_rect(tab_x, ty, TERM_TAB_W, th, 5, Color::from_hex(0x262626));
int text_y = ty + (th - fh) / 2;
c.text(tab_x + 12, text_y, label, Color::from_hex(0x6E6E6E));
c.text(tab_x + TERM_TAB_W - 20, text_y, "x", Color::from_hex(0x444444));
}
tab_x += TERM_TAB_W + TERM_TAB_GAP;
}
if (g_tabs.tab_count < TERM_MAX_TABS) {
int plus_h = 22;
int py = 7;
int px = g_win.width - TERM_PLUS_PAD - TERM_PLUS_W;
c.fill_rounded_rect(px, py, TERM_PLUS_W, plus_h, 5, Color::from_hex(0x262626));
int pw_text = text_width(fonts::system_font, "+", fonts::UI_SIZE);
c.text(px + (TERM_PLUS_W - pw_text) / 2, py + (plus_h - fh) / 2, "+",
Color::from_hex(0x6E6E6E));
}
uint32_t* term_pixels = g_win.pixels + TERM_TAB_BAR_H * g_win.width;
terminal_render(ts, term_pixels, g_win.width, term_h);
if (ts->scrollback_lines > 0 && !ts->alt_screen_active) {
int cell_h = mono_cell_height();
int total_rows = ts->scrollback_lines + ts->rows;
int total_h = total_rows * cell_h;
int view_h = term_h;
if (total_h > view_h) {
Canvas sc(term_pixels, g_win.width, term_h);
int sb_w = 4;
int sb_x = g_win.width - sb_w - 2;
int thumb_h = (view_h * view_h) / total_h;
if (thumb_h < 20) thumb_h = 20;
int scroll_from_top = ts->scrollback_lines - ts->view_offset;
int max_scroll = ts->scrollback_lines;
int thumb_y = (max_scroll > 0)
? (scroll_from_top * (view_h - thumb_h)) / max_scroll : 0;
sc.fill_rect(sb_x, 0, sb_w, view_h, Color::from_rgb(0x33, 0x33, 0x33));
sc.fill_rect(sb_x, thumb_y, sb_w, thumb_h, Color::from_rgb(0x88, 0x88, 0x88));
}
}
g_force_redraw = false;
return true;
}
static void term_handle_mouse(const Montauk::WinEvent& ev) {
if (g_tabs.tab_count <= 0) return;
TerminalState* ts = g_tabs.tabs[g_tabs.active_tab];
if (ev.mouse.scroll != 0 && !ts->alt_screen_active) {
ts->view_offset += (ev.mouse.scroll < 0) ? 3 : -3;
if (ts->view_offset < 0) ts->view_offset = 0;
if (ts->view_offset > ts->scrollback_lines)
ts->view_offset = ts->scrollback_lines;
ts->dirty = true;
}
if (!left_pressed(ev)) return;
if (ev.mouse.y >= TERM_TAB_BAR_H) return;
int tab_x = TERM_TAB_PAD;
for (int i = 0; i < g_tabs.tab_count; i++) {
if (ev.mouse.x >= tab_x && ev.mouse.x < tab_x + TERM_TAB_W) {
if (ev.mouse.x >= tab_x + TERM_TAB_W - 24) {
term_close_tab(i);
} else if (i != g_tabs.active_tab) {
g_tabs.active_tab = i;
g_tabs.tabs[i]->dirty = true;
term_request_redraw();
}
return;
}
tab_x += TERM_TAB_W + TERM_TAB_GAP;
}
int px = g_win.width - TERM_PLUS_PAD - TERM_PLUS_W;
if (g_tabs.tab_count < TERM_MAX_TABS &&
ev.mouse.x >= px && ev.mouse.x < px + TERM_PLUS_W) {
int cols = g_win.width / mono_cell_width();
int rows = (g_win.height - TERM_TAB_BAR_H) / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
term_add_tab(cols, rows);
}
}
static void term_handle_key(const Montauk::KeyEvent& key) {
if (g_tabs.tab_count <= 0) return;
if (key.ctrl && key.pressed && key.scancode == 0x14) {
if (g_tabs.tab_count < TERM_MAX_TABS) {
int cols = g_win.width / mono_cell_width();
int rows = (g_win.height - TERM_TAB_BAR_H) / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
term_add_tab(cols, rows);
}
return;
}
if (key.ctrl && key.pressed && key.scancode == 0x11) {
term_close_tab(g_tabs.active_tab);
return;
}
terminal_handle_key(g_tabs.tabs[g_tabs.active_tab], key);
}
static void term_cleanup() {
for (int i = 0; i < g_tabs.tab_count; i++)
term_free_tab(g_tabs.tabs[i]);
g_tabs.tab_count = 0;
}
extern "C" void _start() {
if (!fonts::init())
montauk::exit(1);
char args[256] = {};
int arglen = montauk::getargs(args, sizeof(args));
if (arglen > 0 && args[0])
montauk::chdir(args);
if (!g_win.create("Terminal", INIT_W, INIT_H))
montauk::exit(1);
int cols = g_win.width / mono_cell_width();
int rows = (g_win.height - TERM_TAB_BAR_H) / mono_cell_height();
if (cols < 1) cols = 1;
if (rows < 1) rows = 1;
g_tabs.tabs[0] = term_create_tab(cols, rows);
if (!g_tabs.tabs[0]) {
g_win.destroy();
montauk::exit(1);
}
g_tabs.tab_count = 1;
g_tabs.active_tab = 0;
g_tabs.prev_active_tab = -1;
g_tabs.last_pixels = g_win.pixels;
if (term_render())
g_win.present();
while (!g_should_exit) {
bool redraw = term_poll_tabs();
if (g_should_exit) break;
Montauk::WinEvent ev;
int r = g_win.poll(&ev);
if (r < 0) break;
if (r == 0) {
if (redraw && term_render())
g_win.present();
montauk::sleep_ms(16);
continue;
}
if (ev.type == 3) break;
if (ev.type == 0) {
term_handle_key(ev.key);
} else if (ev.type == 1) {
term_handle_mouse(ev);
} else if (ev.type == 2 || ev.type == 4) {
term_request_redraw();
if (g_tabs.tab_count > 0)
g_tabs.tabs[g_tabs.active_tab]->dirty = true;
}
if (!g_should_exit && term_render())
g_win.present();
}
term_cleanup();
g_win.destroy();
montauk::exit(0);
}
+11
View File
@@ -0,0 +1,11 @@
[app]
name = "Terminal"
binary = "terminal.elf"
icon = "utilities-terminal.svg"
[menu]
category = "Applications"
visible = true
[launch]
pass_home_dir = true
@@ -0,0 +1,33 @@
/*
* stb_truetype_impl.cpp
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <cstddef>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/stb_math.h>
#define STBTT_ifloor(x) ((int) stb_floor(x))
#define STBTT_iceil(x) ((int) stb_ceil(x))
#define STBTT_sqrt(x) stb_sqrt(x)
#define STBTT_pow(x,y) stb_pow(x,y)
#define STBTT_fmod(x,y) stb_fmod(x,y)
#define STBTT_cos(x) stb_cos(x)
#define STBTT_acos(x) stb_acos(x)
#define STBTT_fabs(x) stb_fabs(x)
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
#define STBTT_strlen(x) montauk::slen(x)
#define STBTT_assert(x) ((void)(x))
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>