938 lines
30 KiB
C++
938 lines
30 KiB
C++
/*
|
|
* main.cpp
|
|
* MontaukOS Process Manager
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include <montauk/syscall.h>
|
|
#include <montauk/string.h>
|
|
#include <montauk/heap.h>
|
|
#include <gui/mtk.hpp>
|
|
|
|
extern "C" {
|
|
#include <stdio.h>
|
|
}
|
|
|
|
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);
|
|
}
|