From 4c2549efd4560787471189bbbdefcbf7557338e7 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Wed, 18 Mar 2026 02:02:37 -0500 Subject: [PATCH] Updated docs. --- README.md | 120 +++++++++++++++++++++++++++++++++++++- apps/README.md | 141 +++++++++++++++++++++++++++++++++++++++++++++ dvx/README.md | 9 ++- dvxshell/README.md | 140 ++++++++++++++++++++++++++++++++++++++++++++ tasks/README.md | 7 +++ 5 files changed, 409 insertions(+), 8 deletions(-) create mode 100644 apps/README.md create mode 100644 dvxshell/README.md diff --git a/README.md b/README.md index 2906629..8fb9c03 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,118 @@ -# DOS Gaming Network Research +# DVX — DOS Visual eXecutive -This has grown to be more than a GUI project. It's a lot of research code -for the Kangaroo Punch MultiPlayer Game Server Mark II. +A Windows 3.x-style desktop shell for DOS, built with DJGPP/DPMI. Combines a +windowed GUI compositor, cooperative task switcher, and DXE3 dynamic loading to +create a multitasking desktop environment where applications are `.app` shared +libraries loaded at runtime. + +## Components + +``` +dvxgui/ + dvx/ GUI compositor library -> lib/libdvx.a + tasks/ Cooperative task switcher -> lib/libtasks.a + dvxshell/ Desktop shell (Program Manager) -> bin/dvxshell.exe + apps/ DXE app modules (.app files) -> bin/apps/*.app + rs232/ ISR-driven UART serial driver -> lib/librs232.a + packet/ HDLC framing, CRC, Go-Back-N -> lib/libpacket.a + security/ DH key exchange, XTEA-CTR cipher -> lib/libsecurity.a + seclink/ Secure serial link wrapper -> lib/libseclink.a + proxy/ Linux SecLink-to-telnet proxy -> bin/secproxy + termdemo/ Encrypted ANSI terminal demo -> bin/termdemo.exe +``` + +## Building + +Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`). + +``` +make -C dvx # builds lib/libdvx.a +make -C tasks # builds lib/libtasks.a +make -C dvxshell # builds bin/dvxshell.exe +make -C apps # builds bin/apps/*.app +``` + +Set `DJGPP_PREFIX` in the Makefiles if your toolchain is installed somewhere +other than `~/djgpp/djgpp`. + +## Architecture + +The shell runs as a single DOS executable (`dvxshell.exe`) that loads +applications dynamically via DJGPP's DXE3 shared library system. Each app +is a `.app` file exporting an `appDescriptor` and `appMain` entry point. + +``` ++-------------------------------------------------------------------+ +| dvxshell.exe (Task 0) | +| +-------------+ +-----------+ +----------+ +----------------+ | +| | shellMain | | shellApp | | shellDxe | | shellDesktop | | +| | (event loop)| | (lifecycle| | (DXE | | (Program Mgr) | | +| | | | + reaper)| | export) | | | | +| +-------------+ +-----------+ +----------+ +----------------+ | +| | | | | +| +------+-------+ +----+-----+ +----+----+ | +| | libdvx.a | |libtasks.a| | libdxe | | +| | (GUI/widgets)| |(scheduler)| | (DJGPP) | | +| +--------------+ +----------+ +---------+ | ++-------------------------------------------------------------------+ + | | + +---------+ +---------+ + | app.app | | app.app | + | (Task N)| | (Task M)| + +---------+ +---------+ +``` + +### App Types + +**Callback-only** (`hasMainLoop = false`): `appMain` creates windows, registers +callbacks, and returns. The app lives through event callbacks in the shell's +main loop. No dedicated task or stack needed. + +**Main-loop** (`hasMainLoop = true`): A cooperative task is created for the app. +`appMain` runs its own loop calling `tsYield()` to share CPU. Used for apps that +need continuous processing (clocks, terminal emulators, games). + +### Crash Recovery + +The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL. If an app +crashes, the handler `longjmp`s back to the shell's main loop, the crashed app +is force-killed, and the shell continues running. Diagnostic information +(registers, faulting EIP) is logged to `dvx.log`. + +## Sample Apps + +| App | Type | Description | +|-----|------|-------------| +| `progman.app` | Callback | Program Manager: app launcher grid, Task Manager | +| `notepad.app` | Callback | Text editor with file I/O | +| `clock.app` | Main-loop | Digital clock (multi-instance capable) | +| `dvxdemo.app` | Callback | Widget system showcase | + +## Target Platform + +- **CPU**: 486 baseline, Pentium-optimized paths where significant +- **Video**: VESA VBE 2.0+ with linear framebuffer +- **OS**: DOS with DPMI (CWSDPMI or equivalent) +- **Test platform**: 86Box + +## Deployment + +``` +mcopy -o -i bin/dvxshell.exe ::DVXSHELL.EXE +mcopy -o -i bin/apps/*.app ::APPS/ +``` + +## Documentation + +Each component directory has its own README with detailed API reference: + +- [`dvx/README.md`](dvx/README.md) — GUI library architecture and API +- [`tasks/README.md`](tasks/README.md) — Task switcher API +- [`dvxshell/README.md`](dvxshell/README.md) — Shell internals and DXE app contract +- [`apps/README.md`](apps/README.md) — Writing DXE applications +- [`rs232/README.md`](rs232/README.md) — Serial port driver +- [`packet/README.md`](packet/README.md) — Packet transport protocol +- [`security/README.md`](security/README.md) — Cryptographic primitives +- [`seclink/README.md`](seclink/README.md) — Secure serial link +- [`proxy/README.md`](proxy/README.md) — Linux SecLink proxy +- [`termdemo/README.md`](termdemo/README.md) — Encrypted terminal demo diff --git a/apps/README.md b/apps/README.md new file mode 100644 index 0000000..42ea9e9 --- /dev/null +++ b/apps/README.md @@ -0,0 +1,141 @@ +# DVX Shell Applications + +DXE3 shared library applications loaded at runtime by the DVX Shell. Each app +is a `.app` file (DXE3 format) placed under the `apps/` directory tree. The +Program Manager scans this directory recursively and displays all discovered +apps. + +## Building + +``` +make # builds all .app files into ../bin/apps/ +make clean # removes objects and binaries +``` + +Requires `lib/libdvx.a`, `lib/libtasks.a`, and the DXE3 tools (`dxe3gen`) +from the DJGPP toolchain. + +## Applications + +| App | File | Type | Description | +|-----|------|------|-------------| +| Program Manager | `progman/progman.c` | Callback | Desktop app: app launcher grid, Task Manager (Ctrl+Esc), window management | +| Notepad | `notepad/notepad.c` | Callback | Text editor with file I/O, dirty tracking via hash | +| Clock | `clock/clock.c` | Main-loop | Digital clock, multi-instance capable | +| DVX Demo | `dvxdemo/dvxdemo.c` | Callback | Widget system showcase with all widget types | + +## Writing a New App + +### Minimal callback-only app + +```c +#include "dvxApp.h" +#include "dvxWidget.h" +#include "shellApp.h" + +AppDescriptorT appDescriptor = { + .name = "My App", + .hasMainLoop = false, + .stackSize = SHELL_STACK_DEFAULT, + .priority = TS_PRIORITY_NORMAL +}; + +static DxeAppContextT *sCtx = NULL; +static WindowT *sWin = NULL; + +static void onClose(WindowT *win) { + dvxDestroyWindow(sCtx->shellCtx, win); + sWin = NULL; +} + +int32_t appMain(DxeAppContextT *ctx) { + sCtx = ctx; + AppContextT *ac = ctx->shellCtx; + + sWin = dvxCreateWindow(ac, "My App", 100, 100, 300, 200, true); + if (!sWin) { + return -1; + } + + sWin->onClose = onClose; + + WidgetT *root = wgtInitWindow(ac, sWin); + wgtLabel(root, "Hello, DVX!"); + + wgtInvalidate(root); + return 0; +} +``` + +### Minimal main-loop app + +```c +#include "dvxApp.h" +#include "dvxWidget.h" +#include "dvxWm.h" +#include "shellApp.h" +#include "taskswitch.h" + +AppDescriptorT appDescriptor = { + .name = "My Task App", + .hasMainLoop = true, + .stackSize = SHELL_STACK_DEFAULT, + .priority = TS_PRIORITY_NORMAL +}; + +static bool sQuit = false; + +static void onClose(WindowT *win) { + sQuit = true; +} + +int32_t appMain(DxeAppContextT *ctx) { + AppContextT *ac = ctx->shellCtx; + + WindowT *win = dvxCreateWindow(ac, "My Task App", 100, 100, 200, 100, false); + if (!win) { + return -1; + } + + win->onClose = onClose; + + while (!sQuit) { + // Do work, update window content + tsYield(); + } + + dvxDestroyWindow(ac, win); + return 0; +} +``` + +### Adding to the build + +Add your app directory and source to `apps/Makefile`. Each app is compiled to +an object file, then linked into a `.app` via `dxe3gen`: + +```makefile +$(BIN_DIR)/myapp.app: $(OBJ_DIR)/myapp/myapp.o + $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< +``` + +The `-E` flags export the required symbols. `-U` marks unresolved symbols as +imports to be resolved from the shell's export table at load time. + +## App Guidelines + +- Include `shellApp.h` for `AppDescriptorT`, `DxeAppContextT`, and + `SHELL_STACK_DEFAULT`. +- Use `ctx->shellCtx` (the `AppContextT *`) for all DVX API calls. +- Callback-only apps must destroy their own windows in `onClose` via + `dvxDestroyWindow()`. The shell detects the last window closing and + reaps the app. +- Main-loop apps must call `tsYield()` regularly. A task that never yields + blocks the entire system. +- Use file-scoped `static` variables for app state. Each DXE has its own data + segment, so there is no collision between apps. +- Set `multiInstance = true` in the descriptor if the app can safely run + multiple copies simultaneously. +- Avoid `static inline` functions in shared headers. Code inlined into the DXE + binary cannot be updated without recompiling the app. Use macros for trivial + expressions or regular functions exported through the shell's DXE table. diff --git a/dvx/README.md b/dvx/README.md index 66c0e9c..36d5c71 100644 --- a/dvx/README.md +++ b/dvx/README.md @@ -15,13 +15,12 @@ terminal emulator. Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`). -The GUI is built as a static library (`lib/libdvx.a`). The demo links -against it. +The GUI is built as a static library (`lib/libdvx.a`). The DVX Shell +(`dvxshell/`) and DXE applications (`apps/`) link against it. ``` -cd dvxdemo -make # builds lib/libdvx.a then bin/demo.exe -make clean # removes obj/ and bin/ +make # builds ../lib/libdvx.a +make clean # removes obj/ and lib/ ``` Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed somewhere diff --git a/dvxshell/README.md b/dvxshell/README.md new file mode 100644 index 0000000..7eaaf09 --- /dev/null +++ b/dvxshell/README.md @@ -0,0 +1,140 @@ +# DVX Shell + +Windows 3.x-style desktop shell for DOS. Loads applications as DXE3 shared +libraries, provides a Program Manager for launching apps, and includes crash +recovery so one bad app doesn't take down the system. + +## Building + +``` +make # builds ../bin/dvxshell.exe +make clean # removes objects and binary +``` + +Requires `lib/libdvx.a` and `lib/libtasks.a` to be built first. + +## Files + +| File | Purpose | +|------|---------| +| `shellMain.c` | Entry point, main loop, crash recovery, logging | +| `shellApp.c` | App loading (dlopen), lifecycle, reaping, resource tracking | +| `shellApp.h` | ShellAppT, AppDescriptorT, AppStateE, DxeAppContextT, shell API | +| `shellExport.c` | DXE export table and wrapper functions | +| `Makefile` | Build rules, links `-ldvx -ltasks -ldxe -lm` | + +## Shell Main Loop + +Each iteration of the main loop: + +1. `dvxUpdate()` — process input events, dispatch callbacks, composite dirty rects +2. `tsYield()` — give CPU time to main-loop app tasks +3. `shellReapApps()` — clean up apps that terminated this frame +4. `desktopUpdate()` — notify Program Manager if anything changed + +An idle callback (`idleYield`) yields to app tasks during quiet periods when +there are no events or dirty rects to process. + +## DXE App Contract + +Every `.app` file must export these symbols: + +```c +// Required: app metadata +AppDescriptorT appDescriptor = { + .name = "My App", + .hasMainLoop = false, + .multiInstance = false, + .stackSize = SHELL_STACK_DEFAULT, + .priority = TS_PRIORITY_NORMAL +}; + +// Required: entry point +int32_t appMain(DxeAppContextT *ctx); + +// Optional: graceful shutdown hook +void appShutdown(void); +``` + +### AppDescriptorT Fields + +| Field | Type | Description | +|-------|------|-------------| +| `name` | `char[64]` | Display name shown in Task Manager | +| `hasMainLoop` | `bool` | `true` = gets its own cooperative task; `false` = callback-only | +| `multiInstance` | `bool` | `true` = allow multiple instances via temp file copy | +| `stackSize` | `int32_t` | Task stack size (`SHELL_STACK_DEFAULT` for 8 KB default) | +| `priority` | `int32_t` | Task priority (`TS_PRIORITY_LOW`/`NORMAL`/`HIGH`) | + +### DxeAppContextT + +Passed to `appMain()`: + +| Field | Type | Description | +|-------|------|-------------| +| `shellCtx` | `AppContextT *` | The shell's GUI context for creating windows, drawing, etc. | +| `appId` | `int32_t` | This app's unique ID (1-based slot index) | +| `appDir` | `char[260]` | Directory containing the `.app` file for relative resource paths | + +## App Types + +**Callback-only** (`hasMainLoop = false`): +- `appMain` called in shell's task 0, creates windows, registers callbacks, returns 0 +- App lives through event callbacks dispatched by `dvxUpdate()` +- Lifecycle ends when the last window is closed + +**Main-loop** (`hasMainLoop = true`): +- Shell creates a cooperative task via `tsCreate()` +- `appMain` runs in that task with its own loop calling `tsYield()` +- Lifecycle ends when `appMain` returns + +## Multi-Instance Support + +DXE3's `dlopen` is reference-counted per path: loading the same `.app` twice +returns the same handle, sharing all global/static state. For apps that support +multiple instances (`multiInstance = true`), the shell copies the `.app` to a +temp file before loading, giving each instance independent code and data. The +temp file is cleaned up when the app terminates. + +Apps that don't support multiple instances (`multiInstance = false`, the default) +are blocked from loading a second time with an error message. + +Temp file paths use the `TEMP` or `TMP` environment variable if set, falling +back to the current directory. + +## Resource Tracking + +The shell tracks which app owns which windows via `sCurrentAppId`, a global +set before calling any app code. The shell's `dvxCreateWindow` wrapper stamps +`win->appId` with the current app ID. On termination, the shell destroys all +windows belonging to the app. + +## Crash Recovery + +Signal handlers for SIGSEGV, SIGFPE, and SIGILL `longjmp` back to the shell's +main loop. The scheduler is fixed via `tsRecoverToMain()`, the crashed app is +force-killed, and a diagnostic message is displayed. Register state and app +identity are logged to `dvx.log`. + +## DXE Export Table + +The shell registers a symbol export table via `dlregsym()` before loading any +apps. Most symbols (all `dvx*`, `wgt*`, `ts*`, drawing functions, and required +libc functions) are exported directly. `dvxCreateWindow` and `dvxDestroyWindow` +are exported as wrappers that add resource tracking. + +## Shell API + +| Function | Description | +|----------|-------------| +| `shellAppInit()` | Initialize the app slot table | +| `shellLoadApp(ctx, path)` | Load and start an app from a `.app` file | +| `shellReapApps(ctx)` | Clean up terminated apps (call each frame) | +| `shellReapApp(ctx, app)` | Gracefully shut down a single app | +| `shellForceKillApp(ctx, app)` | Forcibly kill an app (skip shutdown hook) | +| `shellTerminateAllApps(ctx)` | Kill all running apps (shell shutdown) | +| `shellGetApp(appId)` | Get app slot by ID | +| `shellRunningAppCount()` | Count running apps | +| `shellLog(fmt, ...)` | Write to `dvx.log` | +| `shellRegisterDesktopUpdate(fn)` | Register callback for app state changes | +| `shellExportInit()` | Register DXE symbol export table | diff --git a/tasks/README.md b/tasks/README.md index 5b6a800..195d9cf 100644 --- a/tasks/README.md +++ b/tasks/README.md @@ -104,6 +104,7 @@ for reuse by the next `tsCreate()` call. |------------|----------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------| | `tsCreate` | `int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t ss, int32_t pri)` | Create a ready task. Returns the task ID (>= 0) or a negative error code. Pass 0 for `ss` to use `TS_DEFAULT_STACK_SIZE` (8 KB). Reuses terminated task slots when available. | | `tsExit` | `void tsExit(void)` | Terminate the calling task. Must not be called from the main task. | +| `tsKill` | `int32_t tsKill(uint32_t taskId)` | Forcibly terminate another task. Frees its stack and marks the slot for reuse. Cannot kill the main task (id 0) or the calling task (use `tsExit` instead). | ### Scheduling @@ -125,6 +126,12 @@ for reuse by the next `tsCreate()` call. | `tsSetPriority` | `int32_t tsSetPriority(uint32_t id, int32_t pri)`| Change a task's priority. Credits are reset to `pri + 1` so the change takes effect immediately. | | `tsGetPriority` | `int32_t tsGetPriority(uint32_t id)` | Return the task's priority, or `TS_ERR_PARAM` on an invalid ID. | +### Crash Recovery + +| Function | Signature | Description | +|-------------------|---------------------------------|-----------------------------------------------------------------------------------------------------------------| +| `tsRecoverToMain` | `void tsRecoverToMain(void)` | Reset scheduler state to the main task (id 0) after a `longjmp` from a signal handler. Call before `tsKill` on the crashed task. This fixes the scheduler's bookkeeping when a non-main task crashes and execution is transferred back to main via `longjmp`. | + ### Query | Function | Signature | Description |