DVX_GUI/dvxshell/README.md
2026-03-18 02:08:26 -05:00

140 lines
5.1 KiB
Markdown

# DVX Shell
Windows 3.x-style desktop shell for DOS. Loads applications as DXE3 shared
libraries and includes crash
recovery so one bad app doesn't take down the system.
## Building
```
make # builds ../bin/dvx.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 the desktop app 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 |