Updated docs.

This commit is contained in:
Scott Duensing 2026-03-18 02:02:37 -05:00
parent bcd0b863fb
commit 4c2549efd4
5 changed files with 409 additions and 8 deletions

120
README.md
View file

@ -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 A Windows 3.x-style desktop shell for DOS, built with DJGPP/DPMI. Combines a
for the Kangaroo Punch MultiPlayer Game Server Mark II. 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 <floppy.img> bin/dvxshell.exe ::DVXSHELL.EXE
mcopy -o -i <floppy.img> 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

141
apps/README.md Normal file
View file

@ -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.

View file

@ -15,13 +15,12 @@ terminal emulator.
Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`). Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`).
The GUI is built as a static library (`lib/libdvx.a`). The demo links The GUI is built as a static library (`lib/libdvx.a`). The DVX Shell
against it. (`dvxshell/`) and DXE applications (`apps/`) link against it.
``` ```
cd dvxdemo make # builds ../lib/libdvx.a
make # builds lib/libdvx.a then bin/demo.exe make clean # removes obj/ and lib/
make clean # removes obj/ and bin/
``` ```
Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed somewhere Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed somewhere

140
dvxshell/README.md Normal file
View file

@ -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 |

View file

@ -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. | | `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. | | `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 ### 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. | | `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. | | `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 ### Query
| Function | Signature | Description | | Function | Signature | Description |