958 lines
46 KiB
C
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
|