DVX_GUI/src/libs/kpunch/libdvx/platform/dvxPlat.h
2026-10-13 13:49:12 -05:00

958 lines
46 KiB
C

// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// dvxPlat.h -- Platform abstraction layer for DVX GUI
//
// All OS-specific and CPU-specific code is isolated behind this
// interface. To port DVX to a new platform, implement a new
// dvxPlatformXxx.c against this header.
//
// Two implementations exist:
// dvxPlatformDos.c -- DJGPP/DPMI: real VESA VBE, INT 33h mouse,
// INT 16h keyboard, INT 9 keyboard hook, PIT / INT 8
// timer service, rep movsd/stosl asm spans
// dvxPlatformHost.c -- native Linux test backend: offscreen
// framebuffer, queue-driven input, fake clock
// with simulated timer interrupts
//
// Code shared by every backend lives in dvxPlatformUtil.c (logging,
// path/file helpers, glob matching, Alt scancode table, exit routing),
// dvxPlatformIrq.c (held-key table, timer service; interrupt-time steps
// in the private dvxPlatIsr.h) and dvxMemTrack.c (per-app allocation
// tracking).
//
// The abstraction covers video mode setup, framebuffer flushing,
// optimized memory spans, mouse and keyboard input, system information,
// crash recovery, and DXE symbol export, plus small file-system helpers
// (filename validation, mkdir -p, chdir with drive, copy, read, glob)
// where the C standard library is not enough or not portable.
//
// Design rule: functions in this header must be stateless or manage their
// own internal state. They must not reference AppContextT or any layer
// above dvxTypes.h. This ensures the platform layer can be compiled and
// tested independently.
#ifndef DVX_PLAT_H
#define DVX_PLAT_H
#include "dvxTypes.h"
#include "dvxMem.h"
#include <stddef.h>
#include <setjmp.h>
// Maximum file path length. 260 matches DOS MAX_PATH.
#define DVX_MAX_PATH 260
// ============================================================
// Keyboard event
// ============================================================
//
// Separating ASCII value from scancode handles the DOS keyboard model
// where extended keys (arrows, F-keys) produce a zero ASCII byte followed
// by a scancode. The platform layer normalizes this into a single struct.
typedef struct {
int32_t ascii; // ASCII value, 0 for extended/function keys
int32_t scancode; // PC scan code (0x48=Up, 0x50=Down, etc.)
int32_t modifiers; // KEY_MOD_* state for this key (key-down events only)
} PlatformKeyEventT;
// ============================================================
// Logging
// ============================================================
// Append a line to the DVX log file. Lives in dvxPlatformUtil.c, so it
// is linked into dvx.exe (and exported to all modules) as well as into
// every host-side tool and test binary.
void dvxLog(const char *fmt, ...);
// The DVX log file in the DVX folder: the loader starts it and dvxLog
// appends to it.
#define DVX_LOG_FILE "dvx.log"
// Set the path dvxLog appends to. The pointer is retained, not copied,
// so it must outlive every subsequent dvxLog call (a string literal is
// the intended argument). Defaults to DVX_LOG_FILE in the working
// directory until this is called (and for a NULL path).
void platformSetLogPath(const char *path);
// Keep the log in memory instead of a file (DVX on read-only media with
// no TEMP folder, or SETUP.EXE, which never writes to its disk): the
// latest PLATFORM_LOG_MEM_MAX bytes. platformSetLogPath goes back to a
// file.
#define PLATFORM_LOG_MEM_MAX 16384
void platformSetLogMemory(void);
// The log kept in memory ("" when there is none).
const char *platformLogMemory(void);
// Whether DVX's own folder can be written to. The loader tests it at
// startup (platformDirIsWritable); on read-only media (a CD-ROM, a
// write-protected disk) settings are kept in memory until DVX exits, the
// help files are used as they are, and the shell says so once.
bool platformIsReadOnly(void);
void platformSetReadOnly(bool readOnly);
// True when a file can be created in dir (a scratch file is made and
// removed again).
bool platformDirIsWritable(const char *dir);
// The folder for scratch files: the TEMP (or else TMP) environment
// variable, in native separators (a copy, valid until the next call);
// NULL when neither is set.
const char *platformTempDir(void);
// ============================================================
// Timing
// ============================================================
// Monotonic tick counter for UI timing (double-click, tooltip delay,
// cursor blink, timer widgets). Ticks advance at platformClockHz()
// per second. On DOS this wraps clock()/CLOCKS_PER_SEC; on the host
// test backend it is a fake clock advanced only by hostClockAdvance()
// so timing-dependent behaviour is deterministic under test.
//
// Unsigned so that "now - then" is wrap-safe; always compare intervals
// via subtraction, never compare raw tick values with < or >.
typedef uint32_t PlatformTicksT;
PlatformTicksT platformClock(void);
PlatformTicksT platformClockHz(void);
// Fine-grained, free-running timestamp for input-timing entropy (not a
// clock: units and wrap point are backend-specific). DOS returns the TSC
// when the CPU has one, else BIOS ticks with the PIT counter in the low
// 16 bits.
uint64_t platformEntropyStamp(void);
// Convert a millisecond interval to ticks at the platform rate.
#define PLATFORM_MS_TO_TICKS(ms) ((PlatformTicksT)(((uint64_t)(ms) * platformClockHz()) / 1000))
// Convert ticks to milliseconds: the "now" the protocol engines (conn,
// xfer) take is PLATFORM_TICKS_TO_MS(platformClock()).
#define PLATFORM_TICKS_TO_MS(ticks) ((uint32_t)(((uint64_t)(ticks) * 1000) / platformClockHz()))
// ============================================================
// Timing -- high-resolution clock and the timer service
// ============================================================
//
// One timer interrupt serves everything. On DOS the service owns PIT
// channel 0 (the 8254 timer, rate generator mode) and the INT 8 vector:
// it runs the interrupt as fast as the fastest timer needs (at most
// PLATFORM_TIMER_MAX_HZ, at the BIOS 18.2 Hz while no timer is
// registered) and passes on exactly one interrupt per 65536 PIT input
// clocks to the BIOS handler, so the BIOS tick count, the DOS time of day
// and clock() keep their normal rate. The host backend simulates the same
// interrupts, driven by hostClockAdvance.
#define PLATFORM_PIT_HZ 1193182 // PIT input clock
#define PLATFORM_PIT_BIOS_DIVISOR 65536 // PIT clocks per BIOS tick (18.2 Hz)
#define PLATFORM_TIMER_MAX_HZ 1000 // highest rate a timer may ask for
#define PLATFORM_TIMER_FAST_MAX_HZ 30000 // highest rate platformTimerAddFast accepts
#define PLATFORM_TIMER_SLOTS 16 // timers that can exist at once
#define PLATFORM_US_PER_SEC 1000000
// Microseconds since the timer service started (0 before), monotonic. On
// DOS the PIT clocks counted by the interrupt plus the live channel 0
// count (about 0.84 us steps), or, on a CPU with a time-stamp counter,
// the TSC since the last interrupt scaled by the TSC rate measured over
// the interrupt period before it, so it never drifts from the PIT. Not
// for use inside a timer callback.
uint64_t platformMicros(void);
// A timer callback. It runs INSIDE THE TIMER INTERRUPT, with interrupts
// disabled, on a small interrupt stack: keep it to a few dozen
// microseconds, do not call the C library, DVX or DOS, do not use floating
// point, and only touch code and data pinned with platformLockRegion
// (on DOS a page fault here is fatal). Setting flags or counters, filling
// a buffer or writing I/O ports is what it is for.
typedef void (*PlatformTimerFnT)(void *userData);
// Diagnostic counters of the timer service.
typedef struct {
uint32_t rateHz; // current interrupt rate, rounded
uint32_t divisor; // PIT channel 0 reload value
uint32_t interrupts; // timer interrupts since the service started
uint32_t biosTicks; // of those, passed on to the BIOS
} PlatformTimerStatsT;
// Start / stop the timer service. Init programs the PIT for the BIOS
// rate and takes the timer interrupt; Shutdown gives the PIT and the
// vector back as it found them (BIOS square-wave mode, 18.2 Hz) and drops
// every timer. Init also arranges for Shutdown to run at exit() and on
// a crash that is not recovered. A second Init does nothing.
void platformTimerInit(void);
void platformTimerShutdown(void);
// Call fn(userData) hz times a second (1..PLATFORM_TIMER_MAX_HZ, exact on
// average: the calls fall on timer interrupts, so single intervals jitter
// by up to one interrupt period; the interrupt rate changes at once, the
// PIT restarting with the part of the period already run counted, and
// the first call comes one interval on). ownerAppId is recorded for
// platformTimerRemoveOwner (0 for a library that removes its own timers).
// Returns a timer id, or -1 when hz is out of range, fn is NULL, all
// PLATFORM_TIMER_SLOTS are in use or the service is not running.
int32_t platformTimerAdd(uint32_t hz, PlatformTimerFnT fn, void *userData, int32_t ownerAppId);
// platformTimerAdd for a device that must be fed at an audio rate (the PC
// speaker's digital playback): hz may be up to PLATFORM_TIMER_FAST_MAX_HZ.
// The whole timer interrupt then runs that often, so fn must be very
// short (a few port writes); every other rule of platformTimerAdd holds.
// For a call on every interrupt, ask for PLATFORM_PIT_HZ / divisor.
int32_t platformTimerAddFast(uint32_t hz, PlatformTimerFnT fn, void *userData, int32_t ownerAppId);
// Stop one timer; the interrupt rate drops when it was the fastest. When
// this returns the callback is not running and will not run again.
void platformTimerRemove(int32_t timerId);
// Stop every timer registered for ownerAppId (the shell calls this when an
// app ends).
void platformTimerRemoveOwner(int32_t ownerAppId);
// Current counters of the timer service.
void platformTimerStats(PlatformTimerStatsT *out);
// Disable interrupts and return the previous state, for
// platformIrqRestore: brackets the few instructions that change data an
// interrupt handler reads. Nothing to do on the host.
void platformIrqRestore(uint32_t state);
uint32_t platformIrqSave(void);
// Shutdown hooks: fn runs when DVX ends without its normal shutdown
// (exit() or a crash with no recovery point), before the timer service and
// the keyboard hook are given back, so a module that drives hardware from
// an interrupt (a sound card playing by DMA) can stop it. A module removes
// its hook before it is unloaded. At most PLATFORM_SHUTDOWN_HOOKS at once;
// Add returns false when full (adding one already present is a no-op).
#define PLATFORM_SHUTDOWN_HOOKS 8
typedef void (*PlatformShutdownFnT)(void);
bool platformShutdownHookAdd(PlatformShutdownFnT fn);
void platformShutdownHookRemove(PlatformShutdownFnT fn);
// ============================================================
// System lifecycle
// ============================================================
// One-time platform initialisation (a second call does nothing new). On
// DOS this disables Ctrl+C and Ctrl+Break (runtime, SIGINT and DOS break
// checking) so the user cannot kill the GUI from inside a graphics mode,
// and installs a DOS critical-error (INT 24h) handler that answers Fail:
// a drive that is not ready, a write-protected disk or a removed disk makes
// the DOS call return an error instead of waiting at "Abort, Retry, Fail"
// on the hidden text screen. DOS restores its own handler when DVX exits.
void platformInit(void);
// Cooperative yield -- give up the CPU timeslice when the event loop has
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
// multitaskers (Windows 3.x, OS/2).
void platformYield(void);
// ============================================================
// Video
// ============================================================
// Probe for a suitable video mode, enable it, map the framebuffer, and
// allocate the system RAM backbuffer. On DOS this involves VBE BIOS calls
// and DPMI physical memory mapping. Fills in all DisplayT fields on success.
int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Restore the previous video mode and free all video resources. On DOS
// this restores VGA text mode (mode 3) and frees the DPMI memory mapping.
void platformVideoShutdown(DisplayT *d);
// Enumerate the graphics modes platformVideoInit can set (on DOS: LFB
// modes at 8/15/16/32 bpp), once per distinct width/height/depth. Feeds
// the video-mode list offered to the user; platformVideoInit selects its
// own best match independently.
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
// Program the VGA/VESA DAC palette registers (8-bit mode only). pal
// points to the full 256-entry RGB table (3 bytes per entry); entries
// firstEntry..firstEntry+count-1 are written, clamped to the table.
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
// VGA mode 13h: the boot splash, and the full-screen fallback
#define VGA13_W 320
#define VGA13_H 200
// Full-screen exclusive video (dvxExclusiveBegin). Begin sets the
// smallest mode of at least requestedW x requestedH (preferredBpp first,
// any depth for 0; on DOS VGA mode 13h up to 320x200 and Mode X up to
// 320x240 at 8 bpp when no VBE mode is that small) and fills d with its
// own linear backbuffer; the desktop's display and buffers are left as
// they are. End frees d's buffers and sets the desktop's mode (and 8-bit
// palette) again; call it after a failed Begin too. Flush puts a
// backbuffer rectangle on the screen (Mode X: a whole-screen flush flips
// pages at the next retrace and waits for it). Capture reads what is on
// the screen into out (width x height bytes) where the backbuffer is not
// a copy of it (Mode X), else returns false. WaitRetrace waits for the
// start of the next vertical retrace.
int32_t platformVideoExclusiveBegin(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
bool platformVideoExclusiveCapture(const DisplayT *d, uint8_t *out);
int32_t platformVideoExclusiveEnd(DisplayT *d, const DisplayT *desktop);
void platformVideoExclusiveFlush(const DisplayT *d, const RectT *r);
void platformVideoWaitRetrace(void);
// ============================================================
// Framebuffer flush
// ============================================================
// Copy a rectangle from the system RAM backbuffer (d->backBuf) to the
// display surface (d->lfb). On DOS this copies to real video memory via
// the LFB mapping -- the critical path where PCI bus write speed matters.
// Each scanline is copied as a contiguous block; rep movsd on DOS gives
// near-optimal bus utilization for aligned 32-bit writes.
void platformFlushRect(const DisplayT *d, const RectT *r);
// ============================================================
// Optimised memory operations (span fill / copy)
// ============================================================
//
// These are the innermost loops of the renderer -- called once per
// scanline of every rectangle fill, blit, and text draw. On DOS they
// use inline assembly: rep stosl for fills (one instruction fills an
// entire scanline) and rep movsd for copies.
//
// Three variants per operation (8/16/32 bpp) because the fill semantics
// differ by depth: 8-bit fills a byte per pixel, 16-bit fills a word
// (must handle odd counts), and 32-bit fills a dword. The copy variants
// differ only in the byte count computation (count * bytesPerPixel).
// drawInit() selects the right function pointers into BlitOpsT at startup.
void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count);
void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count);
void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count);
// ============================================================
// Input -- Mouse
// ============================================================
// Initialize the mouse driver and constrain movement to the screen bounds.
// On DOS this resets the INT 33h driver, sets the X/Y range, and centers
// the cursor. Safe to call again after a video-mode change: the speed,
// acceleration and wheel settings applied earlier are re-applied.
void platformMouseInit(int32_t screenW, int32_t screenH);
// Button bits platformMousePoll reports (MOUSE_LEFT/RIGHT/MIDDLE)
#define MOUSE_BUTTON_MASK (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE)
// Poll the current mouse state. Buttons is a bitmask: bit 0 = left,
// bit 1 = right, bit 2 = middle. Polling (rather than event-driven
// callbacks) is the natural model for a cooperative event loop -- the
// main loop polls once per frame and compares with the previous state
// to detect press/release edges.
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);
// Detect and activate mouse wheel support. Returns true if the mouse
// driver supports the CuteMouse Wheel API (INT 33h AX=0011h). This
// call also activates wheel reporting -- after it returns true, function
// 03h will return wheel delta in BH. Must be called after platformMouseInit.
bool platformMouseWheelInit(void);
// Read the accumulated wheel delta since the last call. Positive = scroll
// down, negative = scroll up. Returns 0 if no wheel movement or if wheel
// is not supported. The delta is cleared on each read (accumulated by the
// driver between polls).
int32_t platformMouseWheelPoll(void);
// Set the double-speed threshold in mickeys/second. When the mouse
// moves faster than this, cursor movement is doubled by the driver.
// A very high value (e.g. 10000) effectively disables acceleration.
void platformMouseSetAccel(int32_t threshold);
// Set the mickey-to-pixel ratio. Controls base cursor speed.
// horizMickeys/vertMickeys: mickeys per 8 pixels of cursor movement.
// Higher values = slower cursor. Default is typically 8 horiz, 16 vert.
void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys);
// Move the mouse cursor to an absolute screen position. Used to clamp the
// cursor to window edges during resize operations. On DOS the driver
// cursor is moved too (INT 33h fn 04h) only when tracking absolute
// position; in mickey mode the pending motion counters are flushed.
void platformMouseWarp(int32_t x, int32_t y);
// ============================================================
// Input -- Keyboard
// ============================================================
// Return the current modifier key state in BIOS shift-state format:
// bits 0-1 = either shift, bit 2 = ctrl, bit 3 = alt. On DOS this
// uses INT 16h AH=12h (get extended shift status) and returns the low byte.
int32_t platformKeyboardGetModifiers(void);
// The Shift/Ctrl/Alt bits of the BIOS shift-state byte.
#define KEY_MOD_ALL (KEY_MOD_SHIFT | KEY_MOD_CTRL | KEY_MOD_ALT)
// ASCII byte the enhanced BIOS reports for grey (extended) keys.
#define KEY_ASCII_GREY_PREFIX 0xE0
// Non-blocking read of the next key from the keyboard buffer. Returns
// true if a key was available. On DOS this uses INT 16h AH=11h (check)
// + AH=10h (read). Extended keys (0xE0 prefix from enhanced keyboard)
// are normalized by zeroing the ASCII byte so the scancode identifies
// them unambiguously, and Shift/Ctrl/Alt+F1..F12 and Alt+grey navigation
// keys are reported as the plain key's scancode with evt->modifiers
// holding the modifier the BIOS code proved was down (see
// platformNormalizeKeyEvent).
bool platformKeyboardRead(PlatformKeyEventT *evt);
// Map the BIOS scancodes for Shift/Ctrl/Alt+F1..F12 onto the plain F-key
// scancodes (F1-F10 0x3B-0x44, F11 0x85, F12 0x86) and Alt+grey
// Home/Up/PgUp/Left/Right/End/Down/PgDn/Ins/Del (0x97-0xA3) onto the plain
// navigation scancodes (0x47-0x53). Any other scancode is returned
// unchanged.
int32_t platformNormalizeFKeyScan(int32_t scancode);
// Applied by every backend's platformKeyboardRead. A grey-key ASCII
// byte (KEY_ASCII_GREY_PREFIX) becomes 0. For an extended key (ascii 0)
// then folds the scancode as platformNormalizeFKeyScan does and sets
// evt->modifiers to the modifier the BIOS code implies plus any
// lower-priority modifier in liveMods (Alt over Ctrl over Shift), so a
// modifier released before the key is polled is not lost. Otherwise
// the scancode is kept and evt->modifiers = liveMods.
void platformNormalizeKeyEvent(PlatformKeyEventT *evt, int32_t liveMods);
// ============================================================
// Input -- Raw keys and the held-key table
// ============================================================
//
// The keyboard hook (on DOS a chain on the INT 9 hardware interrupt that
// reads port 60h; the BIOS still stores typed keys for
// platformKeyboardRead) decodes every set-1 byte the keyboard sends:
// 0xE0 prefixes, the fake Shift codes the keyboard wraps around grey keys
// (dropped), the Pause burst (one press and one release) and keyboard
// replies such as ACK and overrun (dropped). Every key going down or up
// updates the held-key table and is queued as a RawKeyEventT. Raw codes
// are KEY_RAW_CODE(scancode, extended) (dvxTypes.h).
// Size of the raw key event queue; when it is full new events are dropped
// (the held-key table is still updated).
#define PLATFORM_KEY_EVENT_QUEUE 64
// Install / remove the keyboard hook. Init before the other functions of
// this section, Shutdown before exit. A second Init does nothing. On DOS
// the hook's code and data are pinned with platformLockRegion; if that
// fails there is no hook (no events, every key reads as up).
void platformKeyHookInit(void);
void platformKeyHookShutdown(void);
// Next queued raw key event, oldest first; false when none is waiting.
bool platformKeyEventRead(RawKeyEventT *evt);
// True while the key with this raw code is held down.
bool platformKeyIsDown(int32_t rawCode);
// Copy the held-key table: out[rawCode] is 1 while that key is down.
void platformKeyStateGet(uint8_t out[KEY_RAW_COUNT]);
// Mark every key up (queued events are kept). A key that is still held
// reads as down again at its next typematic repeat.
void platformKeyStateClear(void);
// ============================================================
// Input -- Test injection
// ============================================================
//
// Used by the test script runner (DVX /SCRIPT=...), the installer and
// BASIC's SysSendKey (keys only). Injected input enters at the lowest
// level each backend has, so it travels the same code as real input.
// Queue one keystroke exactly as the BIOS would store it: ascii is the
// character (0, or 0xE0 for grey keys, for extended keys), scancode the
// BIOS scancode (e.g. 0x54 for Shift+F1) and modifiers the BIOS
// shift-state byte (bits 0-1 shift, bit 2 ctrl, bit 3 alt) that
// platformKeyboardGetModifiers reports from now on. On DOS the key goes
// into the BIOS keyboard buffer (INT 16h AH=05h) and the modifier bits
// into the BIOS shift-state byte. Returns false if the buffer is full.
bool platformInjectKey(int32_t ascii, int32_t scancode, int32_t modifiers);
// Set the reported modifier state without queueing a key.
void platformInjectModifiers(int32_t modifiers);
// Feed one set-1 byte (an 0xE0 prefix, a make code or a break code) to
// the keyboard hook's decoder as if the keyboard had sent it: the
// held-key table and the raw event queue change, the BIOS keyboard
// buffer does not.
void platformInjectScan(uint8_t scan);
// Override the mouse position and buttons platformMousePoll reports
// until the next call. The driver is still polled, so its state machine
// keeps running; only the reported values are replaced.
void platformInjectMouse(int32_t x, int32_t y, int32_t buttons);
// Translate an Alt+key scancode to its corresponding ASCII character.
// When Alt is held, DOS doesn't provide the ASCII value -- only the
// scancode. This function contains a lookup table mapping scancodes
// to their unshifted letter/digit. Returns 0 for scancodes that don't
// map to a printable character (e.g. Alt+F1).
char platformAltScanToChar(int32_t scancode);
// The BIOS scan code INT 16h gives for DVX key code key with modifiers mod
// before the keyboard path folds it onto a plain key: Shift / Ctrl / Alt
// with F1..F12, Alt with Home..Delete and the grey keypad / and Enter, and
// Alt with a letter or digit have codes of their own there. -1 when the
// key with those modifiers has none (its own key code stands). For
// programs that read the raw codes, such as BASIC's INKEY$.
int32_t platformBiosScanCode(int32_t key, int32_t mod);
// ============================================================
// System information
// ============================================================
// Maximum size of the formatted system information text
#define PLATFORM_SYSINFO_MAX 4096
// Gather hardware information (CPU, clock, memory, DOS/DPMI version,
// video, mouse, disk drives) and return as a pre-formatted text string.
// The display pointer provides the current video mode info. Returns a
// pointer to a static buffer valid for the lifetime of the process.
// On DOS this uses CPUID, RDTSC, DPMI, INT 21h, INT 33h, and VBE.
const char *platformGetSystemInfo(const DisplayT *display);
// ============================================================
// File system
// ============================================================
// Longest long file name (platformValidateLongName)
#define PLATFORM_LONG_NAME_MAX 255
// The short (8.3) form of an existing path, for what reads names before a
// long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (a DOS
// command line): on a drive with long names DOS gives every part's short
// name; elsewhere, and when DOS cannot, path itself (platformPathTidy:
// native separators, so "C:/DVX" works the same). False when out is too
// small.
bool platformShortPath(const char *path, char *out, int32_t outSize);
struct stat;
// The size stat reported for a file. DOS sizes are 32-bit unsigned (a
// FAT32 file reaches 4 GB - 1) where the DOS C library's st_size is a
// signed 32-bit off_t, so a file past 2 GB reads as negative there.
uint64_t platformStatSize(const struct stat *st);
// True when the drive holding path (NULL or "": the current drive) takes
// long file names: on DOS when a long-name driver (Windows, DOSLFN) is
// loaded and covers that drive; elsewhere always. Without them DOS names
// are 8.3.
bool platformLongNames(const char *path);
// Validate a name for a new file or folder in folder dir (NULL or "": the
// current one) against the rules of its disk. On DOS: long names where
// platformLongNames(dir) (platformValidateLongName), else 8.3
// (platformValidateShortName), and dir with the name must fit
// DVX_MAX_PATH; elsewhere: not empty, . or .., no separators or control
// characters. Returns NULL if the name is valid, or a human-readable
// error string describing why it's invalid.
const char *platformValidateFilename(const char *dir, const char *name);
// The DOS name rules by themselves (NULL or why not): a long name (1 to
// PLATFORM_LONG_NAME_MAX characters, spaces, several dots and + , ; = [ ]
// allowed, none of control characters and \ / : * ? " < > |, no dot or
// space at the end) or an 8.3 name; neither may be a device name (CON,
// PRN, AUX, NUL, CLOCK$, COMn, LPTn) before its first dot.
const char *platformValidateLongName(const char *name);
const char *platformValidateShortName(const char *name);
// True when a long name may hold character c (not a control character or
// one of \ / : * ? " < > |).
bool platformLongNameChar(char c);
// Query current system memory. Sets totalKb and freeKb to the total
// and free physical memory in kilobytes. Returns false if unavailable.
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
// Per-app memory tracking is declared and documented in dvxMem.h.
// Create a directory and all parent directories (like mkdir -p).
// Returns 0 on success, -1 on failure (including a path that does not fit
// in DVX_MAX_PATH). Existing directories are not an error.
int32_t platformMkdirRecursive(const char *path);
// Change the working directory, including drive letter on DOS. Standard
// chdir() does not switch drives under DJGPP; this wrapper switches drive
// first when the path has a drive-letter prefix (e.g. "A:\DVX") and
// restores the previous drive if the chdir fails. Returns 0 on success,
// -1 on failure (path not found, bad drive letter).
int32_t platformChdir(const char *path);
// Free the backbuffer and palette without restoring text mode. Used
// when switching between graphics modes live.
void platformVideoFreeBuffers(DisplayT *d);
// Return a pointer to the last directory separator in path
// (platformPathIsSep), or NULL if none is found.
char *platformPathDirEnd(const char *path);
// out = the folder part of path in native separators: everything before
// the last separator, a root keeping its own ("C:\\X.TXT" -> "C:\\", since
// "C:" alone is the drive's current folder), the drive of "C:X.TXT", or
// "." when path names no folder. False when it does not fit.
bool platformPathDirName(const char *path, char *out, int32_t outSize);
// True for a path rooted at a separator ('/' or '\\') or qualified with
// a drive letter ("C:...").
bool platformPathIsAbsolute(const char *path);
// True when c separates the parts of a path: '/' and '\\' alike, on every
// platform, so a path a program writes with '/' works on DOS exactly as
// with '\\' (DOS and the host build also share INI files, projects and
// program paths). The one rule all of DVX's path handling follows; the
// price is that a host file name cannot hold a literal '\\'.
bool platformPathIsSep(char c);
// When path is dir itself or lies below it, the rest of path after dir
// and its separator ("" for dir itself); NULL otherwise. Compared as
// platformPathSame does.
const char *platformPathIsUnder(const char *path, const char *dir);
// out = dir and name with one separator between them (none added when
// dir ends in one), every separator native; an absolute name, or an empty
// dir, gives name alone. False when it does not fit in outSize.
bool platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
// Rewrite path's separators as the platform's own (DVX_PATH_SEP), in
// place, before it is shown: on DOS every '/' becomes '\\' (the C
// library hands back "c:/dvx"). Returns path (NULL stays NULL).
char *platformPathNative(char *path);
// True when a and b name the same place by DOS rules, on every platform
// (DVX's own paths mix spellings such as "APPS/X.APP" and "apps\\x.app"):
// the separators interchangeable, a trailing one ignored, case ignored.
// (Text only: "." or a relative path is not resolved.)
bool platformPathSame(const char *a, const char *b);
// platformPathNative, then a run of separators made one (a leading pair,
// a network "\\\\server", kept) and a trailing one dropped unless it is a
// root's ("C:\\", "\\"): the form for a path handed to DOS directly,
// without the C library's own clean-up. In place; returns path.
char *platformPathTidy(char *path);
// Scratch-file extension markers for crash-safe rewrites
// (platformPathSibling + platformReplaceFile). The sibling's extension is
// the marker followed by the leading characters of the original one, so an
// 8.3 name stays 8.3 and files that differ only in extension get distinct
// scratch names: CONFIG\DVX.INI -> CONFIG\DVX.$IN / CONFIG\DVX.~IN,
// FOO.BAS -> FOO.$BA, FOO.FRM -> FOO.$FR.
#define PLATFORM_TMP_EXT ".$"
#define PLATFORM_BAK_EXT ".~"
#define PLATFORM_SIBLING_EXT_MAX 3 // characters in a sibling's extension (8.3)
// out = path with its file-name extension replaced by ext, followed by as
// many leading characters of the original extension as fit in
// PLATFORM_SIBLING_EXT_MAX (a full-length ext replaces it outright; with
// no extension ext is appended). False when the result does not fit in
// outSize or names path itself (compared case-insensitively).
bool platformPathSibling(char *out, int32_t outSize, const char *path, const char *ext);
// Swap a fully written tmpPath in for path. The original is renamed
// aside to bakPath first (DOS rename cannot overwrite) and removed only
// once the replacement is in place; if the swap fails the original is
// restored and tmpPath removed. False when the swap failed or path is a
// directory (never moved aside). A crash between the two renames leaves
// only bakPath; while path is missing a leftover bakPath is never
// deleted, and prefsLoad falls back to reading it.
bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
// Return a pointer to the leaf (basename) portion of path: after the last
// separator, or after a drive ("C:A.TXT" -> "A.TXT"). Never returns
// NULL -- with neither, the whole path is the basename.
const char *platformPathBaseName(const char *path);
// Byte-for-byte file copy. Returns false on any open/read/write failure;
// a partially written destination is removed so no truncated copy is
// left behind. A copy onto itself (platformPathSame) does nothing and
// returns true.
bool platformCopyFile(const char *srcPath, const char *dstPath);
// Slurp a whole file into a freshly-malloc'd, NUL-terminated buffer.
// Returns NULL on error (file missing, OOM, read truncated). On success,
// *outLen (if non-NULL) receives the byte length (not counting the NUL).
// Caller must free() the returned buffer. Binary-safe: the NUL is past
// the end of the reported length.
char *platformReadFile(const char *path, int32_t *outLen);
// Advance past leading ' ' and '\t' characters. Returns s unchanged if
// it's NULL or already points at a non-whitespace byte.
const char *dvxSkipWs(const char *s);
// Bounded formatted append into buf at offset pos, never exceeding
// bufSize. Returns the new position, or -1 if the result would truncate,
// vsnprintf failed, or pos was already out of range. Passing a -1 back in
// as pos propagates, collapsing a chain of appends to one truncation check.
int32_t dvxStrAppendf(char *buf, int32_t bufSize, int32_t pos, const char *fmt, ...) __attribute__((format(printf, 4, 5)));
// Strip trailing ' ', '\t', '\r', '\n' from buf in place. Returns the
// new length. buf may be NULL or empty.
int32_t dvxTrimRight(char *buf);
// dst = src without leading blanks (' ', '\t') or trailing whitespace,
// truncated to fit dstSize. A NULL src gives "". Returns the length.
int32_t dvxCopyTrimmed(char *dst, int32_t dstSize, const char *src);
// Command line arguments are separated by blanks; one with blanks inside
// is written in double quotes.
#define DVX_ARG_QUOTE '"'
#define DVX_ARG_BLANKS " \t"
// Copy the next argument of command line p into out (without its quotes;
// *quoted tells whether it had them) and return the text after it, or
// NULL when only blanks are left. Loop until NULL to walk every argument.
const char *dvxNextArg(const char *p, char *out, int32_t outSize, bool *quoted);
// Append arg to the command line in buf at pos as one argument that
// dvxNextArg reads back whole: in quotes when it holds blanks (a long
// name such as C:\My Programs\Thing.app). Returns the new length, or -1
// when it does not fit (as dvxStrAppendf).
int32_t dvxArgAppend(char *buf, int32_t bufSize, int32_t pos, const char *arg);
// Case-insensitive check that `name` ends with `ext` (which should
// include the leading dot, e.g. ".app"). Returns false if either
// argument is NULL or name is shorter than ext.
bool dvxHasExt(const char *name, const char *ext);
// Read all entries from `dirPath` (except "." and "..") into a
// heap-allocated stb_ds array of malloc'd strings. Returns NULL on
// open failure. An empty directory returns a valid empty array;
// check arrlen() for count. Caller must pass the result to
// dvxReadDirFree when done.
char **dvxReadDir(const char *dirPath);
// Free an array returned by dvxReadDir: frees every entry and the
// stb_ds array header. Safe to call with NULL.
void dvxReadDirFree(char **entries);
// The platform's native directory separator as a string literal. Use
// with string-literal concatenation in format strings or path constants:
// snprintf(buf, sz, "%s" DVX_PATH_SEP "%s", dir, name);
// #define SOMEDIR "CONFIG" DVX_PATH_SEP "WPAPER"
// DJGPP accepts both '/' and '\\' for fopen/stat/etc., so either would
// work, but '\\' is what DOS users expect to see in displayed paths.
// Every other platform uses '/'.
#ifdef __DJGPP__
#define DVX_PATH_SEP "\\"
#else
#define DVX_PATH_SEP "/"
#endif
// Simple glob pattern matching for filenames. Case-insensitive.
// Supports * (zero or more chars) and ? (one char).
bool platformGlobMatch(const char *pattern, const char *name);
// Wildcard filter that matches every file ("*.*" also matches names
// without a dot, as on DOS).
#define PLATFORM_FILTER_ALL "*.*"
// True when name matches filter: platformGlobMatch patterns separated by
// ';', ',' or blanks ("*.txt;*.bat", "*.bmp, *.jpg"). A pattern equal to
// PLATFORM_FILTER_ALL, and a NULL or blank filter, match everything.
// Used by the file dialog and the file operations library.
bool platformFilterMatch(const char *name, const char *filter);
// ============================================================
// Drives and disk space
// ============================================================
// Most drives a system can have (A: through Z:), and the longest drive
// root ("C:\" on DOS, "/" elsewhere) plus its NUL.
#define PLATFORM_DRIVE_MAX 26
#define PLATFORM_DRIVE_ROOT_MAX 4
typedef enum {
PlatformDriveFixedE,
PlatformDriveRemovableE,
PlatformDriveRemoteE // network drive or CD-ROM
} PlatformDriveTypeE;
typedef struct {
char root[PLATFORM_DRIVE_ROOT_MAX];
PlatformDriveTypeE type;
} PlatformDriveT;
// Fill out[0..max) with the drives that exist, in letter order, and return
// the count. Never touches the media (DOS: IOCTL calls only), so empty
// floppy and CD-ROM drives are listed without delay; the second letter of
// a shared floppy drive (B: on a one-floppy system) is left out, as using
// it would make DOS ask for a disk swap. Elsewhere: one drive, "/".
int32_t platformDriveList(PlatformDriveT *out, int32_t max);
// Size and free space in bytes of the disk holding path (on DOS the drive
// letter of path, or the current drive; elsewhere the nearest existing
// folder of path). False when unknown (no such drive, no disk).
bool platformDiskSpace(const char *path, uint64_t *totalBytes, uint64_t *freeBytes);
// Letter of the drive DOS started from ('C'), whose root holds
// AUTOEXEC.BAT; 0 where there is none (every platform but DOS).
char platformBootDrive(void);
// Return the platform's native line ending string.
// "\r\n" on DOS.
const char *platformLineEnding(void);
// Remove every '\r' from buf[0..len) in place and NUL-terminate the
// result, so buf must hold at least len + 1 bytes. Returns the new length.
int32_t platformStripLineEndings(char *buf, int32_t len);
// ============================================================
// DXE module support
// ============================================================
// Symbol-name decoration for dlsym(). DJGPP's COFF toolchain prefixes
// every C symbol with an underscore and its dlsym() expects the
// decorated name; ELF platforms use the bare name. DVX_SYM("appMain")
// yields the correct string literal for the current platform. Code that
// builds names at runtime (snprintf) uses DVX_SYM_PREFIX instead.
#ifdef __DJGPP__
#define DVX_SYM_PREFIX "_"
#else
#define DVX_SYM_PREFIX ""
#endif
#define DVX_SYM(name) DVX_SYM_PREFIX name
// Register platform and C runtime symbols with the dynamic module
// loader so that DXE modules can resolve them at load time. On DOS
// this calls dlregsym() with the full DJGPP libc/libm/libgcc/platform
// export table.
void platformRegisterDxeExports(void);
// Pin / unpin a code (isCode) or data range into physical memory so an
// interrupt handler can touch it without faulting under a paging host.
// Shared by the key-up INT 9 hook and the serial driver's ISR. Returns
// 0 on success, -1 on failure. No-ops returning 0 on hosts without
// paging concerns.
int32_t platformLockRegion(const void *addr, uint32_t size, bool isCode);
int32_t platformUnlockRegion(const void *addr, uint32_t size, bool isCode);
// ============================================================
// Crash recovery
// ============================================================
// Log function signature for crash diagnostics.
typedef void (*PlatformLogFnT)(const char *fmt, ...);
// Install signal handlers for fatal exceptions (SIGSEGV, SIGFPE, SIGILL).
// On crash, logs platform-specific diagnostics (register dump on DJGPP)
// via logFn, stores the signal number in *crashSignal, then longjmps to
// recoveryBuf. The caller is responsible for app-level diagnostics in
// the setjmp recovery block.
void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn);
// Log platform-specific crash diagnostics (signal name and register dump
// on DJGPP). Called internally by the crash handler before longjmp;
// also available for manual invocation if needed.
void platformLogCrashDetail(int sig, PlatformLogFnT logFn);
// ============================================================
// Program exit
// ============================================================
//
// Loaded modules do not get the C library's exit() and abort(): on DOS the
// DXE export table maps them to platformAppExit and platformAppAbort, and
// the host build links every app with the same substitution (see
// HOST_APP_LDFLAGS in common.mk). They call the hook the shell installs,
// which ends just the program that called them, the way End Task does,
// when a program is running; otherwise (or with no hook) they end the
// process as the C library functions would.
// Exit status reported for abort()
#define PLATFORM_ABORT_STATUS 3
// Called with the exit status; returns only when no program is running.
typedef void (*PlatformAppExitFnT)(int status);
void platformSetAppExitHook(PlatformAppExitFnT fn);
// exit() / abort() as seen by loaded modules.
void platformAppAbort(void) __attribute__((noreturn));
void platformAppExit(int status) __attribute__((noreturn));
// Out-of-memory hook for stb_ds (STBDS_OUT_OF_MEMORY, set in
// stb_ds_wrap.h). arrput/hmput/shput have no error channel, so a failed
// growth logs "out of memory (N bytes) in dynamic array" via dvxLog and
// raises SIGSEGV: under platformInstallCrashHandler that takes the normal
// crash-recovery path (the shell kills the current app, or with no app
// current returns to its main loop). With no handler the process
// terminates.
// Never returns.
void platformOutOfMemory(size_t bytes) __attribute__((noreturn));
// ============================================================
// VGA splash screen (mode 13h, 320x200, 256-color)
// ============================================================
// SPLASH.RAW format (written by the bmp2raw tool): SPLASH_PAL_BYTES of
// palette (R, G, B per entry, 6-bit DAC values), then SPLASH_PIXEL_BYTES
// of 8-bit pixels, top-to-bottom scanlines.
#define SPLASH_W VGA13_W
#define SPLASH_H VGA13_H
#define SPLASH_BPP 8
#define SPLASH_PAL_ENTRIES 256
#define SPLASH_PAL_CHANNELS 3
#define SPLASH_PAL_BYTES (SPLASH_PAL_ENTRIES * SPLASH_PAL_CHANNELS)
#define SPLASH_PIXEL_BYTES (SPLASH_W * SPLASH_H)
#define SPLASH_DAC_SHIFT 2 // 8-bit RGB -> 6-bit DAC value
// Enter VGA mode 13h (SPLASH_W x SPLASH_H x 256).
void platformSplashInit(void);
// Return to text mode 03h.
void platformSplashShutdown(void);
// Load and display a SPLASH.RAW file (format above).
// Returns true on success, false if the file could not be loaded.
bool platformSplashLoadRaw(const char *path);
// Fill a rectangle on the VGA mode 13h screen.
void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color);
// Palette index of the loaded splash image closest to an 8-bit RGB color,
// so overlays drawn on the splash do not depend on the image's palette
// layout. 0 when no splash palette is loaded.
uint8_t platformSplashNearestColor(uint8_t r, uint8_t g, uint8_t b);
// ============================================================
// DXE symbol overrides
// ============================================================
// A name/function-pointer pair for registering symbol overrides with
// the dynamic module loader. On DJGPP this maps directly to the
// dxe_symbol_table struct used by dlregsym().
typedef struct {
const char *name;
void *func;
} PlatformSymOverrideT;
// Register function pointer overrides for subsequently loaded modules.
// entries is an array of {name, funcPtr} pairs terminated by {NULL, NULL}.
// On DJGPP this wraps dlregsym(). On platforms without DXE, this is a
// no-op (RTLD_GLOBAL or equivalent handles symbol resolution). Nothing
// in DVX itself calls it: it is SDK API for embedders and test harnesses
// that need to substitute a symbol (e.g. a stub) before loading a module.
void platformRegisterSymOverrides(const PlatformSymOverrideT *entries);
#endif // DVX_PLAT_H