diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index f7e02c2..69ca332 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -322,7 +322,7 @@ namespace Montauk { struct ProcInfo { int32_t pid; int32_t parentPid; - uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated + uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated uint8_t _pad[3]; char name[64]; uint64_t heapUsed; // heapNext - UserHeapBase (bytes) diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index 2653efd..df11755 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -198,7 +198,7 @@ typedef struct { typedef struct { int32_t pid; int32_t parent_pid; - uint8_t state; /* 0=Free, 1=Ready, 2=Running, 3=Terminated */ + uint8_t state; /* 0=Free, 1=Ready, 2=Running, 3=Blocked, 4=Terminated */ uint8_t _pad[3]; char name[64]; uint64_t heap_used; diff --git a/programs/src/procmgr/main.cpp b/programs/src/procmgr/main.cpp index 6bbb2c4..67cb91c 100644 --- a/programs/src/procmgr/main.cpp +++ b/programs/src/procmgr/main.cpp @@ -1,7 +1,6 @@ /* * main.cpp - * MontaukOS Process Manager - standalone Window Server app - * Preserves the old desktop-integrated Processes window layout + * MontaukOS Process Manager * Copyright (c) 2026 Daniel Hammer */ @@ -19,42 +18,191 @@ extern "C" { using namespace gui; -static constexpr int INIT_W = 520; -static constexpr int INIT_H = 400; -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; -static constexpr int FONT_SIZE = 18; +static constexpr int INIT_W = 620; +static constexpr int INIT_H = 420; +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 FONT_SIZE = 18; + +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_WINDOWS = 1, + PM_TAB_COUNT = 2, +}; + +static const char* g_tab_labels[PM_TAB_COUNT] = { + "Processes", + "Windows", +}; struct ProcMgrState { Montauk::ProcInfo procs[PM_MAX_PROCS]; + Montauk::WinInfo windows[PM_MAX_WINDOWS]; int proc_count; + int win_count; int selected; + int selected_window; + int proc_scroll; + int win_scroll; + int active_tab; uint64_t last_poll_ms; }; static WsWindow g_win; static ProcMgrState g_pm = {}; -static void format_size(char* buf, int size) { - if (size < 1024) { - snprintf(buf, 16, "%d B", size); - } else if (size < 1024 * 1024) { - int kb = size / 1024; - int frac = ((size % 1024) * 10) / 1024; - if (kb < 10) snprintf(buf, 16, "%d.%d KB", kb, frac); - else snprintf(buf, 16, "%d KB", kb); +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 { - int mb = size / (1024 * 1024); - int frac = ((size % (1024 * 1024)) * 10) / (1024 * 1024); - if (mb < 10) snprintf(buf, 16, "%d.%d MB", mb, frac); - else snprintf(buf, 16, "%d MB", mb); + 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 bool refresh_processes(bool force) { +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 int process_list_y() { + return content_top() + PM_HEADER_H; +} + +static int window_list_y() { + return content_top() + PM_HEADER_H; +} + +static int process_visible_rows() { + int avail = g_win.height - process_list_y(); + if (avail <= 0) return 0; + return avail / PM_ITEM_H; +} + +static int window_visible_rows() { + int avail = g_win.height - window_list_y(); + if (avail <= 0) return 0; + return avail / PM_ITEM_H; +} + +static int live_process_count() { + int count = 0; + for (int i = 0; i < g_pm.proc_count; i++) { + uint8_t state = g_pm.procs[i].state; + if (state == 1 || state == 2 || state == 3) + count++; + } + return count; +} + +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() { + int proc_vis = process_visible_rows(); + int max_proc_scroll = 0; + if (g_pm.proc_count > proc_vis && proc_vis > 0) + max_proc_scroll = g_pm.proc_count - proc_vis; + g_pm.proc_scroll = gui_clamp(g_pm.proc_scroll, 0, max_proc_scroll); + + int win_vis = window_visible_rows(); + int max_win_scroll = 0; + if (g_pm.win_count > win_vis && win_vis > 0) + max_win_scroll = g_pm.win_count - win_vis; + g_pm.win_scroll = gui_clamp(g_pm.win_scroll, 0, max_win_scroll); +} + +static void ensure_process_selection_visible() { + if (g_pm.selected < 0) return; + int visible = process_visible_rows(); + if (visible <= 0) return; + if (g_pm.selected < g_pm.proc_scroll) + g_pm.proc_scroll = g_pm.selected; + else if (g_pm.selected >= g_pm.proc_scroll + visible) + g_pm.proc_scroll = g_pm.selected - visible + 1; + clamp_scrolls(); +} + +static void ensure_window_selection_visible() { + if (g_pm.selected_window < 0) return; + int visible = window_visible_rows(); + if (visible <= 0) return; + if (g_pm.selected_window < g_pm.win_scroll) + g_pm.win_scroll = g_pm.selected_window; + else if (g_pm.selected_window >= g_pm.win_scroll + visible) + g_pm.win_scroll = g_pm.selected_window - visible + 1; + clamp_scrolls(); +} + +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; @@ -65,25 +213,31 @@ static bool refresh_processes(bool force) { 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); - g_pm.selected = -1; + if (g_pm.proc_count < 0) g_pm.proc_count = 0; - if (prev_pid >= 0) { - for (int i = 0; i < g_pm.proc_count; i++) { - if (g_pm.procs[i].pid == prev_pid) { - g_pm.selected = i; - break; - } - } - } + 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); + + clamp_scrolls(); + ensure_process_selection_visible(); + ensure_window_selection_visible(); return true; } static bool can_kill_selected() { - return g_pm.selected >= 0 + 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].pid != 0 + && g_pm.procs[g_pm.selected].state != 4; } static void kill_selected() { @@ -91,7 +245,187 @@ static void kill_selected() { return; montauk::kill(g_pm.procs[g_pm.selected].pid); - refresh_processes(true); + 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 void draw_text_fit(Canvas& c, TrueTypeFont* font, int x, int y, + const char* text, int max_w, Color color, int size) { + if (!text || !text[0] || max_w <= 4) return; + if (text_width(font, text, size) <= max_w) { + draw_text(c, font, x, y, text, color, size); + return; + } + + const char* ellipsis = "..."; + int ell_w = text_width(font, ellipsis, size); + 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(font, buf, size) + ell_w > max_w) + break; + out++; + } + + if (out <= 0) return; + buf[out] = '.'; + buf[out + 1] = '.'; + buf[out + 2] = '.'; + buf[out + 3] = '\0'; + draw_text(c, font, x, y, buf, color, size); +} + +static void render_toolbar(Canvas& c, TrueTypeFont* font, int fh) { + c.fill_rect(0, 0, g_win.width, PM_TOOLBAR_H, PM_TOOLBAR_BG); + c.hline(0, PM_TOOLBAR_H - 1, g_win.width, PM_BORDER); + + int btn_h = 26; + int btn_y = (PM_TOOLBAR_H - btn_h) / 2; + draw_button(c, font, 8, btn_y, 82, btn_h, "Refresh", + Color::from_rgb(0xE3, 0xE8, 0xEF), colors::TEXT_COLOR, 4, FONT_SIZE); + + Color kill_bg = can_kill_selected() ? PM_DANGER : Color::from_rgb(0xAA, 0xAA, 0xAA); + draw_button(c, font, 98, btn_y, 110, btn_h, "End Process", + kill_bg, colors::WHITE, 4, FONT_SIZE); + + char status[96]; + 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(font, status, FONT_SIZE); + draw_text(c, font, g_win.width - status_w - 12, (PM_TOOLBAR_H - fh) / 2, + status, colors::TEXT_COLOR, FONT_SIZE); +} + +static void render_tabs(Canvas& c, TrueTypeFont* font, int fh) { + int y = PM_TOOLBAR_H; + c.fill_rect(0, y, g_win.width, PM_TAB_H, PM_TOOLBAR_BG); + c.hline(0, y + PM_TAB_H - 1, g_win.width, PM_BORDER); + + int tab_w = g_win.width / PM_TAB_COUNT; + for (int i = 0; i < PM_TAB_COUNT; i++) { + int tx = i * tab_w; + bool active = (i == g_pm.active_tab); + if (active) { + c.fill_rect(tx, y, tab_w, PM_TAB_H, colors::WINDOW_BG); + c.fill_rect(tx + 4, y + PM_TAB_H - 3, tab_w - 8, 3, colors::ACCENT); + } + int tw = text_width(font, g_tab_labels[i], FONT_SIZE); + Color color = active ? colors::ACCENT : PM_DIM_TEXT; + draw_text(c, font, tx + (tab_w - tw) / 2, y + (PM_TAB_H - fh) / 2, + g_tab_labels[i], color, FONT_SIZE); + } +} + +static void render_processes(Canvas& c, TrueTypeFont* font, int fh) { + int header_y = content_top(); + c.fill_rect(0, header_y, g_win.width, PM_HEADER_H, PM_HEADER_BG); + c.hline(0, header_y + PM_HEADER_H - 1, g_win.width, PM_BORDER); + + 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; + int header_text_y = header_y + (PM_HEADER_H - fh) / 2; + + draw_text(c, font, col_pid, header_text_y, "PID", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_ppid, header_text_y, "PPID", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_name, header_text_y, "Name", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_heap, header_text_y, "Heap", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_state, header_text_y, "State", PM_DIM_TEXT, FONT_SIZE); + + int list_y = process_list_y(); + int visible = process_visible_rows(); + for (int row = 0; row < visible; row++) { + int index = g_pm.proc_scroll + row; + if (index >= g_pm.proc_count) break; + + int row_y = list_y + row * PM_ITEM_H; + if (index == g_pm.selected) + c.fill_rect(0, row_y, g_win.width, PM_ITEM_H, colors::MENU_HOVER); + + int text_y = row_y + (PM_ITEM_H - fh) / 2; + char buf[32]; + + snprintf(buf, sizeof(buf), "%d", (int)g_pm.procs[index].pid); + draw_text(c, font, col_pid, text_y, buf, colors::TEXT_COLOR, FONT_SIZE); + + snprintf(buf, sizeof(buf), "%d", (int)g_pm.procs[index].parentPid); + draw_text(c, font, col_ppid, text_y, buf, colors::TEXT_COLOR, FONT_SIZE); + + draw_text_fit(c, font, col_name, text_y, g_pm.procs[index].name, + col_heap - col_name - 10, colors::TEXT_COLOR, FONT_SIZE); + + format_size(buf, g_pm.procs[index].heapUsed); + draw_text(c, font, col_heap, text_y, buf, colors::TEXT_COLOR, FONT_SIZE); + + Color state_color; + const char* state = proc_state_label(g_pm.procs[index].state, &state_color); + draw_text(c, font, col_state, text_y, state, state_color, FONT_SIZE); + } +} + +static void render_windows(Canvas& c, TrueTypeFont* font, int fh) { + int header_y = content_top(); + c.fill_rect(0, header_y, g_win.width, PM_HEADER_H, PM_HEADER_BG); + c.hline(0, header_y + PM_HEADER_H - 1, g_win.width, PM_BORDER); + + 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; + int header_text_y = header_y + (PM_HEADER_H - fh) / 2; + + draw_text(c, font, col_id, header_text_y, "ID", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_pid, header_text_y, "PID", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_title, header_text_y, "Title", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_size, header_text_y, "Size", PM_DIM_TEXT, FONT_SIZE); + draw_text(c, font, col_dirty, header_text_y, "D", PM_DIM_TEXT, FONT_SIZE); + + int list_y = window_list_y(); + int visible = window_visible_rows(); + for (int row = 0; row < visible; row++) { + int index = g_pm.win_scroll + row; + if (index >= g_pm.win_count) break; + + int row_y = list_y + row * PM_ITEM_H; + if (index == g_pm.selected_window) + c.fill_rect(0, row_y, g_win.width, PM_ITEM_H, colors::MENU_HOVER); + + int text_y = row_y + (PM_ITEM_H - fh) / 2; + char buf[64]; + + snprintf(buf, sizeof(buf), "%d", (int)g_pm.windows[index].id); + draw_text(c, font, col_id, text_y, buf, colors::TEXT_COLOR, FONT_SIZE); + + snprintf(buf, sizeof(buf), "%d", (int)g_pm.windows[index].ownerPid); + draw_text(c, font, col_pid, text_y, buf, colors::TEXT_COLOR, FONT_SIZE); + + draw_text_fit(c, font, col_title, text_y, g_pm.windows[index].title, + col_size - col_title - 10, colors::TEXT_COLOR, FONT_SIZE); + + snprintf(buf, sizeof(buf), "%dx%d", (int)g_pm.windows[index].width, (int)g_pm.windows[index].height); + draw_text(c, font, col_size, text_y, buf, colors::TEXT_COLOR, FONT_SIZE); + + draw_text(c, font, col_dirty, text_y, g_pm.windows[index].dirty ? "Y" : "N", + g_pm.windows[index].dirty ? PM_WARNING : PM_DIM_TEXT, FONT_SIZE); + } } static void render() { @@ -100,85 +434,91 @@ static void render() { c.fill(colors::WINDOW_BG); int fh = text_height(font, FONT_SIZE); + render_toolbar(c, font, fh); + render_tabs(c, font, fh); - c.fill_rect(0, 0, g_win.width, PM_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); - c.hline(0, PM_TOOLBAR_H - 1, g_win.width, colors::BORDER); - - int btn_w = 100; - int btn_h = 26; - int btn_x = 8; - int btn_y = (PM_TOOLBAR_H - btn_h) / 2; - Color btn_bg = can_kill_selected() - ? Color::from_rgb(0xCC, 0x33, 0x33) - : Color::from_rgb(0xAA, 0xAA, 0xAA); - draw_button(c, font, btn_x, btn_y, btn_w, btn_h, "End Process", - btn_bg, colors::WHITE, 4, FONT_SIZE); - - char count_str[24]; - snprintf(count_str, sizeof(count_str), "%d processes", g_pm.proc_count); - int count_w = text_width(font, count_str, FONT_SIZE); - draw_text(c, font, g_win.width - count_w - 12, (PM_TOOLBAR_H - fh) / 2, - count_str, colors::TEXT_COLOR, FONT_SIZE); - - int header_y = PM_TOOLBAR_H; - c.fill_rect(0, header_y, g_win.width, PM_HEADER_H, Color::from_rgb(0xF0, 0xF0, 0xF0)); - - int col_pid = 12; - int col_name = 64; - int col_mem = g_win.width - 120; - int col_state = g_win.width - 52; - int header_text_y = header_y + (PM_HEADER_H - fh) / 2; - Color header_color = Color::from_rgb(0x66, 0x66, 0x66); - - draw_text(c, font, col_pid, header_text_y, "PID", header_color, FONT_SIZE); - draw_text(c, font, col_name, header_text_y, "Name", header_color, FONT_SIZE); - draw_text(c, font, col_mem, header_text_y, "Mem", header_color, FONT_SIZE); - draw_text(c, font, col_state, header_text_y, "St", header_color, FONT_SIZE); - c.hline(0, header_y + PM_HEADER_H - 1, g_win.width, colors::BORDER); - - int list_y = PM_TOOLBAR_H + PM_HEADER_H; - for (int i = 0; i < g_pm.proc_count; i++) { - int row_y = list_y + i * PM_ITEM_H; - if (row_y + PM_ITEM_H > g_win.height) - break; - - if (i == g_pm.selected) - c.fill_rect(0, row_y, g_win.width, PM_ITEM_H, colors::MENU_HOVER); - - int text_y = row_y + (PM_ITEM_H - fh) / 2; - - char pid_str[8]; - snprintf(pid_str, sizeof(pid_str), "%d", (int)g_pm.procs[i].pid); - int pid_w = text_width(font, pid_str, FONT_SIZE); - draw_text(c, font, col_pid + 32 - pid_w, text_y, pid_str, colors::TEXT_COLOR, FONT_SIZE); - - draw_text(c, font, col_name, text_y, g_pm.procs[i].name, colors::TEXT_COLOR, FONT_SIZE); - - char mem_str[16]; - format_size(mem_str, (int)g_pm.procs[i].heapUsed); - draw_text(c, font, col_mem, text_y, mem_str, colors::TEXT_COLOR, FONT_SIZE); - - const char* state_str = "?"; - Color state_color = colors::TEXT_COLOR; - switch (g_pm.procs[i].state) { - case 1: - state_str = "Rdy"; - state_color = Color::from_rgb(0x33, 0x66, 0xCC); - break; - case 2: - state_str = "Run"; - state_color = Color::from_rgb(0x22, 0x88, 0x22); - break; - case 3: - state_str = "Term"; - state_color = Color::from_rgb(0xCC, 0x33, 0x33); - break; - } - draw_text(c, font, col_state, text_y, state_str, state_color, FONT_SIZE); + switch (g_pm.active_tab) { + case PM_TAB_PROCESSES: + render_processes(c, font, fh); + break; + case PM_TAB_WINDOWS: + render_windows(c, font, fh); + break; } } +static bool handle_toolbar_click(int mx, int my) { + int btn_h = 26; + int btn_y = (PM_TOOLBAR_H - btn_h) / 2; + Rect refresh_rect = {8, btn_y, 82, btn_h}; + Rect kill_rect = {98, btn_y, 110, btn_h}; + + if (refresh_rect.contains(mx, my)) { + refresh_state(true); + return true; + } + + if (kill_rect.contains(mx, my)) { + kill_selected(); + return true; + } + + return false; +} + +static bool handle_tab_click(int mx, int my) { + if (my < PM_TOOLBAR_H || my >= PM_TOOLBAR_H + PM_TAB_H) + return false; + + int tab_w = g_win.width / PM_TAB_COUNT; + int tab = mx / tab_w; + if (tab >= 0 && tab < PM_TAB_COUNT) { + g_pm.active_tab = tab; + return true; + } + return false; +} + +static bool handle_process_click(int my) { + if (my < process_list_y()) + return false; + + int row = (my - process_list_y()) / PM_ITEM_H; + int index = g_pm.proc_scroll + row; + if (index >= 0 && index < g_pm.proc_count) + g_pm.selected = index; + else + g_pm.selected = -1; + return true; +} + +static bool handle_window_click(int my) { + if (my < window_list_y()) + return false; + + int row = (my - window_list_y()) / PM_ITEM_H; + int index = g_pm.win_scroll + row; + if (index >= 0 && index < g_pm.win_count) + g_pm.selected_window = index; + else + g_pm.selected_window = -1; + return true; +} + static bool handle_mouse(const Montauk::WinEvent& ev) { + 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 false; @@ -186,38 +526,34 @@ static bool handle_mouse(const Montauk::WinEvent& ev) { int mx = ev.mouse.x; int my = ev.mouse.y; - 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(mx, my)) { - kill_selected(); - return true; - } + if (my < PM_TOOLBAR_H) + return handle_toolbar_click(mx, my); + if (my < PM_TOOLBAR_H + PM_TAB_H) + return handle_tab_click(mx, my); - int list_y = PM_TOOLBAR_H + PM_HEADER_H; - if (my < list_y) - return false; - - int row = (my - list_y) / PM_ITEM_H; - if (row >= 0 && row < g_pm.proc_count) g_pm.selected = row; - else g_pm.selected = -1; - return true; + if (g_pm.active_tab == PM_TAB_PROCESSES) + return handle_process_click(my); + if (g_pm.active_tab == PM_TAB_WINDOWS) + return handle_window_click(my); + return false; } static bool handle_key(const Montauk::KeyEvent& key) { if (!key.pressed) return false; - if (key.scancode == 0x48) { - if (g_pm.selected > 0) g_pm.selected--; - else if (g_pm.proc_count > 0) g_pm.selected = 0; + if (key.scancode == 0x0F) { + cycle_tab(key.shift ? -1 : 1); return true; } - if (key.scancode == 0x50) { - if (g_pm.selected < g_pm.proc_count - 1) g_pm.selected++; + if (key.scancode == 0x4B) { + cycle_tab(-1); + return true; + } + + if (key.scancode == 0x4D) { + cycle_tab(1); return true; } @@ -228,6 +564,36 @@ static bool handle_key(const Montauk::KeyEvent& key) { } } + 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; } @@ -238,13 +604,16 @@ extern "C" void _start() { if (!g_win.create("Processes", INIT_W, INIT_H)) montauk::exit(1); + montauk::memset(&g_pm, 0, sizeof(g_pm)); g_pm.selected = -1; - refresh_processes(true); + g_pm.selected_window = -1; + g_pm.active_tab = PM_TAB_PROCESSES; + refresh_state(true); render(); g_win.present(); while (!g_win.closed) { - bool redraw = refresh_processes(false); + bool redraw = refresh_state(false); Montauk::WinEvent ev; int r = g_win.poll(&ev);