feat: TrueType (TTF) font rendering, many new desktop applications and DOOM support, among other improvements
This commit is contained in:
+2
-2
@@ -9,6 +9,6 @@ toolchain/build/
|
||||
toolchain/src/
|
||||
programs/bin/
|
||||
programs/obj/
|
||||
programs/src/doom/obj/
|
||||
programs/src/*/obj/
|
||||
programs/lib/libc/liblibc.a
|
||||
programs/gui/icons/
|
||||
programs/src/desktop/obj/
|
||||
|
||||
+250
-1
@@ -29,6 +29,12 @@
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Graphics/Cursor.hpp>
|
||||
#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
|
||||
extern "C" void SyscallEntry();
|
||||
@@ -605,6 +611,228 @@ namespace Zenith {
|
||||
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 ----
|
||||
|
||||
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);
|
||||
case SYS_CHILDIO_SETTERMSZ:
|
||||
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:
|
||||
return -1;
|
||||
}
|
||||
@@ -769,7 +1018,7 @@ namespace Zenith {
|
||||
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
|
||||
|
||||
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)";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -66,6 +66,20 @@ namespace Zenith {
|
||||
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
|
||||
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_UDP = 2;
|
||||
|
||||
@@ -118,6 +132,48 @@ namespace Zenith {
|
||||
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
|
||||
struct SyscallFrame {
|
||||
uint64_t r15, r14, r13, r12, rbp, rbx; // callee-saved
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
0, 0, 0, nullptr, false);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
@@ -475,7 +477,7 @@ namespace Drivers::USB::UsbDevice {
|
||||
// -----------------------------------------------------------------
|
||||
// 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
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE,
|
||||
(4 << 8), 0, 0, nullptr, false);
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
using namespace Kt;
|
||||
|
||||
namespace Hal {
|
||||
static int g_detectedCpuCount = 0;
|
||||
|
||||
int GetDetectedCpuCount() { return g_detectedCpuCount; }
|
||||
|
||||
void ApicInitialize(ACPI::CommonSDTHeader* xsdt) {
|
||||
KernelLogStream(INFO, "APIC") << "Initializing APIC subsystem";
|
||||
|
||||
@@ -29,6 +33,8 @@ namespace Hal {
|
||||
return;
|
||||
}
|
||||
|
||||
g_detectedCpuCount = madt.LocalApicCount;
|
||||
|
||||
if (madt.IoApicAddress == 0) {
|
||||
KernelLogStream(ERROR, "APIC") << "No IOAPIC found in MADT";
|
||||
return;
|
||||
|
||||
@@ -19,4 +19,7 @@ namespace Hal {
|
||||
//
|
||||
// xsdt: pointer to the XSDT (already HHDM-mapped)
|
||||
void ApicInitialize(ACPI::CommonSDTHeader* xsdt);
|
||||
|
||||
// Number of CPU cores detected via MADT (available after ApicInitialize)
|
||||
int GetDetectedCpuCount();
|
||||
};
|
||||
|
||||
@@ -1,18 +1,30 @@
|
||||
;
|
||||
; 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
|
||||
;
|
||||
|
||||
[bits 64]
|
||||
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
|
||||
; rsi = new RSP to restore
|
||||
; 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
|
||||
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
|
||||
push rbp
|
||||
push rbx
|
||||
@@ -42,4 +54,10 @@ SchedContextSwitch:
|
||||
pop rbx
|
||||
pop rbp
|
||||
|
||||
; Restore FPU state
|
||||
test r9, r9
|
||||
jz .skip_fxrstor
|
||||
fxrstor [r9]
|
||||
.skip_fxrstor:
|
||||
|
||||
ret
|
||||
|
||||
@@ -14,9 +14,11 @@
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
#include <Api/WinServer.hpp>
|
||||
|
||||
// Assembly: context switch with CR3 parameter
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3);
|
||||
// Assembly: context switch with CR3 and FPU state parameters
|
||||
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
|
||||
extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp);
|
||||
@@ -66,7 +68,7 @@ namespace Sched {
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
processTable[i].pid = i;
|
||||
processTable[i].state = ProcessState::Free;
|
||||
processTable[i].name = nullptr;
|
||||
processTable[i].name[0] = '\0';
|
||||
processTable[i].savedRsp = 0;
|
||||
processTable[i].stackBase = 0;
|
||||
processTable[i].entryPoint = 0;
|
||||
@@ -191,7 +193,11 @@ namespace Sched {
|
||||
Process& proc = processTable[slot];
|
||||
proc.pid = nextPid++;
|
||||
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.stackBase = (uint64_t)kernelStackBase;
|
||||
proc.entryPoint = entry;
|
||||
@@ -224,6 +230,11 @@ namespace Sched {
|
||||
proc.termCols = 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;
|
||||
}
|
||||
|
||||
@@ -276,7 +287,9 @@ namespace Sched {
|
||||
// Update TSS RSP0 for hardware interrupts from ring 3
|
||||
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() {
|
||||
@@ -309,6 +322,9 @@ namespace Sched {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up any windows owned by this process
|
||||
WinServer::CleanupProcess(processTable[currentPid].pid);
|
||||
|
||||
processTable[currentPid].state = ProcessState::Terminated;
|
||||
|
||||
int next = -1;
|
||||
@@ -329,11 +345,13 @@ namespace Sched {
|
||||
g_kernelRsp = 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 {
|
||||
int old = currentPid;
|
||||
currentPid = -1;
|
||||
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3());
|
||||
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3(),
|
||||
processTable[old].fpuState, nullptr);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
@@ -362,4 +380,9 @@ namespace Sched {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Process* GetProcessSlot(int slot) {
|
||||
if (slot < 0 || slot >= MaxProcesses) return nullptr;
|
||||
return &processTable[slot];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Sched {
|
||||
struct Process {
|
||||
int pid;
|
||||
ProcessState state;
|
||||
const char* name;
|
||||
char name[64];
|
||||
uint64_t savedRsp;
|
||||
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
|
||||
uint64_t entryPoint;
|
||||
@@ -58,6 +58,9 @@ namespace Sched {
|
||||
// GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE)
|
||||
int termCols = 0;
|
||||
int termRows = 0;
|
||||
|
||||
// FPU/SSE state (FXSAVE format, must be 16-byte aligned)
|
||||
uint8_t fpuState[512] __attribute__((aligned(16)));
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
@@ -82,4 +85,7 @@ namespace Sched {
|
||||
// Find a process by PID (returns nullptr if not found or not alive)
|
||||
Process* GetProcessByPid(int pid);
|
||||
|
||||
// Get a pointer to slot i in the process table (for enumeration)
|
||||
Process* GetProcessSlot(int slot);
|
||||
|
||||
}
|
||||
|
||||
@@ -82,9 +82,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
|
||||
# Home directory placeholder.
|
||||
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.
|
||||
bearssl:
|
||||
@@ -110,6 +110,10 @@ desktop:
|
||||
icons:
|
||||
../scripts/copy_icons.sh
|
||||
|
||||
# Copy TTF fonts for the desktop into bin/fonts/.
|
||||
fonts:
|
||||
../scripts/copy_fonts.sh
|
||||
|
||||
# Build doom via its own Makefile.
|
||||
doom:
|
||||
$(MAKE) -C src/doom
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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 aren’t 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.
|
||||
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.
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.
@@ -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.
|
||||
@@ -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 aren’t 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.
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.
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.
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.
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.
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.
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.
@@ -66,6 +66,20 @@ namespace Zenith {
|
||||
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
|
||||
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_UDP = 2;
|
||||
|
||||
@@ -118,4 +132,46 @@ namespace Zenith {
|
||||
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)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -99,6 +99,10 @@ struct Canvas {
|
||||
// ---- Text ----
|
||||
|
||||
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();
|
||||
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];
|
||||
@@ -118,6 +122,10 @@ struct Canvas {
|
||||
}
|
||||
|
||||
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();
|
||||
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];
|
||||
@@ -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 ----
|
||||
|
||||
void icon(int x, int y, const SvgIcon& ic) {
|
||||
@@ -176,7 +192,8 @@ struct Canvas {
|
||||
|
||||
// ---- 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);
|
||||
*y += line_h;
|
||||
}
|
||||
@@ -190,8 +207,9 @@ struct Canvas {
|
||||
Color bg, Color fg, int radius = 4) {
|
||||
fill_rounded_rect(x, y, bw, bh, radius, bg);
|
||||
int tw = text_width(label);
|
||||
int fh = system_font_height();
|
||||
int tx = x + (bw - tw) / 2;
|
||||
int ty = y + (bh - FONT_HEIGHT) / 2;
|
||||
int ty = y + (bh - fh) / 2;
|
||||
text(tx, ty, label, fg);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,25 @@ namespace gui {
|
||||
static constexpr int MAX_WINDOWS = 8;
|
||||
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 {
|
||||
Framebuffer fb;
|
||||
Window windows[MAX_WINDOWS];
|
||||
@@ -54,6 +73,11 @@ struct DesktopState {
|
||||
SvgIcon icon_settings;
|
||||
SvgIcon icon_reboot;
|
||||
|
||||
SvgIcon icon_doom;
|
||||
SvgIcon icon_procmgr;
|
||||
SvgIcon icon_mandelbrot;
|
||||
SvgIcon icon_devexplorer;
|
||||
|
||||
bool ctx_menu_open;
|
||||
int ctx_menu_x, ctx_menu_y;
|
||||
|
||||
@@ -63,6 +87,8 @@ struct DesktopState {
|
||||
Rect net_icon_rect;
|
||||
|
||||
int screen_w, screen_h;
|
||||
|
||||
DesktopSettings settings;
|
||||
};
|
||||
|
||||
// Forward declarations - implemented in main.cpp
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
/*
|
||||
* font.hpp
|
||||
* ZenithOS 8x16 VGA bitmap font rendering
|
||||
* ZenithOS text rendering — TrueType with bitmap fallback
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "gui/gui.hpp"
|
||||
#include "gui/framebuffer.hpp"
|
||||
#include "gui/truetype.hpp"
|
||||
|
||||
namespace gui {
|
||||
|
||||
@@ -16,6 +17,30 @@ static constexpr int FONT_HEIGHT = 16;
|
||||
// Defined in font_data.cpp
|
||||
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) {
|
||||
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT];
|
||||
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) {
|
||||
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++) {
|
||||
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) {
|
||||
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++) {
|
||||
draw_char_bg(fb, x + i * FONT_WIDTH, y, text[i], fg, bg);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
while (text[len]) len++;
|
||||
return len * FONT_WIDTH;
|
||||
|
||||
@@ -25,13 +25,13 @@ inline fixed_t fixed_from_parts(int whole, int frac_num, int frac_den) {
|
||||
struct Color {
|
||||
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 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_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; }
|
||||
static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; }
|
||||
static constexpr Color from_hex(uint32_t hex) {
|
||||
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
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
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
|
||||
uint32_t bg_px = colors::TERM_BG.to_pixel();
|
||||
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
|
||||
int visible_rows = ph / FONT_HEIGHT;
|
||||
int visible_cols = pw / FONT_WIDTH;
|
||||
int visible_rows = ph / cell_h;
|
||||
int visible_cols = pw / cell_w;
|
||||
if (visible_rows > t->rows) visible_rows = t->rows;
|
||||
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;
|
||||
TermCell& cell = t->cells[idx];
|
||||
|
||||
int px = c * FONT_WIDTH;
|
||||
int py = r * FONT_HEIGHT;
|
||||
int px = c * cell_w;
|
||||
int py = r * cell_h;
|
||||
|
||||
uint32_t cell_bg = cell.bg.to_pixel();
|
||||
uint32_t cell_fg = cell.fg.to_pixel();
|
||||
|
||||
// Draw cell background
|
||||
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
|
||||
for (int fy = 0; fy < cell_h; fy++) {
|
||||
int dy = py + fy;
|
||||
if (dy >= ph) break;
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
for (int fx = 0; fx < cell_w; fx++) {
|
||||
int dx = px + fx;
|
||||
if (dx >= pw) break;
|
||||
pixels[dy * pw + dx] = cell_bg;
|
||||
@@ -460,16 +464,23 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
|
||||
|
||||
// Draw character glyph
|
||||
if (cell.ch > 32 || cell.ch < 0) {
|
||||
const uint8_t* glyph = &font_data[(unsigned char)cell.ch * FONT_HEIGHT];
|
||||
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
|
||||
int dy = py + fy;
|
||||
if (dy >= ph) break;
|
||||
uint8_t bits = glyph[fy];
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
if (bits & (0x80 >> fx)) {
|
||||
int dx = px + fx;
|
||||
if (dx >= pw) break;
|
||||
pixels[dy * pw + dx] = cell_fg;
|
||||
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];
|
||||
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
|
||||
int dy = py + fy;
|
||||
if (dy >= ph) break;
|
||||
uint8_t bits = glyph[fy];
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
if (bits & (0x80 >> fx)) {
|
||||
int dx = px + fx;
|
||||
if (dx >= pw) break;
|
||||
pixels[dy * pw + dx] = cell_fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,13 +490,13 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
|
||||
|
||||
// Draw cursor
|
||||
if (t->cursor_visible && t->cursor_x < visible_cols && t->cursor_y < visible_rows) {
|
||||
int cx = t->cursor_x * FONT_WIDTH;
|
||||
int cy = t->cursor_y * FONT_HEIGHT;
|
||||
int cx = t->cursor_x * cell_w;
|
||||
int cy = t->cursor_y * cell_h;
|
||||
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;
|
||||
if (dy >= ph) break;
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
for (int fx = 0; fx < cell_w; fx++) {
|
||||
int dx = cx + fx;
|
||||
if (dx >= pw) break;
|
||||
pixels[dy * pw + dx] = cursor_px;
|
||||
@@ -496,17 +507,23 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
|
||||
int idx = t->cursor_y * t->cols + t->cursor_x;
|
||||
char ch = t->cells[idx].ch;
|
||||
if (ch > 32 || ch < 0) {
|
||||
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
|
||||
uint32_t black_px = colors::BLACK.to_pixel();
|
||||
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
|
||||
int dy = cy + fy;
|
||||
if (dy >= ph) break;
|
||||
uint8_t bits = glyph[fy];
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
if (bits & (0x80 >> fx)) {
|
||||
int dx = cx + fx;
|
||||
if (dx >= pw) break;
|
||||
pixels[dy * pw + dx] = black_px;
|
||||
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];
|
||||
uint32_t black_px = colors::BLACK.to_pixel();
|
||||
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
|
||||
int dy = cy + fy;
|
||||
if (dy >= ph) break;
|
||||
uint8_t bits = glyph[fy];
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
if (bits & (0x80 >> fx)) {
|
||||
int dx = cx + fx;
|
||||
if (dx >= pw) break;
|
||||
pixels[dy * pw + dx] = black_px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -515,6 +532,58 @@ 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) {
|
||||
if (t->child_pid > 0) {
|
||||
zenith::childio_writekey(t->child_pid, &key);
|
||||
|
||||
@@ -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
|
||||
@@ -79,7 +79,7 @@ struct Button {
|
||||
// Center text
|
||||
int tw = text_width(text);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ struct IconButton {
|
||||
|
||||
// Draw 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);
|
||||
}
|
||||
}
|
||||
@@ -206,13 +206,18 @@ struct TextBox {
|
||||
|
||||
// Draw text with 4px padding
|
||||
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 cursor if focused
|
||||
if (focused) {
|
||||
int cx = tx + cursor * FONT_WIDTH;
|
||||
draw_vline(fb, cx, ty, FONT_HEIGHT, cursor_color);
|
||||
// Measure text up to cursor position for proportional fonts
|
||||
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
Reference in New Issue
Block a user