/* * app_procmgr.cpp * MontaukOS Desktop - Process Manager * Copyright (c) 2026 Daniel Hammer */ #include "apps_common.hpp" // ============================================================================ // Process Manager state // ============================================================================ static constexpr int PM_TOOLBAR_H = 36; static constexpr int PM_HEADER_H = 24; static constexpr int PM_ITEM_H = 28; static constexpr int PM_MAX_PROCS = 16; static constexpr int PM_POLL_MS = 1000; struct ProcMgrState { DesktopState* desktop; Montauk::ProcInfo procs[PM_MAX_PROCS]; int proc_count; int selected; // selected row index (-1 = none) uint64_t last_poll_ms; }; // ============================================================================ // Callbacks // ============================================================================ static void procmgr_on_poll(Window* win) { ProcMgrState* pm = (ProcMgrState*)win->app_data; if (!pm) return; uint64_t now = montauk::get_milliseconds(); if (now - pm->last_poll_ms < PM_POLL_MS) return; pm->last_poll_ms = now; // Remember previously selected PID int prev_pid = -1; if (pm->selected >= 0 && pm->selected < pm->proc_count) { prev_pid = pm->procs[pm->selected].pid; } pm->proc_count = montauk::proclist(pm->procs, PM_MAX_PROCS); // Restore selection by matching PID pm->selected = -1; if (prev_pid >= 0) { for (int i = 0; i < pm->proc_count; i++) { if (pm->procs[i].pid == prev_pid) { pm->selected = i; break; } } } win->dirty = true; } static void procmgr_on_draw(Window* win, Framebuffer& fb) { ProcMgrState* pm = (ProcMgrState*)win->app_data; if (!pm) return; Canvas c(win); c.fill(colors::WINDOW_BG); int fh = system_font_height(); // --- Toolbar --- c.fill_rect(0, 0, c.w, PM_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); c.hline(0, PM_TOOLBAR_H - 1, c.w, colors::BORDER); // "End Process" button int btn_w = 100; int btn_h = 26; int btn_x = 8; int btn_y = (PM_TOOLBAR_H - btn_h) / 2; Color btn_bg = (pm->selected >= 0 && pm->procs[pm->selected].pid != 0) ? Color::from_rgb(0xCC, 0x33, 0x33) : Color::from_rgb(0xAA, 0xAA, 0xAA); c.button(btn_x, btn_y, btn_w, btn_h, "End Process", btn_bg, colors::WHITE, 4); // Process count on right char count_str[24]; snprintf(count_str, sizeof(count_str), "%d processes", pm->proc_count); int cw = text_width(count_str); c.text(c.w - cw - 12, (PM_TOOLBAR_H - fh) / 2, count_str, colors::TEXT_COLOR); // --- Header row --- int header_y = PM_TOOLBAR_H; c.fill_rect(0, header_y, c.w, PM_HEADER_H, Color::from_rgb(0xF0, 0xF0, 0xF0)); int col_pid = 12; int col_name = 64; int col_mem = c.w - 120; int col_state = c.w - 52; int ty = header_y + (PM_HEADER_H - fh) / 2; c.text(col_pid, ty, "PID", Color::from_rgb(0x66, 0x66, 0x66)); c.text(col_name, ty, "Name", Color::from_rgb(0x66, 0x66, 0x66)); c.text(col_mem, ty, "Mem", Color::from_rgb(0x66, 0x66, 0x66)); c.text(col_state, ty, "St", Color::from_rgb(0x66, 0x66, 0x66)); c.hline(0, header_y + PM_HEADER_H - 1, c.w, colors::BORDER); // --- Process rows --- int list_y = PM_TOOLBAR_H + PM_HEADER_H; for (int i = 0; i < pm->proc_count; i++) { int row_y = list_y + i * PM_ITEM_H; if (row_y + PM_ITEM_H > c.h) break; // Selected row highlight if (i == pm->selected) { c.fill_rect(0, row_y, c.w, PM_ITEM_H, colors::MENU_HOVER); } int ry = row_y + (PM_ITEM_H - fh) / 2; // PID (right-aligned in PID column) char pid_str[8]; snprintf(pid_str, sizeof(pid_str), "%d", (int)pm->procs[i].pid); int pid_w = text_width(pid_str); c.text(col_pid + 32 - pid_w, ry, pid_str, colors::TEXT_COLOR); // Name (truncated to fit) c.text(col_name, ry, pm->procs[i].name, colors::TEXT_COLOR); // Memory char mem_str[16]; format_size(mem_str, (int)pm->procs[i].heapUsed); c.text(col_mem, ry, mem_str, colors::TEXT_COLOR); // State const char* st_str; Color st_color; switch (pm->procs[i].state) { case 2: // Running st_str = "Run"; st_color = Color::from_rgb(0x22, 0x88, 0x22); break; case 1: // Ready st_str = "Rdy"; st_color = Color::from_rgb(0x33, 0x66, 0xCC); break; case 3: // Terminated st_str = "Term"; st_color = Color::from_rgb(0xCC, 0x33, 0x33); break; default: st_str = "?"; st_color = colors::TEXT_COLOR; break; } c.text(col_state, ry, st_str, st_color); } } static void procmgr_on_mouse(Window* win, MouseEvent& ev) { ProcMgrState* pm = (ProcMgrState*)win->app_data; if (!pm) return; Rect cr = win->content_rect(); int lx = ev.x - cr.x; int ly = ev.y - cr.y; if (ev.left_pressed()) { // Check "End Process" button click int btn_w = 100; int btn_h = 26; int btn_x = 8; int btn_y = (PM_TOOLBAR_H - btn_h) / 2; Rect btn_rect = {btn_x, btn_y, btn_w, btn_h}; if (btn_rect.contains(lx, ly)) { if (pm->selected >= 0 && pm->selected < pm->proc_count && pm->procs[pm->selected].pid != 0) { montauk::kill(pm->procs[pm->selected].pid); pm->last_poll_ms = 0; // force refresh } return; } // Check row click int list_y = PM_TOOLBAR_H + PM_HEADER_H; if (ly >= list_y) { int row = (ly - list_y) / PM_ITEM_H; if (row >= 0 && row < pm->proc_count) { pm->selected = row; } else { pm->selected = -1; } } } } static void procmgr_on_key(Window* win, const Montauk::KeyEvent& key) { ProcMgrState* pm = (ProcMgrState*)win->app_data; if (!pm || !key.pressed) return; if (key.scancode == 0x48) { // Up arrow if (pm->selected > 0) pm->selected--; else if (pm->proc_count > 0) pm->selected = 0; } else if (key.scancode == 0x50) { // Down arrow if (pm->selected < pm->proc_count - 1) pm->selected++; } else if (key.scancode == 0x53) { // Delete key if (pm->selected >= 0 && pm->selected < pm->proc_count && pm->procs[pm->selected].pid != 0) { montauk::kill(pm->procs[pm->selected].pid); pm->last_poll_ms = 0; // force refresh } } } static void procmgr_on_close(Window* win) { if (win->app_data) { montauk::mfree(win->app_data); win->app_data = nullptr; } } // ============================================================================ // Process Manager launcher // ============================================================================ void open_procmgr(DesktopState* ds) { int idx = desktop_create_window(ds, "Processes", 180, 80, 520, 400); if (idx < 0) return; Window* win = &ds->windows[idx]; ProcMgrState* pm = (ProcMgrState*)montauk::malloc(sizeof(ProcMgrState)); montauk::memset(pm, 0, sizeof(ProcMgrState)); pm->desktop = ds; pm->selected = -1; pm->last_poll_ms = 0; // Initial poll pm->proc_count = montauk::proclist(pm->procs, PM_MAX_PROCS); win->app_data = pm; win->on_draw = procmgr_on_draw; win->on_mouse = procmgr_on_mouse; win->on_key = procmgr_on_key; win->on_close = procmgr_on_close; win->on_poll = procmgr_on_poll; }