Updated docs.
This commit is contained in:
parent
bcd0b863fb
commit
4c2549efd4
5 changed files with 409 additions and 8 deletions
120
README.md
120
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 <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
141
apps/README.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
140
dvxshell/README.md
Normal file
140
dvxshell/README.md
Normal 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 |
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue