/* * main.cpp * MontaukOS capability grant viewer * Copyright (c) 2026 Daniel Hammer */ #include #include #include #include #include #include #include #include extern "C" { #include } using namespace gui; static constexpr int WIN_W = 760; static constexpr int WIN_H = 582; // one grid row per capability static constexpr int TAB_H = 36; static constexpr int FOOTER_H = 44; static constexpr int PAD = 18; static constexpr int BUTTON_W = 88; static constexpr int LIST_W = 200; static constexpr int LIST_ROW_H = 30; static constexpr int GRID_ROW_H = 22; static constexpr int GRID_COL_W = 86; static constexpr int MAX_GRANTS = 64; static constexpr int MAX_ISSUES = 4; static constexpr int MAX_DECISIONS = 64; static constexpr int CAP_COUNT = (int)(sizeof(montauk::caps::NAMES) / sizeof(montauk::caps::NAMES[0])); enum Tab { TAB_PROGRAMS = 0, TAB_CAPS = 1, TAB_CONSENT = 2, TAB_COUNT = 3, }; static const char* const kTabLabels[TAB_COUNT] = { "Programs", "Capabilities", "Consent", }; // What each capability gates, in the order of montauk::caps::NAMES. static const char* const kCapDescriptions[CAP_COUNT] = { "Inspect and terminate other processes", "Ask the session to shut down or restart", "Power the machine off or reset it directly", "Put the machine to sleep (S3)", "Format, mount and manage volumes", "Read and write raw block devices", "Configure network interfaces, DHCP and Wi-Fi", "Set the system clock", "Manage user accounts and session identities", "Change display modes", "Manage hardware devices (Bluetooth, radio)", "Read the kernel log", "Write to 0:/os and 0:/apps (never part of \"all\")", "Read and send input to other windows (never part of \"all\")", "Set the display backlight", }; // ==== Grant table model ==== enum Field { FIELD_EFFECTIVE = 0, FIELD_DELEGABLE = 1, FIELD_COUNT = 2, }; static const char* const kFieldKeys[FIELD_COUNT] = { "effective", "delegable", }; static const char* const kFieldLabels[FIELD_COUNT] = { "Effective", "Delegable", }; struct Grant { char id[32]; char path[96]; uint64_t mask[FIELD_COUNT]; bool declared[FIELD_COUNT]; bool uses_all[FIELD_COUNT]; bool binary_present; char issues[MAX_ISSUES][96]; int issue_count; }; static Grant g_grants[MAX_GRANTS]; static int g_grant_count = 0; static bool g_file_present = false; static bool g_truncated = false; static WsWindow g_win; static Tab g_tab = TAB_PROGRAMS; static int g_mouse_x = -1; static int g_mouse_y = -1; static Color g_accent = colors::ACCENT; static bool g_is_admin = false; static int g_selected_grant = 0; static int g_selected_cap = 0; static int g_selected_decision = 0; // Answers given to consent prompts (0:/config/consent.toml). static montauk::consent::Decision g_decisions[MAX_DECISIONS]; static int g_decision_count = 0; static int g_list_scroll = 0; static bool g_list_sb_dragging = false; static int g_list_sb_drag_offset = 0; static char g_status[128] = {}; static uint64_t g_status_time = 0; // Same test as montauk::user::is_admin(), without pulling in its BearSSL // password-hashing dependency. static bool session_is_admin() { char user[64] = {}; if (montauk::getuser(user, sizeof(user)) <= 0 || !user[0]) return false; char key[96]; snprintf(key, sizeof(key), "users.%s.role", user); montauk::toml::Doc doc = montauk::config::load("users"); bool admin = montauk::streq(doc.get_string(key, ""), "admin"); doc.destroy(); return admin; } static void add_issue(Grant& g, const char* fmt, const char* a = "", const char* b = "") { if (g.issue_count >= MAX_ISSUES) return; snprintf(g.issues[g.issue_count++], sizeof(g.issues[0]), fmt, a, b); } static Grant* find_or_add_grant(const char* id, int idLen) { for (int i = 0; i < g_grant_count; i++) { const char* have = g_grants[i].id; int k = 0; while (k < idLen && have[k] == id[k]) k++; if (k == idLen && have[k] == '\0') return &g_grants[i]; } if (g_grant_count >= MAX_GRANTS) { g_truncated = true; return nullptr; } Grant* g = &g_grants[g_grant_count++]; montauk::memset(g, 0, sizeof(*g)); int n = idLen < (int)sizeof(g->id) - 1 ? idLen : (int)sizeof(g->id) - 1; montauk::memcpy(g->id, id, n); g->id[n] = '\0'; return g; } static void read_field(Grant& g, int field, montauk::toml::Value* v) { g.declared[field] = true; if (v->type != montauk::toml::Type::Array) { add_issue(g, "'%s' is not a list of capability names", kFieldKeys[field]); return; } for (int i = 0; i < v->array.count; i++) { montauk::toml::Value* item = v->array.items[i]; if (item == nullptr || item->type != montauk::toml::Type::String) continue; uint64_t bit = montauk::caps::bit_for_name(item->str); if (bit == 0) { add_issue(g, "Unknown capability \"%s\" in %s (ignored)", item->str, kFieldKeys[field]); continue; } if (montauk::streq(item->str, "all")) g.uses_all[field] = true; g.mask[field] |= bit; } } // Mirrors montauk::caps::lookup(), but keeps everything the launcher would // silently discard so it can be shown. static void load_grants() { g_grant_count = 0; g_truncated = false; int fh = montauk::open("0:/config/capabilities.toml"); g_file_present = fh >= 0; if (fh >= 0) montauk::close(fh); montauk::toml::Doc doc = montauk::config::load(montauk::caps::GRANT_CONFIG); int prefixLen = montauk::slen(montauk::caps::GRANT_PREFIX); for (int i = 0; i < doc.entries.count; i++) { montauk::toml::Value* entry = doc.entries.items[i]; if (entry == nullptr || entry->key == nullptr) continue; if (!montauk::starts_with(entry->key, montauk::caps::GRANT_PREFIX)) continue; const char* rest = entry->key + prefixLen; int lastDot = -1; for (int k = 0; rest[k]; k++) if (rest[k] == '.') lastDot = k; if (lastDot <= 0) continue; Grant* g = find_or_add_grant(rest, lastDot); if (g == nullptr) continue; const char* field = rest + lastDot + 1; if (montauk::streq(field, "path")) { if (entry->type == montauk::toml::Type::String) snprintf(g->path, sizeof(g->path), "%s", entry->str); continue; } bool known = false; for (int f = 0; f < FIELD_COUNT; f++) { if (montauk::streq(field, kFieldKeys[f])) { read_field(*g, f, entry); known = true; } } if (!known) add_issue(*g, "Unknown key '%s' (ignored)", field); } doc.destroy(); for (int i = 0; i < g_grant_count; i++) { Grant& g = g_grants[i]; if (g.path[0] == '\0') { add_issue(g, "No path; this grant never applies"); continue; } int bh = montauk::open(g.path); g.binary_present = bh >= 0; if (bh >= 0) montauk::close(bh); if (!g.binary_present) add_issue(g, "Binary not found on this system"); if (!montauk::caps::is_installed_path(g.path)) add_issue(g, "Never applies: not under 0:/os or 0:/apps, so it runs with no capabilities"); for (int j = 0; j < i; j++) { if (montauk::streq(g_grants[j].path, g.path)) { add_issue(g, "Shadowed by [grant.%s]; the first entry for a path wins", g_grants[j].id); break; } } } if (g_selected_grant >= g_grant_count) g_selected_grant = 0; } static void load_decisions() { g_decision_count = montauk::consent::load_all(g_decisions, MAX_DECISIONS); if (g_selected_decision >= g_decision_count) g_selected_decision = g_decision_count > 0 ? g_decision_count - 1 : 0; } static const char* base_name(const char* path) { const char* base = path; for (const char* p = path; *p; p++) if (*p == '/' || *p == ':') base = p + 1; return base; } static int cap_index(uint64_t bit) { for (int i = 0; i < CAP_COUNT; i++) if (montauk::caps::NAMES[i].bit == bit) return i; return -1; } // ==== Layout ==== static mtk::Theme app_theme() { return mtk::make_theme(g_accent); } static Rect tab_bar_rect() { return {0, 0, g_win.width, TAB_H}; } static Rect footer_rect() { return {0, g_win.height - FOOTER_H, g_win.width, FOOTER_H}; } static constexpr int LIST_TOP_INSET = 10; static int sidebar_w() { return PAD + LIST_W + PAD; } // The whole left column, from the tab bar to the footer. The scrollbar runs // its full height against the divider, like a pane scrollbar. static Rect list_rect() { return {0, TAB_H, sidebar_w(), footer_rect().y - TAB_H}; } static int list_count() { if (g_tab == TAB_PROGRAMS) return g_grant_count; if (g_tab == TAB_CONSENT) return g_decision_count; return CAP_COUNT; } static int* selected_for_tab() { if (g_tab == TAB_PROGRAMS) return &g_selected_grant; if (g_tab == TAB_CONSENT) return &g_selected_decision; return &g_selected_cap; } static int list_visible_rows() { return (list_rect().h - LIST_TOP_INSET) / LIST_ROW_H; } static int list_view_extent() { return list_visible_rows() * LIST_ROW_H; } static int list_content_extent() { return list_count() * LIST_ROW_H; } static bool list_scrollbar_visible() { return mtk::scrollbar_needed(list_content_extent(), list_view_extent()); } static Rect list_scrollbar_track() { if (!list_scrollbar_visible()) return {0, 0, 0, 0}; return mtk::scrollbar_track_rect(list_rect()); } static Rect list_scrollbar_thumb() { Rect track = list_scrollbar_track(); if (track.empty()) return {0, 0, 0, 0}; return mtk::scrollbar_thumb_rect(track, list_content_extent(), list_view_extent(), g_list_scroll * LIST_ROW_H); } static Rect list_row_rect(int visibleIndex) { Rect list = list_rect(); int right = list.w - (list_scrollbar_visible() ? mtk::SCROLLBAR_W + 6 : PAD); return {PAD, list.y + LIST_TOP_INSET + visibleIndex * LIST_ROW_H, right - PAD, LIST_ROW_H}; } static int pane_x() { return sidebar_w() + 1 + PAD; } static int pane_w() { return g_win.width - pane_x() - PAD; } static Rect reload_button() { Rect foot = footer_rect(); return {foot.x + foot.w - PAD - BUTTON_W, foot.y + 7, BUTTON_W, 30}; } static Rect forget_button() { Rect reload = reload_button(); return {reload.x - 8 - BUTTON_W, reload.y, BUTTON_W, reload.h}; } static bool forget_visible() { return g_is_admin && g_tab == TAB_CONSENT && g_decision_count > 0; } static bool mouse_in(const Rect& rect) { return rect.contains(g_mouse_x, g_mouse_y); } static void clamp_list_scroll() { int max_scroll = list_count() - list_visible_rows(); if (max_scroll < 0) max_scroll = 0; if (g_list_scroll > max_scroll) g_list_scroll = max_scroll; if (g_list_scroll < 0) g_list_scroll = 0; } static void set_status(const char* msg) { snprintf(g_status, sizeof(g_status), "%s", msg ? msg : ""); g_status_time = montauk::get_milliseconds(); } static bool status_visible() { return g_status[0] && montauk::get_milliseconds() - g_status_time < 5000; } // ==== Drawing ==== static void fit_text(char* out, int outCap, const char* text, int maxWidth) { if (!out || outCap <= 0) return; out[0] = '\0'; if (!text || !text[0] || maxWidth <= 0) return; if (text_width(text) <= maxWidth) { snprintf(out, outCap, "%s", text); return; } static const char* ellipsis = "..."; int len = 0; while (text[len] && len < outCap - 4) { out[len] = text[len]; out[len + 1] = '\0'; if (text_width(out) + text_width(ellipsis) > maxWidth) break; len++; } len = montauk::utf8::floor_boundary(text, montauk::slen(text), len); out[len++] = '.'; out[len++] = '.'; out[len++] = '.'; out[len] = '\0'; } static void draw_text_fit(Canvas& c, int x, int y, const char* text, int maxWidth, Color color) { char fitted[160]; fit_text(fitted, sizeof(fitted), text, maxWidth); c.text(x, y, fitted, color); } static void draw_list(Canvas& c, const mtk::Theme& theme) { int fh = system_font_height(); int count = list_count(); int visible = list_visible_rows(); for (int row = 0; row < visible; row++) { int index = g_list_scroll + row; if (index >= count) break; Rect r = list_row_rect(row); bool selected = index == *selected_for_tab(); mtk::draw_list_row(c, r, selected, false, theme); if (!selected && mouse_in(r)) c.fill_rounded_rect(r.x, r.y, r.w, r.h, theme.radius_md, theme.surface_hover); const char* label = g_tab == TAB_PROGRAMS ? g_grants[index].id : g_tab == TAB_CONSENT ? base_name(g_decisions[index].path) : montauk::caps::NAMES[index].name; Color fg = selected ? theme.accent_fg : theme.text; int ty = r.y + (r.h - fh) / 2; draw_text_fit(c, r.x + 10, ty, label, r.w - 34, fg); if (g_tab == TAB_CONSENT) { Color dot = selected ? theme.accent_fg : g_decisions[index].allow ? theme.accent : theme.text_muted; fill_circle(c, r.x + r.w - 14, r.y + r.h / 2, 4, dot); } if (g_tab == TAB_PROGRAMS && g_grants[index].issue_count > 0) { Color dot = selected ? theme.accent_fg : theme.danger; fill_circle(c, r.x + r.w - 14, r.y + r.h / 2, 4, dot); } } if (list_scrollbar_visible()) { Rect track = list_scrollbar_track(); bool hovered = track.contains(g_mouse_x, g_mouse_y) || g_list_sb_dragging; mtk::draw_scrollbar(c, track, list_scrollbar_thumb(), hovered, g_list_sb_dragging, theme); } int sx = sidebar_w(); c.fill_rect(sx, TAB_H, 1, footer_rect().y - TAB_H, theme.border); } static void draw_mark(Canvas& c, int cx, int cy, bool on, const mtk::Theme& theme) { Rect cell = {cx - 8, cy - 8, 16, 16}; mtk::draw_checkbox(c, cell, "", mtk::check_state(on), theme, true, false); } static void draw_programs_pane(Canvas& c, const mtk::Theme& theme) { int x = pane_x(); int w = pane_w(); int fh = system_font_height(); int y = TAB_H + PAD; if (g_grant_count == 0) { c.text(x, y, g_file_present ? "No grants are declared." : "0:/config/capabilities.toml was not found.", theme.text_subtle); c.text(x, y + fh + 8, "Programs without a grant run with no capabilities.", theme.text_muted); return; } const Grant& g = g_grants[g_selected_grant]; char title[64]; snprintf(title, sizeof(title), "[grant.%s]", g.id); c.text(x, y, title, theme.text); if (g.issue_count > 0) { const char* state = "Needs attention"; c.text(x + w - text_width(state), y, state, theme.danger); } y += fh + 6; draw_text_fit(c, x, y, g.path[0] ? g.path : "(no path)", w, theme.text_subtle); y += fh + 14; mtk::draw_separator(c, x, y, w, theme); y += 12; // Grid: one row per capability, one column per mask. int colX = x + w - GRID_COL_W * FIELD_COUNT; c.text(x, y, "CAPABILITY", theme.text_muted); for (int f = 0; f < FIELD_COUNT; f++) { const char* label = kFieldLabels[f]; int cx = colX + f * GRID_COL_W + GRID_COL_W / 2; c.text(cx - text_width(label) / 2, y, label, theme.text_muted); } y += fh + 4; for (int f = 0; f < FIELD_COUNT; f++) { const char* note = nullptr; if (g.uses_all[f]) note = "(all)"; if (!note) continue; int cx = colX + f * GRID_COL_W + GRID_COL_W / 2; c.text(cx - text_width(note) / 2, y, note, theme.text_muted); } y += fh + 4; for (int i = 0; i < CAP_COUNT; i++) { uint64_t bit = montauk::caps::NAMES[i].bit; bool any = ((g.mask[FIELD_EFFECTIVE] | g.mask[FIELD_DELEGABLE]) & bit) != 0; if (i % 2 == 1) c.fill_rect(x, y, w, GRID_ROW_H, theme.surface_alt); c.text(x + 6, y + (GRID_ROW_H - fh) / 2, montauk::caps::NAMES[i].name, any ? theme.text : theme.text_muted); for (int f = 0; f < FIELD_COUNT; f++) { int cx = colX + f * GRID_COL_W + GRID_COL_W / 2; draw_mark(c, cx, y + GRID_ROW_H / 2, (g.mask[f] & bit) != 0, theme); } y += GRID_ROW_H; } y += 10; for (int i = 0; i < g.issue_count; i++) { draw_text_fit(c, x, y, g.issues[i], w, theme.danger); y += fh + 4; } } static int draw_holder_list(Canvas& c, int x, int y, int w, int bottom, const char* heading, int field, uint64_t bit, const mtk::Theme& theme) { int fh = system_font_height(); c.text(x, y, heading, theme.text_muted); y += fh + 8; int total = 0; for (int i = 0; i < g_grant_count; i++) if (g_grants[i].mask[field] & bit) total++; if (total == 0) { c.text(x + 10, y, "No program", theme.text_subtle); return y + fh + 16; } int shown = 0; for (int i = 0; i < g_grant_count; i++) { const Grant& g = g_grants[i]; if (!(g.mask[field] & bit)) continue; if (y + (fh + 6) * 2 > bottom && shown < total - 1) { char more[48]; snprintf(more, sizeof(more), "+%d more", total - shown); c.text(x + 10, y, more, theme.text_muted); return y + fh + 16; } c.text(x + 10, y, g.id, theme.text); draw_text_fit(c, x + 130, y, g.path, w - 130, theme.text_subtle); y += fh + 6; shown++; } return y + 12; } static void draw_caps_pane(Canvas& c, const mtk::Theme& theme) { int x = pane_x(); int w = pane_w(); int fh = system_font_height(); int y = TAB_H + PAD; int bottom = footer_rect().y - PAD; const auto& cap = montauk::caps::NAMES[g_selected_cap]; c.text(x, y, cap.name, theme.text); y += fh + 6; draw_text_fit(c, x, y, kCapDescriptions[g_selected_cap], w, theme.text_subtle); y += fh + 14; mtk::draw_separator(c, x, y, w, theme); y += 14; y = draw_holder_list(c, x, y, w, bottom - (fh + 6) * 4, "USES IT (EFFECTIVE)", FIELD_EFFECTIVE, cap.bit, theme); draw_holder_list(c, x, y, w, bottom, "MAY PASS IT ON (DELEGABLE)", FIELD_DELEGABLE, cap.bit, theme); } static void draw_consent_pane(Canvas& c, const mtk::Theme& theme) { int x = pane_x(); int w = pane_w(); int fh = system_font_height(); int y = TAB_H + PAD; if (g_decision_count == 0) { c.text(x, y, "No consent decisions are stored.", theme.text_subtle); c.text(x, y + fh + 8, "Programs without a grant ask the first time they need", theme.text_muted); c.text(x, y + (fh + 8) * 2, "storage, network, time, display or log access.", theme.text_muted); return; } const montauk::consent::Decision& d = g_decisions[g_selected_decision]; c.text(x, y, base_name(d.path), theme.text); const char* state = d.allow ? "Allowed" : "Denied"; c.text(x + w - text_width(state), y, state, d.allow ? theme.accent : theme.danger); y += fh + 6; draw_text_fit(c, x, y, d.path, w, theme.text_subtle); y += fh + 14; mtk::draw_separator(c, x, y, w, theme); y += 14; int ci = cap_index(d.capability); c.text(x, y, "CAPABILITY", theme.text_muted); y += fh + 6; c.text(x + 10, y, ci >= 0 ? montauk::caps::NAMES[ci].name : "?", theme.text); y += fh + 4; if (ci >= 0) draw_text_fit(c, x + 10, y, kCapDescriptions[ci], w - 10, theme.text_subtle); y += fh + 16; c.text(x, y, "Forget makes the program ask again the next time it needs", theme.text_muted); y += fh + 4; c.text(x, y, "this. A copy already running keeps its access until it exits.", theme.text_muted); } static void draw_locked(Canvas& c, const mtk::Theme& theme) { int fh = system_font_height(); int y = TAB_H + 60; const char* title = "Administrator access required"; c.text((g_win.width - text_width(title)) / 2, y, title, theme.text); const char* body = "Only administrators can review the capability grant table."; c.text((g_win.width - text_width(body)) / 2, y + fh + 10, body, theme.text_subtle); } static void draw_footer(Canvas& c, const mtk::Theme& theme) { Rect foot = footer_rect(); c.fill_rect(foot.x, foot.y, foot.w, foot.h, theme.surface); mtk::draw_separator(c, 0, foot.y, g_win.width, theme); char summary[128]; if (status_visible()) { snprintf(summary, sizeof(summary), "%s", g_status); } else if (!g_is_admin) { snprintf(summary, sizeof(summary), "Read-only"); } else { int flagged = 0; for (int i = 0; i < g_grant_count; i++) if (g_grants[i].issue_count > 0) flagged++; if (g_tab == TAB_CONSENT) snprintf(summary, sizeof(summary), "%d stored decision%s", g_decision_count, g_decision_count == 1 ? "" : "s"); else snprintf(summary, sizeof(summary), "%d grant%s%s, %d flagged - read-only view", g_grant_count, g_grant_count == 1 ? "" : "s", g_truncated ? " (list truncated)" : "", flagged); } c.text(PAD, foot.y + (foot.h - system_font_height()) / 2, summary, theme.text_subtle); if (g_is_admin) { Rect reload = reload_button(); mtk::draw_button(c, reload, "Reload", mtk::BUTTON_SECONDARY, mtk::widget_state(false, mouse_in(reload), true), theme); } if (forget_visible()) { Rect forget = forget_button(); mtk::draw_button(c, forget, "Forget", mtk::BUTTON_DANGER, mtk::widget_state(false, mouse_in(forget), true), theme); } } static void render() { mtk::StandaloneHost host(&g_win); Canvas c = host.canvas(); mtk::Theme theme = app_theme(); c.fill(theme.window_bg); if (g_is_admin) { mtk::draw_tab_bar(c, tab_bar_rect(), kTabLabels, TAB_COUNT, (int)g_tab, theme); draw_list(c, theme); if (g_tab == TAB_PROGRAMS) draw_programs_pane(c, theme); else if (g_tab == TAB_CONSENT) draw_consent_pane(c, theme); else draw_caps_pane(c, theme); } else { draw_locked(c, theme); } draw_footer(c, theme); host.present(); } // ==== Input ==== static void reload() { load_grants(); load_decisions(); clamp_list_scroll(); set_status(g_tab == TAB_CONSENT ? "Reloaded 0:/config/consent.toml" : "Reloaded 0:/config/capabilities.toml"); } static void forget_selected() { if (g_decision_count == 0) return; const montauk::consent::Decision& d = g_decisions[g_selected_decision]; if (montauk::consent::forget(d.id) < 0) { set_status("Could not write 0:/config/consent.toml"); return; } char msg[128]; snprintf(msg, sizeof(msg), "Forgot %s for %s", montauk::consent::name_for(d.capability), base_name(d.path)); load_decisions(); clamp_list_scroll(); set_status(msg); } static void select_index(int index) { int count = list_count(); if (count <= 0) return; if (index < 0) index = 0; if (index >= count) index = count - 1; *selected_for_tab() = index; int visible = list_visible_rows(); if (index < g_list_scroll) g_list_scroll = index; else if (index >= g_list_scroll + visible) g_list_scroll = index - visible + 1; clamp_list_scroll(); } static void switch_tab(Tab tab) { g_tab = tab; g_list_scroll = 0; select_index(*selected_for_tab()); } static void handle_mouse(const montauk::abi::WinEvent& ev) { if (!g_is_admin) return; int mx = ev.mouse.x; int my = ev.mouse.y; bool pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); Rect track = list_scrollbar_track(); Rect thumb = list_scrollbar_thumb(); if (g_list_sb_dragging) { if ((ev.mouse.buttons & 1) && !track.empty()) { int offset = mtk::scrollbar_offset_from_thumb_top( track, list_content_extent(), list_view_extent(), thumb.h, my - g_list_sb_drag_offset); g_list_scroll = offset / LIST_ROW_H; clamp_list_scroll(); return; } g_list_sb_dragging = false; } if (ev.mouse.scroll != 0 && list_rect().contains(mx, my)) { g_list_scroll += ev.mouse.scroll > 0 ? -1 : 1; clamp_list_scroll(); return; } if (!pressed) return; int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my); if (tab >= 0) { switch_tab((Tab)tab); return; } if (!track.empty() && track.contains(mx, my)) { if (thumb.contains(mx, my)) { g_list_sb_dragging = true; g_list_sb_drag_offset = my - thumb.y; } else { int visible = list_visible_rows(); int page = visible > 1 ? visible - 1 : 1; g_list_scroll += my < thumb.y ? -page : page; clamp_list_scroll(); } return; } for (int row = 0; row < list_visible_rows(); row++) { int index = g_list_scroll + row; if (index >= list_count()) break; if (list_row_rect(row).contains(mx, my)) { select_index(index); return; } } if (reload_button().contains(mx, my)) reload(); if (forget_visible() && forget_button().contains(mx, my)) forget_selected(); } static bool handle_key(const montauk::abi::KeyEvent& key) { if (!key.pressed) return false; if (key.scancode == 0x01) { g_win.closed = true; return true; } if (!g_is_admin) return false; if (key.ascii == '\t') { switch_tab((Tab)((g_tab + 1) % TAB_COUNT)); return true; } int current = *selected_for_tab(); if (montauk::nav_scancode(key) == 0x48) { select_index(current - 1); return true; } // Up if (montauk::nav_scancode(key) == 0x50) { select_index(current + 1); return true; } // Down if (key.ascii == 'r' || key.ascii == 'R') { reload(); return true; } return false; } extern "C" void _start() { if (!fonts::init()) montauk::exit(1); g_accent = mtk::load_system_accent(); g_is_admin = session_is_admin(); if (g_is_admin) { load_grants(); load_decisions(); } if (!g_win.create("Capabilities", WIN_W, WIN_H)) montauk::exit(1); render(); while (g_win.id >= 0 && !g_win.closed) { montauk::abi::WinEvent ev; int result = g_win.poll(&ev); if (result < 0) break; if (result == 0) { // Keep drawing while a status message is up, plus once more to // clear it when it expires. static bool status_drawn = false; if (status_visible() || status_drawn) { status_drawn = status_visible(); render(); } montauk::sleep_ms(16); continue; } bool redraw = false; if (ev.type == 3) break; if (ev.type == 2 || ev.type == 4) { clamp_list_scroll(); redraw = true; } else if (ev.type == 1) { g_mouse_x = ev.mouse.x; g_mouse_y = ev.mouse.y; handle_mouse(ev); redraw = true; } else if (ev.type == 0) { redraw = handle_key(ev.key); } if (redraw) render(); } g_win.destroy(); montauk::exit(0); }