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