/* * main.cpp * MontaukOS Process Manager * Copyright (c) 2026 Daniel Hammer */ #include #include #include #include extern "C" { #include } using namespace gui; static constexpr int INIT_W = 760; static constexpr int INIT_H = 520; static constexpr int PM_TOOLBAR_H = 36; static constexpr int PM_TAB_H = 32; static constexpr int PM_HEADER_H = 24; static constexpr int PM_ITEM_H = 28; static constexpr int PM_POLL_MS = 1000; static constexpr int PM_MAX_PROCS = 256; static constexpr int PM_MAX_WINDOWS = 64; static constexpr int PM_GRAPH_CAP = 120; static constexpr Color PM_TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5); static constexpr Color PM_HEADER_BG = Color::from_rgb(0xF0, 0xF0, 0xF0); static constexpr Color PM_BORDER = Color::from_rgb(0xD0, 0xD0, 0xD0); static constexpr Color PM_DIM_TEXT = Color::from_rgb(0x66, 0x66, 0x66); static constexpr Color PM_SUCCESS = Color::from_rgb(0x22, 0x88, 0x22); static constexpr Color PM_WARNING = Color::from_rgb(0xCC, 0x88, 0x00); static constexpr Color PM_DANGER = Color::from_rgb(0xCC, 0x33, 0x33); static constexpr Color PM_INFO = Color::from_rgb(0x33, 0x66, 0xCC); enum ProcMgrTab { PM_TAB_PROCESSES = 0, PM_TAB_PERFORMANCE = 1, PM_TAB_WINDOWS = 2, PM_TAB_COUNT = 3, }; static const char* g_tab_labels[PM_TAB_COUNT] = { "Processes", "Performance", "Windows", }; struct ProcCpuSnapshot { int pid; uint64_t cpu_time_ms; }; struct ProcMgrState { Montauk::ProcInfo procs[PM_MAX_PROCS]; Montauk::WinInfo windows[PM_MAX_WINDOWS]; Montauk::MemStats mem; Montauk::SysInfo sys_info; int proc_count; int win_count; int selected; int selected_window; int proc_scroll; int win_scroll; int active_tab; int mouse_x; int mouse_y; int cpu_count; int cpu_percent; int mem_percent; uint64_t total_process_heap; uint64_t last_poll_ms; uint64_t last_cpu_wall_ms; bool have_cpu_sample; ProcCpuSnapshot prev_cpu[PM_MAX_PROCS]; int prev_cpu_count; uint64_t cpu_samples[PM_GRAPH_CAP]; uint64_t mem_samples[PM_GRAPH_CAP]; uint64_t proc_samples[PM_GRAPH_CAP]; uint64_t win_samples[PM_GRAPH_CAP]; uint64_t heap_samples[PM_GRAPH_CAP]; mtk::GraphHistory cpu_history; mtk::GraphHistory mem_history; mtk::GraphHistory proc_history; mtk::GraphHistory win_history; mtk::GraphHistory heap_history; }; static WsWindow g_win; static ProcMgrState g_pm = {}; static mtk::Theme pm_theme() { mtk::Theme theme = mtk::make_theme(); theme.surface = PM_TOOLBAR_BG; theme.surface_alt = PM_HEADER_BG; theme.border = PM_BORDER; theme.text_muted = PM_DIM_TEXT; theme.text_subtle = PM_DIM_TEXT; theme.danger = PM_DANGER; theme.danger_hover = mtk::darken(PM_DANGER, 18); theme.disabled_bg = Color::from_rgb(0xAA, 0xAA, 0xAA); return theme; } static void init_graph_histories() { mtk::graph_history_init(&g_pm.cpu_history, g_pm.cpu_samples, PM_GRAPH_CAP); mtk::graph_history_init(&g_pm.mem_history, g_pm.mem_samples, PM_GRAPH_CAP); mtk::graph_history_init(&g_pm.proc_history, g_pm.proc_samples, PM_GRAPH_CAP); mtk::graph_history_init(&g_pm.win_history, g_pm.win_samples, PM_GRAPH_CAP); mtk::graph_history_init(&g_pm.heap_history, g_pm.heap_samples, PM_GRAPH_CAP); } static int parse_positive_int_after_comma(const char* text) { if (!text) return 0; const char* p = text; while (*p && *p != ',') p++; if (*p == ',') p++; while (*p && (*p < '0' || *p > '9')) p++; int value = 0; while (*p >= '0' && *p <= '9') { value = value * 10 + (*p - '0'); p++; } return value; } static int detect_cpu_count() { Montauk::DevInfo devs[16]; int count = montauk::devlist(devs, 16); if (count < 0) count = 0; for (int i = 0; i < count; i++) { if (devs[i].category != 0) continue; int cores = parse_positive_int_after_comma(devs[i].detail); if (cores > 0) return cores; } return 1; } static void refresh_static_info() { montauk::get_info(&g_pm.sys_info); if (g_pm.sys_info.maxProcesses == 0) g_pm.sys_info.maxProcesses = PM_MAX_PROCS; g_pm.cpu_count = detect_cpu_count(); if (g_pm.cpu_count <= 0) g_pm.cpu_count = 1; } static void format_size(char* buf, uint64_t size) { if (size < 1024ULL) { snprintf(buf, 24, "%llu B", (unsigned long long)size); } else if (size < 1024ULL * 1024ULL) { uint64_t kb = size / 1024ULL; uint64_t frac = ((size % 1024ULL) * 10ULL) / 1024ULL; if (kb < 10ULL) snprintf(buf, 24, "%llu.%llu KB", (unsigned long long)kb, (unsigned long long)frac); else snprintf(buf, 24, "%llu KB", (unsigned long long)kb); } else if (size < 1024ULL * 1024ULL * 1024ULL) { uint64_t mb = size / (1024ULL * 1024ULL); uint64_t frac = ((size % (1024ULL * 1024ULL)) * 10ULL) / (1024ULL * 1024ULL); if (mb < 10ULL) snprintf(buf, 24, "%llu.%llu MB", (unsigned long long)mb, (unsigned long long)frac); else snprintf(buf, 24, "%llu MB", (unsigned long long)mb); } else { uint64_t gb = size / (1024ULL * 1024ULL * 1024ULL); uint64_t frac = ((size % (1024ULL * 1024ULL * 1024ULL)) * 10ULL) / (1024ULL * 1024ULL * 1024ULL); if (gb < 10ULL) snprintf(buf, 24, "%llu.%llu GB", (unsigned long long)gb, (unsigned long long)frac); else snprintf(buf, 24, "%llu GB", (unsigned long long)gb); } } static const char* proc_state_label(uint8_t state, Color* out_color) { Color color = colors::TEXT_COLOR; const char* label = "?"; switch (state) { case 1: label = "Ready"; color = PM_INFO; break; case 2: label = "Run"; color = PM_SUCCESS; break; case 3: label = "Block"; color = PM_WARNING; break; case 4: label = "Term"; color = PM_DANGER; break; default: break; } if (out_color) *out_color = color; return label; } static int content_top() { return PM_TOOLBAR_H + PM_TAB_H; } static mtk::TableLayout process_table_layout() { int top = content_top(); int body_y = top + PM_HEADER_H; return { {0, top, g_win.width, PM_HEADER_H}, {0, body_y, g_win.width, gui_max(g_win.height - body_y, 0)}, PM_ITEM_H }; } static mtk::TableLayout window_table_layout() { int top = content_top(); int body_y = top + PM_HEADER_H; return { {0, top, g_win.width, PM_HEADER_H}, {0, body_y, g_win.width, gui_max(g_win.height - body_y, 0)}, PM_ITEM_H }; } static int process_visible_rows() { return mtk::table_visible_rows(process_table_layout()); } static int window_visible_rows() { return mtk::table_visible_rows(window_table_layout()); } static bool process_state_alive(uint8_t state) { return state == 1 || state == 2 || state == 3; } static int live_process_count() { int count = 0; for (int i = 0; i < g_pm.proc_count; i++) { if (process_state_alive(g_pm.procs[i].state)) count++; } return count; } static uint64_t total_process_heap() { uint64_t total = 0; for (int i = 0; i < g_pm.proc_count; i++) { if (process_state_alive(g_pm.procs[i].state)) total += g_pm.procs[i].heapUsed; } return total; } static void collect_descendant_pids(int parent_pid, int* pids, int* count, int max_count) { for (int i = 0; i < g_pm.proc_count; i++) { if (!process_state_alive(g_pm.procs[i].state)) continue; if (g_pm.procs[i].parentPid != parent_pid) continue; int child_pid = g_pm.procs[i].pid; collect_descendant_pids(child_pid, pids, count, max_count); if (*count < max_count) pids[(*count)++] = child_pid; } } static int find_prev_cpu_snapshot(int pid) { for (int i = 0; i < g_pm.prev_cpu_count; i++) { if (g_pm.prev_cpu[i].pid == pid) return i; } return -1; } static uint64_t process_cpu_delta_ms() { uint64_t delta = 0; for (int i = 0; i < g_pm.proc_count; i++) { int prev = find_prev_cpu_snapshot(g_pm.procs[i].pid); if (prev < 0) continue; uint64_t old_time = g_pm.prev_cpu[prev].cpu_time_ms; uint64_t new_time = g_pm.procs[i].cpuTimeMs; if (new_time >= old_time) delta += new_time - old_time; } return delta; } static void store_cpu_snapshot() { g_pm.prev_cpu_count = gui_min(g_pm.proc_count, PM_MAX_PROCS); for (int i = 0; i < g_pm.prev_cpu_count; i++) { g_pm.prev_cpu[i].pid = g_pm.procs[i].pid; g_pm.prev_cpu[i].cpu_time_ms = g_pm.procs[i].cpuTimeMs; } } static void sample_performance(uint64_t now) { uint64_t elapsed = g_pm.have_cpu_sample ? now - g_pm.last_cpu_wall_ms : 0; uint64_t cpu_delta = process_cpu_delta_ms(); if (g_pm.have_cpu_sample && elapsed > 0) { uint64_t capacity = elapsed * (uint64_t)gui_max(g_pm.cpu_count, 1); uint64_t percent = capacity > 0 ? (cpu_delta * 100ULL) / capacity : 0; if (percent > 100ULL) percent = 100ULL; g_pm.cpu_percent = (int)percent; } else { g_pm.cpu_percent = 0; } store_cpu_snapshot(); g_pm.have_cpu_sample = true; g_pm.last_cpu_wall_ms = now; if (g_pm.mem.totalBytes > 0) { g_pm.mem_percent = (int)((g_pm.mem.usedBytes * 100ULL) / g_pm.mem.totalBytes); if (g_pm.mem_percent > 100) g_pm.mem_percent = 100; } else { g_pm.mem_percent = 0; } g_pm.total_process_heap = total_process_heap(); mtk::graph_history_push(&g_pm.cpu_history, (uint64_t)g_pm.cpu_percent); mtk::graph_history_push(&g_pm.mem_history, g_pm.mem.usedBytes); mtk::graph_history_push(&g_pm.proc_history, (uint64_t)live_process_count()); mtk::graph_history_push(&g_pm.win_history, (uint64_t)g_pm.win_count); mtk::graph_history_push(&g_pm.heap_history, g_pm.total_process_heap); } static int find_process_index_by_pid(int pid) { for (int i = 0; i < g_pm.proc_count; i++) { if (g_pm.procs[i].pid == pid) return i; } return -1; } static int find_window_index_by_id(int id) { for (int i = 0; i < g_pm.win_count; i++) { if (g_pm.windows[i].id == id) return i; } return -1; } static void clamp_scrolls() { mtk::clamp_table_scroll(&g_pm.proc_scroll, process_table_layout(), g_pm.proc_count); mtk::clamp_table_scroll(&g_pm.win_scroll, window_table_layout(), g_pm.win_count); } static void ensure_process_selection_visible() { mtk::ensure_table_row_visible(&g_pm.proc_scroll, process_table_layout(), g_pm.proc_count, g_pm.selected); } static void ensure_window_selection_visible() { mtk::ensure_table_row_visible(&g_pm.win_scroll, window_table_layout(), g_pm.win_count, g_pm.selected_window); } static bool refresh_state(bool force) { uint64_t now = montauk::get_milliseconds(); if (!force && now - g_pm.last_poll_ms < PM_POLL_MS) return false; g_pm.last_poll_ms = now; int prev_pid = -1; if (g_pm.selected >= 0 && g_pm.selected < g_pm.proc_count) prev_pid = g_pm.procs[g_pm.selected].pid; int prev_win_id = -1; if (g_pm.selected_window >= 0 && g_pm.selected_window < g_pm.win_count) prev_win_id = g_pm.windows[g_pm.selected_window].id; g_pm.proc_count = montauk::proclist(g_pm.procs, PM_MAX_PROCS); if (g_pm.proc_count < 0) g_pm.proc_count = 0; g_pm.selected = find_process_index_by_pid(prev_pid); g_pm.win_count = montauk::win_enumerate(g_pm.windows, PM_MAX_WINDOWS); if (g_pm.win_count < 0) g_pm.win_count = 0; g_pm.selected_window = find_window_index_by_id(prev_win_id); montauk::memstats(&g_pm.mem); sample_performance(now); clamp_scrolls(); ensure_process_selection_visible(); ensure_window_selection_visible(); return true; } static bool can_kill_selected() { return g_pm.active_tab == PM_TAB_PROCESSES && g_pm.selected >= 0 && g_pm.selected < g_pm.proc_count && g_pm.procs[g_pm.selected].pid != 0 && g_pm.procs[g_pm.selected].state != 4; } static void kill_selected() { if (!can_kill_selected()) return; int root_pid = g_pm.procs[g_pm.selected].pid; int descendants[PM_MAX_PROCS]; int descendant_count = 0; collect_descendant_pids(root_pid, descendants, &descendant_count, PM_MAX_PROCS); for (int i = 0; i < descendant_count; i++) montauk::kill(descendants[i]); montauk::kill(root_pid); refresh_state(true); } static void cycle_tab(int delta) { int next = g_pm.active_tab + delta; while (next < 0) next += PM_TAB_COUNT; while (next >= PM_TAB_COUNT) next -= PM_TAB_COUNT; g_pm.active_tab = next; clamp_scrolls(); } static Rect toolbar_rect() { return {0, 0, g_win.width, PM_TOOLBAR_H}; } static Rect refresh_button_rect() { int btn_h = 26; int btn_y = (PM_TOOLBAR_H - btn_h) / 2; return {8, btn_y, 82, btn_h}; } static Rect kill_button_rect() { int btn_h = 26; int btn_y = (PM_TOOLBAR_H - btn_h) / 2; return {98, btn_y, 110, btn_h}; } static Rect tab_bar_rect() { return {0, PM_TOOLBAR_H, g_win.width, PM_TAB_H}; } static bool mouse_in_rect(const Rect& rect) { return rect.contains(g_pm.mouse_x, g_pm.mouse_y); } static void build_process_columns(mtk::TableColumn* cols) { int col_pid = 12; int col_ppid = 64; int col_name = 124; int col_heap = g_win.width - 150; int col_state = g_win.width - 70; cols[0] = {"PID", col_pid, gui_max(col_ppid - col_pid - 8, 0)}; cols[1] = {"PPID", col_ppid, gui_max(col_name - col_ppid - 10, 0)}; cols[2] = {"Name", col_name, gui_max(col_heap - col_name - 10, 0)}; cols[3] = {"Heap", col_heap, gui_max(col_state - col_heap - 8, 0)}; cols[4] = {"State", col_state, gui_max(g_win.width - col_state - 8, 0)}; } static void build_window_columns(mtk::TableColumn* cols) { int col_id = 12; int col_pid = 60; int col_title = 118; int col_size = g_win.width - 130; int col_dirty = g_win.width - 52; cols[0] = {"ID", col_id, gui_max(col_pid - col_id - 8, 0)}; cols[1] = {"PID", col_pid, gui_max(col_title - col_pid - 10, 0)}; cols[2] = {"Title", col_title, gui_max(col_size - col_title - 10, 0)}; cols[3] = {"Size", col_size, gui_max(col_dirty - col_size - 8, 0)}; cols[4] = {"D", col_dirty, gui_max(g_win.width - col_dirty - 8, 0)}; } static int hovered_process_row() { if (g_pm.active_tab != PM_TAB_PROCESSES) return -1; return mtk::table_hit_row(process_table_layout(), g_pm.proc_scroll, g_pm.proc_count, g_pm.mouse_x, g_pm.mouse_y); } static int hovered_window_row() { if (g_pm.active_tab != PM_TAB_WINDOWS) return -1; return mtk::table_hit_row(window_table_layout(), g_pm.win_scroll, g_pm.win_count, g_pm.mouse_x, g_pm.mouse_y); } static void draw_text_fit(Canvas& c, int x, int y, const char* text, int max_w, Color color) { if (!text || !text[0] || max_w <= 4) return; if (text_width(text) <= max_w) { c.text(x, y, text, color); return; } const char* ellipsis = "..."; int ell_w = text_width(ellipsis); if (ell_w >= max_w) return; char buf[96]; int out = 0; while (text[out] && out < (int)sizeof(buf) - 4) { buf[out] = text[out]; buf[out + 1] = '\0'; if (text_width(buf) + ell_w > max_w) break; out++; } if (out <= 0) return; buf[out] = '.'; buf[out + 1] = '.'; buf[out + 2] = '.'; buf[out + 3] = '\0'; c.text(x, y, buf, color); } static void render_toolbar(Canvas& c, const mtk::Theme& theme, int fh) { Rect bar = toolbar_rect(); c.fill_rect(bar.x, bar.y, bar.w, bar.h, theme.surface); c.hline(bar.x, bar.y + bar.h - 1, bar.w, theme.border); Rect refresh_rect = refresh_button_rect(); Rect kill_rect = kill_button_rect(); mtk::draw_button(c, refresh_rect, "Refresh", mtk::BUTTON_TONAL, mtk::widget_state(false, mouse_in_rect(refresh_rect)), theme); mtk::draw_button(c, kill_rect, "End Process", mtk::BUTTON_DANGER, mtk::widget_state(false, mouse_in_rect(kill_rect), can_kill_selected()), theme); char status[96]; if (g_pm.active_tab == PM_TAB_PERFORMANCE) { snprintf(status, sizeof(status), "CPU %d%%, Memory %d%%", g_pm.cpu_percent, g_pm.mem_percent); } else if (g_pm.active_tab == PM_TAB_PROCESSES) { snprintf(status, sizeof(status), "%d shown, %d live", g_pm.proc_count, live_process_count()); } else { snprintf(status, sizeof(status), "%d windows", g_pm.win_count); } int status_w = text_width(status); c.text(g_win.width - status_w - 12, (PM_TOOLBAR_H - fh) / 2, status, theme.text); } static void render_tabs(Canvas& c, const mtk::Theme& theme) { mtk::draw_tab_bar(c, tab_bar_rect(), g_tab_labels, PM_TAB_COUNT, g_pm.active_tab, theme); } static void draw_process_row(Canvas& c, void* userdata, int row_index, const Rect& row_rect, int text_y, const mtk::TableColumn* cols, int col_count, const mtk::Theme& theme, const mtk::TableStyle& style) { (void)userdata; (void)col_count; (void)style; char buf[32]; Rect pid_cell = mtk::table_cell_rect(row_rect, cols[0]); snprintf(buf, sizeof(buf), "%d", (int)g_pm.procs[row_index].pid); c.text(pid_cell.x, text_y, buf, theme.text); Rect ppid_cell = mtk::table_cell_rect(row_rect, cols[1]); snprintf(buf, sizeof(buf), "%d", (int)g_pm.procs[row_index].parentPid); c.text(ppid_cell.x, text_y, buf, theme.text); Rect name_cell = mtk::table_cell_rect(row_rect, cols[2]); draw_text_fit(c, name_cell.x, text_y, g_pm.procs[row_index].name, name_cell.w, theme.text); Rect heap_cell = mtk::table_cell_rect(row_rect, cols[3]); format_size(buf, g_pm.procs[row_index].heapUsed); c.text(heap_cell.x, text_y, buf, theme.text); Rect state_cell = mtk::table_cell_rect(row_rect, cols[4]); Color state_color; const char* state = proc_state_label(g_pm.procs[row_index].state, &state_color); c.text(state_cell.x, text_y, state, state_color); } static void render_processes(Canvas& c, const mtk::Theme& theme, int fh) { (void)fh; mtk::TableLayout layout = process_table_layout(); mtk::TableColumn cols[5]; build_process_columns(cols); mtk::TableStyle style = mtk::make_table_style(theme); style.row_selected_bg = colors::MENU_HOVER; style.row_hover_bg = mtk::mix(theme.window_bg, theme.accent, 12); mtk::draw_table(c, layout, cols, 5, g_pm.proc_count, g_pm.proc_scroll, g_pm.selected, hovered_process_row(), theme, style, draw_process_row, nullptr); } static void draw_window_row(Canvas& c, void* userdata, int row_index, const Rect& row_rect, int text_y, const mtk::TableColumn* cols, int col_count, const mtk::Theme& theme, const mtk::TableStyle& style) { (void)userdata; (void)col_count; (void)style; char buf[64]; Rect id_cell = mtk::table_cell_rect(row_rect, cols[0]); snprintf(buf, sizeof(buf), "%d", (int)g_pm.windows[row_index].id); c.text(id_cell.x, text_y, buf, theme.text); Rect pid_cell = mtk::table_cell_rect(row_rect, cols[1]); snprintf(buf, sizeof(buf), "%d", (int)g_pm.windows[row_index].ownerPid); c.text(pid_cell.x, text_y, buf, theme.text); Rect title_cell = mtk::table_cell_rect(row_rect, cols[2]); draw_text_fit(c, title_cell.x, text_y, g_pm.windows[row_index].title, title_cell.w, theme.text); Rect size_cell = mtk::table_cell_rect(row_rect, cols[3]); snprintf(buf, sizeof(buf), "%dx%d", (int)g_pm.windows[row_index].width, (int)g_pm.windows[row_index].height); c.text(size_cell.x, text_y, buf, theme.text); Rect dirty_cell = mtk::table_cell_rect(row_rect, cols[4]); c.text(dirty_cell.x, text_y, g_pm.windows[row_index].dirty ? "Y" : "N", g_pm.windows[row_index].dirty ? PM_WARNING : theme.text_subtle); } static void render_windows(Canvas& c, const mtk::Theme& theme, int fh) { (void)fh; mtk::TableLayout layout = window_table_layout(); mtk::TableColumn cols[5]; build_window_columns(cols); mtk::TableStyle style = mtk::make_table_style(theme); style.row_selected_bg = colors::MENU_HOVER; style.row_hover_bg = mtk::mix(theme.window_bg, theme.accent, 12); mtk::draw_table(c, layout, cols, 5, g_pm.win_count, g_pm.win_scroll, g_pm.selected_window, hovered_window_row(), theme, style, draw_window_row, nullptr); } static uint64_t scale_with_headroom(uint64_t observed, uint64_t floor) { uint64_t scale = observed > floor ? observed : floor; if (scale == 0) return 1; return scale + scale / 4; } static void format_memory_value(char* buf, int max) { char used[24]; char total[24]; format_size(used, g_pm.mem.usedBytes); format_size(total, g_pm.mem.totalBytes); snprintf(buf, max, "%s / %s", used, total); } static void render_graph(Canvas& c, const Rect& rect, const mtk::GraphHistory& history, uint64_t max_value, const char* title, const char* value, Color line, const mtk::Theme& theme) { mtk::GraphStyle style = mtk::make_graph_style(theme, line); style.background = theme.window_bg; style.plot_bg = Color::from_rgb(0xFA, 0xFA, 0xFA); style.grid = mtk::mix(style.plot_bg, line, 16); style.fill = mtk::lighten(line, 225); mtk::draw_line_graph(c, rect, history, max_value, title, value, theme, style); } static void render_performance(Canvas& c, const mtk::Theme& theme, int fh) { (void)fh; Rect body = {0, content_top(), g_win.width, gui_max(g_win.height - content_top(), 0)}; int margin = 12; int gap = 10; int col_w = gui_max((body.w - margin * 2 - gap) / 2, 80); int row_h = gui_max((body.h - margin * 2 - gap * 2) / 3, 72); int x0 = body.x + margin; int x1 = x0 + col_w + gap; int y0 = body.y + margin; int y1 = y0 + row_h + gap; int y2 = y1 + row_h + gap; Rect cpu_rect = {x0, y0, col_w, row_h}; Rect mem_rect = {x1, y0, col_w, row_h}; Rect proc_rect = {x0, y1, col_w, row_h}; Rect heap_rect = {x1, y1, col_w, row_h}; Rect win_rect = {x0, y2, body.w - margin * 2, row_h}; char cpu_value[32]; snprintf(cpu_value, sizeof(cpu_value), "%d%%", g_pm.cpu_percent); render_graph(c, cpu_rect, g_pm.cpu_history, 100, "CPU", cpu_value, PM_INFO, theme); char mem_value[64]; format_memory_value(mem_value, sizeof(mem_value)); render_graph(c, mem_rect, g_pm.mem_history, g_pm.mem.totalBytes > 0 ? g_pm.mem.totalBytes : 1, "Memory", mem_value, PM_SUCCESS, theme); char proc_value[48]; snprintf(proc_value, sizeof(proc_value), "%d live", live_process_count()); render_graph(c, proc_rect, g_pm.proc_history, g_pm.sys_info.maxProcesses > 0 ? g_pm.sys_info.maxProcesses : PM_MAX_PROCS, "Processes", proc_value, Color::from_rgb(0x7B, 0x3E, 0xB8), theme); char heap_value[32]; format_size(heap_value, g_pm.total_process_heap); uint64_t heap_scale = scale_with_headroom(mtk::graph_history_max(g_pm.heap_history), 1024ULL * 1024ULL); render_graph(c, heap_rect, g_pm.heap_history, heap_scale, "Process Heap", heap_value, PM_WARNING, theme); char win_value[40]; snprintf(win_value, sizeof(win_value), "%d windows", g_pm.win_count); render_graph(c, win_rect, g_pm.win_history, PM_MAX_WINDOWS, "Windows", win_value, Color::from_rgb(0x00, 0x88, 0x88), theme); } static void render() { mtk::StandaloneHost host(&g_win); Canvas c = host.canvas(); mtk::Theme theme = pm_theme(); c.fill(theme.window_bg); int fh = system_font_height(); render_toolbar(c, theme, fh); render_tabs(c, theme); switch (g_pm.active_tab) { case PM_TAB_PERFORMANCE: render_performance(c, theme, fh); break; case PM_TAB_PROCESSES: render_processes(c, theme, fh); break; case PM_TAB_WINDOWS: render_windows(c, theme, fh); break; } } static bool handle_toolbar_click(int mx, int my) { if (refresh_button_rect().contains(mx, my)) { refresh_state(true); return true; } if (kill_button_rect().contains(mx, my)) { kill_selected(); return true; } return false; } static bool handle_tab_click(int mx, int my) { int tab = mtk::hit_tab_bar(tab_bar_rect(), PM_TAB_COUNT, mx, my); if (tab >= 0) { g_pm.active_tab = tab; return true; } return false; } static bool handle_process_click(int mx, int my) { int index = mtk::table_hit_row(process_table_layout(), g_pm.proc_scroll, g_pm.proc_count, mx, my); g_pm.selected = index; return true; } static bool handle_window_click(int mx, int my) { int index = mtk::table_hit_row(window_table_layout(), g_pm.win_scroll, g_pm.win_count, mx, my); g_pm.selected_window = index; return true; } static bool handle_mouse(const Montauk::WinEvent& ev) { bool refresh_hover_before = mouse_in_rect(refresh_button_rect()); bool kill_hover_before = mouse_in_rect(kill_button_rect()); g_pm.mouse_x = ev.mouse.x; g_pm.mouse_y = ev.mouse.y; bool redraw = false; if (refresh_hover_before != mouse_in_rect(refresh_button_rect()) || kill_hover_before != mouse_in_rect(kill_button_rect())) { redraw = true; } if (ev.mouse.scroll != 0) { if (g_pm.active_tab == PM_TAB_PROCESSES) { g_pm.proc_scroll -= ev.mouse.scroll * 3; clamp_scrolls(); return true; } if (g_pm.active_tab == PM_TAB_WINDOWS) { g_pm.win_scroll -= ev.mouse.scroll * 3; clamp_scrolls(); return true; } } bool left_pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); if (!left_pressed) return redraw; int mx = ev.mouse.x; int my = ev.mouse.y; if (toolbar_rect().contains(mx, my)) return handle_toolbar_click(mx, my) || redraw; if (tab_bar_rect().contains(mx, my)) return handle_tab_click(mx, my) || redraw; if (g_pm.active_tab == PM_TAB_PROCESSES) return handle_process_click(mx, my) || redraw; if (g_pm.active_tab == PM_TAB_WINDOWS) return handle_window_click(mx, my) || redraw; return redraw; } static bool handle_key(const Montauk::KeyEvent& key) { if (!key.pressed) return false; if (key.scancode == 0x0F) { cycle_tab(key.shift ? -1 : 1); return true; } if (key.scancode == 0x4B) { cycle_tab(-1); return true; } if (key.scancode == 0x4D) { cycle_tab(1); return true; } if (key.scancode == 0x53) { if (can_kill_selected()) { kill_selected(); return true; } } if (key.scancode == 0x48) { if (g_pm.active_tab == PM_TAB_PROCESSES) { if (g_pm.selected > 0) g_pm.selected--; else if (g_pm.proc_count > 0) g_pm.selected = 0; ensure_process_selection_visible(); return true; } if (g_pm.active_tab == PM_TAB_WINDOWS) { if (g_pm.selected_window > 0) g_pm.selected_window--; else if (g_pm.win_count > 0) g_pm.selected_window = 0; ensure_window_selection_visible(); return true; } } if (key.scancode == 0x50) { if (g_pm.active_tab == PM_TAB_PROCESSES) { if (g_pm.selected < g_pm.proc_count - 1) g_pm.selected++; else if (g_pm.selected < 0 && g_pm.proc_count > 0) g_pm.selected = 0; ensure_process_selection_visible(); return true; } if (g_pm.active_tab == PM_TAB_WINDOWS) { if (g_pm.selected_window < g_pm.win_count - 1) g_pm.selected_window++; else if (g_pm.selected_window < 0 && g_pm.win_count > 0) g_pm.selected_window = 0; ensure_window_selection_visible(); return true; } } return false; } extern "C" void _start() { if (!fonts::init()) montauk::exit(1); if (!g_win.create("Processes", INIT_W, INIT_H)) montauk::exit(1); montauk::memset(&g_pm, 0, sizeof(g_pm)); init_graph_histories(); refresh_static_info(); g_pm.selected = -1; g_pm.selected_window = -1; g_pm.active_tab = PM_TAB_PROCESSES; g_pm.mouse_x = -1; g_pm.mouse_y = -1; refresh_state(true); render(); g_win.present(); while (!g_win.closed) { bool redraw = refresh_state(false); Montauk::WinEvent ev; int r = g_win.poll(&ev); if (r < 0) break; if (r == 0) { if (redraw) { render(); g_win.present(); } montauk::sleep_ms(16); continue; } if (ev.type == 3) break; if (ev.type == 1) redraw |= handle_mouse(ev); else if (ev.type == 0) redraw |= handle_key(ev.key); else if (ev.type == 2 || ev.type == 4) redraw = true; if (redraw) { render(); g_win.present(); } } g_win.destroy(); montauk::exit(0); }