feat: move fonts/icons/man to 0:/os, add capabilities GUI app

This commit is contained in:
daniel committed 2026-09-19 09:50:58 +02:00
1 parent 80b5beb17d
commit 75e85f24ef
35 files changed
+1035 -177

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
# Makefile for capabilities (capability grant viewer) on MontaukOS
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-montauk-
ifeq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX = $(error x86_64-montauk toolchain not found. Run ./toolchain/build-montauk-toolchain.sh first)
else
CXX := $(TOOLCHAIN_PREFIX)g++
endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
LIBDIR := ../../lib
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-msse \
-msse2 \
-MMD -MP \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc
LDFLAGS := \
-nostdlib \
-Wl,--gc-sections \
-T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
TARGET := $(BINDIR)/apps/capabilities/capabilities.elf
LIBS := $(LIBDIR)/libc/liblibc.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBS)
mkdir -p $(BINDIR)/apps/capabilities
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -c $< -o $@
-include $(OBJS:.o=.d)
clean:
rm -rf $(OBJDIR) $(TARGET)
+744
View File
@@ -0,0 +1,744 @@
/*
* main.cpp
* MontaukOS capability grant viewer
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/config.h>
#include <montauk/capabilities.h>
#include <gui/mtk.hpp>
#include <gui/mtk/settings.hpp>
#include <gui/standalone.hpp>
extern "C" {
#include <stdio.h>
}
using namespace gui;
static constexpr int WIN_W = 760;
static constexpr int WIN_H = 560;
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 CAP_COUNT =
(int)(sizeof(montauk::caps::NAMES) / sizeof(montauk::caps::NAMES[0]));
enum Tab {
TAB_PROGRAMS = 0,
TAB_CAPS = 1,
TAB_COUNT = 2,
};
static const char* const kTabLabels[TAB_COUNT] = {
"Programs",
"Capabilities",
};
// 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 and panel brightness",
"Manage hardware devices (Bluetooth, radio)",
"Read the kernel log",
"Write to 0:/os and 0:/apps (never part of \"all\")",
};
// ==== Grant table model ====
enum Field {
FIELD_EFFECTIVE = 0,
FIELD_DELEGABLE = 1,
FIELD_PERMITTED = 2,
FIELD_COUNT = 3,
};
static const char* const kFieldKeys[FIELD_COUNT] = {
"effective", "delegable", "permitted",
};
static const char* const kFieldLabels[FIELD_COUNT] = {
"Effective", "Delegable", "Permitted",
};
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_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];
uint64_t* m = g.mask;
// Same rule as caps::lookup(): an undeclared permitted set is exactly
// what the grant can use or pass on.
if (!g.declared[FIELD_PERMITTED] || m[FIELD_PERMITTED] == 0)
m[FIELD_PERMITTED] = m[FIELD_EFFECTIVE] | m[FIELD_DELEGABLE];
else if ((m[FIELD_EFFECTIVE] | m[FIELD_DELEGABLE]) & ~m[FIELD_PERMITTED])
add_issue(g, "Effective/delegable exceed permitted; the excess is dropped at launch");
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");
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;
}
// ==== 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() {
return g_tab == TAB_PROGRAMS ? g_grant_count : CAP_COUNT;
}
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 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++;
}
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 == (g_tab == TAB_PROGRAMS ? g_selected_grant : g_selected_cap);
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 : 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_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)";
else if (f == FIELD_PERMITTED && !g.declared[f]) note = "(implied)";
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_PERMITTED] & 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_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++;
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);
}
}
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 draw_caps_pane(c, theme);
} else {
draw_locked(c, theme);
}
draw_footer(c, theme);
host.present();
}
// ==== Input ====
static void reload() {
load_grants();
clamp_list_scroll();
set_status("Reloaded 0:/config/capabilities.toml");
}
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;
if (g_tab == TAB_PROGRAMS) g_selected_grant = index;
else g_selected_cap = 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(tab == TAB_PROGRAMS ? g_selected_grant : g_selected_cap);
}
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();
}
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(g_tab == TAB_PROGRAMS ? TAB_CAPS : TAB_PROGRAMS);
return true;
}
int current = g_tab == TAB_PROGRAMS ? g_selected_grant : g_selected_cap;
if (key.scancode == 0x48) { select_index(current - 1); return true; } // Up
if (key.scancode == 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();
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);
}
+13
View File
@@ -0,0 +1,13 @@
[app]
id = "capabilities"
name = "Capabilities"
binary = "capabilities.elf"
icon = "stock_lock.svg"
[menu]
category = "System"
visible = false
[desktop]
section = "settings"
admin_only = true
@@ -0,0 +1,30 @@
/*
* stb_truetype_impl.cpp
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <cstddef>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/stb_math.h>
#define STBTT_ifloor(x) ((int) stb_floor(x))
#define STBTT_iceil(x) ((int) stb_ceil(x))
#define STBTT_sqrt(x) stb_sqrt(x)
#define STBTT_pow(x,y) stb_pow(x,y)
#define STBTT_fmod(x,y) stb_fmod(x,y)
#define STBTT_cos(x) stb_cos(x)
#define STBTT_acos(x) stb_acos(x)
#define STBTT_fabs(x) stb_fabs(x)
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
#define STBTT_strlen(x) montauk::slen(x)
#define STBTT_assert(x) ((void)(x))
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>
+18 -18
View File
@@ -78,33 +78,33 @@ void ensure_filemanager_icons_loaded(DesktopState* ds) {
if (!ds || ds->icon_drive.pixels) return;
Color defColor = colors::ICON_COLOR;
ds->icon_drive = svg_load("0:/icons/drive-harddisk.svg", 16, 16, defColor);
ds->icon_drive_lg = svg_load("0:/icons/drive-harddisk.svg", 48, 48, defColor);
ds->icon_drive_usb = svg_load("0:/icons/drive-harddisk-usb.svg", 16, 16, defColor);
ds->icon_drive_usb_lg = svg_load("0:/icons/drive-harddisk-usb.svg", 48, 48, defColor);
ds->icon_home_folder = svg_load("0:/icons/folder-blue-home.svg", 16, 16, defColor);
ds->icon_home_folder_lg = svg_load("0:/icons/folder-blue-home.svg", 48, 48, defColor);
ds->icon_apps = svg_load("0:/icons/folder-blue-development.svg", 16, 16, defColor);
ds->icon_apps_lg = svg_load("0:/icons/folder-blue-development.svg", 48, 48, defColor);
ds->icon_drive = svg_load("0:/os/icons/drive-harddisk.svg", 16, 16, defColor);
ds->icon_drive_lg = svg_load("0:/os/icons/drive-harddisk.svg", 48, 48, defColor);
ds->icon_drive_usb = svg_load("0:/os/icons/drive-harddisk-usb.svg", 16, 16, defColor);
ds->icon_drive_usb_lg = svg_load("0:/os/icons/drive-harddisk-usb.svg", 48, 48, defColor);
ds->icon_home_folder = svg_load("0:/os/icons/folder-blue-home.svg", 16, 16, defColor);
ds->icon_home_folder_lg = svg_load("0:/os/icons/folder-blue-home.svg", 48, 48, defColor);
ds->icon_apps = svg_load("0:/os/icons/folder-blue-development.svg", 16, 16, defColor);
ds->icon_apps_lg = svg_load("0:/os/icons/folder-blue-development.svg", 48, 48, defColor);
ds->icon_system_configuration_menu =
svg_load("0:/icons/preferences-system.svg", 20, 20, defColor);
svg_load("0:/os/icons/preferences-system.svg", 20, 20, defColor);
ds->icon_system_configuration =
svg_load("0:/icons/preferences-system.svg", 16, 16, defColor);
svg_load("0:/os/icons/preferences-system.svg", 16, 16, defColor);
ds->icon_system_configuration_lg =
svg_load("0:/icons/preferences-system.svg", 48, 48, defColor);
svg_load("0:/os/icons/preferences-system.svg", 48, 48, defColor);
for (int sf = 0; sf < filemanager::SF_COUNT; sf++) {
char icon_path[128];
snprintf(icon_path, 128, "0:/icons/%s", filemanager::sf_icons[sf]);
snprintf(icon_path, 128, "0:/os/icons/%s", filemanager::sf_icons[sf]);
ds->icon_special_folder[sf] = svg_load(icon_path, 16, 16, defColor);
ds->icon_special_folder_lg[sf] = svg_load(icon_path, 48, 48, defColor);
}
ds->icon_delete = svg_load("0:/icons/trash-empty.svg", 16, 16, defColor);
ds->icon_copy = svg_load("0:/icons/edit-copy.svg", 16, 16, defColor);
ds->icon_cut = svg_load("0:/icons/edit-cut.svg", 16, 16, defColor);
ds->icon_paste = svg_load("0:/icons/edit-paste.svg", 16, 16, defColor);
ds->icon_rename = svg_load("0:/icons/edit-rename.svg", 16, 16, defColor);
ds->icon_folder_new = svg_load("0:/icons/folder-new.svg", 16, 16, defColor);
ds->icon_delete = svg_load("0:/os/icons/trash-empty.svg", 16, 16, defColor);
ds->icon_copy = svg_load("0:/os/icons/edit-copy.svg", 16, 16, defColor);
ds->icon_cut = svg_load("0:/os/icons/edit-cut.svg", 16, 16, defColor);
ds->icon_paste = svg_load("0:/os/icons/edit-paste.svg", 16, 16, defColor);
ds->icon_rename = svg_load("0:/os/icons/edit-rename.svg", 16, 16, defColor);
ds->icon_folder_new = svg_load("0:/os/icons/folder-new.svg", 16, 16, defColor);
}
void open_filemanager(DesktopState* ds) {
+3 -3
View File
@@ -63,7 +63,7 @@ static void battery_icon_path(const DesktopState* ds, char* out, int cap) {
const montauk::abi::BatteryInfo& b = ds->battery_info;
if (!b.present) {
montauk::strncpy(out, "0:/icons/battery-missing-symbolic.svg", cap);
montauk::strncpy(out, "0:/os/icons/battery-missing-symbolic.svg", cap);
return;
}
@@ -72,11 +72,11 @@ static void battery_icon_path(const DesktopState* ds, char* out, int cap) {
// A full battery on the charger gets the "charged" glyph; the pack has no
// 100% charging variant, and "charged" is what that state means anyway.
if (b.charging && bucket >= 100) {
montauk::strncpy(out, "0:/icons/battery-level-100-charged-symbolic.svg", cap);
montauk::strncpy(out, "0:/os/icons/battery-level-100-charged-symbolic.svg", cap);
return;
}
snprintf(out, cap, "0:/icons/battery-level-%d%s-symbolic.svg",
snprintf(out, cap, "0:/os/icons/battery-level-%d%s-symbolic.svg",
bucket, b.charging ? "-charging" : "");
}
+4 -4
View File
@@ -8,13 +8,13 @@
#include <montauk/user.h>
static const DesktopBuiltinEntry builtin_entries[] = {
{ DESKTOP_BUILTIN_FILES, "Files", 0, false, nullptr, "0:/icons/system-file-manager.svg" },
{ DESKTOP_BUILTIN_FILES, "Files", 0, false, nullptr, "0:/os/icons/system-file-manager.svg" },
{ DESKTOP_BUILTIN_DESKTOP_SETTINGS, "Desktop Settings", -1, false, "desktop-settings",
"0:/icons/preferences-desktop-apps.svg" },
"0:/os/icons/preferences-desktop-apps.svg" },
{ DESKTOP_BUILTIN_SYSTEM_CONFIGURATION, "Settings", -1, true, nullptr,
"0:/icons/preferences-system.svg" },
"0:/os/icons/preferences-system.svg" },
{ DESKTOP_BUILTIN_USER_MANAGEMENT, "User Management", -1, false, "user-management",
"0:/icons/user-home.svg" },
"0:/os/icons/user-home.svg" },
{ DESKTOP_BUILTIN_LOCK_SCREEN, "Lock Screen", -1, true, nullptr, nullptr },
{ DESKTOP_BUILTIN_LOG_OUT, "Log Out", -1, true, nullptr, nullptr },
{ DESKTOP_BUILTIN_SLEEP, "Sleep", -1, true, nullptr, nullptr },
+23 -23
View File
@@ -269,33 +269,33 @@ void gui::desktop_init(DesktopState* ds) {
// Load SVG icons — scalable (colorful) for app menu, symbolic for toolbar/panel
Color defColor = colors::ICON_COLOR;
ds->icon_terminal = svg_load("0:/icons/utilities-terminal.svg", 20, 20, defColor);
ds->icon_filemanager = svg_load("0:/icons/system-file-manager.svg", 20, 20, defColor);
ds->icon_appmenu = svg_load("0:/icons/view-app-grid-symbolic.svg", 20, 20, colors::PANEL_TEXT);
ds->icon_folder = svg_load("0:/icons/folder.svg", 16, 16, defColor);
ds->icon_file = svg_load("0:/icons/text-x-generic.svg", 16, 16, defColor);
ds->icon_network = svg_load("0:/icons/network-wired-symbolic.svg", 16, 16, colors::PANEL_TEXT);
ds->icon_wifi = svg_load("0:/icons/network-wireless-symbolic.svg", 16, 16, colors::PANEL_TEXT);
ds->icon_go_up = svg_load("0:/icons/go-up-symbolic.svg", 16, 16, defColor);
ds->icon_go_back = svg_load("0:/icons/go-previous-symbolic.svg", 16, 16, defColor);
ds->icon_go_forward = svg_load("0:/icons/go-next-symbolic.svg", 16, 16, defColor);
ds->icon_home = svg_load("0:/icons/user-home.svg", 16, 16, defColor);
ds->icon_refresh = svg_load("0:/icons/view-refresh-symbolic.svg", 16, 16, defColor);
ds->icon_exec = svg_load("0:/icons/utilities-terminal.svg", 16, 16, defColor);
ds->icon_terminal = svg_load("0:/os/icons/utilities-terminal.svg", 20, 20, defColor);
ds->icon_filemanager = svg_load("0:/os/icons/system-file-manager.svg", 20, 20, defColor);
ds->icon_appmenu = svg_load("0:/os/icons/view-app-grid-symbolic.svg", 20, 20, colors::PANEL_TEXT);
ds->icon_folder = svg_load("0:/os/icons/folder.svg", 16, 16, defColor);
ds->icon_file = svg_load("0:/os/icons/text-x-generic.svg", 16, 16, defColor);
ds->icon_network = svg_load("0:/os/icons/network-wired-symbolic.svg", 16, 16, colors::PANEL_TEXT);
ds->icon_wifi = svg_load("0:/os/icons/network-wireless-symbolic.svg", 16, 16, colors::PANEL_TEXT);
ds->icon_go_up = svg_load("0:/os/icons/go-up-symbolic.svg", 16, 16, defColor);
ds->icon_go_back = svg_load("0:/os/icons/go-previous-symbolic.svg", 16, 16, defColor);
ds->icon_go_forward = svg_load("0:/os/icons/go-next-symbolic.svg", 16, 16, defColor);
ds->icon_home = svg_load("0:/os/icons/user-home.svg", 16, 16, defColor);
ds->icon_refresh = svg_load("0:/os/icons/view-refresh-symbolic.svg", 16, 16, defColor);
ds->icon_exec = svg_load("0:/os/icons/utilities-terminal.svg", 16, 16, defColor);
ds->icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, defColor);
ds->icon_file_lg = svg_load("0:/icons/text-x-generic.svg", 48, 48, defColor);
ds->icon_exec_lg = svg_load("0:/icons/utilities-terminal.svg", 48, 48, defColor);
ds->icon_folder_lg = svg_load("0:/os/icons/folder.svg", 48, 48, defColor);
ds->icon_file_lg = svg_load("0:/os/icons/text-x-generic.svg", 48, 48, defColor);
ds->icon_exec_lg = svg_load("0:/os/icons/utilities-terminal.svg", 48, 48, defColor);
// drive icons loaded lazily by file manager to reduce startup heap pressure
ds->icon_settings = svg_load("0:/icons/preferences-desktop-apps.svg", 20, 20, defColor);
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor);
ds->icon_sleep = svg_load("0:/icons/sleep.svg", 20, 20, defColor);
ds->icon_logout = svg_load("0:/icons/gnome-logout.svg", 20, 20, defColor);
ds->icon_settings = svg_load("0:/os/icons/preferences-desktop-apps.svg", 20, 20, defColor);
ds->icon_reboot = svg_load("0:/os/icons/system-reboot.svg", 20, 20, defColor);
ds->icon_shutdown = svg_load("0:/os/icons/system-shutdown.svg", 20, 20, defColor);
ds->icon_sleep = svg_load("0:/os/icons/sleep.svg", 20, 20, defColor);
ds->icon_logout = svg_load("0:/os/icons/gnome-logout.svg", 20, 20, defColor);
ds->icon_volume = svg_load("0:/icons/audio-volume-high-symbolic.svg", 16, 16, colors::PANEL_TEXT);
ds->icon_lock = svg_load("0:/icons/lock.svg", 20, 20, defColor);
ds->icon_volume = svg_load("0:/os/icons/audio-volume-high-symbolic.svg", 16, 16, colors::PANEL_TEXT);
ds->icon_lock = svg_load("0:/os/icons/lock.svg", 20, 20, defColor);
// Scan 0:/apps/ for external app manifests and build the menu
desktop_scan_apps(ds);
+13 -13
View File
@@ -381,21 +381,21 @@ int special_folder_index(const char* name) {
void load_file_icons(FileDialogState* st) {
Color def = colors::ICON_COLOR;
st->icons.icon_go_back = svg_load("0:/icons/go-previous-symbolic.svg", 16, 16, def);
st->icons.icon_go_forward = svg_load("0:/icons/go-next-symbolic.svg", 16, 16, def);
st->icons.icon_go_up = svg_load("0:/icons/go-up-symbolic.svg", 16, 16, def);
st->icons.icon_home = svg_load("0:/icons/user-home.svg", 16, 16, def);
st->icons.icon_folder = svg_load("0:/icons/folder.svg", 16, 16, def);
st->icons.icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, def);
st->icons.icon_file = svg_load("0:/icons/text-x-generic.svg", 16, 16, def);
st->icons.icon_file_lg = svg_load("0:/icons/text-x-generic.svg", 48, 48, def);
st->icons.icon_drive = svg_load("0:/icons/drive-harddisk.svg", 16, 16, def);
st->icons.icon_drive_lg = svg_load("0:/icons/drive-harddisk.svg", 48, 48, def);
st->icons.icon_home_folder = svg_load("0:/icons/folder-blue-home.svg", 16, 16, def);
st->icons.icon_home_folder_lg = svg_load("0:/icons/folder-blue-home.svg", 48, 48, def);
st->icons.icon_go_back = svg_load("0:/os/icons/go-previous-symbolic.svg", 16, 16, def);
st->icons.icon_go_forward = svg_load("0:/os/icons/go-next-symbolic.svg", 16, 16, def);
st->icons.icon_go_up = svg_load("0:/os/icons/go-up-symbolic.svg", 16, 16, def);
st->icons.icon_home = svg_load("0:/os/icons/user-home.svg", 16, 16, def);
st->icons.icon_folder = svg_load("0:/os/icons/folder.svg", 16, 16, def);
st->icons.icon_folder_lg = svg_load("0:/os/icons/folder.svg", 48, 48, def);
st->icons.icon_file = svg_load("0:/os/icons/text-x-generic.svg", 16, 16, def);
st->icons.icon_file_lg = svg_load("0:/os/icons/text-x-generic.svg", 48, 48, def);
st->icons.icon_drive = svg_load("0:/os/icons/drive-harddisk.svg", 16, 16, def);
st->icons.icon_drive_lg = svg_load("0:/os/icons/drive-harddisk.svg", 48, 48, def);
st->icons.icon_home_folder = svg_load("0:/os/icons/folder-blue-home.svg", 16, 16, def);
st->icons.icon_home_folder_lg = svg_load("0:/os/icons/folder-blue-home.svg", 48, 48, def);
for (int i = 0; i < SPECIAL_FOLDER_COUNT; i++) {
char path[128];
snprintf(path, sizeof(path), "0:/icons/%s", SPECIAL_FOLDER_ICONS[i]);
snprintf(path, sizeof(path), "0:/os/icons/%s", SPECIAL_FOLDER_ICONS[i]);
st->icons.icon_special[i] = svg_load(path, 16, 16, def);
st->icons.icon_special_lg[i] = svg_load(path, 48, 48, def);
}
+1 -1
View File
@@ -279,7 +279,7 @@ void init_print_dialog(const gui::dialogs::Request& req) {
PrintDialogState* st = &g_print;
montauk::memset(st, 0, sizeof(*st));
Color def = colors::ICON_COLOR;
st->printer_icon = svg_load("0:/icons/printer-symbolic.svg", 24, 24, def);
st->printer_icon = svg_load("0:/os/icons/printer-symbolic.svg", 24, 24, def);
safe_copy(st->source_path, sizeof(st->source_path), req.source_path);
safe_copy(st->job_name, sizeof(st->job_name), req.job_name);
st->submit_mode = req.mode == gui::dialogs::PRINT_DIALOG_SUBMIT;
+1 -1
View File
@@ -327,7 +327,7 @@ extern "C" void _start() {
if (!f->init(path)) { montauk::mfree(f); return nullptr; }
return f;
};
g_ui_font = load_font("0:/fonts/Roboto-Medium.ttf");
g_ui_font = load_font("0:/os/fonts/Roboto-Medium.ttf");
// Window title from filename
char title[64] = "Font Preview";
+1 -1
View File
@@ -539,7 +539,7 @@ extern "C" void _start() {
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (f) {
montauk::memset(f, 0, sizeof(TrueTypeFont));
if (!f->init("0:/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
if (!f->init("0:/os/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
}
g_font = f;
}
+2 -2
View File
@@ -146,7 +146,7 @@ extern "C" void _start() {
char path[128];
if (section > 0) {
const char* prefix = "0:/man/";
const char* prefix = "0:/os/man/";
int p = 0;
while (prefix[p]) { path[p] = prefix[p]; p++; }
int t = 0;
@@ -159,7 +159,7 @@ extern "C" void _start() {
if (handle >= 0) foundSection = section;
} else {
for (int s = 1; s <= 9; s++) {
const char* prefix = "0:/man/";
const char* prefix = "0:/os/man/";
int p = 0;
while (prefix[p]) { path[p] = prefix[p]; p++; }
int t = 0;
+1 -1
View File
@@ -286,7 +286,7 @@ extern "C" void _start() {
g_font = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (g_font) {
montauk::memset(g_font, 0, sizeof(TrueTypeFont));
if (!g_font->init("0:/fonts/Roboto-Medium.ttf")) {
if (!g_font->init("0:/os/fonts/Roboto-Medium.ttf")) {
montauk::mfree(g_font);
g_font = nullptr;
}
+17 -17
View File
@@ -1562,7 +1562,7 @@ extern "C" void _start() {
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (f) {
montauk::memset(f, 0, sizeof(TrueTypeFont));
if (!f->init("0:/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
if (!f->init("0:/os/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
}
g.font = f;
}
@@ -1570,22 +1570,22 @@ extern "C" void _start() {
// Load transport icons (dark + white variants for colored backgrounds)
{
Color ic_color = TEXT_COLOR;
g.ico_rewind = svg_load("0:/icons/media-rewind.svg", 16, 16, ic_color);
g.ico_play = svg_load("0:/icons/media-play.svg", 16, 16, ic_color);
g.ico_pause = svg_load("0:/icons/media-pause.svg", 16, 16, ic_color);
g.ico_stop = svg_load("0:/icons/media-stop.svg", 16, 16, ic_color);
g.ico_forward = svg_load("0:/icons/media-forward.svg", 16, 16, ic_color);
g.ico_play_w = svg_load("0:/icons/media-play.svg", 16, 16, WHITE);
g.ico_pause_w = svg_load("0:/icons/media-pause.svg", 16, 16, WHITE);
g.ico_stop_w = svg_load("0:/icons/media-stop.svg", 16, 16, WHITE);
g.ico_visualizer = svg_load("0:/icons/view-media-equalizer.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_visualizer_w = svg_load("0:/icons/view-media-equalizer.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_shuffle = svg_load("0:/icons/media-playlist-shuffle.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_shuffle_w = svg_load("0:/icons/media-playlist-shuffle.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_repeat_all = svg_load("0:/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_repeat_all_w = svg_load("0:/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_repeat_one = svg_load("0:/icons/media-playlist-repeat-song.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_repeat_one_w = svg_load("0:/icons/media-playlist-repeat-song.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_rewind = svg_load("0:/os/icons/media-rewind.svg", 16, 16, ic_color);
g.ico_play = svg_load("0:/os/icons/media-play.svg", 16, 16, ic_color);
g.ico_pause = svg_load("0:/os/icons/media-pause.svg", 16, 16, ic_color);
g.ico_stop = svg_load("0:/os/icons/media-stop.svg", 16, 16, ic_color);
g.ico_forward = svg_load("0:/os/icons/media-forward.svg", 16, 16, ic_color);
g.ico_play_w = svg_load("0:/os/icons/media-play.svg", 16, 16, WHITE);
g.ico_pause_w = svg_load("0:/os/icons/media-pause.svg", 16, 16, WHITE);
g.ico_stop_w = svg_load("0:/os/icons/media-stop.svg", 16, 16, WHITE);
g.ico_visualizer = svg_load("0:/os/icons/view-media-equalizer.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_visualizer_w = svg_load("0:/os/icons/view-media-equalizer.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_shuffle = svg_load("0:/os/icons/media-playlist-shuffle.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_shuffle_w = svg_load("0:/os/icons/media-playlist-shuffle.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_repeat_all = svg_load("0:/os/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_repeat_all_w = svg_load("0:/os/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_repeat_one = svg_load("0:/os/icons/media-playlist-repeat-song.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, ic_color);
g.ico_repeat_one_w = svg_load("0:/os/icons/media-playlist-repeat-song.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
}
// Scan for audio files
+1 -1
View File
@@ -256,7 +256,7 @@ extern "C" void _start() {
g_font = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (g_font) {
montauk::memset(g_font, 0, sizeof(TrueTypeFont));
if (!g_font->init("0:/fonts/Roboto-Medium.ttf")) {
if (!g_font->init("0:/os/fonts/Roboto-Medium.ttf")) {
montauk::mfree(g_font);
g_font = nullptr;
}
+5 -5
View File
@@ -162,14 +162,14 @@ extern "C" void _start() {
if (!f->init(path)) { montauk::mfree(f); return nullptr; }
return f;
};
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/fonts/Roboto-Bold.ttf");
g_font_mono = load_font("0:/fonts/JetBrainsMono-Regular.ttf");
g_font = load_font("0:/os/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/os/fonts/Roboto-Bold.ttf");
g_font_mono = load_font("0:/os/fonts/JetBrainsMono-Regular.ttf");
// Load toolbar icons
Color icon_color = HEADER_TEXT;
g_icon_folder = svg_load("0:/icons/folder.svg", 16, 16, icon_color);
g_icon_print = svg_load("0:/icons/printer-symbolic.svg", 16, 16, icon_color);
g_icon_folder = svg_load("0:/os/icons/folder.svg", 16, 16, icon_color);
g_icon_print = svg_load("0:/os/icons/printer-symbolic.svg", 16, 16, icon_color);
// Check for file argument
char args[512] = {};
+2 -2
View File
@@ -26,8 +26,8 @@ namespace {
static constexpr int PAGE_W = 1240;
static constexpr int PAGE_H = 1754;
static constexpr const char* ROBOTO_MEDIUM_PATH = "0:/fonts/Roboto-Medium.ttf";
static constexpr const char* ROBOTO_BOLD_PATH = "0:/fonts/Roboto-Bold.ttf";
static constexpr const char* ROBOTO_MEDIUM_PATH = "0:/os/fonts/Roboto-Medium.ttf";
static constexpr const char* ROBOTO_BOLD_PATH = "0:/os/fonts/Roboto-Bold.ttf";
struct JpegBuffer {
uint8_t* data;
+1 -1
View File
@@ -173,7 +173,7 @@ static void load_accent() {
static void load_icons() {
if (g_printer_icon.pixels) svg_free(g_printer_icon);
g_printer_icon = svg_load("0:/icons/printer-symbolic.svg",
g_printer_icon = svg_load("0:/os/icons/printer-symbolic.svg",
PRINTER_ICON_SIZE, PRINTER_ICON_SIZE,
Color::from_rgb(0x6A, 0x72, 0x7D));
}
+7 -7
View File
@@ -18,15 +18,15 @@ SvgIcon g_icon_paste = {};
void ss_load_icons() {
Color def_color = HEADER_TEXT;
if (!g_icon_folder.pixels)
g_icon_folder = svg_load("0:/icons/folder.svg", 16, 16, def_color);
g_icon_folder = svg_load("0:/os/icons/folder.svg", 16, 16, def_color);
if (!g_icon_save.pixels)
g_icon_save = svg_load("0:/icons/document-save-symbolic.svg", 16, 16, def_color);
g_icon_save = svg_load("0:/os/icons/document-save-symbolic.svg", 16, 16, def_color);
if (!g_icon_cut.pixels)
g_icon_cut = svg_load("0:/icons/edit-cut.svg", 16, 16, def_color);
g_icon_cut = svg_load("0:/os/icons/edit-cut.svg", 16, 16, def_color);
if (!g_icon_copy.pixels)
g_icon_copy = svg_load("0:/icons/edit-copy.svg", 16, 16, def_color);
g_icon_copy = svg_load("0:/os/icons/edit-copy.svg", 16, 16, def_color);
if (!g_icon_paste.pixels)
g_icon_paste = svg_load("0:/icons/edit-paste.svg", 16, 16, def_color);
g_icon_paste = svg_load("0:/os/icons/edit-paste.svg", 16, 16, def_color);
}
void ss_unload_icons() {
@@ -339,8 +339,8 @@ extern "C" void _start() {
if (!f->init(path)) { montauk::mfree(f); return nullptr; }
return f;
};
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/fonts/Roboto-Bold.ttf");
g_font = load_font("0:/os/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/os/fonts/Roboto-Bold.ttf");
ss_load_icons();
+4 -4
View File
@@ -1438,13 +1438,13 @@ extern "C" void _start() {
if (!f->init(path)) { montauk::mfree(f); return nullptr; }
return f;
};
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
g_font_mono = load_font("0:/fonts/JetBrainsMono-Regular.ttf");
g_font = load_font("0:/os/fonts/Roboto-Medium.ttf");
g_font_mono = load_font("0:/os/fonts/JetBrainsMono-Regular.ttf");
// Load toolbar icons
Color icon_color = Color::from_rgb(0x44, 0x44, 0x44);
g_icon_folder = svg_load("0:/icons/folder.svg", 16, 16, icon_color);
g_icon_save = svg_load("0:/icons/document-save-symbolic.svg", 16, 16, icon_color);
g_icon_folder = svg_load("0:/os/icons/folder.svg", 16, 16, icon_color);
g_icon_save = svg_load("0:/os/icons/document-save-symbolic.svg", 16, 16, icon_color);
// Check for file argument
char args[512] = {};
+11 -11
View File
@@ -1311,7 +1311,7 @@ extern "C" void _start() {
TrueTypeFont* font = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (font) {
montauk::memset(font, 0, sizeof(TrueTypeFont));
if (!font->init("0:/fonts/Roboto-Medium.ttf")) {
if (!font->init("0:/os/fonts/Roboto-Medium.ttf")) {
montauk::mfree(font);
font = nullptr;
}
@@ -1319,16 +1319,16 @@ extern "C" void _start() {
g.font = font;
Color icon_fg = TEXT;
g.ico_rewind = svg_load("0:/icons/media-rewind.svg", 16, 16, icon_fg);
g.ico_play = svg_load("0:/icons/media-play.svg", 16, 16, icon_fg);
g.ico_pause = svg_load("0:/icons/media-pause.svg", 16, 16, icon_fg);
g.ico_stop = svg_load("0:/icons/media-stop.svg", 16, 16, icon_fg);
g.ico_forward = svg_load("0:/icons/media-forward.svg", 16, 16, icon_fg);
g.ico_play_w = svg_load("0:/icons/media-play.svg", 16, 16, WHITE);
g.ico_pause_w = svg_load("0:/icons/media-pause.svg", 16, 16, WHITE);
g.ico_stop_w = svg_load("0:/icons/media-stop.svg", 16, 16, WHITE);
g.ico_repeat = svg_load("0:/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, icon_fg);
g.ico_repeat_w = svg_load("0:/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
g.ico_rewind = svg_load("0:/os/icons/media-rewind.svg", 16, 16, icon_fg);
g.ico_play = svg_load("0:/os/icons/media-play.svg", 16, 16, icon_fg);
g.ico_pause = svg_load("0:/os/icons/media-pause.svg", 16, 16, icon_fg);
g.ico_stop = svg_load("0:/os/icons/media-stop.svg", 16, 16, icon_fg);
g.ico_forward = svg_load("0:/os/icons/media-forward.svg", 16, 16, icon_fg);
g.ico_play_w = svg_load("0:/os/icons/media-play.svg", 16, 16, WHITE);
g.ico_pause_w = svg_load("0:/os/icons/media-pause.svg", 16, 16, WHITE);
g.ico_stop_w = svg_load("0:/os/icons/media-stop.svg", 16, 16, WHITE);
g.ico_repeat = svg_load("0:/os/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, icon_fg);
g.ico_repeat_w = svg_load("0:/os/icons/media-playlist-repeat.svg", TOOL_ICON_SIZE, TOOL_ICON_SIZE, WHITE);
scan_directory();
+3 -3
View File
@@ -172,7 +172,7 @@ static void load_weather_icon(const char* icon_name) {
if (g_icon.pixels) svg_free(g_icon);
static char path[80];
snprintf(path, sizeof(path), "0:/icons/%s", icon_name);
snprintf(path, sizeof(path), "0:/os/icons/%s", icon_name);
// ColorScheme-Text paths (main shape) use this fill_color; accent classes
// (ColorScheme-Highlight=blue, ColorScheme-NeutralText=yellow, etc.) use their
// CSS-defined colors as parsed from the SVG's <style> block by svg_load.
@@ -379,8 +379,8 @@ extern "C" void _start() {
if (!f->init(path)) { montauk::mfree(f); return nullptr; }
return f;
};
g_font = load_font("0:/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/fonts/Roboto-Bold.ttf");
g_font = load_font("0:/os/fonts/Roboto-Medium.ttf");
g_font_bold = load_font("0:/os/fonts/Roboto-Bold.ttf");
if (!g_font) montauk::exit(1);
apply_scale(montauk::win_getscale());
+7 -7
View File
@@ -20,16 +20,16 @@ static bool init_buffers_and_fonts() {
g_extract_buf = (char*)malloc(RESP_MAX + 1);
if (!g_lines || !g_resp_buf || !g_extract_buf) return false;
g_body_fonts[(int)ReaderFontFace::SANS] = load_font("0:/fonts/Roboto-Regular.ttf");
g_body_fonts[(int)ReaderFontFace::SANS] = load_font("0:/os/fonts/Roboto-Regular.ttf");
if (!g_body_fonts[(int)ReaderFontFace::SANS])
g_body_fonts[(int)ReaderFontFace::SANS] = load_font("0:/fonts/Roboto-Medium.ttf");
g_heading_fonts[(int)ReaderFontFace::SANS] = load_font("0:/fonts/Roboto-Bold.ttf");
g_body_fonts[(int)ReaderFontFace::SANS] = load_font("0:/os/fonts/Roboto-Medium.ttf");
g_heading_fonts[(int)ReaderFontFace::SANS] = load_font("0:/os/fonts/Roboto-Bold.ttf");
g_body_fonts[(int)ReaderFontFace::SERIF] = load_font("0:/fonts/NotoSerif-Regular.ttf");
g_heading_fonts[(int)ReaderFontFace::SERIF] = load_font("0:/fonts/NotoSerif-SemiBold.ttf");
g_body_fonts[(int)ReaderFontFace::SERIF] = load_font("0:/os/fonts/NotoSerif-Regular.ttf");
g_heading_fonts[(int)ReaderFontFace::SERIF] = load_font("0:/os/fonts/NotoSerif-SemiBold.ttf");
g_body_fonts[(int)ReaderFontFace::BOOK] = load_font("0:/fonts/C059-Roman.ttf");
g_heading_fonts[(int)ReaderFontFace::BOOK] = load_font("0:/fonts/C059-Bold.ttf");
g_body_fonts[(int)ReaderFontFace::BOOK] = load_font("0:/os/fonts/C059-Roman.ttf");
g_heading_fonts[(int)ReaderFontFace::BOOK] = load_font("0:/os/fonts/C059-Bold.ttf");
if (!g_body_fonts[(int)ReaderFontFace::SANS]) return false;
+1 -1
View File
@@ -37,7 +37,7 @@ static constexpr int RESP_MAX = 131072;
static constexpr int MAX_LINES = 2000;
inline const char WIKI_HOST[] = "en.wikipedia.org";
inline const char DISPLAY_ICON_PATH[] = "0:/icons/cog.svg";
inline const char DISPLAY_ICON_PATH[] = "0:/os/icons/cog.svg";
// ============================================================================
// Types
+4 -4
View File
@@ -446,7 +446,7 @@ static int wp_split_at(WordProcessorState* wp, int abs_pos) {
// ============================================================================
// Font discovery
//
// Fonts live in 0:/fonts as "<Family>-<Style>.ttf" (e.g. Roboto-BoldItalic.ttf).
// Fonts live in 0:/os/fonts as "<Family>-<Style>.ttf" (e.g. Roboto-BoldItalic.ttf).
// We scan that directory at startup, group files by family, and slot each file
// into one of four style variants. Families are sorted deterministically so a
// given set of installed fonts yields stable font ids across runs. Documents
@@ -560,7 +560,7 @@ static void wp_register_font_file(const char* filename) {
WpFontFamily* fam = &g_wp_fonts.families[fi];
if (fam->paths[variant][0]) return; // first file wins for this slot
montauk::strncpy(fam->paths[variant], "0:/fonts/", WP_FONT_PATH_MAX);
montauk::strncpy(fam->paths[variant], "0:/os/fonts/", WP_FONT_PATH_MAX);
int plen = montauk::slen(fam->paths[variant]);
montauk::strncpy(fam->paths[variant] + plen, filename, WP_FONT_PATH_MAX - plen);
montauk::strncpy(fam->basenames[variant], stem, WP_FONT_BASENAME_MAX);
@@ -601,7 +601,7 @@ void wp_load_fonts() {
g_wp_fonts.count = 0;
const char* names[256];
int count = montauk::readdir("0:/fonts", names, 256);
int count = montauk::readdir("0:/os/fonts", names, 256);
// Sort entries so per-slot conflicts resolve deterministically. With
// first-wins below, "Roboto-Medium" sorts before "Roboto-Regular" and
@@ -653,7 +653,7 @@ void wp_load_fonts() {
}
}
// Defensive fallback: if 0:/fonts yielded nothing usable, expose the
// Defensive fallback: if 0:/os/fonts yielded nothing usable, expose the
// shared system font so the editor still renders.
if (g_wp_fonts.count == 0 && fonts::system_font) {
WpFontFamily* fam = &g_wp_fonts.families[0];
+15 -15
View File
@@ -31,36 +31,36 @@ TrueTypeFont* g_ui_bold = nullptr;
void wp_load_icons() {
Color def_color = colors::ICON_COLOR;
if (!g_icon_folder.pixels)
g_icon_folder = svg_load("0:/icons/folder.svg", 16, 16, def_color);
g_icon_folder = svg_load("0:/os/icons/folder.svg", 16, 16, def_color);
if (!g_icon_save.pixels)
g_icon_save = svg_load("0:/icons/document-save-symbolic.svg", 16, 16, def_color);
g_icon_save = svg_load("0:/os/icons/document-save-symbolic.svg", 16, 16, def_color);
if (!g_icon_print.pixels)
g_icon_print = svg_load("0:/icons/printer-symbolic.svg", 16, 16, def_color);
g_icon_print = svg_load("0:/os/icons/printer-symbolic.svg", 16, 16, def_color);
if (!g_icon_export_pdf.pixels) {
g_icon_export_pdf = svg_load("0:/icons/document-export-symbolic.svg", 16, 16, def_color);
g_icon_export_pdf = svg_load("0:/os/icons/document-export-symbolic.svg", 16, 16, def_color);
if (!g_icon_export_pdf.pixels)
g_icon_export_pdf = svg_load("0:/icons/text-x-generic-symbolic.svg", 16, 16, def_color);
g_icon_export_pdf = svg_load("0:/os/icons/text-x-generic-symbolic.svg", 16, 16, def_color);
}
if (!g_icon_undo.pixels)
g_icon_undo = svg_load("0:/icons/edit-undo-symbolic.svg", 16, 16, def_color);
g_icon_undo = svg_load("0:/os/icons/edit-undo-symbolic.svg", 16, 16, def_color);
if (!g_icon_redo.pixels)
g_icon_redo = svg_load("0:/icons/edit-redo-symbolic.svg", 16, 16, def_color);
g_icon_redo = svg_load("0:/os/icons/edit-redo-symbolic.svg", 16, 16, def_color);
if (!g_icon_align_left.pixels)
g_icon_align_left = svg_load("0:/icons/format-justify-left-symbolic.svg", 16, 16, def_color);
g_icon_align_left = svg_load("0:/os/icons/format-justify-left-symbolic.svg", 16, 16, def_color);
if (!g_icon_align_center.pixels)
g_icon_align_center = svg_load("0:/icons/format-justify-center-symbolic.svg", 16, 16, def_color);
g_icon_align_center = svg_load("0:/os/icons/format-justify-center-symbolic.svg", 16, 16, def_color);
if (!g_icon_align_right.pixels)
g_icon_align_right = svg_load("0:/icons/format-justify-right-symbolic.svg", 16, 16, def_color);
g_icon_align_right = svg_load("0:/os/icons/format-justify-right-symbolic.svg", 16, 16, def_color);
if (!g_icon_list_bullet.pixels)
g_icon_list_bullet = svg_load("0:/icons/view-list-bullet-symbolic.svg", 16, 16, def_color);
g_icon_list_bullet = svg_load("0:/os/icons/view-list-bullet-symbolic.svg", 16, 16, def_color);
if (!g_icon_list_number.pixels)
g_icon_list_number = svg_load("0:/icons/view-list-ordered-symbolic.svg", 16, 16, def_color);
g_icon_list_number = svg_load("0:/os/icons/view-list-ordered-symbolic.svg", 16, 16, def_color);
if (!g_icon_indent_less.pixels)
g_icon_indent_less = svg_load("0:/icons/format-indent-less-symbolic.svg", 16, 16, def_color);
g_icon_indent_less = svg_load("0:/os/icons/format-indent-less-symbolic.svg", 16, 16, def_color);
if (!g_icon_indent_more.pixels)
g_icon_indent_more = svg_load("0:/icons/format-indent-more-symbolic.svg", 16, 16, def_color);
g_icon_indent_more = svg_load("0:/os/icons/format-indent-more-symbolic.svg", 16, 16, def_color);
if (!g_icon_page_view.pixels)
g_icon_page_view = svg_load("0:/icons/view-paged-symbolic.svg", 16, 16, def_color);
g_icon_page_view = svg_load("0:/os/icons/view-paged-symbolic.svg", 16, 16, def_color);
}
void wp_cleanup_state() {
+2 -2
View File
@@ -82,12 +82,12 @@ static constexpr int WP_DIVIDER_FLYOUT_W = 216;
static constexpr int WP_SPECIAL_CHAR_ROW_H = 24;
static constexpr int WP_SPECIAL_CHAR_FLYOUT_W = 144;
// Fonts are discovered at runtime by scanning 0:/fonts (see wp_load_fonts).
// Fonts are discovered at runtime by scanning 0:/os/fonts (see wp_load_fonts).
// A font id is an index into the discovered family table (g_wp_fonts). The
// table is bounded by WP_MAX_FONTS; ids beyond the table fall back to default.
static constexpr int WP_MAX_FONTS = 24; // max discovered families
static constexpr int WP_FONT_NAME_MAX = 32; // family/display name
static constexpr int WP_FONT_PATH_MAX = 96; // "0:/fonts/<file>.ttf"
static constexpr int WP_FONT_PATH_MAX = 96; // "0:/os/fonts/<file>.ttf"
static constexpr int WP_FONT_BASENAME_MAX = 48; // filename stem (PDF base font)
static constexpr uint8_t STYLE_BOLD = 0x01;