140 lines
5.2 KiB
Markdown
140 lines
5.2 KiB
Markdown
# 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 |
|