/* * main.cpp * MontaukOS Desktop Environment - initialization, external window polling, run loop * Copyright (c) 2026 Daniel Hammer */ #include "desktop_internal.hpp" #include #include #include #include // ============================================================================ // App Manifest Scanning // ============================================================================ // Extract the basename from a readdir entry. // readdir returns full paths from drive root (e.g. "apps/doom/") — // strip the directory prefix and any trailing slash to get just "doom". static void extract_basename(char* out, int outSz, const char* entry) { // Strip trailing slash int len = montauk::slen(entry); while (len > 0 && entry[len - 1] == '/') len--; // Find last slash before that int last_slash = -1; for (int i = 0; i < len; i++) { if (entry[i] == '/') last_slash = i; } const char* base = (last_slash >= 0) ? entry + last_slash + 1 : entry; int base_len = len - (last_slash >= 0 ? last_slash + 1 : 0); if (base_len >= outSz) base_len = outSz - 1; montauk::memcpy(out, base, base_len); out[base_len] = '\0'; } void desktop_scan_apps(DesktopState* ds) { ds->external_app_count = 0; // Ensure the apps directory exists montauk::fmkdir("0:/apps"); const char* entries[32]; int count = montauk::readdir("0:/apps", entries, 32); for (int i = 0; i < count && ds->external_app_count < MAX_EXTERNAL_APPS; i++) { // readdir returns paths like "apps/doom/" — extract just "doom" char dirname[64]; extract_basename(dirname, sizeof(dirname), entries[i]); if (dirname[0] == '\0') continue; // Try to open the manifest in this app directory char manifest_path[128]; snprintf(manifest_path, sizeof(manifest_path), "0:/apps/%s/manifest.toml", dirname); int fh = montauk::open(manifest_path); if (fh < 0) continue; uint64_t sz = montauk::getsize(fh); if (sz == 0 || sz > 4096) { montauk::close(fh); continue; } char* text = (char*)montauk::malloc(sz + 1); if (!text) { montauk::close(fh); continue; } int bytes_read = montauk::read(fh, (uint8_t*)text, 0, sz); montauk::close(fh); if (bytes_read != (int)sz) { montauk::mfree(text); continue; } text[sz] = '\0'; auto doc = montauk::toml::parse(text); montauk::mfree(text); ExternalApp* app = &ds->external_apps[ds->external_app_count]; // Read manifest fields const char* name = doc.get_string("app.name", "Unknown"); const char* binary = doc.get_string("app.binary", ""); const char* icon_file = doc.get_string("app.icon", ""); const char* category = doc.get_string("menu.category", "Applications"); bool visible = doc.get_bool("menu.visible", true); bool launch_with_home = doc.get_bool("launch.pass_home_dir", true); if (binary[0] == '\0') { doc.destroy(); continue; } montauk::strncpy(app->name, name, sizeof(app->name)); montauk::strncpy(app->category, category, sizeof(app->category)); app->menu_visible = visible; app->launch_with_home = launch_with_home; // Build full binary path: 0:/apps// snprintf(app->binary_path, sizeof(app->binary_path), "0:/apps/%s/%s", dirname, binary); // Load icon from app directory app->icon = {}; app->icon_path[0] = '\0'; if (icon_file[0]) { snprintf(app->icon_path, sizeof(app->icon_path), "0:/apps/%s/%s", dirname, icon_file); Color defColor = colors::ICON_COLOR; app->icon = svg_load(app->icon_path, 20, 20, defColor); } doc.destroy(); ds->external_app_count++; } } // ============================================================================ // Menu Builder // ============================================================================ // Category definitions and their ordering static const char* CATEGORY_NAMES[] = { "Applications", "Internet", "System", "Games" }; static constexpr int NUM_CATEGORIES = 4; void desktop_build_menu(DesktopState* ds) { menu_row_count = 0; int builtin_count = 0; const DesktopBuiltinEntry* builtins = desktop_builtin_registry(&builtin_count); // Build each category for (int cat = 0; cat < NUM_CATEGORIES; cat++) { menu_add_category(CATEGORY_NAMES[cat]); // Add desktop builtins in this category for (int b = 0; b < builtin_count; b++) { if (builtins[b].launcher_category == cat) { menu_add_embedded(builtins[b].label, builtins[b].id, desktop_builtin_menu_icon(ds, builtins[b].id)); } } // Add external apps in this category for (int x = 0; x < ds->external_app_count; x++) { ExternalApp* app = &ds->external_apps[x]; if (!app->menu_visible) continue; if (montauk::streq(app->category, CATEGORY_NAMES[cat])) { menu_add_external(app->name, app->binary_path, &app->icon, app->launch_with_home); } } } // Divider + always-visible entries menu_add_category(""); // divider (cat 4, always expanded) for (int b = 0; b < builtin_count; b++) { if (builtins[b].launcher_footer) { menu_add_embedded(builtins[b].label, builtins[b].id, desktop_builtin_menu_icon(ds, builtins[b].id)); } } } // ============================================================================ // Initialization // ============================================================================ void gui::desktop_init(DesktopState* ds) { ds->screen_w = ds->fb.width(); ds->screen_h = ds->fb.height(); ds->background_cache = nullptr; ds->background_cache_pitch = ds->fb.pitch(); ds->background_cache_dirty = true; // Immediately clear the screen to hide flanterm boot text ds->fb.clear(colors::DESKTOP_BG); ds->fb.flip(); // Load TrueType fonts fonts::init(); ds->window_count = 0; ds->focused_window = -1; ds->prev_buttons = 0; ds->app_menu_open = false; desktop_init_launcher(ds); montauk::memset(&ds->mouse, 0, sizeof(montauk::abi::MouseState)); montauk::set_mouse_bounds(ds->screen_w - 1, ds->screen_h - 1); gui::clipboard_clear(); // Load SVG icons — scalable (colorful) for app menu, symbolic for toolbar/panel Color defColor = colors::ICON_COLOR; ds->icon_terminal = svg_load("0:/icons/utilities-terminal.svg", 20, 20, defColor); ds->icon_filemanager = svg_load("0:/icons/system-file-manager.svg", 20, 20, defColor); ds->icon_sysinfo = svg_load("0:/icons/preferences-desktop-apps.svg", 20, 20, defColor); ds->icon_appmenu = svg_load("0:/icons/view-app-grid-symbolic.svg", 20, 20, colors::PANEL_TEXT); ds->icon_folder = svg_load("0:/icons/folder.svg", 16, 16, defColor); ds->icon_file = svg_load("0:/icons/text-x-generic.svg", 16, 16, defColor); ds->icon_network = svg_load("0:/icons/network-wired-symbolic.svg", 16, 16, colors::PANEL_TEXT); ds->icon_go_up = svg_load("0:/icons/go-up-symbolic.svg", 16, 16, defColor); ds->icon_go_back = svg_load("0:/icons/go-previous-symbolic.svg", 16, 16, defColor); ds->icon_go_forward = svg_load("0:/icons/go-next-symbolic.svg", 16, 16, defColor); ds->icon_home = svg_load("0:/icons/user-home.svg", 16, 16, defColor); ds->icon_refresh = svg_load("0:/icons/view-refresh-symbolic.svg", 16, 16, defColor); ds->icon_exec = svg_load("0:/icons/utilities-terminal.svg", 16, 16, defColor); ds->icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, defColor); ds->icon_file_lg = svg_load("0:/icons/text-x-generic.svg", 48, 48, defColor); ds->icon_exec_lg = svg_load("0:/icons/utilities-terminal.svg", 48, 48, defColor); // drive icons loaded lazily by file manager to reduce startup heap pressure ds->icon_settings = svg_load("0:/icons/preferences-desktop-apps.svg", 20, 20, defColor); ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor); ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor); ds->icon_sleep = svg_load("0:/icons/sleep.svg", 20, 20, defColor); ds->icon_logout = svg_load("0:/icons/gnome-logout.svg", 20, 20, defColor); ds->icon_volume = svg_load("0:/icons/audio-volume-high-symbolic.svg", 16, 16, colors::PANEL_TEXT); ds->icon_lock = svg_load("0:/icons/lock.svg", 20, 20, defColor); // Scan 0:/apps/ for external app manifests and build the menu desktop_scan_apps(ds); desktop_build_menu(ds); // Settings defaults ds->settings.bg_gradient = true; ds->settings.bg_image = false; ds->settings.bg_grad_top = Color::from_rgb(0xD0, 0xD8, 0xE8); ds->settings.bg_grad_bottom = Color::from_rgb(0xA0, 0xA8, 0xB8); ds->settings.bg_solid = Color::from_rgb(0xD0, 0xD8, 0xE8); ds->settings.bg_image_path[0] = '\0'; ds->settings.bg_wallpaper = nullptr; ds->settings.bg_wallpaper_w = 0; ds->settings.bg_wallpaper_h = 0; ds->settings.panel_color = Color::from_rgb(0x10, 0x10, 0x10); ds->settings.accent_color = colors::ACCENT; ds->settings.show_shadows = true; ds->settings.clock_24h = true; ds->settings.ui_scale = 1; ds->settings.tz_offset_minutes = 60; // default CET (UTC+1) // Load per-user desktop settings { auto doc = montauk::config::load_user(ds->current_user, "desktop"); const char* wp = doc.get_string("wallpaper.path", ""); if (wp[0] != '\0') { wallpaper_load(&ds->settings, wp, ds->screen_w, ds->screen_h); } else { // Fall back to system-wide default wallpaper from 0:/config/desktop.toml auto sys = montauk::config::load("desktop"); const char* def_wp = sys.get_string("wallpaper.path", ""); if (def_wp[0] != '\0') { wallpaper_load(&ds->settings, def_wp, ds->screen_w, ds->screen_h); } sys.destroy(); } // Restore other settings from user config const char* bg_mode = doc.get_string("background.mode", ""); if (montauk::streq(bg_mode, "solid")) { ds->settings.bg_gradient = false; ds->settings.bg_image = false; } else if (montauk::streq(bg_mode, "gradient")) { ds->settings.bg_gradient = true; ds->settings.bg_image = false; } // (image mode is set by wallpaper_load above) // Background colors int64_t solid = doc.get_int("background.solid_color", -1); if (solid >= 0) ds->settings.bg_solid = Color::from_rgb( (uint8_t)((solid >> 16) & 0xFF), (uint8_t)((solid >> 8) & 0xFF), (uint8_t)(solid & 0xFF)); int64_t gtop = doc.get_int("background.grad_top", -1); if (gtop >= 0) ds->settings.bg_grad_top = Color::from_rgb( (uint8_t)((gtop >> 16) & 0xFF), (uint8_t)((gtop >> 8) & 0xFF), (uint8_t)(gtop & 0xFF)); int64_t gbot = doc.get_int("background.grad_bottom", -1); if (gbot >= 0) ds->settings.bg_grad_bottom = Color::from_rgb( (uint8_t)((gbot >> 16) & 0xFF), (uint8_t)((gbot >> 8) & 0xFF), (uint8_t)(gbot & 0xFF)); // Appearance colors int64_t panel = doc.get_int("appearance.panel_color", -1); if (panel >= 0) ds->settings.panel_color = Color::from_rgb( (uint8_t)((panel >> 16) & 0xFF), (uint8_t)((panel >> 8) & 0xFF), (uint8_t)(panel & 0xFF)); int64_t accent = doc.get_int("appearance.accent_color", -1); if (accent >= 0) ds->settings.accent_color = Color::from_rgb( (uint8_t)((accent >> 16) & 0xFF), (uint8_t)((accent >> 8) & 0xFF), (uint8_t)(accent & 0xFF)); int64_t scale = doc.get_int("display.ui_scale", 1); ds->settings.ui_scale = (int)scale; ds->settings.clock_24h = doc.get_bool("display.clock_24h", true); ds->settings.show_shadows = doc.get_bool("display.show_shadows", true); doc.destroy(); } // Load timezone config { auto tz = montauk::config::load("timezone"); ds->settings.tz_offset_minutes = (int)tz.get_int("timezone.offset_minutes", 60); tz.destroy(); } montauk::settz(ds->settings.tz_offset_minutes); montauk::win_setscale(ds->settings.ui_scale); ds->ctx_menu_open = false; ds->ctx_menu_x = 0; ds->ctx_menu_y = 0; ds->net_popup_open = false; montauk::get_netcfg(&ds->cached_net_cfg); ds->net_cfg_last_poll = montauk::get_milliseconds(); ds->net_icon_rect = {0, 0, 0, 0}; ds->vol_popup_open = false; ds->vol_icon_rect = {0, 0, 0, 0}; int vol = montauk::audio_get_master_volume(); ds->vol_level = vol >= 0 ? vol : 80; ds->vol_muted = montauk::audio_get_master_mute() == 1; ds->vol_pre_mute = ds->vol_level; ds->vol_dragging = false; ds->vol_last_poll = montauk::get_milliseconds(); ds->vol_serial = montauk::audio_wait(0, 0); ds->closing_ext_count = 0; ds->screen_locked = false; ds->lock_password[0] = '\0'; ds->lock_password_len = 0; ds->lock_error[0] = '\0'; ds->lock_show_error = false; } // ============================================================================ // External Window Polling // ============================================================================ static uint64_t desktop_clock_token() { montauk::abi::DateTime dt; montauk::gettime(&dt); return ((uint64_t)dt.Year << 32) | ((uint64_t)dt.Month << 24) | ((uint64_t)dt.Day << 16) | ((uint64_t)dt.Hour << 8) | (uint64_t)dt.Minute; } static bool desktop_has_visible_dirty_window(const DesktopState* ds) { for (int i = 0; i < ds->window_count; i++) { const Window& win = ds->windows[i]; if (win.state == WIN_CLOSED || win.state == WIN_MINIMIZED) continue; if (win.dirty) return true; } return false; } static bool desktop_has_active_interaction(const DesktopState* ds) { if (ds->vol_dragging) return true; for (int i = 0; i < ds->window_count; i++) { const Window& win = ds->windows[i]; if (win.state == WIN_CLOSED || win.state == WIN_MINIMIZED) continue; if (win.dragging || win.resizing) return true; } return false; } static void desktop_clear_window_dirty(DesktopState* ds) { for (int i = 0; i < ds->window_count; i++) { ds->windows[i].dirty = false; } } static bool desktop_netcfg_equal(const montauk::abi::NetCfg& a, const montauk::abi::NetCfg& b) { if (a.ipAddress != b.ipAddress || a.subnetMask != b.subnetMask || a.gateway != b.gateway || a.dnsServer != b.dnsServer) { return false; } for (int i = 0; i < 6; i++) { if (a.macAddress[i] != b.macAddress[i]) return false; } return true; } static bool desktop_refresh_panel_state(DesktopState* ds, uint64_t now) { if (ds->screen_locked) return false; bool changed = false; if (now - ds->net_cfg_last_poll > 5000) { montauk::abi::NetCfg next; montauk::get_netcfg(&next); if (!desktop_netcfg_equal(next, ds->cached_net_cfg)) { ds->cached_net_cfg = next; changed = true; } ds->net_cfg_last_poll = now; } // Event-driven sync: the kernel mixer bumps a serial on every state // change. Reading it is one cheap syscall; refresh only when the serial // moved since we last looked. The audio app (and any other client) will // bump this serial when they change volume/mute, so the popup and the // panel speaker icon stay in sync without polling them blindly. // // Skip the refresh while our own slider is being dragged so a kernel // snapshot mid-drag can't clobber the in-flight value. if (!ds->vol_dragging) { uint64_t serial = montauk::audio_wait(ds->vol_serial, 0); if (serial != ds->vol_serial) { ds->vol_serial = serial; int v = montauk::audio_get_master_volume(); bool muted = montauk::audio_get_master_mute() == 1; if (v >= 0 && v != ds->vol_level) { ds->vol_level = v; changed = true; } if (muted != ds->vol_muted) { ds->vol_muted = muted; changed = true; } } ds->vol_last_poll = now; } return changed; } bool desktop_poll_external_windows(DesktopState* ds) { bool changed = false; montauk::abi::WinInfo extWins[8]; int extCount = montauk::win_enumerate(extWins, 8); // Check for new external windows and map them for (int e = 0; e < extCount; e++) { int extId = extWins[e].id; // Check if we already have this window bool found = false; for (int i = 0; i < ds->window_count; i++) { if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) { found = true; bool remapRequested = false; bool wasFullscreen = desktop_window_fullscreen(&ds->windows[i]); uint64_t currentVa = (uint64_t)(uintptr_t)ds->windows[i].content; if (ds->windows[i].content_w != extWins[e].width || ds->windows[i].content_h != extWins[e].height) { ds->windows[i].content_w = extWins[e].width; ds->windows[i].content_h = extWins[e].height; ds->windows[i].dirty = true; remapRequested = true; if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) { changed = true; } } if (!montauk::streq(ds->windows[i].title, extWins[e].title)) { montauk::strncpy(ds->windows[i].title, extWins[e].title, MAX_TITLE_LEN); if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) { changed = true; } } if (ds->windows[i].ext_flags != extWins[e].flags) { ds->windows[i].ext_flags = extWins[e].flags; bool isFullscreen = desktop_window_fullscreen(&ds->windows[i]); if (isFullscreen) { if (!wasFullscreen) ds->windows[i].saved_frame = ds->windows[i].frame; ds->windows[i].frame = {0, 0, ds->screen_w, ds->screen_h}; ds->windows[i].state = WIN_NORMAL; } else if (wasFullscreen) { ds->windows[i].frame = ds->windows[i].saved_frame; ds->windows[i].state = WIN_NORMAL; } changed = true; } if (desktop_window_fullscreen(&ds->windows[i]) && (ds->windows[i].frame.x != 0 || ds->windows[i].frame.y != 0 || ds->windows[i].frame.w != ds->screen_w || ds->windows[i].frame.h != ds->screen_h)) { ds->windows[i].frame = {0, 0, ds->screen_w, ds->screen_h}; ds->windows[i].state = WIN_NORMAL; changed = true; } // Update dirty flag and cursor if (extWins[e].dirty) { ds->windows[i].dirty = true; if (ds->windows[i].state != WIN_MINIMIZED && ds->windows[i].state != WIN_CLOSED) { changed = true; } } if (ds->windows[i].ext_cursor != extWins[e].cursor) { changed = true; } ds->windows[i].ext_cursor = extWins[e].cursor; if (remapRequested && currentVa != 0) { montauk::win_unmap(extId); ds->windows[i].content = nullptr; currentVa = 0; } uint64_t va = currentVa; if (remapRequested || currentVa == 0) { va = montauk::win_map(extId); } if (va != 0 && va != currentVa) { ds->windows[i].content = (uint32_t*)va; ds->windows[i].content_w = extWins[e].width; ds->windows[i].content_h = extWins[e].height; ds->windows[i].dirty = true; changed = true; // Clear from closing list if the slot was reused for (int c = 0; c < ds->closing_ext_count; c++) { if (ds->closing_ext_ids[c] == extId) { ds->closing_ext_ids[c] = ds->closing_ext_ids[--ds->closing_ext_count]; break; } } } break; } } if (!found && ds->window_count < MAX_WINDOWS) { // Skip windows we've already sent a close event to — the owning // process hasn't destroyed them yet, so they still appear in // win_enumerate. Re-creating them would place them at the // default position instead of where the user dragged them. bool closing = false; for (int c = 0; c < ds->closing_ext_count; c++) { if (ds->closing_ext_ids[c] == extId) { closing = true; break; } } if (closing) continue; // Map the pixel buffer into our address space uint64_t va = montauk::win_map(extId); if (va == 0) continue; int idx = ds->window_count; Window* win = &ds->windows[idx]; montauk::memset(win, 0, sizeof(Window)); montauk::strncpy(win->title, extWins[e].title, MAX_TITLE_LEN); int w = extWins[e].width; int h = extWins[e].height; // Position the window centered-ish int wx = (ds->screen_w - w - 2 * BORDER_WIDTH) / 2 + idx * 30; int wy = PANEL_HEIGHT + 20 + idx * 30; win->frame = {wx, wy, w + 2 * BORDER_WIDTH, h + TITLEBAR_HEIGHT + BORDER_WIDTH}; win->state = WIN_NORMAL; win->z_order = idx; win->focused = true; win->dirty = true; win->dragging = false; win->resizing = false; win->saved_frame = win->frame; // Point content to the shared pixel buffer win->content = (uint32_t*)va; win->content_w = w; win->content_h = h; win->on_draw = nullptr; win->on_mouse = nullptr; win->on_key = nullptr; win->on_close = nullptr; win->on_poll = nullptr; win->app_data = nullptr; win->external = true; win->ext_win_id = extId; win->ext_cursor = extWins[e].cursor; win->ext_flags = extWins[e].flags; if (desktop_window_fullscreen(win)) { win->frame = {0, 0, ds->screen_w, ds->screen_h}; win->state = WIN_NORMAL; } // Unfocus previous window if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) { ds->windows[ds->focused_window].focused = false; } ds->focused_window = idx; ds->window_count++; changed = true; } } // Check for removed external windows (process exited) for (int i = ds->window_count - 1; i >= 0; i--) { if (!ds->windows[i].external) continue; int extId = ds->windows[i].ext_win_id; bool stillExists = false; for (int e = 0; e < extCount; e++) { if (extWins[e].id == extId) { stillExists = true; break; } } if (!stillExists) { // Window gone — remove without freeing content (shared memory) if (ds->windows[i].content != nullptr) { montauk::win_unmap(extId); } ds->windows[i].content = nullptr; // prevent free gui::desktop_close_window(ds, i); changed = true; } } // Purge closing IDs that the kernel no longer reports (fully destroyed) for (int c = ds->closing_ext_count - 1; c >= 0; c--) { bool alive = false; for (int e = 0; e < extCount; e++) { if (extWins[e].id == ds->closing_ext_ids[c]) { alive = true; break; } } if (!alive) { ds->closing_ext_ids[c] = ds->closing_ext_ids[--ds->closing_ext_count]; } } return changed; } // ============================================================================ // Run Loop // ============================================================================ void gui::desktop_run(DesktopState* ds) { uint64_t lastClockToken = 0; uint64_t nextClockPollMs = 0; uint64_t lastLauncherBlinkToken = ~0ull; uint64_t inputSerial = montauk::input_wait(0, 0); bool firstFrame = true; for (;;) { bool mouseChanged = false; bool keyboardChanged = false; bool sceneChanged = false; // Poll mouse state int prevMouseX = ds->mouse.x; int prevMouseY = ds->mouse.y; uint8_t prevMouseButtons = ds->mouse.buttons; ds->prev_buttons = ds->mouse.buttons; montauk::mouse_state(&ds->mouse); mouseChanged = ds->mouse.x != prevMouseX || ds->mouse.y != prevMouseY || ds->mouse.buttons != prevMouseButtons || ds->mouse.scrollDelta != 0; sceneChanged |= mouseChanged; // Poll keyboard events while (montauk::is_key_available()) { montauk::abi::KeyEvent key; montauk::getkey(&key); desktop_handle_keyboard(ds, key); keyboardChanged = true; } sceneChanged |= keyboardChanged; // Poll external windows (discover new, remove dead, update dirty) sceneChanged |= desktop_poll_external_windows(ds); if (!ds->screen_locked) { // Poll windows that have a poll callback for (int i = 0; i < ds->window_count; i++) { Window* win = &ds->windows[i]; if (win->state == WIN_CLOSED) continue; if (win->on_poll) { win->on_poll(win); } } } // Handle mouse events if (mouseChanged) { desktop_handle_mouse(ds); } if (!ds->screen_locked && (mouseChanged || keyboardChanged)) { // Re-poll external windows so that any killed during mouse/key // handling are removed before we touch their pixel buffers. sceneChanged |= desktop_poll_external_windows(ds); } uint64_t now = montauk::get_milliseconds(); sceneChanged |= desktop_refresh_panel_state(ds, now); // Pick up any wallpaper buffer produced by a background loader. if (wallpaper_poll(&ds->settings)) { desktop_mark_background_dirty(ds); sceneChanged = true; } uint64_t launcherBlinkToken = desktop_launcher_blink_token(ds, now); if (launcherBlinkToken != lastLauncherBlinkToken) { lastLauncherBlinkToken = launcherBlinkToken; sceneChanged = true; } if (now >= nextClockPollMs) { nextClockPollMs = now + 1000; uint64_t clockToken = desktop_clock_token(); if (clockToken != lastClockToken) { lastClockToken = clockToken; sceneChanged = true; } } sceneChanged |= desktop_has_visible_dirty_window(ds); if (firstFrame || sceneChanged) { desktop_compose(ds); ds->fb.flip(); desktop_clear_window_dirty(ds); firstFrame = false; } uint64_t waitMs = (desktop_has_active_interaction(ds) || mouseChanged || keyboardChanged) ? 1 : (sceneChanged ? 4 : 16); inputSerial = montauk::input_wait(inputSerial, waitMs); } } // ============================================================================ // Entry Point // ============================================================================ static DesktopState* g_desktop; static constexpr const char kDesktopBinaryName[] = "desktop.elf"; static constexpr int DESKTOP_PROC_SCAN_MAX = 256; static bool desktop_proc_alive(const montauk::abi::ProcInfo& proc) { return proc.state == 1 || proc.state == 2 || proc.state == 3; } static bool proc_name_is_desktop(const char* name) { int name_len = montauk::slen(name); int binary_len = (int)sizeof(kDesktopBinaryName) - 1; if (name_len < binary_len) return false; const char* suffix = name + name_len - binary_len; if (!montauk::streq(suffix, kDesktopBinaryName)) return false; return name_len == binary_len || suffix[-1] == '/'; } static bool existing_desktop_running() { static montauk::abi::ProcInfo procs[DESKTOP_PROC_SCAN_MAX]; int count = montauk::proclist(procs, DESKTOP_PROC_SCAN_MAX); if (count <= 0) return false; int self_pid = montauk::getpid(); for (int i = 0; i < count; i++) { if (procs[i].pid == self_pid) continue; if (!desktop_proc_alive(procs[i])) continue; if (proc_name_is_desktop(procs[i].name)) return true; } return false; } extern "C" void _start() { if (existing_desktop_running()) { gui::dialogs::message_box( "Desktop Already Running", "Cannot spawn new desktop process whilst an existing desktop session is active.", gui::dialogs::MESSAGE_BOX_OK); montauk::exit(0); } DesktopState* ds = (DesktopState*)montauk::malloc(sizeof(DesktopState)); montauk::memset(ds, 0, sizeof(DesktopState)); // Placement-new the Framebuffer since it has a constructor new (&ds->fb) Framebuffer(); // Read username from spawn args char username[32] = {}; montauk::getargs(username, 32); if (username[0] == '\0') { montauk::strcpy(username, "default"); } montauk::strncpy(ds->current_user, username, 31); // Build user paths montauk::user::home_dir(username, ds->home_dir, sizeof(ds->home_dir)); montauk::user::config_dir(username, ds->user_config_dir, sizeof(ds->user_config_dir)); // Check if user is admin ds->is_admin = false; { montauk::user::UserInfo users[16]; int count = montauk::user::load_users(users, 16); for (int i = 0; i < count; i++) { if (montauk::streq(users[i].username, username)) { ds->is_admin = montauk::streq(users[i].role, "admin"); break; } } } g_desktop = ds; desktop_init(ds); desktop_run(ds); montauk::exit(0); }