feat: add Console Session mode
This commit is contained in:
@@ -32,12 +32,21 @@ enum LoginMode {
|
||||
MODE_LOGIN,
|
||||
};
|
||||
|
||||
// Which session to launch after a successful login. Desktop is the default
|
||||
// graphical environment; Console launches the Terminal full-screen in place of
|
||||
// the desktop (no window server).
|
||||
enum SessionMode {
|
||||
SESSION_DESKTOP,
|
||||
SESSION_CONSOLE,
|
||||
};
|
||||
|
||||
struct LoginState {
|
||||
gui::Framebuffer fb;
|
||||
int screen_w, screen_h;
|
||||
|
||||
LoginMode mode;
|
||||
int active_field;
|
||||
SessionMode session_mode;
|
||||
|
||||
char username[32];
|
||||
int username_len;
|
||||
@@ -115,6 +124,9 @@ struct LoginLayout {
|
||||
gui::Rect submit_button;
|
||||
gui::Rect shutdown_button;
|
||||
gui::Rect reboot_button;
|
||||
gui::Rect desktop_radio;
|
||||
gui::Rect console_radio;
|
||||
int session_label_y;
|
||||
int content_x;
|
||||
int content_w;
|
||||
int heading_y;
|
||||
|
||||
@@ -33,14 +33,33 @@ void finish_desktop_session(LoginState* ls) {
|
||||
ls->show_error = false;
|
||||
}
|
||||
|
||||
void launch_desktop_session(LoginState* ls) {
|
||||
// Build "--console 0:/users/<username>" so the full-screen Terminal starts in
|
||||
// the user's home directory, mirroring how the desktop is handed the home dir.
|
||||
void build_console_args(LoginState* ls, char* out, int cap) {
|
||||
const char* prefix = "--console 0:/users/";
|
||||
int i = 0;
|
||||
while (prefix[i] && i < cap - 1) { out[i] = prefix[i]; i++; }
|
||||
int j = 0;
|
||||
while (ls->username[j] && i < cap - 1) out[i++] = ls->username[j++];
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
void launch_session(LoginState* ls) {
|
||||
// Authentication already succeeded and the session is set, so the password
|
||||
// is no longer needed. Wipe it before the desktop runs rather than waiting
|
||||
// is no longer needed. Wipe it before the session runs rather than waiting
|
||||
// for finish_desktop_session, so it does not sit in the heap for the whole
|
||||
// session.
|
||||
montauk::memset(ls->password, 0, sizeof(ls->password));
|
||||
ls->password_len = 0;
|
||||
int pid = montauk::spawn("0:/os/desktop.elf", ls->username);
|
||||
|
||||
int pid;
|
||||
if (ls->session_mode == SESSION_CONSOLE) {
|
||||
char console_args[96];
|
||||
build_console_args(ls, console_args, (int)sizeof(console_args));
|
||||
pid = montauk::spawn("0:/apps/terminal/terminal.elf", console_args);
|
||||
} else {
|
||||
pid = montauk::spawn("0:/os/desktop.elf", ls->username);
|
||||
}
|
||||
if (pid >= 0) {
|
||||
montauk::setuser(pid, ls->username);
|
||||
montauk::waitpid(pid);
|
||||
@@ -49,7 +68,8 @@ void launch_desktop_session(LoginState* ls) {
|
||||
// The desktop hands off a graceful power-off by posting a request and
|
||||
// exiting (see desktop_request_power). Pick it up now that waitpid has
|
||||
// returned and run the shutdown stages; perform_graceful_shutdown never
|
||||
// returns. A normal logout leaves no request, so we fall through.
|
||||
// returns. A normal logout (or the console session exiting) leaves no
|
||||
// request, so we fall through and return to the login screen.
|
||||
int req = montauk::power_request(montauk::abi::POWER_REQ_QUERY);
|
||||
if (req == montauk::abi::POWER_REQ_SHUTDOWN || req == montauk::abi::POWER_REQ_REBOOT) {
|
||||
perform_graceful_shutdown(ls, req);
|
||||
@@ -172,7 +192,7 @@ void handle_key(LoginState* ls, const montauk::abi::KeyEvent& key) {
|
||||
if (ls->mode == MODE_FIRST_BOOT) {
|
||||
if (try_first_boot_submit(ls)) transition_to_login_mode(ls);
|
||||
} else if (try_login(ls)) {
|
||||
launch_desktop_session(ls);
|
||||
launch_session(ls);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -212,11 +232,17 @@ void handle_mouse(LoginState* ls) {
|
||||
if (clicked >= 0) {
|
||||
ls->active_field = clicked;
|
||||
ls->show_error = false;
|
||||
} else if (ls->mode == MODE_LOGIN && lo.desktop_radio.contains(mx, my)) {
|
||||
ls->session_mode = SESSION_DESKTOP;
|
||||
ls->show_error = false;
|
||||
} else if (ls->mode == MODE_LOGIN && lo.console_radio.contains(mx, my)) {
|
||||
ls->session_mode = SESSION_CONSOLE;
|
||||
ls->show_error = false;
|
||||
} else if (lo.submit_button.contains(mx, my)) {
|
||||
if (ls->mode == MODE_FIRST_BOOT) {
|
||||
if (try_first_boot_submit(ls)) transition_to_login_mode(ls);
|
||||
} else if (try_login(ls)) {
|
||||
launch_desktop_session(ls);
|
||||
launch_session(ls);
|
||||
}
|
||||
return;
|
||||
} else if (lo.shutdown_button.contains(mx, my)) {
|
||||
|
||||
@@ -51,6 +51,20 @@ LoginLayout layout_login_screen(LoginState* ls) {
|
||||
y = lo.password_field.y + FIELD_H + FIELD_GAP;
|
||||
}
|
||||
|
||||
// Session selector (login mode only): a "Session" label above two radio
|
||||
// options that pick the desktop or the full-screen console.
|
||||
if (ls->mode == MODE_LOGIN) {
|
||||
lo.session_label_y = y;
|
||||
int ry = y + sfh + LABEL_GAP;
|
||||
int radio_h = 22;
|
||||
int half = lo.content_w / 2;
|
||||
lo.desktop_radio = {lo.content_x, ry, half, radio_h};
|
||||
lo.console_radio = {lo.content_x + half, ry, half, radio_h};
|
||||
y = ry + radio_h + FIELD_GAP;
|
||||
} else {
|
||||
lo.session_label_y = -1;
|
||||
}
|
||||
|
||||
if (ls->show_error) {
|
||||
lo.error_y = y;
|
||||
y += sfh + 12;
|
||||
@@ -81,11 +95,14 @@ LoginLayout layout_login_screen(LoginState* ls) {
|
||||
lo.shutdown_button = offset_rect(lo.shutdown_button, card_x, card_y);
|
||||
lo.reboot_button = offset_rect(lo.reboot_button, card_x, card_y);
|
||||
lo.submit_button = offset_rect(lo.submit_button, card_x, card_y);
|
||||
lo.desktop_radio = offset_rect(lo.desktop_radio, card_x, card_y);
|
||||
lo.console_radio = offset_rect(lo.console_radio, card_x, card_y);
|
||||
|
||||
lo.content_x += card_x;
|
||||
lo.heading_y += card_y;
|
||||
lo.subtitle_y += card_y;
|
||||
if (lo.error_y >= 0) lo.error_y += card_y;
|
||||
if (lo.session_label_y >= 0) lo.session_label_y += card_y;
|
||||
return lo;
|
||||
}
|
||||
|
||||
@@ -155,6 +172,15 @@ void draw_login_screen(LoginState* ls) {
|
||||
ls->password_input, true, ls->active_field == 1);
|
||||
}
|
||||
|
||||
// ==== Session selector ====
|
||||
if (ls->mode == MODE_LOGIN && lo.session_label_y >= 0) {
|
||||
c.text(lo.content_x, lo.session_label_y, "Session", th.text_muted);
|
||||
mtk::draw_radio(c, lo.desktop_radio, "Desktop",
|
||||
ls->session_mode == SESSION_DESKTOP, th);
|
||||
mtk::draw_radio(c, lo.console_radio, "Console",
|
||||
ls->session_mode == SESSION_CONSOLE, th);
|
||||
}
|
||||
|
||||
if (ls->show_error && lo.error_y >= 0)
|
||||
c.text(lo.content_x, lo.error_y, ls->error_msg, th.danger);
|
||||
|
||||
|
||||
+261
-39
@@ -1,7 +1,13 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Terminal - standalone Window Server app
|
||||
* Preserves the old desktop-integrated terminal layout and tab behavior
|
||||
* Preserves the old desktop-integrated terminal layout and tab behavior.
|
||||
*
|
||||
* Two run modes:
|
||||
* - Windowed (default): runs as a Window Server client inside the desktop.
|
||||
* - Console (--console): takes over the whole framebuffer with no window
|
||||
* server, so it can stand in for desktop.elf as a console-only session
|
||||
* launched directly from login.elf.
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -10,6 +16,8 @@
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/canvas.hpp>
|
||||
#include <gui/draw.hpp>
|
||||
#include <gui/framebuffer.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/terminal.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
@@ -45,6 +53,8 @@ static bool g_should_exit = false;
|
||||
static bool g_force_redraw = true;
|
||||
static int g_last_win_w = 0;
|
||||
static int g_last_win_h = 0;
|
||||
static bool g_show_clock = false; // console (full-screen) mode draws a live clock
|
||||
static char g_clock_text[16] = {};
|
||||
static montauk::abi::ProcInfo g_kill_procs[256];
|
||||
static int g_kill_pids[256];
|
||||
static int g_kill_pid_count = 0;
|
||||
@@ -169,25 +179,63 @@ static bool term_poll_tabs() {
|
||||
return changed || g_force_redraw;
|
||||
}
|
||||
|
||||
static bool term_render() {
|
||||
if (!g_win.pixels || g_tabs.tab_count <= 0) return false;
|
||||
// Right-hand tab-bar furniture. In console mode a live clock sits at the far
|
||||
// right and the new-tab (+) button shifts left to make room; in windowed mode
|
||||
// there is no clock and the + keeps its original far-right position.
|
||||
struct TabBarRight {
|
||||
int clock_x;
|
||||
int plus_x;
|
||||
bool has_clock;
|
||||
};
|
||||
|
||||
static TabBarRight term_tabbar_right(int width) {
|
||||
TabBarRight r;
|
||||
r.has_clock = g_show_clock && g_clock_text[0];
|
||||
int right_limit = width - TERM_PLUS_PAD;
|
||||
r.clock_x = right_limit;
|
||||
if (r.has_clock) {
|
||||
int cw = text_width(fonts::system_font, g_clock_text, fonts::UI_SIZE);
|
||||
r.clock_x = width - TERM_PLUS_PAD - cw;
|
||||
right_limit = r.clock_x - 12;
|
||||
}
|
||||
r.plus_x = right_limit - TERM_PLUS_W;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Refresh the cached clock string from the wall clock; returns true when the
|
||||
// displayed text changed (so the caller can request a repaint).
|
||||
static bool term_refresh_clock() {
|
||||
montauk::abi::DateTime dt;
|
||||
montauk::gettime(&dt);
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", dt.Hour, dt.Minute, dt.Second);
|
||||
if (montauk::streq(buf, g_clock_text)) return false;
|
||||
montauk::strcpy(g_clock_text, buf);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Render the tab bar + active terminal into a tightly packed pixel buffer of
|
||||
// the given dimensions. Shared by the windowed (window surface) and console
|
||||
// (framebuffer-backed) run loops.
|
||||
static bool term_render_into(uint32_t* pixels, int width, int height) {
|
||||
if (!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;
|
||||
int term_h = height - TERM_TAB_BAR_H;
|
||||
if (term_h < 1) term_h = 1;
|
||||
|
||||
int new_cols = g_win.width / mono_cell_width();
|
||||
int new_cols = 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];
|
||||
|
||||
// Only resize the terminal buffer when window pixel dimensions change,
|
||||
// Only resize the terminal buffer when surface pixel dimensions change,
|
||||
// not when font size (zoom) changes
|
||||
if (g_win.width != g_last_win_w || term_h != g_last_win_h) {
|
||||
g_last_win_w = g_win.width;
|
||||
if (width != g_last_win_w || term_h != g_last_win_h) {
|
||||
g_last_win_w = width;
|
||||
g_last_win_h = term_h;
|
||||
for (int i = 0; i < g_tabs.tab_count; i++)
|
||||
terminal_resize(g_tabs.tabs[i], new_cols, new_rows);
|
||||
@@ -199,17 +247,17 @@ static bool term_render() {
|
||||
g_tabs.prev_active_tab = g_tabs.active_tab;
|
||||
}
|
||||
|
||||
if (g_win.pixels != g_tabs.last_pixels) {
|
||||
if (pixels != g_tabs.last_pixels) {
|
||||
g_tabs.tabs[g_tabs.active_tab]->dirty = true;
|
||||
g_tabs.last_pixels = g_win.pixels;
|
||||
g_tabs.last_pixels = pixels;
|
||||
}
|
||||
|
||||
ts = g_tabs.tabs[g_tabs.active_tab];
|
||||
if (!g_force_redraw && !ts->dirty) return false;
|
||||
|
||||
Canvas c = g_win.canvas();
|
||||
Canvas c(pixels, width, height);
|
||||
Color bar_bg = Color::from_hex(0x1C1C1C);
|
||||
c.fill_rect(0, 0, g_win.width, TERM_TAB_BAR_H, bar_bg);
|
||||
c.fill_rect(0, 0, width, TERM_TAB_BAR_H, bar_bg);
|
||||
|
||||
int fh = system_font_height();
|
||||
int tab_x = TERM_TAB_PAD;
|
||||
@@ -241,18 +289,25 @@ static bool term_render() {
|
||||
tab_x += TERM_TAB_W + TERM_TAB_GAP;
|
||||
}
|
||||
|
||||
TabBarRight right = term_tabbar_right(width);
|
||||
|
||||
if (right.has_clock) {
|
||||
int text_y = (TERM_TAB_BAR_H - fh) / 2;
|
||||
c.text(right.clock_x, text_y, g_clock_text, Color::from_hex(0x9A9A9A));
|
||||
}
|
||||
|
||||
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;
|
||||
int px = right.plus_x;
|
||||
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);
|
||||
uint32_t* term_pixels = pixels + TERM_TAB_BAR_H * width;
|
||||
terminal_render(ts, term_pixels, width, term_h);
|
||||
|
||||
if (ts->scrollback_lines > 0 && !ts->alt_screen_active) {
|
||||
int cell_h = mono_cell_height();
|
||||
@@ -260,9 +315,9 @@ static bool term_render() {
|
||||
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);
|
||||
Canvas sc(term_pixels, width, term_h);
|
||||
int sb_w = 4;
|
||||
int sb_x = g_win.width - sb_w - 2;
|
||||
int sb_x = 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;
|
||||
@@ -278,25 +333,31 @@ static bool term_render() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void term_handle_mouse(const montauk::abi::WinEvent& ev) {
|
||||
static bool term_render() {
|
||||
return term_render_into(g_win.pixels, g_win.width, g_win.height);
|
||||
}
|
||||
|
||||
// Mouse handling shared by both run modes. left_click is the press edge.
|
||||
static void term_handle_mouse_core(int mx, int my, int scroll, bool left_click,
|
||||
int width, int height) {
|
||||
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 (scroll != 0 && !ts->alt_screen_active) {
|
||||
ts->view_offset += (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;
|
||||
if (!left_click) return;
|
||||
if (my >= 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) {
|
||||
if (mx >= tab_x && mx < tab_x + TERM_TAB_W) {
|
||||
if (mx >= tab_x + TERM_TAB_W - 24) {
|
||||
term_close_tab(i);
|
||||
} else if (i != g_tabs.active_tab) {
|
||||
g_tabs.active_tab = i;
|
||||
@@ -308,24 +369,31 @@ static void term_handle_mouse(const montauk::abi::WinEvent& ev) {
|
||||
tab_x += TERM_TAB_W + TERM_TAB_GAP;
|
||||
}
|
||||
|
||||
int px = g_win.width - TERM_PLUS_PAD - TERM_PLUS_W;
|
||||
int px = term_tabbar_right(width).plus_x;
|
||||
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();
|
||||
mx >= px && mx < px + TERM_PLUS_W) {
|
||||
int cols = width / mono_cell_width();
|
||||
int rows = (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::abi::KeyEvent& key) {
|
||||
static void term_handle_mouse(const montauk::abi::WinEvent& ev) {
|
||||
term_handle_mouse_core(ev.mouse.x, ev.mouse.y, ev.mouse.scroll,
|
||||
left_pressed(ev), g_win.width, g_win.height);
|
||||
}
|
||||
|
||||
// Keyboard handling shared by both run modes.
|
||||
static void term_handle_key_core(const montauk::abi::KeyEvent& key,
|
||||
int width, int height) {
|
||||
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();
|
||||
int cols = width / mono_cell_width();
|
||||
int rows = (height - TERM_TAB_BAR_H) / mono_cell_height();
|
||||
if (cols < 1) cols = 1;
|
||||
if (rows < 1) rows = 1;
|
||||
term_add_tab(cols, rows);
|
||||
@@ -363,21 +431,19 @@ static void term_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
terminal_handle_key(g_tabs.tabs[g_tabs.active_tab], key);
|
||||
}
|
||||
|
||||
static void term_handle_key(const montauk::abi::KeyEvent& key) {
|
||||
term_handle_key_core(key, g_win.width, g_win.height);
|
||||
}
|
||||
|
||||
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);
|
||||
// ==== Windowed mode (Window Server client) ====
|
||||
|
||||
static void run_windowed() {
|
||||
if (!g_win.create("Terminal", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
@@ -465,5 +531,161 @@ extern "C" void _start() {
|
||||
|
||||
term_cleanup();
|
||||
g_win.destroy();
|
||||
}
|
||||
|
||||
// ==== Console mode (full-screen, no window server) ====
|
||||
|
||||
static void console_present(Framebuffer& fb, uint32_t* pixels, int sw, int mx, int my) {
|
||||
// The terminal renders into a tightly packed scene buffer; copy it across
|
||||
// (handling pitch), composite the mouse cursor, then flip. The full copy
|
||||
// each present keeps the cursor trail-free without per-tile damage tracking.
|
||||
fb.copy_from(pixels, sw * (int)sizeof(uint32_t));
|
||||
draw_cursor(fb, mx, my);
|
||||
fb.flip();
|
||||
}
|
||||
|
||||
static void run_console() {
|
||||
Framebuffer fb;
|
||||
int sw = fb.width();
|
||||
int sh = fb.height();
|
||||
if (sw <= 0 || sh <= 0)
|
||||
montauk::exit(1);
|
||||
|
||||
montauk::set_mouse_bounds(sw - 1, sh - 1);
|
||||
g_show_clock = true;
|
||||
term_refresh_clock();
|
||||
|
||||
uint32_t* pixels = (uint32_t*)montauk::alloc((uint64_t)sw * sh * sizeof(uint32_t));
|
||||
if (!pixels)
|
||||
montauk::exit(1);
|
||||
|
||||
// Paint the dark background immediately so the first frame is not a flash
|
||||
// of uninitialized memory while the shell spawns and the first render runs.
|
||||
{
|
||||
uint32_t bar_px = Color::from_hex(0x1C1C1C).to_pixel();
|
||||
uint32_t bg_px = colors::TERM_BG.to_pixel();
|
||||
int bar_pixels = TERM_TAB_BAR_H * sw;
|
||||
int total = sw * sh;
|
||||
if (bar_pixels > total) bar_pixels = total;
|
||||
for (int i = 0; i < bar_pixels; i++) pixels[i] = bar_px;
|
||||
for (int i = bar_pixels; i < total; i++) pixels[i] = bg_px;
|
||||
}
|
||||
|
||||
int term_h = sh - TERM_TAB_BAR_H;
|
||||
g_last_win_w = sw;
|
||||
g_last_win_h = term_h;
|
||||
|
||||
int cols = sw / mono_cell_width();
|
||||
int rows = term_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])
|
||||
montauk::exit(1);
|
||||
g_tabs.tab_count = 1;
|
||||
g_tabs.active_tab = 0;
|
||||
g_tabs.prev_active_tab = -1;
|
||||
g_tabs.last_pixels = pixels;
|
||||
|
||||
montauk::abi::MouseState mouse = {};
|
||||
montauk::mouse_state(&mouse);
|
||||
int prev_mx = mouse.x;
|
||||
int prev_my = mouse.y;
|
||||
uint8_t prev_buttons = mouse.buttons;
|
||||
|
||||
term_render_into(pixels, sw, sh);
|
||||
console_present(fb, pixels, sw, mouse.x, mouse.y);
|
||||
|
||||
uint64_t input_serial = montauk::input_wait(0, 0);
|
||||
while (!g_should_exit) {
|
||||
bool redraw = false;
|
||||
|
||||
while (montauk::is_key_available()) {
|
||||
montauk::abi::KeyEvent key;
|
||||
montauk::getkey(&key);
|
||||
term_handle_key_core(key, sw, sh);
|
||||
redraw = true;
|
||||
if (g_should_exit) break;
|
||||
}
|
||||
if (g_should_exit) break;
|
||||
|
||||
montauk::mouse_state(&mouse);
|
||||
bool moved = mouse.x != prev_mx || mouse.y != prev_my;
|
||||
bool left_click = (mouse.buttons & 1) && !(prev_buttons & 1);
|
||||
int scroll = mouse.scrollDelta;
|
||||
if (left_click || scroll != 0)
|
||||
term_handle_mouse_core(mouse.x, mouse.y, scroll, left_click, sw, sh);
|
||||
// A bare mouse move still needs a present so the cursor follows the
|
||||
// pointer, even when the terminal scene itself is unchanged.
|
||||
if (moved || left_click || scroll != 0)
|
||||
redraw = true;
|
||||
prev_mx = mouse.x;
|
||||
prev_my = mouse.y;
|
||||
prev_buttons = mouse.buttons;
|
||||
|
||||
// Tick the clock: when the displayed time changes, force a tab-bar
|
||||
// repaint even if the terminal content itself is idle.
|
||||
if (term_refresh_clock())
|
||||
term_request_redraw();
|
||||
|
||||
redraw |= term_poll_tabs();
|
||||
if (g_should_exit) break;
|
||||
|
||||
if (redraw) {
|
||||
term_render_into(pixels, sw, sh);
|
||||
console_present(fb, pixels, sw, mouse.x, mouse.y);
|
||||
}
|
||||
|
||||
// Wake on input, but cap the wait so child (shell) output is drained
|
||||
// and rendered promptly even with no keyboard/mouse activity.
|
||||
input_serial = montauk::input_wait(input_serial, 16);
|
||||
}
|
||||
|
||||
term_cleanup();
|
||||
}
|
||||
|
||||
// ==== Entry point ====
|
||||
|
||||
// Recognizes a leading "--console" token (optionally followed by a working
|
||||
// directory). Returns the number of bytes to skip past the flag, or 0 if the
|
||||
// arguments do not start with the flag.
|
||||
static int console_flag_skip(const char* args) {
|
||||
const char* flag = "--console";
|
||||
int i = 0;
|
||||
while (flag[i]) {
|
||||
if (args[i] != flag[i]) return 0;
|
||||
i++;
|
||||
}
|
||||
if (args[i] != '\0' && args[i] != ' ') return 0;
|
||||
return i;
|
||||
}
|
||||
|
||||
extern "C" void _start() {
|
||||
if (!fonts::init())
|
||||
montauk::exit(1);
|
||||
|
||||
char args[256] = {};
|
||||
int arglen = montauk::getargs(args, sizeof(args));
|
||||
|
||||
bool console_mode = false;
|
||||
const char* workdir = args;
|
||||
if (arglen > 0) {
|
||||
int skip = console_flag_skip(args);
|
||||
if (skip > 0) {
|
||||
console_mode = true;
|
||||
workdir = args + skip;
|
||||
while (*workdir == ' ') workdir++;
|
||||
}
|
||||
}
|
||||
|
||||
if (workdir && workdir[0])
|
||||
montauk::chdir(workdir);
|
||||
|
||||
if (console_mode)
|
||||
run_console();
|
||||
else
|
||||
run_windowed();
|
||||
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user