feat: expand Intel GPU display management

This commit is contained in:
2026-07-30 17:12:08 +01:00
parent 86de3400a9
commit 471903bafb
25 changed files with 1715 additions and 16 deletions
+8 -4
View File
@@ -49,7 +49,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
CUSTOM_BUILDS := 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs libloader crashpad
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
@@ -89,9 +89,9 @@ WPDIR := data/wallpapers
WPSRC := $(wildcard $(WPDIR)/*.jpg)
WPDST := $(patsubst $(WPDIR)/%,$(BINDIR)/os/wallpapers/%,$(WPSRC))
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
.PHONY: all clean 2048 doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs icons fonts configs bearssl libc tls libjpeg libjpegwrite install-apps libloader crashpad check-syscalls gen-syscalls
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
all: bearssl libc libjpeg libjpegwrite tls libloader devkit $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap 2048 doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers timezone printd printctl dialogs login desktop shell icons fonts install-apps crashpad $(MANDST) $(WWWDST) $(CA_CERTS) $(CONFIGDST) $(FWDST) $(LICDST) $(WPDST)
# Build BearSSL static library (cross-compiled for freestanding x86_64).
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
@@ -192,6 +192,10 @@ bluetooth: libc
network: libc
$(MAKE) -C src/network
# Build display/graphics settings applet (depends on libc).
display: libc
$(MAKE) -C src/display
# Build terminal standalone GUI tool (depends on libc).
terminal: libc
$(MAKE) -C src/terminal
@@ -316,7 +320,7 @@ crashpad: libc
$(MAKE) -C src/crashpad
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
install-apps: 2048 doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer audio music video bluetooth network display terminal klog procmgr powermgr calculator charmap screenshot texteditor mandelbrot printers timezone crashpad
../scripts/install_apps.sh
# Copy man pages into bin/man/ so mkramdisk.sh picks them up.
+51
View File
@@ -213,6 +213,12 @@ namespace montauk::abi {
static constexpr uint64_t SYS_STAT = 152;
static constexpr uint64_t SYS_SETUNIXTIME = 153;
// Display/modesetting control
static constexpr uint64_t SYS_DISPLAYINFO = 154;
static constexpr uint64_t SYS_DISPLAYMODES = 155;
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
@@ -312,6 +318,51 @@ namespace montauk::abi {
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
};
enum DisplayConnectorType : uint8_t {
DISPLAY_CONNECTOR_UNKNOWN = 0,
DISPLAY_CONNECTOR_EDP = 1,
DISPLAY_CONNECTOR_DP = 2,
DISPLAY_CONNECTOR_HDMI = 3,
DISPLAY_CONNECTOR_DVI = 4,
DISPLAY_CONNECTOR_VGA = 5,
DISPLAY_CONNECTOR_LVDS = 6,
};
static constexpr uint32_t DISPLAY_CAP_MODESET = 1u << 0;
static constexpr uint32_t DISPLAY_CAP_EDID = 1u << 1;
static constexpr uint32_t DISPLAY_CAP_BRIGHTNESS = 1u << 2;
static constexpr uint32_t DISPLAY_CAP_PAGE_FLIP = 1u << 3;
static constexpr uint32_t DISPLAY_CAP_VBLANK_IRQ = 1u << 4;
static constexpr uint32_t DISPLAY_CAP_SAFE_SWITCH = 1u << 5;
static constexpr uint32_t DISPLAY_MODE_PREFERRED = 1u << 0;
static constexpr uint32_t DISPLAY_MODE_CURRENT = 1u << 1;
static constexpr uint32_t DISPLAY_MODE_DRIVER_VALID = 1u << 2;
struct DisplayModeInfo {
uint32_t width;
uint32_t height;
uint32_t refreshMilliHz;
uint32_t pixelClockKHz;
uint32_t flags;
uint32_t _pad;
};
struct DisplayInfo {
uint32_t capabilities;
uint32_t modeCount;
int32_t currentMode;
int32_t brightness; // 0..100, or -1 when unavailable
uint16_t deviceId;
uint8_t generation;
uint8_t connectorType;
uint8_t connected;
uint8_t _pad0[3];
char gpuName[64];
char connectorName[32];
char monitorName[64];
};
struct SysInfo {
char osName[32];
char osVersion[32];
+39 -5
View File
@@ -161,24 +161,58 @@ struct Canvas {
// ---- Text ----
void text_bitmap(int x, int y, const char* str, Color c, int scale = 1) {
if (!str || !font_data || scale <= 0) return;
uint32_t pixel = c.to_pixel();
int cx = x;
for (int i = 0; str[i]; i++, cx += FONT_WIDTH * scale) {
const uint8_t* glyph =
&font_data[(unsigned char)str[i] * FONT_HEIGHT];
for (int row = 0; row < FONT_HEIGHT; row++) {
uint8_t bits = glyph[row];
if (!bits) continue;
for (int col = 0; col < FONT_WIDTH; col++) {
if (!(bits & (0x80 >> col))) continue;
int px = cx + col * scale;
int py = y + row * scale;
for (int sy = 0; sy < scale; sy++) {
int dy = py + sy;
if (dy < 0 || dy >= h) continue;
for (int sx = 0; sx < scale; sx++) {
int dx = px + sx;
if (dx >= 0 && dx < w) pixels[dy * w + dx] = pixel;
}
}
}
}
}
}
void text(int x, int y, const char* str, Color c) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::UI_SIZE);
if (fonts::system_font->draw_to_buffer(
pixels, w, h, x, y, str, c, fonts::UI_SIZE))
return;
}
text_bitmap(x, y, str, c);
}
void text_2x(int x, int y, const char* str, Color c) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::LARGE_SIZE);
if (fonts::system_font->draw_to_buffer(
pixels, w, h, x, y, str, c, fonts::LARGE_SIZE))
return;
}
text_bitmap(x, y, str, c, 2);
}
void text_mono(int x, int y, const char* str, Color c) {
if (fonts::mono && fonts::mono->valid) {
fonts::mono->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::TERM_SIZE);
return;
if (fonts::mono->draw_to_buffer(
pixels, w, h, x, y, str, c, fonts::TERM_SIZE))
return;
}
text(x, y, str, c);
text_bitmap(x, y, str, c);
}
// ---- Icons ----
+4 -2
View File
@@ -14,8 +14,10 @@ namespace gui {
static constexpr int FONT_WIDTH = 8;
static constexpr int FONT_HEIGHT = 16;
// Defined in font_data.cpp
extern const uint8_t font_data[256 * 16];
// Defined by programs that ship the built-in VGA fallback. Keep the symbol
// weak so Canvas remains usable by small apps that intentionally rely only on
// TrueType fonts, while login/desktop can recover when TrueType setup fails.
extern const uint8_t font_data[256 * 16] __attribute__((weak));
// Dynamic font height: TTF line height or 16 (bitmap fallback)
inline int system_font_height() {
+17 -1
View File
@@ -8,6 +8,7 @@
#include <cstdint>
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/config.h>
#include "gui/gui.hpp"
namespace gui {
@@ -61,6 +62,20 @@ class Framebuffer {
return 0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
static bool prefer_hardware_page_flip() {
char user[64] = {};
if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) {
auto doc = montauk::config::load_user(user, "display");
bool enabled = doc.get_bool("graphics.tear_free", true);
doc.destroy();
return enabled;
}
auto doc = montauk::config::load("display");
bool enabled = doc.get_bool("graphics.tear_free", true);
doc.destroy();
return enabled;
}
public:
Framebuffer() : hw_fb(nullptr), hw_fb2(nullptr), back_buf(nullptr),
fb_width(0), fb_height(0), fb_pitch(0), hw_next(1) {
@@ -76,7 +91,8 @@ public:
// Hardware page flipping: fb_map() maps the second scanout buffer
// directly after the first when the kernel supports flipping.
if (hw_fb && montauk::fb_flip(-1, 0) == 1) {
if (hw_fb && prefer_hardware_page_flip()
&& montauk::fb_flip(-1, 0) == 1) {
uint64_t pages = ((uint64_t)fb_height * fb_pitch + 0xFFF) / 0x1000;
hw_fb2 = (uint32_t*)((uint8_t*)hw_fb + pages * 0x1000);
}
+5 -2
View File
@@ -311,13 +311,14 @@ struct TrueTypeFont {
draw(fb, x, y, text, fg, pixel_size);
}
void draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
bool draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
int x, int y, const char* text,
Color color, int pixel_size) {
if (!valid) return;
if (!valid) return false;
GlyphCache* gc = get_cache(pixel_size);
int cx = x;
int baseline = y + gc->ascent;
bool drew_pixel = false;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
@@ -334,6 +335,7 @@ struct TrueTypeFont {
if (dx < 0 || dx >= buf_w) continue;
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha == 0) continue;
drew_pixel = true;
if (alpha == 255) {
pixels[dy * buf_w + dx] =
@@ -356,6 +358,7 @@ struct TrueTypeFont {
}
cx += g->advance;
}
return drew_pixel;
}
// Draw text to buffer with clip rectangle (pixels outside clip_x..clip_x+clip_w are not drawn)
+4
View File
@@ -177,6 +177,10 @@ extern "C" {
#define MTK_SYS_GETEXECPATH 151
#define MTK_SYS_STAT 152
#define MTK_SYS_SETUNIXTIME 153
#define MTK_SYS_DISPLAYINFO 154
#define MTK_SYS_DISPLAYMODES 155
#define MTK_SYS_DISPLAYSETMODE 156
#define MTK_SYS_DISPLAYBRIGHTNESS 157
/* @SYSCALLS-END */
#define MTK_SOCK_TCP 1
+16
View File
@@ -343,6 +343,22 @@ namespace montauk {
return syscall2(montauk::abi::SYS_FBFLIP, (uint64_t)index, flags);
}
inline int display_info(montauk::abi::DisplayInfo* out) {
return (int)syscall1(montauk::abi::SYS_DISPLAYINFO, (uint64_t)out);
}
inline int display_modes(montauk::abi::DisplayModeInfo* out, int maxCount) {
return (int)syscall2(montauk::abi::SYS_DISPLAYMODES,
(uint64_t)out, (uint64_t)maxCount);
}
inline int display_set_mode(int modeIndex) {
return (int)syscall1(montauk::abi::SYS_DISPLAYSETMODE,
(uint64_t)modeIndex);
}
inline int display_brightness(int percent = -1) {
return (int)syscall1(montauk::abi::SYS_DISPLAYBRIGHTNESS,
(uint64_t)(int64_t)percent);
}
// Arguments
inline int getargs(char* buf, uint64_t maxLen) {
return (int)syscall2(montauk::abi::SYS_GETARGS, (uint64_t)buf, maxLen);
+66
View File
@@ -0,0 +1,66 @@
# Makefile for display (Display and Graphics settings applet) 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/display/display.elf
LIBS := $(LIBDIR)/libc/liblibc.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBS)
mkdir -p $(BINDIR)/apps/display
$(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)
+661
View File
@@ -0,0 +1,661 @@
/*
* main.cpp
* MontaukOS Display and Graphics settings
* Copyright (c) 2026 Daniel Hammer
*/
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/config.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 = 620;
static constexpr int WIN_H = 500;
static constexpr int TAB_H = 36;
static constexpr int FOOTER_H = 44;
static constexpr int PAD = 18;
static constexpr int GAP = 12;
static constexpr int BUTTON_W = 88;
static constexpr int SLIDER_H = 6;
static constexpr int KNOB_R = 8;
static constexpr int MODE_ROW_H = 36;
static constexpr int VISIBLE_MODES = 6;
static constexpr int MAX_MODES = 32;
static constexpr int INFO_LABEL_W = 175;
enum Tab {
TAB_DISPLAY = 0,
TAB_GRAPHICS = 1,
TAB_COUNT = 2,
};
static const char* const kTabLabels[TAB_COUNT] = {
"Display",
"Graphics",
};
static WsWindow g_win;
static Tab g_tab = TAB_DISPLAY;
static int g_mouse_x = -1;
static int g_mouse_y = -1;
static bool g_brightness_drag = false;
static Color g_accent = colors::ACCENT;
static montauk::abi::DisplayInfo g_info = {};
static montauk::abi::DisplayModeInfo g_modes[MAX_MODES] = {};
static int g_mode_count = 0;
static bool g_driver_available = false;
static int g_selected_mode = -1;
static int g_pending_brightness = -1;
static bool g_tear_free = true;
static bool g_saved_tear_free = true;
static int g_mode_scroll = 0;
static char g_status[128] = {};
static uint64_t g_status_time = 0;
static uint64_t g_last_refresh = 0;
static mtk::Theme app_theme() {
mtk::Theme theme = mtk::make_theme(g_accent);
theme.danger_soft = Color::from_rgb(0xF8, 0xE0, 0xE0);
return theme;
}
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 int display_heading_y() {
return TAB_H + 20;
}
static int brightness_row_y() {
return display_heading_y() + system_font_height() + 18;
}
static Rect brightness_slider_rect() {
int y = brightness_row_y() + (system_font_height() - SLIDER_H) / 2;
return {PAD + INFO_LABEL_W, y,
g_win.width - PAD * 2 - INFO_LABEL_W - 44, SLIDER_H};
}
static int modes_separator_y() {
return brightness_row_y() + system_font_height() + 26;
}
static int modes_header_y() {
return modes_separator_y() + 16;
}
static int mode_list_y() {
return modes_header_y() + system_font_height() + 8;
}
static Rect mode_row_rect(int visibleIndex) {
return {PAD, mode_list_y() + visibleIndex * MODE_ROW_H,
g_win.width - PAD * 2, MODE_ROW_H};
}
static Rect apply_button() {
Rect foot = footer_rect();
return {foot.x + foot.w - PAD - BUTTON_W, foot.y + 7, BUTTON_W, 30};
}
static Rect revert_button() {
Rect apply = apply_button();
return {apply.x - GAP - BUTTON_W, apply.y, BUTTON_W, apply.h};
}
static Rect refresh_button() {
Rect apply = apply_button();
return {apply.x - GAP - BUTTON_W, apply.y, BUTTON_W, apply.h};
}
static int graphics_table_y() {
return TAB_H + 24 + system_font_height() + 18;
}
static int graphics_row_step() {
return system_font_height() + 10;
}
static int graphics_separator_y() {
return graphics_table_y() + graphics_row_step() * 5 + 4;
}
static int graphics_section_y() {
return graphics_separator_y() + 16;
}
static Rect tear_free_option() {
return {PAD, graphics_section_y() + system_font_height() + 8,
g_win.width - PAD * 2, 24};
}
static bool mouse_in(const Rect& rect) {
return rect.contains(g_mouse_x, g_mouse_y);
}
static mtk::WidgetState button_state(const Rect& rect, bool enabled = true) {
return mtk::widget_state(false, mouse_in(rect), enabled);
}
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;
}
static void load_graphics_preference() {
char user[64] = {};
if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) {
auto doc = montauk::config::load_user(user, "display");
g_tear_free = doc.get_bool("graphics.tear_free", true);
doc.destroy();
} else {
auto doc = montauk::config::load("display");
g_tear_free = doc.get_bool("graphics.tear_free", true);
doc.destroy();
}
g_saved_tear_free = g_tear_free;
}
static bool save_graphics_preference() {
montauk::toml::Doc doc;
doc.init();
montauk::config::set_bool(&doc, "graphics.tear_free", g_tear_free);
char user[64] = {};
int result;
if (montauk::getuser(user, sizeof(user)) > 0 && user[0])
result = montauk::config::save_user(user, "display", &doc);
else
result = montauk::config::save("display", &doc);
doc.destroy();
if (result >= 0) g_saved_tear_free = g_tear_free;
return result >= 0;
}
static void clamp_mode_scroll() {
int max_scroll = g_mode_count > VISIBLE_MODES
? g_mode_count - VISIBLE_MODES : 0;
if (g_mode_scroll < 0) g_mode_scroll = 0;
if (g_mode_scroll > max_scroll) g_mode_scroll = max_scroll;
}
static void refresh_state(bool resetPending) {
montauk::abi::DisplayInfo next = {};
if (montauk::display_info(&next) < 0) {
g_driver_available = false;
g_mode_count = 0;
g_last_refresh = montauk::get_milliseconds();
return;
}
g_info = next;
int count = montauk::display_modes(g_modes, MAX_MODES);
g_mode_count = count > 0 ? count : 0;
g_driver_available = true;
if (resetPending) {
g_selected_mode = g_info.currentMode;
g_pending_brightness = g_info.brightness;
if (g_selected_mode >= VISIBLE_MODES)
g_mode_scroll = g_selected_mode - VISIBLE_MODES + 1;
}
clamp_mode_scroll();
g_last_refresh = montauk::get_milliseconds();
}
static bool dirty() {
bool display_dirty = g_driver_available
&& (g_selected_mode != g_info.currentMode
|| (g_info.brightness >= 0
&& g_pending_brightness != g_info.brightness));
return display_dirty || g_tear_free != g_saved_tear_free;
}
static void format_mode(char* out, int cap,
const montauk::abi::DisplayModeInfo& mode) {
uint32_t hz = (mode.refreshMilliHz + 500) / 1000;
snprintf(out, cap, "%ux%u %u Hz",
(unsigned)mode.width, (unsigned)mode.height, (unsigned)hz);
}
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_kv(Canvas& c, int* y, const char* key, const char* value,
const mtk::Theme& theme) {
c.text(PAD, *y, key, theme.text_muted);
draw_text_fit(c, PAD + INFO_LABEL_W, *y, value,
g_win.width - PAD * 2 - INFO_LABEL_W, theme.text);
*y += graphics_row_step();
}
static void draw_slider(Canvas& c, const Rect& rect, int value,
const mtk::Theme& theme) {
Color track = mtk::mix(theme.surface, theme.border, 80);
c.fill_rounded_rect(rect.x, rect.y, rect.w, rect.h, rect.h / 2, track);
int fill_w = (value * rect.w) / 100;
if (fill_w > 0)
c.fill_rounded_rect(rect.x, rect.y, fill_w, rect.h,
rect.h / 2, theme.accent);
int kx = rect.x + fill_w;
int ky = rect.y + rect.h / 2;
fill_circle(c, kx, ky, KNOB_R, theme.accent);
fill_circle(c, kx, ky, KNOB_R - 3, colors::WHITE);
}
static void draw_display_tab(Canvas& c, const mtk::Theme& theme) {
int y = display_heading_y();
if (!g_driver_available) {
c.text(PAD, y, "Intel display driver unavailable", theme.danger);
c.text(PAD, y + 28,
"The firmware framebuffer remains active, but hardware controls are unavailable.",
theme.text_subtle);
return;
}
char headline[128];
snprintf(headline, sizeof(headline), "%s on %s",
g_info.monitorName[0] ? g_info.monitorName : "Display",
g_info.connectorName[0] ? g_info.connectorName : "unknown connector");
draw_text_fit(c, PAD, y, headline, g_win.width - PAD * 2,
g_info.connected ? theme.text : theme.danger);
y = brightness_row_y();
c.text(PAD, y, "Brightness", theme.text_subtle);
if (g_info.brightness >= 0) {
Rect slider = brightness_slider_rect();
draw_slider(c, slider, g_pending_brightness, theme);
char percent[16];
snprintf(percent, sizeof(percent), "%d%%", g_pending_brightness);
c.text(slider.x + slider.w + 14, slider.y - 6, percent, theme.text);
} else {
draw_text_fit(c, PAD + INFO_LABEL_W, y,
"Controlled by the external display",
g_win.width - PAD * 2 - INFO_LABEL_W, theme.text_muted);
}
y = modes_separator_y();
mtk::draw_separator(c, PAD, y, g_win.width - PAD * 2, theme);
y = modes_header_y();
c.text(PAD, y, "AVAILABLE MODES", theme.text_muted);
const char* hint = "Grey modes require link training";
c.text(g_win.width - PAD - text_width(hint), y, hint, theme.text_muted);
for (int row = 0; row < VISIBLE_MODES; row++) {
int index = g_mode_scroll + row;
if (index >= g_mode_count) break;
Rect option = mode_row_rect(row);
bool valid = (g_modes[index].flags
& montauk::abi::DISPLAY_MODE_DRIVER_VALID) != 0;
bool selected = index == g_selected_mode;
if (selected)
c.fill_rounded_rect(option.x, option.y + 2, option.w, option.h - 4,
4, theme.accent_soft);
Rect radio = {option.x + 4, option.y, 18, option.h};
mtk::draw_radio(c, radio, "", selected, theme);
char mode_text[64];
format_mode(mode_text, sizeof(mode_text), g_modes[index]);
Color text = valid ? theme.text : theme.text_muted;
int textY = option.y + (option.h - system_font_height()) / 2;
c.text(option.x + 34, textY, mode_text, text);
const char* state = nullptr;
Color stateColor = theme.text_subtle;
if (g_modes[index].flags & montauk::abi::DISPLAY_MODE_CURRENT) {
state = "Current";
stateColor = theme.accent;
} else if (g_modes[index].flags & montauk::abi::DISPLAY_MODE_PREFERRED) {
state = "Preferred";
} else if (!valid) {
state = "Link training required";
stateColor = theme.text_muted;
}
if (state)
c.text(option.x + option.w - text_width(state) - 10,
textY, state, stateColor);
}
if (g_mode_count == 0) {
c.text(PAD + 4, mode_list_y() + 9,
"No EDID modes were reported; the firmware mode remains active.",
theme.text_subtle);
}
if (g_mode_count > VISIBLE_MODES) {
char page[48];
snprintf(page, sizeof(page), "Modes %d-%d of %d (scroll)",
g_mode_scroll + 1,
g_mode_scroll + VISIBLE_MODES < g_mode_count
? g_mode_scroll + VISIBLE_MODES : g_mode_count,
g_mode_count);
c.text(PAD, footer_rect().y - 22, page, theme.text_muted);
}
}
static const char* yes_no(bool value) {
return value ? "Available" : "Unavailable";
}
static void draw_graphics_tab(Canvas& c, const mtk::Theme& theme) {
int y = TAB_H + 24;
c.text(PAD, y, "Intel Graphics", theme.text);
y = graphics_table_y();
if (!g_driver_available) {
c.text(PAD, y, "No managed Intel GPU was detected.", theme.danger);
return;
}
char device[96];
snprintf(device, sizeof(device), "%s (Gen %u, PCI %04X)",
g_info.gpuName, (unsigned)g_info.generation,
(unsigned)g_info.deviceId);
draw_kv(c, &y, "Adapter", device, theme);
draw_kv(c, &y, "EDID modes",
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_EDID) != 0),
theme);
draw_kv(c, &y, "Page flipping",
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_PAGE_FLIP) != 0),
theme);
draw_kv(c, &y, "VBlank interrupt",
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_VBLANK_IRQ) != 0),
theme);
draw_kv(c, &y, "Safe mode switch",
yes_no((g_info.capabilities & montauk::abi::DISPLAY_CAP_SAFE_SWITCH) != 0),
theme);
y = graphics_separator_y();
mtk::draw_separator(c, PAD, y, g_win.width - PAD * 2, theme);
y = graphics_section_y();
c.text(PAD, y, "PRESENTATION", theme.text_muted);
Rect option = tear_free_option();
bool flipAvailable =
(g_info.capabilities & montauk::abi::DISPLAY_CAP_PAGE_FLIP) != 0;
mtk::draw_checkbox(c, option, "Prefer tear-free hardware page flipping",
mtk::check_state(g_tear_free), theme,
flipAvailable, mouse_in(option));
c.text(option.x + 24, option.y + option.h + 2,
flipAvailable
? "Applied when the next desktop session starts."
: "Unavailable on this boot; the software copy path is active.",
flipAvailable ? theme.text_subtle : theme.text_muted);
int safetySeparator = option.y + option.h + system_font_height() + 18;
mtk::draw_separator(c, PAD, safetySeparator, g_win.width - PAD * 2, theme);
c.text(PAD, safetySeparator + 16, "MODESETTING SAFETY", theme.text_muted);
c.text(PAD, safetySeparator + 16 + system_font_height() + 8,
"The driver keeps the firmware-trained display link and only exposes modes",
theme.text_subtle);
c.text(PAD, safetySeparator + 16 + system_font_height() * 2 + 10,
"that can be changed without unsafe PLL or DisplayPort retraining.",
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);
const char* message = status_visible()
? g_status : (dirty() ? "Unsaved display changes" : "Display settings ready");
c.text(PAD, foot.y + (foot.h - system_font_height()) / 2,
message, dirty() ? theme.text : theme.text_subtle);
if (g_tab == TAB_DISPLAY || dirty()) {
Rect revert = revert_button();
Rect apply = apply_button();
mtk::draw_button(c, revert, "Revert", mtk::BUTTON_SECONDARY,
button_state(revert, dirty()), theme);
mtk::draw_button(c, apply, "Apply", mtk::BUTTON_PRIMARY,
button_state(apply, dirty()), theme);
} else {
Rect refresh = refresh_button();
mtk::draw_button(c, refresh, "Refresh", mtk::BUTTON_SECONDARY,
button_state(refresh), theme);
}
}
static void render() {
mtk::StandaloneHost host(&g_win);
Canvas c = host.canvas();
mtk::Theme theme = app_theme();
c.fill(theme.window_bg);
mtk::draw_tab_bar(c, tab_bar_rect(), kTabLabels, TAB_COUNT, (int)g_tab, theme);
if (g_tab == TAB_DISPLAY) draw_display_tab(c, theme);
else draw_graphics_tab(c, theme);
draw_footer(c, theme);
host.present();
}
static int brightness_from_x(int mx) {
Rect slider = brightness_slider_rect();
int value = ((mx - slider.x) * 100) / slider.w;
if (value < 0) value = 0;
if (value > 100) value = 100;
return value;
}
static bool slider_hit(int mx, int my) {
Rect slider = brightness_slider_rect();
return mx >= slider.x - KNOB_R && mx <= slider.x + slider.w + KNOB_R
&& my >= slider.y - KNOB_R - 4 && my <= slider.y + slider.h + KNOB_R + 4;
}
static void revert_changes() {
g_selected_mode = g_info.currentMode;
g_pending_brightness = g_info.brightness;
g_tear_free = g_saved_tear_free;
set_status("Changes reverted");
}
static void apply_changes() {
if (!dirty()) return;
bool graphics_changed = g_tear_free != g_saved_tear_free;
if (g_driver_available && g_info.brightness >= 0
&& g_pending_brightness != g_info.brightness) {
int result = montauk::display_brightness(g_pending_brightness);
if (result < 0) {
set_status("Could not change panel brightness");
return;
}
}
if (g_driver_available && g_selected_mode != g_info.currentMode) {
int result = montauk::display_set_mode(g_selected_mode);
if (result < 0) {
set_status(result == -2
? "Mode needs unsupported display-link training"
: "Mode change failed; previous mode restored");
refresh_state(true);
return;
}
}
if (graphics_changed && !save_graphics_preference()) {
set_status("Could not save graphics preference");
return;
}
refresh_state(true);
set_status(graphics_changed
? "Saved; takes effect next desktop session"
: "Display settings applied");
}
static bool handle_mouse(const montauk::abi::WinEvent& ev) {
int mx = ev.mouse.x;
int my = ev.mouse.y;
uint8_t buttons = ev.mouse.buttons;
uint8_t previous = ev.mouse.prev_buttons;
bool pressed = (buttons & 1) && !(previous & 1);
bool released = !(buttons & 1) && (previous & 1);
if (pressed) {
int tab = mtk::hit_tab_bar(tab_bar_rect(), TAB_COUNT, mx, my);
if (tab >= 0) {
g_tab = (Tab)tab;
return true;
}
}
if (g_tab == TAB_DISPLAY && g_info.brightness >= 0) {
if (pressed && slider_hit(mx, my)) g_brightness_drag = true;
if (g_brightness_drag && (buttons & 1))
g_pending_brightness = brightness_from_x(mx);
if (released) g_brightness_drag = false;
}
if (g_tab == TAB_DISPLAY && ev.mouse.scroll != 0) {
g_mode_scroll += ev.mouse.scroll > 0 ? -1 : 1;
clamp_mode_scroll();
return true;
}
if (!pressed) return g_brightness_drag;
if (g_tab == TAB_DISPLAY) {
for (int row = 0; row < VISIBLE_MODES; row++) {
int index = g_mode_scroll + row;
if (index >= g_mode_count) break;
if (mode_row_rect(row).contains(mx, my)
&& (g_modes[index].flags
& montauk::abi::DISPLAY_MODE_DRIVER_VALID)) {
g_selected_mode = index;
return true;
}
}
} else if (tear_free_option().contains(mx, my)
&& (g_info.capabilities
& montauk::abi::DISPLAY_CAP_PAGE_FLIP)) {
g_tear_free = !g_tear_free;
return true;
}
if (revert_button().contains(mx, my) && dirty()) {
revert_changes();
return true;
}
if (apply_button().contains(mx, my) && dirty()) {
apply_changes();
return true;
}
if (refresh_button().contains(mx, my) && !dirty()) {
refresh_state(true);
set_status("Display status refreshed");
return true;
}
return false;
}
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 (key.ascii == '\t') {
g_tab = g_tab == TAB_DISPLAY ? TAB_GRAPHICS : TAB_DISPLAY;
return true;
}
if (key.ascii == '\n' || key.ascii == '\r') {
apply_changes();
return true;
}
if (key.ascii == 'r' || key.ascii == 'R') {
refresh_state(!dirty());
set_status("Display status refreshed");
return true;
}
return false;
}
extern "C" void _start() {
if (!fonts::init()) montauk::exit(1);
g_accent = mtk::load_system_accent();
load_graphics_preference();
if (!g_win.create("Display", WIN_W, WIN_H)) montauk::exit(1);
refresh_state(true);
render();
while (g_win.id >= 0 && !g_win.closed) {
montauk::abi::WinEvent ev;
int result = g_win.poll(&ev);
bool redraw = false;
if (result < 0) break;
if (result == 0) {
uint64_t now = montauk::get_milliseconds();
if (!dirty() && now - g_last_refresh >= 3000) {
refresh_state(true);
redraw = true;
}
if (status_visible()) redraw = true;
if (redraw) render();
montauk::sleep_ms(16);
continue;
}
if (ev.type == 3) break;
if (ev.type == 2 || ev.type == 4) {
redraw = true;
} else if (ev.type == 1) {
g_mouse_x = ev.mouse.x;
g_mouse_y = ev.mouse.y;
redraw = true;
handle_mouse(ev);
} 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 = "display"
name = "Display"
binary = "display.elf"
icon = "preferences-desktop-display.svg"
[menu]
category = "System"
visible = false
[desktop]
section = "settings"
admin_only = false
@@ -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>