feat: expand user mode, add DOOM game, add manpages
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# Makefile for DOOM (doomgeneric) on ZenithOS
|
||||
# Copyright (c) 2025 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CC := $(TOOLCHAIN_PREFIX)gcc
|
||||
LD := $(TOOLCHAIN_PREFIX)gcc
|
||||
else
|
||||
CC := gcc
|
||||
LD := gcc
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
DOOM_SRC := ../../../doomgeneric/doomgeneric
|
||||
LIBC_INC := ../../include/libc
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CFLAGS := \
|
||||
-std=gnu11 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-variable \
|
||||
-Wno-missing-field-initializers \
|
||||
-Wno-sign-compare \
|
||||
-Wno-pointer-sign \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-DNORMALUNIX \
|
||||
-DLINUX \
|
||||
-isystem $(LIBC_INC) \
|
||||
-I $(PROG_INC) \
|
||||
-I $(DOOM_SRC) \
|
||||
-isystem $(shell $(CC) -print-file-name=include)
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- DOOM source files (excluding platform-specific ports and sound backends) ----
|
||||
|
||||
DOOM_SRCS := \
|
||||
am_map.c \
|
||||
d_event.c \
|
||||
d_items.c \
|
||||
d_iwad.c \
|
||||
d_loop.c \
|
||||
d_main.c \
|
||||
d_mode.c \
|
||||
d_net.c \
|
||||
doomdef.c \
|
||||
doomgeneric.c \
|
||||
doomstat.c \
|
||||
dstrings.c \
|
||||
dummy.c \
|
||||
f_finale.c \
|
||||
f_wipe.c \
|
||||
g_game.c \
|
||||
gusconf.c \
|
||||
hu_lib.c \
|
||||
hu_stuff.c \
|
||||
i_cdmus.c \
|
||||
i_endoom.c \
|
||||
i_input.c \
|
||||
i_joystick.c \
|
||||
i_scale.c \
|
||||
i_sound.c \
|
||||
i_system.c \
|
||||
i_timer.c \
|
||||
i_video.c \
|
||||
info.c \
|
||||
m_argv.c \
|
||||
m_bbox.c \
|
||||
m_cheat.c \
|
||||
m_config.c \
|
||||
m_controls.c \
|
||||
m_fixed.c \
|
||||
m_menu.c \
|
||||
m_misc.c \
|
||||
m_random.c \
|
||||
memio.c \
|
||||
mus2mid.c \
|
||||
p_ceilng.c \
|
||||
p_doors.c \
|
||||
p_enemy.c \
|
||||
p_floor.c \
|
||||
p_inter.c \
|
||||
p_lights.c \
|
||||
p_map.c \
|
||||
p_maputl.c \
|
||||
p_mobj.c \
|
||||
p_plats.c \
|
||||
p_pspr.c \
|
||||
p_saveg.c \
|
||||
p_setup.c \
|
||||
p_sight.c \
|
||||
p_spec.c \
|
||||
p_switch.c \
|
||||
p_telept.c \
|
||||
p_tick.c \
|
||||
p_user.c \
|
||||
r_bsp.c \
|
||||
r_data.c \
|
||||
r_draw.c \
|
||||
r_main.c \
|
||||
r_plane.c \
|
||||
r_segs.c \
|
||||
r_sky.c \
|
||||
r_things.c \
|
||||
s_sound.c \
|
||||
sha1.c \
|
||||
sounds.c \
|
||||
st_lib.c \
|
||||
st_stuff.c \
|
||||
statdump.c \
|
||||
tables.c \
|
||||
v_video.c \
|
||||
w_checksum.c \
|
||||
w_file.c \
|
||||
w_file_stdc.c \
|
||||
w_main.c \
|
||||
w_wad.c \
|
||||
wi_stuff.c \
|
||||
z_zone.c
|
||||
|
||||
# Local source files
|
||||
LOCAL_SRCS := doomgeneric_zenith.c libc.c
|
||||
|
||||
# ---- Object files ----
|
||||
|
||||
DOOM_OBJS := $(addprefix $(OBJDIR)/,$(DOOM_SRCS:.c=.o))
|
||||
LOCAL_OBJS := $(addprefix $(OBJDIR)/,$(LOCAL_SRCS:.c=.o))
|
||||
ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS)
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/doom.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)
|
||||
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
|
||||
|
||||
# DOOM source files (from doomgeneric directory)
|
||||
$(OBJDIR)/%.o: $(DOOM_SRC)/%.c Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
# Local source files
|
||||
$(OBJDIR)/%.o: %.c Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* doomgeneric_zenith.c
|
||||
* DOOM platform implementation for ZenithOS
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "doomgeneric.h"
|
||||
#include "doomkeys.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* ---- Raw syscall interface (C versions) ---- */
|
||||
|
||||
static inline long _zos_syscall0(long nr) {
|
||||
long ret;
|
||||
__asm__ volatile("syscall" : "=a"(ret) : "a"(nr)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _zos_syscall1(long nr, long a1) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Syscall numbers (must match kernel/src/Api/Syscall.hpp) */
|
||||
#define SYS_EXIT 0
|
||||
#define SYS_SLEEP_MS 2
|
||||
#define SYS_PRINT 4
|
||||
#define SYS_GETMILLISECONDS 14
|
||||
#define SYS_ISKEYAVAILABLE 16
|
||||
#define SYS_GETKEY 17
|
||||
#define SYS_FBINFO 21
|
||||
#define SYS_FBMAP 22
|
||||
|
||||
/* FbInfo struct (must match kernel definition) */
|
||||
struct FbInfo {
|
||||
unsigned long width;
|
||||
unsigned long height;
|
||||
unsigned long pitch;
|
||||
unsigned long bpp;
|
||||
unsigned long userAddr;
|
||||
};
|
||||
|
||||
/* KeyEvent struct (must match kernel definition) */
|
||||
struct KeyEvent {
|
||||
unsigned char scancode;
|
||||
char ascii;
|
||||
unsigned char pressed;
|
||||
unsigned char shift;
|
||||
unsigned char ctrl;
|
||||
unsigned char alt;
|
||||
};
|
||||
|
||||
/* ---- Framebuffer state ---- */
|
||||
|
||||
static uint32_t* g_fbPtr = 0;
|
||||
static uint32_t g_fbWidth = 0;
|
||||
static uint32_t g_fbHeight = 0;
|
||||
static uint32_t g_fbPitch = 0; /* bytes per scanline */
|
||||
|
||||
/* ---- Circular key queue ---- */
|
||||
|
||||
#define KEY_QUEUE_SIZE 64
|
||||
|
||||
struct KeyQueueEntry {
|
||||
int pressed;
|
||||
unsigned char doomkey;
|
||||
};
|
||||
|
||||
static struct KeyQueueEntry g_keyQueue[KEY_QUEUE_SIZE];
|
||||
static int g_keyQueueRead = 0;
|
||||
static int g_keyQueueWrite = 0;
|
||||
|
||||
static void key_queue_push(int pressed, unsigned char doomkey) {
|
||||
int next = (g_keyQueueWrite + 1) % KEY_QUEUE_SIZE;
|
||||
if (next == g_keyQueueRead) return; /* full, drop */
|
||||
g_keyQueue[g_keyQueueWrite].pressed = pressed;
|
||||
g_keyQueue[g_keyQueueWrite].doomkey = doomkey;
|
||||
g_keyQueueWrite = next;
|
||||
}
|
||||
|
||||
static int key_queue_pop(int* pressed, unsigned char* doomkey) {
|
||||
if (g_keyQueueRead == g_keyQueueWrite) return 0;
|
||||
*pressed = g_keyQueue[g_keyQueueRead].pressed;
|
||||
*doomkey = g_keyQueue[g_keyQueueRead].doomkey;
|
||||
g_keyQueueRead = (g_keyQueueRead + 1) % KEY_QUEUE_SIZE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ---- PS/2 scancode to ASCII table (set 1, unshifted) ---- */
|
||||
|
||||
static const char scancode_to_ascii[128] = {
|
||||
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=','\b',
|
||||
'\t','q','w','e','r','t','y','u','i','o','p','[',']','\n',
|
||||
0, 'a','s','d','f','g','h','j','k','l',';','\'','`',
|
||||
0, '\\','z','x','c','v','b','n','m',',','.','/', 0,
|
||||
'*', 0, ' '
|
||||
};
|
||||
|
||||
/* ---- PS/2 scancode to DOOM key mapping ---- */
|
||||
|
||||
static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) {
|
||||
switch (scancode) {
|
||||
case 0x48: return KEY_UPARROW;
|
||||
case 0x50: return KEY_DOWNARROW;
|
||||
case 0x4B: return KEY_LEFTARROW;
|
||||
case 0x4D: return KEY_RIGHTARROW;
|
||||
case 0x1C: return KEY_ENTER;
|
||||
case 0x01: return KEY_ESCAPE;
|
||||
case 0x39: return ' '; /* Space = use */
|
||||
case 0x1D: return KEY_RCTRL; /* LCtrl = fire */
|
||||
case 0x2A: return KEY_RSHIFT; /* LShift = run */
|
||||
case 0x36: return KEY_RSHIFT; /* RShift = run */
|
||||
case 0x38: return KEY_RALT; /* Alt = strafe */
|
||||
case 0x0E: return KEY_BACKSPACE;
|
||||
case 0x0F: return KEY_TAB;
|
||||
/* F1-F10 */
|
||||
case 0x3B: return KEY_F1;
|
||||
case 0x3C: return KEY_F2;
|
||||
case 0x3D: return KEY_F3;
|
||||
case 0x3E: return KEY_F4;
|
||||
case 0x3F: return KEY_F5;
|
||||
case 0x40: return KEY_F6;
|
||||
case 0x41: return KEY_F7;
|
||||
case 0x42: return KEY_F8;
|
||||
case 0x43: return KEY_F9;
|
||||
case 0x44: return KEY_F10;
|
||||
case 0x57: return KEY_F11;
|
||||
case 0x58: return KEY_F12;
|
||||
/* Equals and minus for screen size */
|
||||
case 0x0D: return KEY_EQUALS;
|
||||
case 0x0C: return KEY_MINUS;
|
||||
default:
|
||||
/* Pass through printable ASCII as lowercase */
|
||||
if (ascii >= 'a' && ascii <= 'z') return (unsigned char)ascii;
|
||||
if (ascii >= '0' && ascii <= '9') return (unsigned char)ascii;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Poll keyboard and enqueue events ---- */
|
||||
|
||||
static void poll_keyboard(void) {
|
||||
while (_zos_syscall0(SYS_ISKEYAVAILABLE)) {
|
||||
struct KeyEvent evt;
|
||||
_zos_syscall1(SYS_GETKEY, (long)&evt);
|
||||
|
||||
unsigned char baseSc = evt.scancode & 0x7F; /* strip break bit */
|
||||
|
||||
char ascii = 0;
|
||||
if (baseSc < 128)
|
||||
ascii = scancode_to_ascii[baseSc];
|
||||
|
||||
unsigned char dk = scancode_to_doomkey(baseSc, ascii);
|
||||
if (dk != 0) {
|
||||
key_queue_push(evt.pressed ? 1 : 0, dk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- DG platform functions ---- */
|
||||
|
||||
void DG_Init(void) {
|
||||
struct FbInfo info;
|
||||
_zos_syscall1(SYS_FBINFO, (long)&info);
|
||||
|
||||
g_fbWidth = (uint32_t)info.width;
|
||||
g_fbHeight = (uint32_t)info.height;
|
||||
g_fbPitch = (uint32_t)info.pitch;
|
||||
|
||||
g_fbPtr = (uint32_t*)(unsigned long)_zos_syscall0(SYS_FBMAP);
|
||||
|
||||
printf("DOOM: framebuffer %ux%u pitch=%u mapped at %p\n",
|
||||
g_fbWidth, g_fbHeight, g_fbPitch, (void*)g_fbPtr);
|
||||
}
|
||||
|
||||
void DG_DrawFrame(void) {
|
||||
/* Poll keyboard first */
|
||||
poll_keyboard();
|
||||
|
||||
/* Copy DG_ScreenBuffer (DOOMGENERIC_RESX x DOOMGENERIC_RESY) to framebuffer */
|
||||
if (g_fbPtr == 0 || DG_ScreenBuffer == 0) return;
|
||||
|
||||
uint32_t copyW = DOOMGENERIC_RESX;
|
||||
uint32_t copyH = DOOMGENERIC_RESY;
|
||||
if (copyW > g_fbWidth) copyW = g_fbWidth;
|
||||
if (copyH > g_fbHeight) copyH = g_fbHeight;
|
||||
|
||||
uint32_t fbStride = g_fbPitch / 4; /* pixels per scanline */
|
||||
|
||||
for (uint32_t y = 0; y < copyH; y++) {
|
||||
uint32_t* dst = g_fbPtr + y * fbStride;
|
||||
uint32_t* src = DG_ScreenBuffer + y * DOOMGENERIC_RESX;
|
||||
memcpy(dst, src, copyW * sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
void DG_SleepMs(uint32_t ms) {
|
||||
_zos_syscall1(SYS_SLEEP_MS, (long)ms);
|
||||
}
|
||||
|
||||
uint32_t DG_GetTicksMs(void) {
|
||||
return (uint32_t)_zos_syscall0(SYS_GETMILLISECONDS);
|
||||
}
|
||||
|
||||
int DG_GetKey(int* pressed, unsigned char* doomKey) {
|
||||
return key_queue_pop(pressed, doomKey);
|
||||
}
|
||||
|
||||
void DG_SetWindowTitle(const char* title) {
|
||||
(void)title;
|
||||
}
|
||||
|
||||
/* ---- Entry point ---- */
|
||||
|
||||
void _start(void) {
|
||||
char *argv[] = { "doom", "-iwad", "0:/doom1.wad", 0 };
|
||||
doomgeneric_Create(3, argv);
|
||||
for (;;) {
|
||||
doomgeneric_Tick();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* Manual page viewer for ZenithOS
|
||||
* Fullscreen pager with ANSI formatting and keyboard navigation
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <zenith/syscall.h>
|
||||
#include <zenith/heap.h>
|
||||
|
||||
// ---- Utility functions ----
|
||||
|
||||
static bool starts_with(const char* str, const char* prefix) {
|
||||
while (*prefix) {
|
||||
if (*str != *prefix) return false;
|
||||
str++; prefix++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char* skip_spaces(const char* s) {
|
||||
while (*s == ' ') s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
static int slen(const char* s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void print_int(uint64_t n) {
|
||||
if (n == 0) {
|
||||
zenith::putchar('0');
|
||||
return;
|
||||
}
|
||||
char buf[20];
|
||||
int i = 0;
|
||||
while (n > 0) {
|
||||
buf[i++] = '0' + (n % 10);
|
||||
n /= 10;
|
||||
}
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
zenith::putchar(buf[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Pager rendering ----
|
||||
|
||||
static constexpr int MAN_MAX_LINES = 2048;
|
||||
|
||||
static int int_to_buf(char* buf, int n) {
|
||||
if (n == 0) { buf[0] = '0'; return 1; }
|
||||
char tmp[12];
|
||||
int i = 0;
|
||||
while (n > 0) { tmp[i++] = '0' + (n % 10); n /= 10; }
|
||||
for (int j = 0; j < i; j++) buf[j] = tmp[i - 1 - j];
|
||||
return i;
|
||||
}
|
||||
|
||||
static void cursor_to(int row, int col) {
|
||||
char seq[24] = "\033[";
|
||||
int p = 2;
|
||||
p += int_to_buf(seq + p, row);
|
||||
seq[p++] = ';';
|
||||
p += int_to_buf(seq + p, col);
|
||||
seq[p++] = 'H';
|
||||
seq[p] = '\0';
|
||||
zenith::print(seq);
|
||||
}
|
||||
|
||||
struct ManLine {
|
||||
const char* text;
|
||||
int len;
|
||||
bool isSH;
|
||||
bool isSS;
|
||||
bool isBold;
|
||||
bool isTH;
|
||||
};
|
||||
|
||||
static void man_render(ManLine* lines, int totalLines, int scroll, int rows, int cols,
|
||||
const char* name, int section) {
|
||||
int contentRows = rows - 1;
|
||||
|
||||
for (int r = 0; r < contentRows; r++) {
|
||||
cursor_to(r + 1, 1);
|
||||
zenith::print("\033[2K");
|
||||
|
||||
int idx = scroll + r;
|
||||
if (idx < 0 || idx >= totalLines) continue;
|
||||
|
||||
ManLine& ln = lines[idx];
|
||||
if (ln.isTH) continue;
|
||||
|
||||
if (ln.isSH || ln.isSS || ln.isBold) {
|
||||
zenith::print("\033[1m");
|
||||
}
|
||||
|
||||
if (ln.isSS) {
|
||||
zenith::print(" ");
|
||||
}
|
||||
|
||||
int maxW = cols;
|
||||
if (ln.isSS) maxW -= 3;
|
||||
int printLen = ln.len;
|
||||
if (printLen > maxW) printLen = maxW;
|
||||
for (int c = 0; c < printLen; c++) {
|
||||
zenith::putchar(ln.text[c]);
|
||||
}
|
||||
|
||||
if (ln.isSH || ln.isSS || ln.isBold) {
|
||||
zenith::print("\033[0m");
|
||||
}
|
||||
}
|
||||
|
||||
// Status bar
|
||||
cursor_to(rows, 1);
|
||||
zenith::print("\033[7m");
|
||||
zenith::print(" Manual page ");
|
||||
zenith::print(name);
|
||||
zenith::putchar('(');
|
||||
print_int((uint64_t)section);
|
||||
zenith::putchar(')');
|
||||
zenith::print(" line ");
|
||||
print_int((uint64_t)(scroll + 1));
|
||||
zenith::putchar('/');
|
||||
print_int((uint64_t)totalLines);
|
||||
|
||||
int padCount = cols - 30 - slen(name);
|
||||
for (int i = 0; i < padCount; i++) zenith::putchar(' ');
|
||||
|
||||
zenith::print("\033[0m");
|
||||
}
|
||||
|
||||
// ---- Main ----
|
||||
|
||||
extern "C" void _start() {
|
||||
// Get arguments passed by the shell (e.g. "intro" or "2 syscalls")
|
||||
char argbuf[256];
|
||||
zenith::getargs(argbuf, sizeof(argbuf));
|
||||
|
||||
const char* arg = skip_spaces(argbuf);
|
||||
if (*arg == '\0') {
|
||||
zenith::print("Usage: man <topic>\n");
|
||||
zenith::print(" man <section> <topic>\n");
|
||||
zenith::print("Try: man intro\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse optional section number and topic name
|
||||
int section = 0;
|
||||
const char* topic = arg;
|
||||
|
||||
if (arg[0] >= '1' && arg[0] <= '9' && arg[1] == ' ') {
|
||||
section = arg[0] - '0';
|
||||
topic = skip_spaces(arg + 2);
|
||||
}
|
||||
|
||||
// Try to open man page file
|
||||
int handle = -1;
|
||||
int foundSection = 0;
|
||||
char path[128];
|
||||
|
||||
if (section > 0) {
|
||||
const char* prefix = "0:/man/";
|
||||
int p = 0;
|
||||
while (prefix[p]) { path[p] = prefix[p]; p++; }
|
||||
int t = 0;
|
||||
while (topic[t] && p < 120) { path[p++] = topic[t++]; }
|
||||
path[p++] = '.';
|
||||
path[p++] = '0' + section;
|
||||
path[p] = '\0';
|
||||
|
||||
handle = zenith::open(path);
|
||||
if (handle >= 0) foundSection = section;
|
||||
} else {
|
||||
for (int s = 1; s <= 3; s++) {
|
||||
const char* prefix = "0:/man/";
|
||||
int p = 0;
|
||||
while (prefix[p]) { path[p] = prefix[p]; p++; }
|
||||
int t = 0;
|
||||
while (topic[t] && p < 120) { path[p++] = topic[t++]; }
|
||||
path[p++] = '.';
|
||||
path[p++] = '0' + s;
|
||||
path[p] = '\0';
|
||||
|
||||
handle = zenith::open(path);
|
||||
if (handle >= 0) {
|
||||
foundSection = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (handle < 0) {
|
||||
zenith::print("No manual entry for ");
|
||||
zenith::print(topic);
|
||||
zenith::putchar('\n');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load entire file into heap
|
||||
uint64_t fileSize = zenith::getsize(handle);
|
||||
if (fileSize == 0) {
|
||||
zenith::close(handle);
|
||||
zenith::print("Empty manual page.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char* fileData = (char*)zenith::malloc(fileSize + 1);
|
||||
if (fileData == nullptr) {
|
||||
zenith::close(handle);
|
||||
zenith::print("Out of memory.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t offset = 0;
|
||||
while (offset < fileSize) {
|
||||
uint64_t chunk = fileSize - offset;
|
||||
if (chunk > 4096) chunk = 4096;
|
||||
int bytesRead = zenith::read(handle, (uint8_t*)fileData + offset, offset, chunk);
|
||||
if (bytesRead <= 0) break;
|
||||
offset += bytesRead;
|
||||
}
|
||||
fileData[offset] = '\0';
|
||||
zenith::close(handle);
|
||||
|
||||
// Parse into lines
|
||||
ManLine* lines = (ManLine*)zenith::malloc(MAN_MAX_LINES * sizeof(ManLine));
|
||||
if (lines == nullptr) {
|
||||
zenith::mfree(fileData);
|
||||
zenith::print("Out of memory.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int totalLines = 0;
|
||||
const char* p = fileData;
|
||||
while (*p && totalLines < MAN_MAX_LINES) {
|
||||
const char* lineStart = p;
|
||||
while (*p && *p != '\n') p++;
|
||||
int lineLen = (int)(p - lineStart);
|
||||
if (*p == '\n') p++;
|
||||
|
||||
ManLine& ln = lines[totalLines];
|
||||
ln.isSH = false;
|
||||
ln.isSS = false;
|
||||
ln.isBold = false;
|
||||
ln.isTH = false;
|
||||
|
||||
if (starts_with(lineStart, ".TH ")) {
|
||||
ln.isTH = true;
|
||||
ln.text = lineStart + 4;
|
||||
ln.len = lineLen - 4;
|
||||
} else if (starts_with(lineStart, ".SH ")) {
|
||||
ln.isSH = true;
|
||||
ln.text = lineStart + 4;
|
||||
ln.len = lineLen - 4;
|
||||
} else if (starts_with(lineStart, ".SS ")) {
|
||||
ln.isSS = true;
|
||||
ln.text = lineStart + 4;
|
||||
ln.len = lineLen - 4;
|
||||
} else if (starts_with(lineStart, ".B ")) {
|
||||
ln.isBold = true;
|
||||
ln.text = lineStart + 3;
|
||||
ln.len = lineLen - 3;
|
||||
} else if (starts_with(lineStart, ".BI ")) {
|
||||
ln.isBold = true;
|
||||
ln.text = lineStart + 4;
|
||||
ln.len = lineLen - 4;
|
||||
} else {
|
||||
ln.text = lineStart;
|
||||
ln.len = lineLen;
|
||||
}
|
||||
|
||||
totalLines++;
|
||||
}
|
||||
|
||||
if (totalLines == 0) {
|
||||
zenith::mfree(lines);
|
||||
zenith::mfree(fileData);
|
||||
zenith::print("Empty manual page.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get terminal dimensions
|
||||
int cols = 80, rows = 25;
|
||||
zenith::termsize(&cols, &rows);
|
||||
|
||||
// Enter alternate screen, hide cursor
|
||||
zenith::print("\033[?1049h");
|
||||
zenith::print("\033[?25l");
|
||||
|
||||
int scroll = 0;
|
||||
int maxScroll = totalLines - (rows - 1);
|
||||
if (maxScroll < 0) maxScroll = 0;
|
||||
|
||||
man_render(lines, totalLines, scroll, rows, cols, topic, foundSection);
|
||||
|
||||
// Event loop — yield while waiting for key input
|
||||
bool running = true;
|
||||
while (running) {
|
||||
while (!zenith::is_key_available()) {
|
||||
zenith::yield();
|
||||
}
|
||||
|
||||
Zenith::KeyEvent ev;
|
||||
zenith::getkey(&ev);
|
||||
if (!ev.pressed) continue;
|
||||
|
||||
int contentRows = rows - 1;
|
||||
|
||||
switch (ev.ascii) {
|
||||
case 'q':
|
||||
running = false;
|
||||
break;
|
||||
case 'j':
|
||||
if (scroll < maxScroll) scroll++;
|
||||
break;
|
||||
case 'k':
|
||||
if (scroll > 0) scroll--;
|
||||
break;
|
||||
case ' ':
|
||||
scroll += contentRows;
|
||||
if (scroll > maxScroll) scroll = maxScroll;
|
||||
break;
|
||||
case 'b':
|
||||
scroll -= contentRows;
|
||||
if (scroll < 0) scroll = 0;
|
||||
break;
|
||||
case 'g':
|
||||
scroll = 0;
|
||||
break;
|
||||
case 'G':
|
||||
scroll = maxScroll;
|
||||
break;
|
||||
default:
|
||||
// Handle scancodes for special keys
|
||||
switch (ev.scancode) {
|
||||
case 0x48: // Up arrow
|
||||
if (scroll > 0) scroll--;
|
||||
break;
|
||||
case 0x50: // Down arrow
|
||||
if (scroll < maxScroll) scroll++;
|
||||
break;
|
||||
case 0x49: // Page Up
|
||||
scroll -= contentRows;
|
||||
if (scroll < 0) scroll = 0;
|
||||
break;
|
||||
case 0x51: // Page Down
|
||||
scroll += contentRows;
|
||||
if (scroll > maxScroll) scroll = maxScroll;
|
||||
break;
|
||||
case 0x47: // Home
|
||||
scroll = 0;
|
||||
break;
|
||||
case 0x4F: // End
|
||||
scroll = maxScroll;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (running) {
|
||||
man_render(lines, totalLines, scroll, rows, cols, topic, foundSection);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore screen
|
||||
zenith::print("\033[?25h");
|
||||
zenith::print("\033[?1049l");
|
||||
|
||||
zenith::mfree(lines);
|
||||
zenith::mfree(fileData);
|
||||
}
|
||||
@@ -44,15 +44,17 @@ static void print_int(uint64_t n) {
|
||||
}
|
||||
|
||||
static void prompt() {
|
||||
zenith::print("zenith> ");
|
||||
zenith::print("% ");
|
||||
}
|
||||
|
||||
static void cmd_help() {
|
||||
zenith::print("Available commands:\n");
|
||||
zenith::print(" help Show this help message\n");
|
||||
zenith::print(" info Show system information\n");
|
||||
zenith::print(" man <topic> View manual pages\n");
|
||||
zenith::print(" ls List ramdisk files\n");
|
||||
zenith::print(" cat <file> Display file contents\n");
|
||||
zenith::print(" run <file> Spawn a new process from an ELF file\n");
|
||||
zenith::print(" ping <ip> Send ICMP echo requests\n");
|
||||
zenith::print(" uptime Show uptime in milliseconds\n");
|
||||
zenith::print(" clear Clear the screen\n");
|
||||
@@ -223,11 +225,52 @@ static void cmd_ping(const char* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_clear() {
|
||||
// Print enough newlines to scroll past visible content
|
||||
for (int i = 0; i < 50; i++) {
|
||||
zenith::putchar('\n');
|
||||
static void cmd_run(const char* arg) {
|
||||
arg = skip_spaces(arg);
|
||||
if (*arg == '\0') {
|
||||
zenith::print("Usage: run <filename>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build path "0:/<filename>"
|
||||
char path[128];
|
||||
const char* prefix = "0:/";
|
||||
int i = 0;
|
||||
while (prefix[i]) { path[i] = prefix[i]; i++; }
|
||||
int j = 0;
|
||||
while (arg[j] && i < 126) { path[i++] = arg[j++]; }
|
||||
path[i] = '\0';
|
||||
|
||||
int pid = zenith::spawn(path);
|
||||
if (pid < 0) {
|
||||
zenith::print("Error: failed to spawn '");
|
||||
zenith::print(arg);
|
||||
zenith::print("'\n");
|
||||
} else {
|
||||
zenith::waitpid(pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_man(const char* arg) {
|
||||
arg = skip_spaces(arg);
|
||||
if (*arg == '\0') {
|
||||
zenith::print("Usage: man <topic>\n");
|
||||
zenith::print(" man <section> <topic>\n");
|
||||
zenith::print("Try: man intro\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int pid = zenith::spawn("0:/man.elf", arg);
|
||||
if (pid < 0) {
|
||||
zenith::print("Error: failed to start man viewer\n");
|
||||
} else {
|
||||
zenith::waitpid(pid);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_clear() {
|
||||
zenith::print("\033[2J"); // Clear entire screen
|
||||
zenith::print("\033[H"); // Move cursor to top-left
|
||||
}
|
||||
|
||||
static void process_command(const char* line) {
|
||||
@@ -241,10 +284,18 @@ static void process_command(const char* line) {
|
||||
cmd_info();
|
||||
} else if (streq(line, "ls")) {
|
||||
cmd_ls();
|
||||
} else if (starts_with(line, "man ")) {
|
||||
cmd_man(line + 4);
|
||||
} else if (streq(line, "man")) {
|
||||
cmd_man("");
|
||||
} else if (starts_with(line, "cat ")) {
|
||||
cmd_cat(line + 4);
|
||||
} else if (streq(line, "cat")) {
|
||||
cmd_cat("");
|
||||
} else if (starts_with(line, "run ")) {
|
||||
cmd_run(line + 4);
|
||||
} else if (streq(line, "run")) {
|
||||
cmd_run("");
|
||||
} else if (starts_with(line, "ping ")) {
|
||||
cmd_ping(line + 5);
|
||||
} else if (streq(line, "ping")) {
|
||||
@@ -265,7 +316,10 @@ static void process_command(const char* line) {
|
||||
|
||||
extern "C" void _start() {
|
||||
zenith::print("\n");
|
||||
zenith::print(" ZenithOS Shell v0.1\n");
|
||||
zenith::print(" ZenithOS\n");
|
||||
zenith::print(" Copyright (c) 2025-2026 Daniel Hammer\n");
|
||||
zenith::print("\n");
|
||||
|
||||
zenith::print(" Type 'help' for available commands.\n");
|
||||
zenith::print("\n");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user