feat: TrueType (TTF) font rendering, many new desktop applications and DOOM support, among other improvements

This commit is contained in:
2026-02-20 22:46:41 +01:00
parent a0db5899ef
commit 596be25eaf
124 changed files with 9021 additions and 355 deletions
+2 -2
View File
@@ -9,6 +9,6 @@ toolchain/build/
toolchain/src/ toolchain/src/
programs/bin/ programs/bin/
programs/obj/ programs/obj/
programs/src/doom/obj/ programs/src/*/obj/
programs/lib/libc/liblibc.a
programs/gui/icons/ programs/gui/icons/
programs/src/desktop/obj/
+250 -1
View File
@@ -29,6 +29,12 @@
#include <Hal/GDT.hpp> #include <Hal/GDT.hpp>
#include <Graphics/Cursor.hpp> #include <Graphics/Cursor.hpp>
#include "../Libraries/flanterm/src/flanterm.h" #include "../Libraries/flanterm/src/flanterm.h"
#include "WinServer.hpp"
#include <Pci/Pci.hpp>
#include <Drivers/USB/Xhci.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Drivers/PS2/PS2Controller.hpp>
#include <Hal/Apic/ApicInit.hpp>
// Assembly entry point // Assembly entry point
extern "C" void SyscallEntry(); extern "C" void SyscallEntry();
@@ -605,6 +611,228 @@ namespace Zenith {
return 0; return 0;
} }
// ---- Process listing / kill ----
static int Sys_ProcList(ProcInfo* buf, int maxCount) {
if (buf == nullptr || maxCount <= 0) return 0;
int count = 0;
for (int i = 0; i < Sched::MaxProcesses && count < maxCount; i++) {
auto* proc = Sched::GetProcessSlot(i);
if (!proc || proc->state == Sched::ProcessState::Free) continue;
buf[count].pid = (int32_t)proc->pid;
buf[count].parentPid = (int32_t)proc->parentPid;
buf[count].state = (uint8_t)proc->state;
buf[count]._pad[0] = 0;
buf[count]._pad[1] = 0;
buf[count]._pad[2] = 0;
{
int j = 0;
for (; j < 63 && proc->name[j]; j++)
buf[count].name[j] = proc->name[j];
buf[count].name[j] = '\0';
}
buf[count].heapUsed = (proc->heapNext > Sched::UserHeapBase)
? proc->heapNext - Sched::UserHeapBase : 0;
count++;
}
return count;
}
static int Sys_Kill(int pid) {
// Refuse to kill PID 0 (init)
if (pid == 0) return -1;
// Refuse to kill the caller's own process
if (pid == Sched::GetCurrentPid()) return -1;
auto* proc = Sched::GetProcessByPid(pid);
if (!proc) return -1;
// Clean up any windows owned by this process
WinServer::CleanupProcess(pid);
proc->state = Sched::ProcessState::Terminated;
return 0;
}
// ---- Device list ----
static void dl_strcpy(char* dst, const char* src, int max) {
int i = 0;
for (; i < max - 1 && src[i]; i++) dst[i] = src[i];
dst[i] = '\0';
}
static int dl_append(char* dst, int pos, const char* src, int max) {
for (int i = 0; src[i] && pos < max - 1; i++) dst[pos++] = src[i];
dst[pos] = '\0';
return pos;
}
static int dl_append_hex(char* dst, int pos, unsigned val, int digits, int max) {
const char* hex = "0123456789abcdef";
char tmp[8];
for (int i = digits - 1; i >= 0; i--) { tmp[i] = hex[val & 0xF]; val >>= 4; }
for (int i = 0; i < digits && pos < max - 1; i++) dst[pos++] = tmp[i];
dst[pos] = '\0';
return pos;
}
static int dl_append_dec(char* dst, int pos, int val, int max) {
if (val == 0) { if (pos < max - 1) dst[pos++] = '0'; dst[pos] = '\0'; return pos; }
char tmp[12]; int i = 0;
while (val > 0) { tmp[i++] = '0' + (val % 10); val /= 10; }
while (i > 0 && pos < max - 1) dst[pos++] = tmp[--i];
dst[pos] = '\0';
return pos;
}
static int Sys_DevList(DevInfo* buf, int maxCount) {
if (buf == nullptr || maxCount <= 0) return 0;
int count = 0;
auto add = [&](uint8_t cat, const char* name, const char* detail) {
if (count >= maxCount) return;
buf[count].category = cat;
buf[count]._pad[0] = 0; buf[count]._pad[1] = 0; buf[count]._pad[2] = 0;
dl_strcpy(buf[count].name, name, 48);
dl_strcpy(buf[count].detail, detail, 48);
count++;
};
// CPU cores (category 0)
int cpuCount = Hal::GetDetectedCpuCount();
if (cpuCount > 0) {
char detail[48];
int p = 0;
p = dl_append(detail, p, "x86_64, ", 48);
p = dl_append_dec(detail, p, cpuCount, 48);
p = dl_append(detail, p, " core(s)", 48);
add(0, "Processor", detail);
}
// Interrupt controllers (category 1)
add(1, "Local APIC", "Per-CPU interrupt controller");
add(1, "I/O APIC", "System interrupt router");
// Timer (category 2)
add(2, "LAPIC Timer", "Local APIC periodic timer");
// PS/2 Input (category 3)
add(3, "PS/2 Keyboard", "IRQ 1, scan code set 1");
if (Drivers::PS2::IsDualChannel()) {
add(3, "PS/2 Mouse", "IRQ 12, dual-channel 8042");
}
// USB devices (category 4)
if (Drivers::USB::Xhci::IsInitialized()) {
for (uint8_t slot = 1; slot <= Drivers::USB::Xhci::MAX_SLOTS && count < maxCount; slot++) {
auto* dev = Drivers::USB::Xhci::GetDevice(slot);
if (!dev || !dev->Active) continue;
const char* devName = "USB Device";
if (dev->InterfaceClass == 3) {
if (dev->InterfaceProtocol == 1) devName = "USB HID Keyboard";
else if (dev->InterfaceProtocol == 2) devName = "USB HID Mouse";
else devName = "USB HID Device";
} else if (dev->InterfaceClass == 8) {
devName = "USB Mass Storage";
} else if (dev->InterfaceClass == 9) {
devName = "USB Hub";
}
char detail[48];
int p = 0;
p = dl_append(detail, p, "Port ", 48);
p = dl_append_dec(detail, p, dev->PortId, 48);
p = dl_append(detail, p, ", VID:", 48);
p = dl_append_hex(detail, p, dev->VendorId, 4, 48);
p = dl_append(detail, p, " PID:", 48);
p = dl_append_hex(detail, p, dev->ProductId, 4, 48);
add(4, devName, detail);
}
}
// Network (category 5)
if (Drivers::Net::E1000::IsInitialized()) {
add(5, "Intel E1000", "Gigabit Ethernet (82540EM)");
}
if (Drivers::Net::E1000E::IsInitialized()) {
add(5, "Intel E1000E", "Gigabit Ethernet (82574L)");
}
// Display (category 6)
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
auto* gpu = Drivers::Graphics::IntelGPU::GetGpuInfo();
if (gpu) {
add(6, gpu->name, "Intel Integrated Graphics");
}
}
// PCI devices (category 7)
auto& pciDevs = Pci::GetDevices();
for (int i = 0; i < (int)pciDevs.size() && count < maxCount; i++) {
auto& d = pciDevs[i];
const char* className = Pci::GetClassName(d.ClassCode, d.SubClass);
char detail[48];
int p = 0;
p = dl_append_hex(detail, p, d.Bus, 2, 48);
p = dl_append(detail, p, ":", 48);
p = dl_append_hex(detail, p, d.Device, 2, 48);
p = dl_append(detail, p, ".", 48);
p = dl_append_dec(detail, p, d.Function, 48);
p = dl_append(detail, p, " ", 48);
p = dl_append_hex(detail, p, d.VendorId, 4, 48);
p = dl_append(detail, p, ":", 48);
p = dl_append_hex(detail, p, d.DeviceId, 4, 48);
add(7, className, detail);
}
return count;
}
// ---- Window server syscalls ----
static int Sys_WinCreate(const char* title, int w, int h, WinCreateResult* result) {
if (result == nullptr || title == nullptr) return -1;
auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr) return -1;
uint64_t outVa = 0;
int id = WinServer::Create(proc->pid, proc->pml4Phys, title, w, h,
proc->heapNext, outVa);
result->id = id;
result->pixelVa = (id >= 0) ? outVa : 0;
return id >= 0 ? 0 : -1;
}
static int Sys_WinDestroy(int windowId) {
return WinServer::Destroy(windowId, Sched::GetCurrentPid());
}
static int Sys_WinPresent(int windowId) {
return WinServer::Present(windowId, Sched::GetCurrentPid());
}
static int Sys_WinPoll(int windowId, WinEvent* outEvent) {
if (outEvent == nullptr) return -1;
return WinServer::Poll(windowId, Sched::GetCurrentPid(), outEvent);
}
static int Sys_WinEnum(WinInfo* outArray, int maxCount) {
if (outArray == nullptr || maxCount <= 0) return 0;
return WinServer::Enumerate(outArray, maxCount);
}
static uint64_t Sys_WinMap(int windowId) {
auto* proc = Sched::GetCurrentProcessPtr();
if (proc == nullptr) return 0;
return WinServer::Map(windowId, proc->pid, proc->pml4Phys, proc->heapNext);
}
static int Sys_WinSendEvent(int windowId, const WinEvent* event) {
if (event == nullptr) return -1;
return WinServer::SendEvent(windowId, event);
}
// ---- Dispatch ---- // ---- Dispatch ----
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) { extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
@@ -743,6 +971,27 @@ namespace Zenith {
return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEvent*)frame->arg2); return (int64_t)Sys_ChildIoWriteKey((int)frame->arg1, (const KeyEvent*)frame->arg2);
case SYS_CHILDIO_SETTERMSZ: case SYS_CHILDIO_SETTERMSZ:
return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3); return (int64_t)Sys_ChildIoSetTermsz((int)frame->arg1, (int)frame->arg2, (int)frame->arg3);
case SYS_WINCREATE:
return (int64_t)Sys_WinCreate((const char*)frame->arg1, (int)frame->arg2,
(int)frame->arg3, (WinCreateResult*)frame->arg4);
case SYS_WINDESTROY:
return (int64_t)Sys_WinDestroy((int)frame->arg1);
case SYS_WINPRESENT:
return (int64_t)Sys_WinPresent((int)frame->arg1);
case SYS_WINPOLL:
return (int64_t)Sys_WinPoll((int)frame->arg1, (WinEvent*)frame->arg2);
case SYS_WINENUM:
return (int64_t)Sys_WinEnum((WinInfo*)frame->arg1, (int)frame->arg2);
case SYS_WINMAP:
return (int64_t)Sys_WinMap((int)frame->arg1);
case SYS_WINSENDEVENT:
return (int64_t)Sys_WinSendEvent((int)frame->arg1, (const WinEvent*)frame->arg2);
case SYS_PROCLIST:
return (int64_t)Sys_ProcList((ProcInfo*)frame->arg1, (int)frame->arg2);
case SYS_KILL:
return (int64_t)Sys_Kill((int)frame->arg1);
case SYS_DEVLIST:
return (int64_t)Sys_DevList((DevInfo*)frame->arg1, (int)frame->arg2);
default: default:
return -1; return -1;
} }
@@ -769,7 +1018,7 @@ namespace Zenith {
Hal::WriteMSR(Hal::IA32_FMASK, 0x200); Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 53 syscalls)"; << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 64 syscalls)";
} }
} }
+56
View File
@@ -66,6 +66,20 @@ namespace Zenith {
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52; static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53; static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
// Window server syscalls
static constexpr uint64_t SYS_WINCREATE = 54;
static constexpr uint64_t SYS_WINDESTROY = 55;
static constexpr uint64_t SYS_WINPRESENT = 56;
static constexpr uint64_t SYS_WINPOLL = 57;
static constexpr uint64_t SYS_WINENUM = 58;
static constexpr uint64_t SYS_WINMAP = 59;
static constexpr uint64_t SYS_WINSENDEVENT = 60;
// Process management syscalls
static constexpr uint64_t SYS_PROCLIST = 61;
static constexpr uint64_t SYS_KILL = 62;
static constexpr uint64_t SYS_DEVLIST = 63;
static constexpr int SOCK_TCP = 1; static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2; static constexpr int SOCK_UDP = 2;
@@ -118,6 +132,48 @@ namespace Zenith {
uint8_t buttons; uint8_t buttons;
}; };
// Window server shared types
struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close
uint8_t _pad[3];
union {
KeyEvent key;
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
struct { int32_t w, h; } resize;
};
};
struct WinInfo {
int32_t id;
int32_t ownerPid;
char title[64];
int32_t width, height;
uint8_t dirty;
uint8_t _pad[3];
};
struct WinCreateResult {
int32_t id; // -1 on failure
uint32_t _pad;
uint64_t pixelVa; // VA of pixel buffer in caller's address space
};
struct DevInfo {
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI
uint8_t _pad[3];
char name[48];
char detail[48];
};
struct ProcInfo {
int32_t pid;
int32_t parentPid;
uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated
uint8_t _pad[3];
char name[64];
uint64_t heapUsed; // heapNext - UserHeapBase (bytes)
};
// Stack frame pushed by SyscallEntry.asm // Stack frame pushed by SyscallEntry.asm
struct SyscallFrame { struct SyscallFrame {
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
+175
View File
@@ -0,0 +1,175 @@
/*
* WinServer.cpp
* Window server kernel implementation for external process windows
* Copyright (c) 2026 Daniel Hammer
*/
#include "WinServer.hpp"
#include <Memory/PageFrameAllocator.hpp>
#include <Memory/Paging.hpp>
#include <Memory/HHDM.hpp>
#include <Libraries/Memory.hpp>
#include <Terminal/Terminal.hpp>
namespace WinServer {
static WindowSlot g_slots[MaxWindows];
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
uint64_t& heapNext, uint64_t& outVa) {
// Find a free slot
int slotIdx = -1;
for (int i = 0; i < MaxWindows; i++) {
if (!g_slots[i].used) {
slotIdx = i;
break;
}
}
if (slotIdx < 0) return -1;
// Validate dimensions
if (w <= 0 || h <= 0) return -1;
uint64_t bufSize = (uint64_t)w * h * 4;
int numPages = (int)((bufSize + 0xFFF) / 0x1000);
if (numPages > MaxPixelPages) return -1;
WindowSlot& slot = g_slots[slotIdx];
memset(&slot, 0, sizeof(WindowSlot));
slot.used = true;
slot.ownerPid = ownerPid;
slot.width = w;
slot.height = h;
slot.pixelNumPages = numPages;
slot.eventHead = 0;
slot.eventTail = 0;
slot.dirty = false;
slot.desktopVa = 0;
slot.desktopPid = 0;
// Copy title
int tlen = 0;
while (title[tlen] && tlen < 63) {
slot.title[tlen] = title[tlen];
tlen++;
}
slot.title[tlen] = '\0';
// Allocate physical pages and map into owner's address space
uint64_t userVa = heapNext;
for (int i = 0; i < numPages; i++) {
void* page = Memory::g_pfa->AllocateZeroed();
if (page == nullptr) {
// Cleanup on failure - mark slot unused
slot.used = false;
return -1;
}
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
slot.pixelPhysPages[i] = physAddr;
Memory::VMM::Paging::MapUserIn(ownerPml4, physAddr, userVa + (uint64_t)i * 0x1000);
}
slot.ownerVa = userVa;
heapNext += (uint64_t)numPages * 0x1000;
outVa = userVa;
Kt::KernelLogStream(Kt::OK, "WinServer") << "Created window " << slotIdx
<< " (" << w << "x" << h << ") for PID " << ownerPid;
return slotIdx;
}
int Destroy(int windowId, int callerPid) {
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
slot.used = false;
return 0;
}
int Present(int windowId, int callerPid) {
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
slot.dirty = true;
return 0;
}
int Poll(int windowId, int callerPid, Zenith::WinEvent* outEvent) {
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used || slot.ownerPid != callerPid) return -1;
if (slot.eventHead == slot.eventTail) return 0; // no events
*outEvent = slot.events[slot.eventTail];
slot.eventTail = (slot.eventTail + 1) % MaxEvents;
return 1;
}
int Enumerate(Zenith::WinInfo* outArray, int maxCount) {
int count = 0;
for (int i = 0; i < MaxWindows && count < maxCount; i++) {
if (!g_slots[i].used) continue;
Zenith::WinInfo& info = outArray[count];
info.id = i;
info.ownerPid = g_slots[i].ownerPid;
for (int j = 0; j < 64; j++) info.title[j] = g_slots[i].title[j];
info.width = g_slots[i].width;
info.height = g_slots[i].height;
info.dirty = g_slots[i].dirty ? 1 : 0;
g_slots[i].dirty = false; // clear dirty after read
count++;
}
return count;
}
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext) {
if (windowId < 0 || windowId >= MaxWindows) return 0;
WindowSlot& slot = g_slots[windowId];
if (!slot.used) return 0;
// If already mapped into this process, return existing VA
if (slot.desktopPid == callerPid && slot.desktopVa != 0) {
return slot.desktopVa;
}
uint64_t userVa = heapNext;
for (int i = 0; i < slot.pixelNumPages; i++) {
Memory::VMM::Paging::MapUserIn(callerPml4, slot.pixelPhysPages[i],
userVa + (uint64_t)i * 0x1000);
}
slot.desktopVa = userVa;
slot.desktopPid = callerPid;
heapNext += (uint64_t)slot.pixelNumPages * 0x1000;
return userVa;
}
int SendEvent(int windowId, const Zenith::WinEvent* event) {
if (windowId < 0 || windowId >= MaxWindows) return -1;
WindowSlot& slot = g_slots[windowId];
if (!slot.used) return -1;
int nextHead = (slot.eventHead + 1) % MaxEvents;
if (nextHead == slot.eventTail) return -1; // queue full, drop event
slot.events[slot.eventHead] = *event;
slot.eventHead = nextHead;
return 0;
}
void CleanupProcess(int pid) {
for (int i = 0; i < MaxWindows; i++) {
if (g_slots[i].used && g_slots[i].ownerPid == pid) {
Kt::KernelLogStream(Kt::INFO, "WinServer") << "Cleaning up window "
<< i << " for exited PID " << pid;
g_slots[i].used = false;
}
}
}
}
+42
View File
@@ -0,0 +1,42 @@
/*
* WinServer.hpp
* Window server kernel state for external process windows
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "Syscall.hpp"
#include <cstdint>
namespace WinServer {
static constexpr int MaxWindows = 8;
static constexpr int MaxEvents = 64;
static constexpr int MaxPixelPages = 2048; // up to 2048x1024 @ 32bpp = 8MB
struct WindowSlot {
bool used;
int ownerPid;
char title[64];
int width, height;
uint64_t pixelPhysPages[MaxPixelPages];
int pixelNumPages;
uint64_t ownerVa; // VA in owner's address space
uint64_t desktopVa; // VA in desktop's address space (0 = not yet mapped)
int desktopPid; // PID of the process that mapped it
Zenith::WinEvent events[MaxEvents];
int eventHead, eventTail;
bool dirty;
};
int Create(int ownerPid, uint64_t ownerPml4, const char* title, int w, int h,
uint64_t& heapNext, uint64_t& outVa);
int Destroy(int windowId, int callerPid);
int Present(int windowId, int callerPid);
int Poll(int windowId, int callerPid, Zenith::WinEvent* outEvent);
int Enumerate(Zenith::WinInfo* outArray, int maxCount);
uint64_t Map(int windowId, int callerPid, uint64_t callerPml4, uint64_t& heapNext);
int SendEvent(int windowId, const Zenith::WinEvent* event);
void CleanupProcess(int pid);
}
+5 -3
View File
@@ -461,9 +461,11 @@ namespace Drivers::USB::UsbDevice {
} }
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Step 10: SET_PROTOCOL(0) -- request Boot Protocol // Step 10: SET_PROTOCOL -- Boot Protocol for keyboards only
// ----------------------------------------------------------------- // -----------------------------------------------------------------
if (foundEp) { // Boot Protocol constrains mice to 3-byte reports (no scroll wheel).
// Keep mice in Report Protocol (the default) so scroll data is included.
if (foundEp && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL, cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL,
0, 0, 0, nullptr, false); 0, 0, 0, nullptr, false);
if (cc != Xhci::CC_SUCCESS) { if (cc != Xhci::CC_SUCCESS) {
@@ -475,7 +477,7 @@ namespace Drivers::USB::UsbDevice {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// Step 11: SET_IDLE(4) -- 16ms idle rate for software typematic // Step 11: SET_IDLE(4) -- 16ms idle rate for software typematic
// ----------------------------------------------------------------- // -----------------------------------------------------------------
if (foundEp) { if (foundEp && dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
// wValue upper byte = duration in 4ms units, lower byte = report ID // wValue upper byte = duration in 4ms units, lower byte = report ID
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE, cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE,
(4 << 8), 0, 0, nullptr, false); (4 << 8), 0, 0, nullptr, false);
+6
View File
@@ -19,6 +19,10 @@
using namespace Kt; using namespace Kt;
namespace Hal { namespace Hal {
static int g_detectedCpuCount = 0;
int GetDetectedCpuCount() { return g_detectedCpuCount; }
void ApicInitialize(ACPI::CommonSDTHeader* xsdt) { void ApicInitialize(ACPI::CommonSDTHeader* xsdt) {
KernelLogStream(INFO, "APIC") << "Initializing APIC subsystem"; KernelLogStream(INFO, "APIC") << "Initializing APIC subsystem";
@@ -29,6 +33,8 @@ namespace Hal {
return; return;
} }
g_detectedCpuCount = madt.LocalApicCount;
if (madt.IoApicAddress == 0) { if (madt.IoApicAddress == 0) {
KernelLogStream(ERROR, "APIC") << "No IOAPIC found in MADT"; KernelLogStream(ERROR, "APIC") << "No IOAPIC found in MADT";
return; return;
+3
View File
@@ -19,4 +19,7 @@ namespace Hal {
// //
// xsdt: pointer to the XSDT (already HHDM-mapped) // xsdt: pointer to the XSDT (already HHDM-mapped)
void ApicInitialize(ACPI::CommonSDTHeader* xsdt); void ApicInitialize(ACPI::CommonSDTHeader* xsdt);
// Number of CPU cores detected via MADT (available after ApicInitialize)
int GetDetectedCpuCount();
}; };
+20 -2
View File
@@ -1,18 +1,30 @@
; ;
; Context.asm ; Context.asm
; Context switch: save/restore callee-saved registers, stack pointer, and CR3 ; Context switch: save/restore callee-saved registers, stack pointer, CR3, and FPU state
; Copyright (c) 2025 Daniel Hammer ; Copyright (c) 2025 Daniel Hammer
; ;
[bits 64] [bits 64]
section .text section .text
; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3) ; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
; uint8_t* oldFpuArea, uint8_t* newFpuArea)
; rdi = pointer to save old RSP ; rdi = pointer to save old RSP
; rsi = new RSP to restore ; rsi = new RSP to restore
; rdx = new PML4 physical address (for CR3) ; rdx = new PML4 physical address (for CR3)
; rcx = old FPU state area (may be null)
; r8 = new FPU state area (may be null)
global SchedContextSwitch global SchedContextSwitch
SchedContextSwitch: SchedContextSwitch:
; Save FPU state before pushing registers (rcx/r8 are caller-saved)
test rcx, rcx
jz .skip_fxsave
fxsave [rcx]
.skip_fxsave:
; Stash r8 in r9 (callee-saved registers will clobber r8 slot on stack)
mov r9, r8
; Save callee-saved registers on the current stack ; Save callee-saved registers on the current stack
push rbp push rbp
push rbx push rbx
@@ -42,4 +54,10 @@ SchedContextSwitch:
pop rbx pop rbx
pop rbp pop rbp
; Restore FPU state
test r9, r9
jz .skip_fxrstor
fxrstor [r9]
.skip_fxrstor:
ret ret
+30 -7
View File
@@ -14,9 +14,11 @@
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Hal/Apic/Apic.hpp> #include <Hal/Apic/Apic.hpp>
#include <Hal/GDT.hpp> #include <Hal/GDT.hpp>
#include <Api/WinServer.hpp>
// Assembly: context switch with CR3 parameter // Assembly: context switch with CR3 and FPU state parameters
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3); extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3,
uint8_t* oldFpuArea, uint8_t* newFpuArea);
// Assembly: jump to user mode via IRETQ // Assembly: jump to user mode via IRETQ
extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp); extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp);
@@ -66,7 +68,7 @@ namespace Sched {
for (int i = 0; i < MaxProcesses; i++) { for (int i = 0; i < MaxProcesses; i++) {
processTable[i].pid = i; processTable[i].pid = i;
processTable[i].state = ProcessState::Free; processTable[i].state = ProcessState::Free;
processTable[i].name = nullptr; processTable[i].name[0] = '\0';
processTable[i].savedRsp = 0; processTable[i].savedRsp = 0;
processTable[i].stackBase = 0; processTable[i].stackBase = 0;
processTable[i].entryPoint = 0; processTable[i].entryPoint = 0;
@@ -191,7 +193,11 @@ namespace Sched {
Process& proc = processTable[slot]; Process& proc = processTable[slot];
proc.pid = nextPid++; proc.pid = nextPid++;
proc.state = ProcessState::Ready; proc.state = ProcessState::Ready;
proc.name = vfsPath; {
int i = 0;
for (; i < 63 && vfsPath[i]; i++) proc.name[i] = vfsPath[i];
proc.name[i] = '\0';
}
proc.savedRsp = (uint64_t)sp; proc.savedRsp = (uint64_t)sp;
proc.stackBase = (uint64_t)kernelStackBase; proc.stackBase = (uint64_t)kernelStackBase;
proc.entryPoint = entry; proc.entryPoint = entry;
@@ -224,6 +230,11 @@ namespace Sched {
proc.termCols = 0; proc.termCols = 0;
proc.termRows = 0; proc.termRows = 0;
// Initialize FPU state: zero out, then set default FCW and MXCSR
memset(proc.fpuState, 0, 512);
*(uint16_t*)&proc.fpuState[0] = 0x037F; // FCW: default x87 control word
*(uint32_t*)&proc.fpuState[24] = 0x1F80; // MXCSR: default SSE control/status
return proc.pid; return proc.pid;
} }
@@ -276,7 +287,9 @@ namespace Sched {
// Update TSS RSP0 for hardware interrupts from ring 3 // Update TSS RSP0 for hardware interrupts from ring 3
Hal::g_tss.rsp0 = processTable[next].kernelStackTop; Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3); uint8_t* oldFpu = (currentPid >= 0) ? processTable[currentPid].fpuState : nullptr;
uint8_t* newFpu = processTable[next].fpuState;
SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3, oldFpu, newFpu);
} }
void Tick() { void Tick() {
@@ -309,6 +322,9 @@ namespace Sched {
return; return;
} }
// Clean up any windows owned by this process
WinServer::CleanupProcess(processTable[currentPid].pid);
processTable[currentPid].state = ProcessState::Terminated; processTable[currentPid].state = ProcessState::Terminated;
int next = -1; int next = -1;
@@ -329,11 +345,13 @@ namespace Sched {
g_kernelRsp = processTable[next].kernelStackTop; g_kernelRsp = processTable[next].kernelStackTop;
Hal::g_tss.rsp0 = processTable[next].kernelStackTop; Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3); SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3,
processTable[old].fpuState, processTable[next].fpuState);
} else { } else {
int old = currentPid; int old = currentPid;
currentPid = -1; currentPid = -1;
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3()); SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3(),
processTable[old].fpuState, nullptr);
} }
for (;;) { for (;;) {
@@ -362,4 +380,9 @@ namespace Sched {
return nullptr; return nullptr;
} }
Process* GetProcessSlot(int slot) {
if (slot < 0 || slot >= MaxProcesses) return nullptr;
return &processTable[slot];
}
} }
+7 -1
View File
@@ -30,7 +30,7 @@ namespace Sched {
struct Process { struct Process {
int pid; int pid;
ProcessState state; ProcessState state;
const char* name; char name[64];
uint64_t savedRsp; uint64_t savedRsp;
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address) uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
uint64_t entryPoint; uint64_t entryPoint;
@@ -58,6 +58,9 @@ namespace Sched {
// GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE) // GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE)
int termCols = 0; int termCols = 0;
int termRows = 0; int termRows = 0;
// FPU/SSE state (FXSAVE format, must be 16-byte aligned)
uint8_t fpuState[512] __attribute__((aligned(16)));
}; };
void Initialize(); void Initialize();
@@ -82,4 +85,7 @@ namespace Sched {
// Find a process by PID (returns nullptr if not found or not alive) // Find a process by PID (returns nullptr if not found or not alive)
Process* GetProcessByPid(int pid); Process* GetProcessByPid(int pid);
// Get a pointer to slot i in the process table (for enumeration)
Process* GetProcessSlot(int slot);
} }
+6 -2
View File
@@ -82,9 +82,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
# Home directory placeholder. # Home directory placeholder.
HOMEKEEP := $(BINDIR)/home/.keep HOMEKEEP := $(BINDIR)/home/.keep
.PHONY: all clean doom fetch wiki desktop icons bearssl libc .PHONY: all clean doom fetch wiki desktop icons fonts bearssl libc
all: bearssl libc $(TARGETS) fetch wiki doom desktop icons $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP) all: bearssl libc $(TARGETS) fetch wiki doom desktop icons fonts $(GAME_DATA) $(MANDST) $(WWWDST) $(CA_CERTS) $(HOMEKEEP)
# Build BearSSL static library. # Build BearSSL static library.
bearssl: bearssl:
@@ -110,6 +110,10 @@ desktop:
icons: icons:
../scripts/copy_icons.sh ../scripts/copy_icons.sh
# Copy TTF fonts for the desktop into bin/fonts/.
fonts:
../scripts/copy_fonts.sh
# Build doom via its own Makefile. # Build doom via its own Makefile.
doom: doom:
$(MAKE) -C src/doom $(MAKE) -C src/doom
+93
View File
@@ -0,0 +1,93 @@
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,79 @@
JetBrains Mono Variable Font
============================
This download contains JetBrains Mono as both variable fonts and static fonts.
JetBrains Mono is a variable font with this axis:
wght
This means all the styles are contained in these files:
JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf
JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for JetBrains Mono:
JetBrains_Mono/static/JetBrainsMono-Thin.ttf
JetBrains_Mono/static/JetBrainsMono-ExtraLight.ttf
JetBrains_Mono/static/JetBrainsMono-Light.ttf
JetBrains_Mono/static/JetBrainsMono-Regular.ttf
JetBrains_Mono/static/JetBrainsMono-Medium.ttf
JetBrains_Mono/static/JetBrainsMono-SemiBold.ttf
JetBrains_Mono/static/JetBrainsMono-Bold.ttf
JetBrains_Mono/static/JetBrainsMono-ExtraBold.ttf
JetBrains_Mono/static/JetBrainsMono-ThinItalic.ttf
JetBrains_Mono/static/JetBrainsMono-ExtraLightItalic.ttf
JetBrains_Mono/static/JetBrainsMono-LightItalic.ttf
JetBrains_Mono/static/JetBrainsMono-Italic.ttf
JetBrains_Mono/static/JetBrainsMono-MediumItalic.ttf
JetBrains_Mono/static/JetBrainsMono-SemiBoldItalic.ttf
JetBrains_Mono/static/JetBrainsMono-BoldItalic.ttf
JetBrains_Mono/static/JetBrainsMono-ExtraBoldItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.
+93
View File
@@ -0,0 +1,93 @@
Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+118
View File
@@ -0,0 +1,118 @@
Roboto Variable Font
====================
This download contains Roboto as both variable fonts and static fonts.
Roboto is a variable font with these axes:
wdth
wght
This means all the styles are contained in these files:
Roboto/Roboto-VariableFont_wdth,wght.ttf
Roboto/Roboto-Italic-VariableFont_wdth,wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Roboto:
Roboto/static/Roboto_Condensed-Thin.ttf
Roboto/static/Roboto_Condensed-ExtraLight.ttf
Roboto/static/Roboto_Condensed-Light.ttf
Roboto/static/Roboto_Condensed-Regular.ttf
Roboto/static/Roboto_Condensed-Medium.ttf
Roboto/static/Roboto_Condensed-SemiBold.ttf
Roboto/static/Roboto_Condensed-Bold.ttf
Roboto/static/Roboto_Condensed-ExtraBold.ttf
Roboto/static/Roboto_Condensed-Black.ttf
Roboto/static/Roboto_SemiCondensed-Thin.ttf
Roboto/static/Roboto_SemiCondensed-ExtraLight.ttf
Roboto/static/Roboto_SemiCondensed-Light.ttf
Roboto/static/Roboto_SemiCondensed-Regular.ttf
Roboto/static/Roboto_SemiCondensed-Medium.ttf
Roboto/static/Roboto_SemiCondensed-SemiBold.ttf
Roboto/static/Roboto_SemiCondensed-Bold.ttf
Roboto/static/Roboto_SemiCondensed-ExtraBold.ttf
Roboto/static/Roboto_SemiCondensed-Black.ttf
Roboto/static/Roboto-Thin.ttf
Roboto/static/Roboto-ExtraLight.ttf
Roboto/static/Roboto-Light.ttf
Roboto/static/Roboto-Regular.ttf
Roboto/static/Roboto-Medium.ttf
Roboto/static/Roboto-SemiBold.ttf
Roboto/static/Roboto-Bold.ttf
Roboto/static/Roboto-ExtraBold.ttf
Roboto/static/Roboto-Black.ttf
Roboto/static/Roboto_Condensed-ThinItalic.ttf
Roboto/static/Roboto_Condensed-ExtraLightItalic.ttf
Roboto/static/Roboto_Condensed-LightItalic.ttf
Roboto/static/Roboto_Condensed-Italic.ttf
Roboto/static/Roboto_Condensed-MediumItalic.ttf
Roboto/static/Roboto_Condensed-SemiBoldItalic.ttf
Roboto/static/Roboto_Condensed-BoldItalic.ttf
Roboto/static/Roboto_Condensed-ExtraBoldItalic.ttf
Roboto/static/Roboto_Condensed-BlackItalic.ttf
Roboto/static/Roboto_SemiCondensed-ThinItalic.ttf
Roboto/static/Roboto_SemiCondensed-ExtraLightItalic.ttf
Roboto/static/Roboto_SemiCondensed-LightItalic.ttf
Roboto/static/Roboto_SemiCondensed-Italic.ttf
Roboto/static/Roboto_SemiCondensed-MediumItalic.ttf
Roboto/static/Roboto_SemiCondensed-SemiBoldItalic.ttf
Roboto/static/Roboto_SemiCondensed-BoldItalic.ttf
Roboto/static/Roboto_SemiCondensed-ExtraBoldItalic.ttf
Roboto/static/Roboto_SemiCondensed-BlackItalic.ttf
Roboto/static/Roboto-ThinItalic.ttf
Roboto/static/Roboto-ExtraLightItalic.ttf
Roboto/static/Roboto-LightItalic.ttf
Roboto/static/Roboto-Italic.ttf
Roboto/static/Roboto-MediumItalic.ttf
Roboto/static/Roboto-SemiBoldItalic.ttf
Roboto/static/Roboto-BoldItalic.ttf
Roboto/static/Roboto-ExtraBoldItalic.ttf
Roboto/static/Roboto-BlackItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+56
View File
@@ -66,6 +66,20 @@ namespace Zenith {
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52; static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53; static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
// Window server syscalls
static constexpr uint64_t SYS_WINCREATE = 54;
static constexpr uint64_t SYS_WINDESTROY = 55;
static constexpr uint64_t SYS_WINPRESENT = 56;
static constexpr uint64_t SYS_WINPOLL = 57;
static constexpr uint64_t SYS_WINENUM = 58;
static constexpr uint64_t SYS_WINMAP = 59;
static constexpr uint64_t SYS_WINSENDEVENT = 60;
// Process management syscalls
static constexpr uint64_t SYS_PROCLIST = 61;
static constexpr uint64_t SYS_KILL = 62;
static constexpr uint64_t SYS_DEVLIST = 63;
static constexpr int SOCK_TCP = 1; static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2; static constexpr int SOCK_UDP = 2;
@@ -118,4 +132,46 @@ namespace Zenith {
uint8_t buttons; uint8_t buttons;
}; };
// Window server shared types
struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close
uint8_t _pad[3];
union {
KeyEvent key;
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
struct { int32_t w, h; } resize;
};
};
struct WinInfo {
int32_t id;
int32_t ownerPid;
char title[64];
int32_t width, height;
uint8_t dirty;
uint8_t _pad2[3];
};
struct WinCreateResult {
int32_t id; // -1 on failure
uint32_t _pad;
uint64_t pixelVa; // VA of pixel buffer in caller's address space
};
struct DevInfo {
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI
uint8_t _pad[3];
char name[48];
char detail[48];
};
struct ProcInfo {
int32_t pid;
int32_t parentPid;
uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated
uint8_t _pad[3];
char name[64];
uint64_t heapUsed; // heapNext - UserHeapBase (bytes)
};
} }
+20 -2
View File
@@ -99,6 +99,10 @@ struct Canvas {
// ---- Text ---- // ---- Text ----
void text(int x, int y, const char* str, Color c) { 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);
return;
}
uint32_t px = c.to_pixel(); uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH <= w; i++) { for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT]; const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
@@ -118,6 +122,10 @@ struct Canvas {
} }
void text_2x(int x, int y, const char* str, Color 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);
return;
}
uint32_t px = c.to_pixel(); uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH * 2 <= w; i++) { for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH * 2 <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT]; const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
@@ -141,6 +149,14 @@ struct Canvas {
} }
} }
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;
}
text(x, y, str, c);
}
// ---- Icons ---- // ---- Icons ----
void icon(int x, int y, const SvgIcon& ic) { void icon(int x, int y, const SvgIcon& ic) {
@@ -176,7 +192,8 @@ struct Canvas {
// ---- High-level helpers ---- // ---- High-level helpers ----
void kv_line(int x, int* y, const char* line, Color c, int line_h = FONT_HEIGHT + 6) { void kv_line(int x, int* y, const char* line, Color c, int line_h = 0) {
if (line_h == 0) line_h = system_font_height() + 6;
text(x, *y, line, c); text(x, *y, line, c);
*y += line_h; *y += line_h;
} }
@@ -190,8 +207,9 @@ struct Canvas {
Color bg, Color fg, int radius = 4) { Color bg, Color fg, int radius = 4) {
fill_rounded_rect(x, y, bw, bh, radius, bg); fill_rounded_rect(x, y, bw, bh, radius, bg);
int tw = text_width(label); int tw = text_width(label);
int fh = system_font_height();
int tx = x + (bw - tw) / 2; int tx = x + (bw - tw) / 2;
int ty = y + (bh - FONT_HEIGHT) / 2; int ty = y + (bh - fh) / 2;
text(tx, ty, label, fg); text(tx, ty, label, fg);
} }
}; };
+26
View File
@@ -18,6 +18,25 @@ namespace gui {
static constexpr int MAX_WINDOWS = 8; static constexpr int MAX_WINDOWS = 8;
static constexpr int PANEL_HEIGHT = 32; static constexpr int PANEL_HEIGHT = 32;
struct DesktopSettings {
// Background
bool bg_gradient; // true = gradient, false = solid
Color bg_solid; // solid background color
Color bg_grad_top; // gradient top color
Color bg_grad_bottom; // gradient bottom color
// Panel
Color panel_color; // panel background color
// Accent
Color accent_color; // buttons, highlights, active indicators
// Display
bool show_shadows; // window shadows on/off
bool clock_24h; // 24-hour clock format
int ui_scale; // 0=Small, 1=Default, 2=Large
};
struct DesktopState { struct DesktopState {
Framebuffer fb; Framebuffer fb;
Window windows[MAX_WINDOWS]; Window windows[MAX_WINDOWS];
@@ -54,6 +73,11 @@ struct DesktopState {
SvgIcon icon_settings; SvgIcon icon_settings;
SvgIcon icon_reboot; SvgIcon icon_reboot;
SvgIcon icon_doom;
SvgIcon icon_procmgr;
SvgIcon icon_mandelbrot;
SvgIcon icon_devexplorer;
bool ctx_menu_open; bool ctx_menu_open;
int ctx_menu_x, ctx_menu_y; int ctx_menu_x, ctx_menu_y;
@@ -63,6 +87,8 @@ struct DesktopState {
Rect net_icon_rect; Rect net_icon_rect;
int screen_w, screen_h; int screen_w, screen_h;
DesktopSettings settings;
}; };
// Forward declarations - implemented in main.cpp // Forward declarations - implemented in main.cpp
+37 -1
View File
@@ -1,12 +1,13 @@
/* /*
* font.hpp * font.hpp
* ZenithOS 8x16 VGA bitmap font rendering * ZenithOS text rendering — TrueType with bitmap fallback
* Copyright (c) 2025 Daniel Hammer * Copyright (c) 2025 Daniel Hammer
*/ */
#pragma once #pragma once
#include "gui/gui.hpp" #include "gui/gui.hpp"
#include "gui/framebuffer.hpp" #include "gui/framebuffer.hpp"
#include "gui/truetype.hpp"
namespace gui { namespace gui {
@@ -16,6 +17,30 @@ static constexpr int FONT_HEIGHT = 16;
// Defined in font_data.cpp // Defined in font_data.cpp
extern const uint8_t font_data[256 * 16]; extern const uint8_t font_data[256 * 16];
// Dynamic font height: TTF line height or 16 (bitmap fallback)
inline int system_font_height() {
if (fonts::system_font && fonts::system_font->valid)
return fonts::system_font->get_line_height(fonts::UI_SIZE);
return FONT_HEIGHT;
}
// Dynamic mono font cell dimensions
inline int mono_cell_width() {
if (fonts::mono && fonts::mono->valid) {
// Monospace: all glyphs have the same advance
GlyphCache* gc = fonts::mono->get_cache(fonts::TERM_SIZE);
CachedGlyph* g = fonts::mono->get_glyph(gc, 'M');
if (g) return g->advance;
}
return FONT_WIDTH;
}
inline int mono_cell_height() {
if (fonts::mono && fonts::mono->valid)
return fonts::mono->get_line_height(fonts::TERM_SIZE);
return FONT_HEIGHT;
}
inline void draw_char(Framebuffer& fb, int x, int y, char c, Color fg) { inline void draw_char(Framebuffer& fb, int x, int y, char c, Color fg) {
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT]; const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT];
for (int row = 0; row < FONT_HEIGHT; row++) { for (int row = 0; row < FONT_HEIGHT; row++) {
@@ -43,18 +68,29 @@ inline void draw_char_bg(Framebuffer& fb, int x, int y, char c, Color fg, Color
} }
inline void draw_text(Framebuffer& fb, int x, int y, const char* text, Color fg) { inline void draw_text(Framebuffer& fb, int x, int y, const char* text, Color fg) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw(fb, x, y, text, fg, fonts::UI_SIZE);
return;
}
for (int i = 0; text[i]; i++) { for (int i = 0; text[i]; i++) {
draw_char(fb, x + i * FONT_WIDTH, y, text[i], fg); draw_char(fb, x + i * FONT_WIDTH, y, text[i], fg);
} }
} }
inline void draw_text_bg(Framebuffer& fb, int x, int y, const char* text, Color fg, Color bg) { inline void draw_text_bg(Framebuffer& fb, int x, int y, const char* text, Color fg, Color bg) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_bg(fb, x, y, text, fg, bg, fonts::UI_SIZE);
return;
}
for (int i = 0; text[i]; i++) { for (int i = 0; text[i]; i++) {
draw_char_bg(fb, x + i * FONT_WIDTH, y, text[i], fg, bg); draw_char_bg(fb, x + i * FONT_WIDTH, y, text[i], fg, bg);
} }
} }
inline int text_width(const char* text) { inline int text_width(const char* text) {
if (fonts::system_font && fonts::system_font->valid) {
return fonts::system_font->measure_text(text, fonts::UI_SIZE);
}
int len = 0; int len = 0;
while (text[len]) len++; while (text[len]) len++;
return len * FONT_WIDTH; return len * FONT_WIDTH;
+4 -4
View File
@@ -25,13 +25,13 @@ inline fixed_t fixed_from_parts(int whole, int frac_num, int frac_den) {
struct Color { struct Color {
uint8_t r, g, b, a; uint8_t r, g, b, a;
static Color from_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; } static constexpr Color from_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; }
static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; } static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; }
static Color from_hex(uint32_t hex) { static constexpr Color from_hex(uint32_t hex) {
return {(uint8_t)((hex >> 16) & 0xFF), (uint8_t)((hex >> 8) & 0xFF), (uint8_t)(hex & 0xFF), 255}; return {(uint8_t)((hex >> 16) & 0xFF), (uint8_t)((hex >> 8) & 0xFF), (uint8_t)(hex & 0xFF), 255};
} }
uint32_t to_pixel() const { return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } constexpr uint32_t to_pixel() const { return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; }
}; };
// Named colors for the desktop theme // Named colors for the desktop theme
+95
View File
@@ -0,0 +1,95 @@
/*
* stb_math.h
* Math functions for stb_truetype in freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#ifndef STB_MATH_H
#define STB_MATH_H
#ifdef __cplusplus
extern "C" {
#endif
static inline double stb_floor(double x) {
double i = (double)(long long)x;
return (x < i) ? i - 1.0 : i;
}
static inline double stb_ceil(double x) {
double f = stb_floor(x);
return (x > f) ? f + 1.0 : f;
}
static inline double stb_fabs(double x) {
return x < 0.0 ? -x : x;
}
static inline double stb_fmod(double x, double y) {
if (y == 0.0) return 0.0;
return x - (double)((long long)(x / y)) * y;
}
static inline double stb_sqrt(double x) {
if (x <= 0.0) return 0.0;
double guess = x;
for (int i = 0; i < 30; i++)
guess = (guess + x / guess) * 0.5;
return guess;
}
static inline double stb_pow(double base, double exp) {
if (exp == 0.0) return 1.0;
if (exp == 1.0) return base;
if (base == 0.0) return 0.0;
// Integer exponent fast path
if (exp == (double)(long long)exp) {
long long e = (long long)exp;
int neg = 0;
if (e < 0) { neg = 1; e = -e; }
double r = 1.0;
double b = base;
while (e > 0) {
if (e & 1) r *= b;
b *= b;
e >>= 1;
}
return neg ? 1.0 / r : r;
}
return 0.0;
}
static inline double stb_cos(double x) {
// Reduce to [0, 2*pi]
const double PI = 3.14159265358979323846;
const double TWO_PI = 6.28318530717958647692;
x = stb_fmod(stb_fabs(x), TWO_PI);
// Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ...
double x2 = x * x;
double term = 1.0;
double result = 1.0;
for (int i = 1; i <= 10; i++) {
term *= -x2 / (double)((2 * i - 1) * (2 * i));
result += term;
}
return result;
}
static inline double stb_acos(double x) {
// Clamp input
if (x <= -1.0) return 3.14159265358979323846;
if (x >= 1.0) return 0.0;
// Polynomial approximation (Abramowitz & Stegun style)
double ax = stb_fabs(x);
double result = (-0.0187293 * ax + 0.0742610) * ax - 0.2121144;
result = (result * ax + 1.5707288) * stb_sqrt(1.0 - ax);
if (x < 0.0)
return 3.14159265358979323846 - result;
return result;
}
#ifdef __cplusplus
}
#endif
#endif // STB_MATH_H
File diff suppressed because it is too large Load Diff
+80 -11
View File
@@ -423,6 +423,11 @@ static inline void terminal_feed(TerminalState* t, const char* data, int len) {
} }
static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, int ph) { static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, int ph) {
int cell_w = mono_cell_width();
int cell_h = mono_cell_height();
bool use_ttf = fonts::mono && fonts::mono->valid;
GlyphCache* gc = use_ttf ? fonts::mono->get_cache(fonts::TERM_SIZE) : nullptr;
// Fill background // Fill background
uint32_t bg_px = colors::TERM_BG.to_pixel(); uint32_t bg_px = colors::TERM_BG.to_pixel();
int total = pw * ph; int total = pw * ph;
@@ -431,8 +436,8 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
} }
// Render each visible cell // Render each visible cell
int visible_rows = ph / FONT_HEIGHT; int visible_rows = ph / cell_h;
int visible_cols = pw / FONT_WIDTH; int visible_cols = pw / cell_w;
if (visible_rows > t->rows) visible_rows = t->rows; if (visible_rows > t->rows) visible_rows = t->rows;
if (visible_cols > t->cols) visible_cols = t->cols; if (visible_cols > t->cols) visible_cols = t->cols;
@@ -441,17 +446,16 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
int idx = r * t->cols + c; int idx = r * t->cols + c;
TermCell& cell = t->cells[idx]; TermCell& cell = t->cells[idx];
int px = c * FONT_WIDTH; int px = c * cell_w;
int py = r * FONT_HEIGHT; int py = r * cell_h;
uint32_t cell_bg = cell.bg.to_pixel(); uint32_t cell_bg = cell.bg.to_pixel();
uint32_t cell_fg = cell.fg.to_pixel();
// Draw cell background // Draw cell background
for (int fy = 0; fy < FONT_HEIGHT; fy++) { for (int fy = 0; fy < cell_h; fy++) {
int dy = py + fy; int dy = py + fy;
if (dy >= ph) break; if (dy >= ph) break;
for (int fx = 0; fx < FONT_WIDTH; fx++) { for (int fx = 0; fx < cell_w; fx++) {
int dx = px + fx; int dx = px + fx;
if (dx >= pw) break; if (dx >= pw) break;
pixels[dy * pw + dx] = cell_bg; pixels[dy * pw + dx] = cell_bg;
@@ -460,6 +464,12 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
// Draw character glyph // Draw character glyph
if (cell.ch > 32 || cell.ch < 0) { if (cell.ch > 32 || cell.ch < 0) {
if (use_ttf) {
int baseline = py + gc->ascent;
fonts::mono->draw_char_to_buffer(pixels, pw, ph,
px, baseline, (unsigned char)cell.ch, cell.fg, gc);
} else {
uint32_t cell_fg = cell.fg.to_pixel();
const uint8_t* glyph = &font_data[(unsigned char)cell.ch * FONT_HEIGHT]; const uint8_t* glyph = &font_data[(unsigned char)cell.ch * FONT_HEIGHT];
for (int fy = 0; fy < FONT_HEIGHT; fy++) { for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = py + fy; int dy = py + fy;
@@ -476,16 +486,17 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
} }
} }
} }
}
// Draw cursor // Draw cursor
if (t->cursor_visible && t->cursor_x < visible_cols && t->cursor_y < visible_rows) { if (t->cursor_visible && t->cursor_x < visible_cols && t->cursor_y < visible_rows) {
int cx = t->cursor_x * FONT_WIDTH; int cx = t->cursor_x * cell_w;
int cy = t->cursor_y * FONT_HEIGHT; int cy = t->cursor_y * cell_h;
uint32_t cursor_px = colors::WHITE.to_pixel(); uint32_t cursor_px = colors::WHITE.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) { for (int fy = 0; fy < cell_h; fy++) {
int dy = cy + fy; int dy = cy + fy;
if (dy >= ph) break; if (dy >= ph) break;
for (int fx = 0; fx < FONT_WIDTH; fx++) { for (int fx = 0; fx < cell_w; fx++) {
int dx = cx + fx; int dx = cx + fx;
if (dx >= pw) break; if (dx >= pw) break;
pixels[dy * pw + dx] = cursor_px; pixels[dy * pw + dx] = cursor_px;
@@ -496,6 +507,11 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
int idx = t->cursor_y * t->cols + t->cursor_x; int idx = t->cursor_y * t->cols + t->cursor_x;
char ch = t->cells[idx].ch; char ch = t->cells[idx].ch;
if (ch > 32 || ch < 0) { if (ch > 32 || ch < 0) {
if (use_ttf) {
int baseline = cy + gc->ascent;
fonts::mono->draw_char_to_buffer(pixels, pw, ph,
cx, baseline, (unsigned char)ch, colors::BLACK, gc);
} else {
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT]; const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
uint32_t black_px = colors::BLACK.to_pixel(); uint32_t black_px = colors::BLACK.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) { for (int fy = 0; fy < FONT_HEIGHT; fy++) {
@@ -514,6 +530,59 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
} }
} }
} }
}
static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows) {
if (new_cols == t->cols && new_rows == t->rows) return;
if (new_cols < 1 || new_rows < 1) return;
int new_total = new_cols * new_rows;
TermCell* new_cells = (TermCell*)zenith::alloc(new_total * sizeof(TermCell));
TermCell* new_alt = (TermCell*)zenith::alloc(new_total * sizeof(TermCell));
// Clear new buffers
for (int i = 0; i < new_total; i++) {
new_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
new_alt[i] = {' ', colors::TERM_FG, colors::TERM_BG};
}
// Copy existing content (as much as fits)
int copy_rows = t->rows < new_rows ? t->rows : new_rows;
int copy_cols = t->cols < new_cols ? t->cols : new_cols;
// If cursor is beyond the new grid, scroll content up to keep cursor visible
int row_offset = 0;
if (t->cursor_y >= new_rows) {
row_offset = t->cursor_y - new_rows + 1;
}
for (int r = 0; r < copy_rows && (r + row_offset) < t->rows; r++) {
for (int c = 0; c < copy_cols; c++) {
new_cells[r * new_cols + c] = t->cells[(r + row_offset) * t->cols + c];
}
}
if (t->cells) zenith::mfree(t->cells);
if (t->alt_cells) zenith::mfree(t->alt_cells);
t->cells = new_cells;
t->alt_cells = new_alt;
int old_cols = t->cols;
t->cols = new_cols;
t->rows = new_rows;
// Adjust cursor position
t->cursor_y -= row_offset;
if (t->cursor_x >= new_cols) t->cursor_x = new_cols - 1;
if (t->cursor_y >= new_rows) t->cursor_y = new_rows - 1;
if (t->cursor_y < 0) t->cursor_y = 0;
// Notify child process of new terminal size
if (t->child_pid > 0) {
zenith::childio_settermsz(t->child_pid, new_cols, new_rows);
}
}
static inline void terminal_handle_key(TerminalState* t, const Zenith::KeyEvent& key) { static inline void terminal_handle_key(TerminalState* t, const Zenith::KeyEvent& key) {
if (t->child_pid > 0) { if (t->child_pid > 0) {
+335
View File
@@ -0,0 +1,335 @@
/*
* truetype.hpp
* ZenithOS TrueType font rendering via stb_truetype
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <zenith/syscall.h>
#include <zenith/heap.h>
#include <zenith/string.h>
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
// Forward-declare stbtt_fontinfo to avoid including stb_truetype.h in every TU.
// The actual struct is defined in stb_truetype.h and only used in truetype.hpp
// method bodies (which are inline but only instantiated where stb_truetype.h
// is also included — i.e. stb_truetype_impl.cpp). We include the full header
// here since our inline methods need the complete type.
#include "gui/stb_math.h"
// We need the stb macros defined before including stb_truetype.h (header-only mode)
#ifndef STBTT_ifloor
#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), zenith::malloc(x))
#define STBTT_free(x,u) ((void)(u), zenith::mfree(x))
#define STBTT_memcpy(d,s,n) zenith::memcpy(d,s,n)
#define STBTT_memset(d,v,n) zenith::memset(d,v,n)
#define STBTT_strlen(x) zenith::slen(x)
#define STBTT_assert(x) ((void)(x))
#endif
#include "gui/stb_truetype.h"
namespace gui {
struct CachedGlyph {
uint8_t* bitmap;
int width, height;
int xoff, yoff;
int advance;
bool loaded;
};
struct GlyphCache {
CachedGlyph glyphs[128];
int pixel_size;
float scale;
int ascent, descent, line_gap;
int line_height;
};
struct TrueTypeFont {
stbtt_fontinfo info;
uint8_t* data;
GlyphCache caches[4];
int cache_count;
bool valid;
bool init(const char* vfs_path) {
valid = false;
data = nullptr;
cache_count = 0;
int fd = zenith::open(vfs_path);
if (fd < 0) return false;
uint64_t size = zenith::getsize(fd);
if (size == 0 || size > 1024 * 1024) {
zenith::close(fd);
return false;
}
data = (uint8_t*)zenith::alloc(size);
if (!data) {
zenith::close(fd);
return false;
}
zenith::read(fd, data, 0, size);
zenith::close(fd);
if (!stbtt_InitFont(&info, data, stbtt_GetFontOffsetForIndex(data, 0))) {
zenith::free(data);
data = nullptr;
return false;
}
valid = true;
return true;
}
GlyphCache* get_cache(int pixel_size) {
// Search existing caches
for (int i = 0; i < cache_count; i++) {
if (caches[i].pixel_size == pixel_size)
return &caches[i];
}
// Create new cache
if (cache_count >= 4) return &caches[0]; // fallback to first
GlyphCache* gc = &caches[cache_count++];
gc->pixel_size = pixel_size;
gc->scale = stbtt_ScaleForPixelHeight(&info, (float)pixel_size);
int asc, desc, lg;
stbtt_GetFontVMetrics(&info, &asc, &desc, &lg);
gc->ascent = (int)(asc * gc->scale);
gc->descent = (int)(desc * gc->scale);
gc->line_gap = (int)(lg * gc->scale);
gc->line_height = gc->ascent - gc->descent + gc->line_gap;
for (int i = 0; i < 128; i++) {
gc->glyphs[i].bitmap = nullptr;
gc->glyphs[i].loaded = false;
}
return gc;
}
CachedGlyph* get_glyph(GlyphCache* gc, int codepoint) {
if (codepoint < 0 || codepoint >= 128) return nullptr;
CachedGlyph* g = &gc->glyphs[codepoint];
if (g->loaded) return g;
g->loaded = true;
int advance, lsb;
stbtt_GetCodepointHMetrics(&info, codepoint, &advance, &lsb);
g->advance = (int)(advance * gc->scale);
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&info, codepoint, gc->scale, gc->scale,
&x0, &y0, &x1, &y1);
g->width = x1 - x0;
g->height = y1 - y0;
g->xoff = x0;
g->yoff = y0;
if (g->width > 0 && g->height > 0) {
g->bitmap = (uint8_t*)zenith::malloc(g->width * g->height);
stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height,
g->width, gc->scale, gc->scale, codepoint);
}
return g;
}
int measure_text(const char* text, int pixel_size) {
if (!valid) return 0;
GlyphCache* gc = get_cache(pixel_size);
int w = 0;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
if (g) w += g->advance;
}
return w;
}
int get_line_height(int pixel_size) {
if (!valid) return 16;
GlyphCache* gc = get_cache(pixel_size);
return gc->line_height;
}
void draw(Framebuffer& fb, int x, int y, const char* text,
Color color, int pixel_size) {
if (!valid) return;
GlyphCache* gc = get_cache(pixel_size);
int cx = x;
int baseline = y + gc->ascent;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
if (!g) continue;
if (g->bitmap) {
int gx = cx + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
for (int col = 0; col < g->width; col++) {
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha > 0) {
Color c = {color.r, color.g, color.b, alpha};
fb.put_pixel_alpha(gx + col, gy + row, c);
}
}
}
}
cx += g->advance;
}
}
void draw_bg(Framebuffer& fb, int x, int y, const char* text,
Color fg, Color bg, int pixel_size) {
if (!valid) return;
GlyphCache* gc = get_cache(pixel_size);
// Fill background for the text extent
int tw = measure_text(text, pixel_size);
fb.fill_rect(x, y, tw, gc->line_height, bg);
// Then draw foreground
draw(fb, x, y, text, fg, pixel_size);
}
void 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;
GlyphCache* gc = get_cache(pixel_size);
int cx = x;
int baseline = y + gc->ascent;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
if (!g) continue;
if (g->bitmap) {
int gx = cx + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
int dy = gy + row;
if (dy < 0 || dy >= buf_h) continue;
for (int col = 0; col < g->width; col++) {
int dx = gx + col;
if (dx < 0 || dx >= buf_w) continue;
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha == 0) continue;
if (alpha == 255) {
pixels[dy * buf_w + dx] =
0xFF000000 | ((uint32_t)color.r << 16) |
((uint32_t)color.g << 8) | color.b;
} else {
uint32_t dst = pixels[dy * buf_w + dx];
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = dst & 0xFF;
uint32_t a = alpha, inv_a = 255 - alpha;
uint32_t rr = (a * color.r + inv_a * dr + 128) / 255;
uint32_t gg = (a * color.g + inv_a * dg + 128) / 255;
uint32_t bb = (a * color.b + inv_a * db + 128) / 255;
pixels[dy * buf_w + dx] =
0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
}
cx += g->advance;
}
}
// Draw single character to buffer, returning advance width
int draw_char_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
int x, int baseline, int codepoint,
Color color, GlyphCache* gc) {
CachedGlyph* g = get_glyph(gc, codepoint);
if (!g) return 0;
if (g->bitmap) {
int gx = x + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
int dy = gy + row;
if (dy < 0 || dy >= buf_h) continue;
for (int col = 0; col < g->width; col++) {
int dx = gx + col;
if (dx < 0 || dx >= buf_w) continue;
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha == 0) continue;
if (alpha == 255) {
pixels[dy * buf_w + dx] =
0xFF000000 | ((uint32_t)color.r << 16) |
((uint32_t)color.g << 8) | color.b;
} else {
uint32_t dst = pixels[dy * buf_w + dx];
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = dst & 0xFF;
uint32_t a = alpha, inv_a = 255 - alpha;
uint32_t rr = (a * color.r + inv_a * dr + 128) / 255;
uint32_t gg = (a * color.g + inv_a * dg + 128) / 255;
uint32_t bb = (a * color.b + inv_a * db + 128) / 255;
pixels[dy * buf_w + dx] =
0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
}
return g->advance;
}
};
// Global font manager
namespace fonts {
inline TrueTypeFont* system_font = nullptr;
inline TrueTypeFont* system_bold = nullptr;
inline TrueTypeFont* mono = nullptr;
inline TrueTypeFont* mono_bold = nullptr;
inline int UI_SIZE = 18;
inline int TITLE_SIZE = 18;
inline int TERM_SIZE = 18;
inline int LARGE_SIZE = 28;
inline bool init() {
auto load = [](const char* path) -> TrueTypeFont* {
TrueTypeFont* f = (TrueTypeFont*)zenith::malloc(sizeof(TrueTypeFont));
zenith::memset(f, 0, sizeof(TrueTypeFont));
if (!f->init(path)) {
zenith::mfree(f);
return nullptr;
}
return f;
};
system_font = load("0:/fonts/Roboto-Medium.ttf");
system_bold = load("0:/fonts/Roboto-Bold.ttf");
mono = load("0:/fonts/JetBrainsMono-Regular.ttf");
mono_bold = load("0:/fonts/JetBrainsMono-Bold.ttf");
return system_font != nullptr;
}
}
} // namespace gui
+10 -5
View File
@@ -79,7 +79,7 @@ struct Button {
// Center text // Center text
int tw = text_width(text); int tw = text_width(text);
int tx = bounds.x + (bounds.w - tw) / 2; int tx = bounds.x + (bounds.w - tw) / 2;
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2; int ty = bounds.y + (bounds.h - system_font_height()) / 2;
draw_text(fb, tx, ty, text, fg); draw_text(fb, tx, ty, text, fg);
} }
@@ -150,7 +150,7 @@ struct IconButton {
// Draw text // Draw text
if (text) { if (text) {
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2; int ty = bounds.y + (bounds.h - system_font_height()) / 2;
draw_text(fb, content_x, ty, text, text_color); draw_text(fb, content_x, ty, text, text_color);
} }
} }
@@ -206,13 +206,18 @@ struct TextBox {
// Draw text with 4px padding // Draw text with 4px padding
int tx = bounds.x + 4; int tx = bounds.x + 4;
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2; int fh = system_font_height();
int ty = bounds.y + (bounds.h - fh) / 2;
draw_text(fb, tx, ty, text, fg); draw_text(fb, tx, ty, text, fg);
// Draw cursor if focused // Draw cursor if focused
if (focused) { if (focused) {
int cx = tx + cursor * FONT_WIDTH; // Measure text up to cursor position for proportional fonts
draw_vline(fb, cx, ty, FONT_HEIGHT, cursor_color); char prefix[256];
for (int i = 0; i < cursor && i < 255; i++) prefix[i] = text[i];
prefix[cursor < 255 ? cursor : 255] = '\0';
int cx = tx + text_width(prefix);
draw_vline(fb, cx, ty, fh, cursor_color);
} }
} }

Some files were not shown because too many files have changed in this diff Show More