/* * main.cpp * MontaukOS graphical login screen * Copyright (c) 2026 Daniel Hammer */ #include #include #include #include #include #include #include #include #include #include #include // Forward-declare stb_image functions (implementation in libjpeg.a). extern "C" { unsigned char* stbi_load_from_memory(const unsigned char* buffer, int len, int* x, int* y, int* channels_in_file, int desired_channels); void stbi_image_free(void* retval_from_stbi_load); } using namespace gui; // Placement new inline void* operator new(unsigned long, void* p) { return p; } // ---- Minimal snprintf ---- using va_list = __builtin_va_list; #define va_start __builtin_va_start #define va_end __builtin_va_end #define va_arg __builtin_va_arg struct PfState { char* buf; int pos; int max; }; static void pf_putc(PfState* st, char c) { if (st->pos < st->max) st->buf[st->pos] = c; st->pos++; } static void pf_putnum(PfState* st, unsigned long val, int base, int width, char pad, int neg) { char tmp[24]; int i = 0; const char* digits = "0123456789abcdef"; if (val == 0) { tmp[i++] = '0'; } else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } } int total = (neg ? 1 : 0) + i; if (neg && pad == '0') pf_putc(st, '-'); for (int w = total; w < width; w++) pf_putc(st, pad); if (neg && pad != '0') pf_putc(st, '-'); while (i > 0) pf_putc(st, tmp[--i]); } static int snprintf(char* buf, int size, const char* fmt, ...) { va_list ap; va_start(ap, fmt); PfState st; st.buf = buf; st.pos = 0; st.max = size > 0 ? size - 1 : 0; while (*fmt) { if (*fmt != '%') { pf_putc(&st, *fmt++); continue; } fmt++; char pad = ' '; if (*fmt == '0') { pad = '0'; fmt++; } int width = 0; while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; } if (*fmt == 'l') fmt++; switch (*fmt) { case 'd': case 'i': { long val = va_arg(ap, int); int neg = 0; unsigned long uval; if (val < 0) { neg = 1; uval = (unsigned long)(-val); } else uval = (unsigned long)val; pf_putnum(&st, uval, 10, width, pad, neg); break; } case 'u': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 10, width, pad, 0); break; } case 'x': { unsigned val = va_arg(ap, unsigned); pf_putnum(&st, val, 16, width, pad, 0); break; } case 's': { const char* s = va_arg(ap, const char*); if (!s) s = "(null)"; int slen = 0; while (s[slen]) slen++; for (int w = slen; w < width; w++) pf_putc(&st, ' '); for (int j = 0; j < slen; j++) pf_putc(&st, s[j]); break; } case 'c': { char c = (char)va_arg(ap, int); pf_putc(&st, c); break; } case '%': pf_putc(&st, '%'); break; default: pf_putc(&st, '%'); pf_putc(&st, *fmt); break; } if (*fmt) fmt++; } if (size > 0) { if (st.pos < size) st.buf[st.pos] = '\0'; else st.buf[size - 1] = '\0'; } va_end(ap); return st.pos; } // ============================================================================ // Login state // ============================================================================ enum LoginMode { MODE_FIRST_BOOT, // Create admin account MODE_LOGIN, // Normal login }; struct LoginState { Framebuffer fb; int screen_w, screen_h; LoginMode mode; int active_field; // 0=username, 1=password, 2=confirm (first-boot only) char username[32]; int username_len; char display_name[64]; int display_name_len; char password[64]; int password_len; char confirm[64]; int confirm_len; char error_msg[128]; bool show_error; Montauk::MouseState mouse; uint8_t prev_buttons; // Power option icons (loaded once at startup) SvgIcon icon_shutdown; SvgIcon icon_reboot; // Background wallpaper (loaded from config) uint32_t* bg_wallpaper; int bg_wallpaper_w, bg_wallpaper_h; bool has_wallpaper; }; // ============================================================================ // Wallpaper loading // ============================================================================ static bool load_login_wallpaper(LoginState* ls) { // Load system-wide desktop config and read wallpaper.path auto doc = montauk::config::load("desktop"); const char* wp = doc.get_string("wallpaper.path", ""); if (wp[0] == '\0') return false; // Read JPEG file int fd = montauk::open(wp); if (fd < 0) return false; uint64_t size = montauk::getsize(fd); if (size == 0 || size > 16 * 1024 * 1024) { montauk::close(fd); return false; } uint8_t* filedata = (uint8_t*)montauk::malloc(size); if (!filedata) { montauk::close(fd); return false; } int bytes_read = montauk::read(fd, filedata, 0, size); montauk::close(fd); if (bytes_read <= 0) { montauk::mfree(filedata); return false; } // Decode JPEG int img_w, img_h, channels; unsigned char* rgb = stbi_load_from_memory(filedata, bytes_read, &img_w, &img_h, &channels, 3); montauk::mfree(filedata); if (!rgb) return false; // Scale to cover screen (same algorithm as desktop wallpaper.hpp) int dst_w = ls->screen_w; int dst_h = ls->screen_h; uint32_t* scaled = (uint32_t*)montauk::malloc((uint64_t)dst_w * dst_h * 4); if (!scaled) { stbi_image_free(rgb); return false; } int src_crop_w, src_crop_h, src_x0, src_y0; if ((int64_t)img_w * dst_h > (int64_t)img_h * dst_w) { src_crop_h = img_h; src_crop_w = (int)((int64_t)img_h * dst_w / dst_h); src_x0 = (img_w - src_crop_w) / 2; src_y0 = 0; } else { src_crop_w = img_w; src_crop_h = (int)((int64_t)img_w * dst_h / dst_w); src_x0 = 0; src_y0 = (img_h - src_crop_h) / 2; } for (int y = 0; y < dst_h; y++) { int sy = src_y0 + (int)((int64_t)y * src_crop_h / dst_h); if (sy < 0) sy = 0; if (sy >= img_h) sy = img_h - 1; for (int x = 0; x < dst_w; x++) { int sx = src_x0 + (int)((int64_t)x * src_crop_w / dst_w); if (sx < 0) sx = 0; if (sx >= img_w) sx = img_w - 1; int si = (sy * img_w + sx) * 3; scaled[y * dst_w + x] = 0xFF000000u | ((uint32_t)rgb[si] << 16) | ((uint32_t)rgb[si + 1] << 8) | (uint32_t)rgb[si + 2]; } } stbi_image_free(rgb); ls->bg_wallpaper = scaled; ls->bg_wallpaper_w = dst_w; ls->bg_wallpaper_h = dst_h; ls->has_wallpaper = true; return true; } // ============================================================================ // Drawing helpers // ============================================================================ static constexpr Color BG_COLOR = Color::from_rgb(0x2B, 0x3E, 0x50); static constexpr Color CARD_BG = Color::from_rgb(0xFF, 0xFF, 0xFF); static constexpr Color FIELD_BG = Color::from_rgb(0xF5, 0xF5, 0xF5); static constexpr Color FIELD_BORDER = Color::from_rgb(0xCC, 0xCC, 0xCC); static constexpr Color FIELD_ACTIVE = Color::from_rgb(0x36, 0x7B, 0xF0); static constexpr Color BTN_COLOR = Color::from_rgb(0x36, 0x7B, 0xF0); static constexpr Color BTN_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF); static constexpr Color TEXT_COLOR = Color::from_rgb(0x33, 0x33, 0x33); static constexpr Color LABEL_COLOR = Color::from_rgb(0x66, 0x66, 0x66); static constexpr Color ERROR_COLOR = Color::from_rgb(0xE0, 0x40, 0x40); static constexpr Color TITLE_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF); static constexpr int CARD_W = 360; static constexpr int FIELD_H = 36; static constexpr int FIELD_PAD = 10; static constexpr int BTN_H = 40; static constexpr int LABEL_GAP = 4; static constexpr int POWER_ICON_SZ = 32; static constexpr int POWER_GAP = 48; // horizontal spacing between icon centers static constexpr int POWER_TOP_PAD = 24; // padding below error message area static void draw_field(Framebuffer& fb, int x, int y, int w, const char* text, bool is_password, bool active) { // Background fb.fill_rect(x, y, w, FIELD_H, FIELD_BG); // Border Color border = active ? FIELD_ACTIVE : FIELD_BORDER; // Top for (int i = 0; i < w; i++) fb.put_pixel(x + i, y, border); // Bottom for (int i = 0; i < w; i++) fb.put_pixel(x + i, y + FIELD_H - 1, border); // Left for (int i = 0; i < FIELD_H; i++) fb.put_pixel(x, y + i, border); // Right for (int i = 0; i < FIELD_H; i++) fb.put_pixel(x + w - 1, y + i, border); if (active) { // Draw 2px border for active field for (int i = 0; i < w; i++) fb.put_pixel(x + i, y + 1, border); for (int i = 0; i < w; i++) fb.put_pixel(x + i, y + FIELD_H - 2, border); for (int i = 0; i < FIELD_H; i++) fb.put_pixel(x + 1, y + i, border); for (int i = 0; i < FIELD_H; i++) fb.put_pixel(x + w - 2, y + i, border); } // Text (clipped to field width, showing trailing portion) int ty = y + (FIELD_H - system_font_height()) / 2; int max_text_w = w - FIELD_PAD * 2 - 2; // left pad, right pad, cursor if (is_password) { int len = montauk::slen(text); if (len > 64) len = 64; char full[65]; for (int i = 0; i < len; i++) full[i] = '*'; full[len] = '\0'; // Show only trailing portion that fits int vis = len; while (vis > 0 && text_width(full + (len - vis)) > max_text_w) vis--; char masked[65]; for (int i = 0; i < vis; i++) masked[i] = '*'; masked[vis] = '\0'; draw_text(fb, x + FIELD_PAD, ty, masked, TEXT_COLOR); if (active) { int cx = x + FIELD_PAD + text_width(masked); fb.fill_rect(cx, ty, 2, system_font_height(), FIELD_ACTIVE); } } else { int len = montauk::slen(text); // Show only trailing portion that fits int offset = 0; while (text_width(text + offset) > max_text_w && offset < len) offset++; draw_text(fb, x + FIELD_PAD, ty, text + offset, TEXT_COLOR); if (active) { int cx = x + FIELD_PAD + text_width(text + offset); fb.fill_rect(cx, ty, 2, system_font_height(), FIELD_ACTIVE); } } } static void draw_button(Framebuffer& fb, int x, int y, int w, int h, const char* label, Color bg) { fb.fill_rect(x, y, w, h, bg); int tw = text_width(label); int tx = x + (w - tw) / 2; int ty = y + (h - system_font_height()) / 2; draw_text(fb, tx, ty, label, BTN_TEXT); } // ============================================================================ // Screen drawing // ============================================================================ static void draw_login_screen(LoginState* ls) { Framebuffer& fb = ls->fb; // Background if (ls->has_wallpaper) { fb.blit(0, 0, ls->bg_wallpaper_w, ls->bg_wallpaper_h, ls->bg_wallpaper); } else { fb.clear(BG_COLOR); } int card_h; const char* title; int sfh = system_font_height(); int power_section_h = POWER_ICON_SZ + sfh + 6 + 4; // icon + label + gap + padding int error_section_h = ls->show_error ? sfh + 8 : 0; // error text + padding if (ls->mode == MODE_FIRST_BOOT) { card_h = 420 + error_section_h + power_section_h; title = "Create Administrator Account"; } else { card_h = 284 + error_section_h + power_section_h; title = "Log In"; } int card_x = (ls->screen_w - CARD_W) / 2; int card_y = (ls->screen_h - card_h) / 2; // Card background fb.fill_rect(card_x, card_y, CARD_W, card_h, CARD_BG); int x = card_x + 24; int content_w = CARD_W - 48; int y = card_y + 20; // Card title { int tw = text_width(title); draw_text(fb, card_x + (CARD_W - tw) / 2, y, title, TEXT_COLOR); y += sfh + 16; } if (ls->mode == MODE_FIRST_BOOT) { // Username field draw_text(fb, x, y, "Username", LABEL_COLOR); y += sfh + LABEL_GAP; draw_field(fb, x, y, content_w, ls->username, false, ls->active_field == 0); y += FIELD_H + 12; // Display name field draw_text(fb, x, y, "Display Name", LABEL_COLOR); y += sfh + LABEL_GAP; draw_field(fb, x, y, content_w, ls->display_name, false, ls->active_field == 1); y += FIELD_H + 12; // Password field draw_text(fb, x, y, "Password", LABEL_COLOR); y += sfh + LABEL_GAP; draw_field(fb, x, y, content_w, ls->password, true, ls->active_field == 2); y += FIELD_H + 12; // Confirm password field draw_text(fb, x, y, "Confirm Password", LABEL_COLOR); y += sfh + LABEL_GAP; draw_field(fb, x, y, content_w, ls->confirm, true, ls->active_field == 3); y += FIELD_H + 16; // Create button draw_button(fb, x, y, content_w, BTN_H, "Create Account", BTN_COLOR); y += BTN_H; } else { // Username field draw_text(fb, x, y, "Username", LABEL_COLOR); y += sfh + LABEL_GAP; draw_field(fb, x, y, content_w, ls->username, false, ls->active_field == 0); y += FIELD_H + 12; // Password field draw_text(fb, x, y, "Password", LABEL_COLOR); y += sfh + LABEL_GAP; draw_field(fb, x, y, content_w, ls->password, true, ls->active_field == 1); y += FIELD_H + 16; // Login button draw_button(fb, x, y, content_w, BTN_H, "Log In", BTN_COLOR); y += BTN_H; } // Error message (inside card, between button and separator) if (ls->show_error) { y += 8; int tw = text_width(ls->error_msg); draw_text(fb, card_x + (CARD_W - tw) / 2, y, ls->error_msg, ERROR_COLOR); y += sfh; } // Separator line y += 16; fb.fill_rect(x, y, content_w, 1, FIELD_BORDER); y += 16; // Power options (inside card, centered) { int total_w = POWER_ICON_SZ + POWER_GAP + POWER_ICON_SZ; int start_x = card_x + (CARD_W - total_w) / 2; // Shutdown icon if (ls->icon_shutdown.pixels) { int ix = start_x; fb.blit_alpha(ix, y, ls->icon_shutdown.width, ls->icon_shutdown.height, ls->icon_shutdown.pixels); const char* lbl = "Shut Down"; int lw = text_width(lbl); draw_text(fb, ix + (POWER_ICON_SZ - lw) / 2, y + POWER_ICON_SZ + 6, lbl, LABEL_COLOR); } // Reboot icon if (ls->icon_reboot.pixels) { int ix = start_x + POWER_ICON_SZ + POWER_GAP; fb.blit_alpha(ix, y, ls->icon_reboot.width, ls->icon_reboot.height, ls->icon_reboot.pixels); const char* lbl = "Restart"; int lw = text_width(lbl); draw_text(fb, ix + (POWER_ICON_SZ - lw) / 2, y + POWER_ICON_SZ + 6, lbl, LABEL_COLOR); } } // Mouse cursor (shared with desktop) draw_cursor(fb, ls->mouse.x, ls->mouse.y); fb.flip(); } // ============================================================================ // Input handling // ============================================================================ static void append_char(char* buf, int* len, int max, char c) { if (*len < max - 1) { buf[*len] = c; (*len)++; buf[*len] = '\0'; } } static void backspace(char* buf, int* len) { if (*len > 0) { (*len)--; buf[*len] = '\0'; } } static int max_fields(LoginState* ls) { return ls->mode == MODE_FIRST_BOOT ? 4 : 2; } static bool try_first_boot_submit(LoginState* ls) { ls->show_error = false; if (ls->username_len == 0) { montauk::strcpy(ls->error_msg, "Username is required"); ls->show_error = true; return false; } if (ls->password_len == 0) { montauk::strcpy(ls->error_msg, "Password is required"); ls->show_error = true; return false; } if (!montauk::streq(ls->password, ls->confirm)) { montauk::strcpy(ls->error_msg, "Passwords do not match"); ls->show_error = true; return false; } // Create the users directory montauk::fmkdir("0:/users"); const char* dname = ls->display_name_len > 0 ? ls->display_name : ls->username; if (!montauk::user::create_user(ls->username, dname, ls->password, "admin")) { montauk::strcpy(ls->error_msg, "Failed to create user"); ls->show_error = true; return false; } return true; } static bool try_login(LoginState* ls) { ls->show_error = false; if (ls->username_len == 0) { montauk::strcpy(ls->error_msg, "Username is required"); ls->show_error = true; return false; } if (!montauk::user::authenticate(ls->username, ls->password)) { montauk::strcpy(ls->error_msg, "Invalid username or password"); ls->show_error = true; return false; } // Write session so any app can query the current user montauk::user::set_session(ls->username); return true; } static void handle_key(LoginState* ls, const Montauk::KeyEvent& key) { if (!key.pressed) return; // Tab switches fields if (key.scancode == 0x0F) { int mf = max_fields(ls); if (key.shift) { ls->active_field = (ls->active_field + mf - 1) % mf; } else { ls->active_field = (ls->active_field + 1) % mf; } ls->show_error = false; return; } // Enter submits if (key.ascii == '\n' || key.ascii == '\r') { if (ls->mode == MODE_FIRST_BOOT) { if (try_first_boot_submit(ls)) { // Switch to login mode ls->mode = MODE_LOGIN; ls->active_field = 0; // Keep username, clear password fields ls->password[0] = '\0'; ls->password_len = 0; ls->confirm[0] = '\0'; ls->confirm_len = 0; ls->show_error = false; } } else { if (try_login(ls)) { // Spawn desktop with username int pid = montauk::spawn("0:/os/desktop.elf", ls->username); if (pid >= 0) { montauk::setuser(pid, ls->username); montauk::waitpid(pid); } // Desktop exited (logout) — clear session and fields montauk::user::clear_session(); ls->password[0] = '\0'; ls->password_len = 0; ls->active_field = 1; // Focus password field ls->show_error = false; } } return; } // Backspace if (key.ascii == '\b' || key.scancode == 0x0E) { if (ls->mode == MODE_FIRST_BOOT) { switch (ls->active_field) { case 0: backspace(ls->username, &ls->username_len); break; case 1: backspace(ls->display_name, &ls->display_name_len); break; case 2: backspace(ls->password, &ls->password_len); break; case 3: backspace(ls->confirm, &ls->confirm_len); break; } } else { switch (ls->active_field) { case 0: backspace(ls->username, &ls->username_len); break; case 1: backspace(ls->password, &ls->password_len); break; } } return; } // Printable characters if (key.ascii >= 0x20 && key.ascii < 0x7F) { if (ls->mode == MODE_FIRST_BOOT) { switch (ls->active_field) { case 0: append_char(ls->username, &ls->username_len, 31, key.ascii); break; case 1: append_char(ls->display_name, &ls->display_name_len, 63, key.ascii); break; case 2: append_char(ls->password, &ls->password_len, 63, key.ascii); break; case 3: append_char(ls->confirm, &ls->confirm_len, 63, key.ascii); break; } } else { switch (ls->active_field) { case 0: append_char(ls->username, &ls->username_len, 31, key.ascii); break; case 1: append_char(ls->password, &ls->password_len, 63, key.ascii); break; } } } } static void handle_mouse(LoginState* ls) { int mx = ls->mouse.x; int my = ls->mouse.y; uint8_t buttons = ls->mouse.buttons; uint8_t prev = ls->prev_buttons; bool left_pressed = (buttons & 0x01) && !(prev & 0x01); if (!left_pressed) return; int sfh = system_font_height(); int power_section_h = POWER_ICON_SZ + sfh + 6 + 8; int error_section_h = ls->show_error ? sfh + 8 : 0; int card_h; if (ls->mode == MODE_FIRST_BOOT) { card_h = 420 + error_section_h + power_section_h; } else { card_h = 284 + error_section_h + power_section_h; } int card_x = (ls->screen_w - CARD_W) / 2; int card_y = (ls->screen_h - card_h) / 2; int x = card_x + 24; int content_w = CARD_W - 48; int y = card_y + 20; // Skip title y += sfh + 16; if (ls->mode == MODE_FIRST_BOOT) { // Username label + field y += sfh + LABEL_GAP; if (mx >= x && mx < x + content_w && my >= y && my < y + FIELD_H) { ls->active_field = 0; return; } y += FIELD_H + 12; // Display name label + field y += sfh + LABEL_GAP; if (mx >= x && mx < x + content_w && my >= y && my < y + FIELD_H) { ls->active_field = 1; return; } y += FIELD_H + 12; // Password label + field y += sfh + LABEL_GAP; if (mx >= x && mx < x + content_w && my >= y && my < y + FIELD_H) { ls->active_field = 2; return; } y += FIELD_H + 12; // Confirm label + field y += sfh + LABEL_GAP; if (mx >= x && mx < x + content_w && my >= y && my < y + FIELD_H) { ls->active_field = 3; return; } y += FIELD_H + 16; // Create button if (mx >= x && mx < x + content_w && my >= y && my < y + BTN_H) { if (try_first_boot_submit(ls)) { ls->mode = MODE_LOGIN; ls->active_field = 0; ls->password[0] = '\0'; ls->password_len = 0; ls->confirm[0] = '\0'; ls->confirm_len = 0; ls->show_error = false; } return; } y += BTN_H; } else { // Username label + field y += sfh + LABEL_GAP; if (mx >= x && mx < x + content_w && my >= y && my < y + FIELD_H) { ls->active_field = 0; return; } y += FIELD_H + 12; // Password label + field y += sfh + LABEL_GAP; if (mx >= x && mx < x + content_w && my >= y && my < y + FIELD_H) { ls->active_field = 1; return; } y += FIELD_H + 16; // Login button if (mx >= x && mx < x + content_w && my >= y && my < y + BTN_H) { if (try_login(ls)) { int pid = montauk::spawn("0:/os/desktop.elf", ls->username); if (pid >= 0) { montauk::setuser(pid, ls->username); montauk::waitpid(pid); } montauk::user::clear_session(); ls->password[0] = '\0'; ls->password_len = 0; ls->active_field = 1; ls->show_error = false; } return; } y += BTN_H; } // Skip error section if visible if (ls->show_error) y += error_section_h; // Power options (inside card, after separator) y += 16 + 1 + 16; // padding + separator + padding { int total_w = POWER_ICON_SZ + POWER_GAP + POWER_ICON_SZ; int start_x = card_x + (CARD_W - total_w) / 2; int hit_h = POWER_ICON_SZ + sfh + 6; // Shutdown if (mx >= start_x && mx < start_x + POWER_ICON_SZ && my >= y && my < y + hit_h) { montauk::shutdown(); } // Reboot int rx = start_x + POWER_ICON_SZ + POWER_GAP; if (mx >= rx && mx < rx + POWER_ICON_SZ && my >= y && my < y + hit_h) { montauk::reset(); } } } // ============================================================================ // Entry point // ============================================================================ extern "C" void _start() { LoginState* ls = (LoginState*)montauk::malloc(sizeof(LoginState)); montauk::memset(ls, 0, sizeof(LoginState)); new (&ls->fb) Framebuffer(); ls->screen_w = ls->fb.width(); ls->screen_h = ls->fb.height(); // Load TrueType fonts fonts::init(); // Set mouse bounds montauk::set_mouse_bounds(ls->screen_w - 1, ls->screen_h - 1); // Load power option icons Color icon_color = Color::from_rgb(0x66, 0x66, 0x66); ls->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", POWER_ICON_SZ, POWER_ICON_SZ, icon_color); ls->icon_reboot = svg_load("0:/icons/system-reboot.svg", POWER_ICON_SZ, POWER_ICON_SZ, icon_color); // Load wallpaper from system desktop config load_login_wallpaper(ls); // Check for setup environment (installation medium / ramdisk boot). // If setup.toml exists with environment.mode = "setup", skip login // entirely and launch the desktop in a passwordless live session. { auto doc = montauk::config::load("setup"); const char* mode = doc.get_string("environment.mode", ""); if (montauk::streq(mode, "setup")) { const char* user = doc.get_string("session.username", "liveuser"); const char* display = doc.get_string("session.display_name", "Live User"); const char* role = doc.get_string("session.role", "admin"); // Create a temporary user entry so the desktop and apps can // query session info normally. montauk::fmkdir("0:/users"); montauk::user::create_user(user, display, "", role); montauk::user::set_session(user); doc.destroy(); // Launch desktop directly -- no login required int pid = montauk::spawn("0:/os/desktop.elf", user); if (pid >= 0) { montauk::setuser(pid, user); montauk::waitpid(pid); } // Desktop exited (reboot/shutdown expected in setup mode). // Clear session and fall through to normal login in case // setup.toml was removed during installation. montauk::user::clear_session(); } else { doc.destroy(); } } // Check if users.toml exists (first boot detection) int fh = montauk::open("0:/config/users.toml"); if (fh < 0) { ls->mode = MODE_FIRST_BOOT; ls->active_field = 0; // Pre-fill username with "admin" montauk::strcpy(ls->username, "admin"); ls->username_len = 5; } else { montauk::close(fh); ls->mode = MODE_LOGIN; ls->active_field = 0; } // Main loop for (;;) { // Poll mouse ls->prev_buttons = ls->mouse.buttons; montauk::mouse_state(&ls->mouse); // Poll keyboard while (montauk::is_key_available()) { Montauk::KeyEvent key; montauk::getkey(&key); handle_key(ls, key); } // Handle mouse handle_mouse(ls); // Draw draw_login_screen(ls); } }