180 lines
6.6 KiB
Markdown
180 lines
6.6 KiB
Markdown
# DVX Shell (dvxshell.lib)
|
|
|
|
The DVX Shell is a DXE3 module loaded by the DVX loader at startup.
|
|
It initializes the GUI subsystem, loads DXE3 application modules on
|
|
demand, runs the cooperative main loop, and provides crash recovery so
|
|
a faulting app does not bring down the entire system.
|
|
|
|
|
|
## Entry Point
|
|
|
|
The loader finds and calls `shellMain()` after all libs and widgets
|
|
are loaded. `shellMain()`:
|
|
|
|
1. Loads preferences from `CONFIG/DVX.INI`
|
|
2. Initializes the GUI via `dvxInit()` with configured video mode
|
|
3. Applies saved mouse, color, and wallpaper settings from INI
|
|
4. Shows a splash screen ("DVX - DOS Visual eXecutive / Loading...")
|
|
5. Initializes the cooperative task system (`tsInit()`)
|
|
6. Sets shell task (task 0) to `TS_PRIORITY_HIGH`
|
|
7. Gathers system information via the platform layer
|
|
8. Initializes the app slot table
|
|
9. Points the memory tracker at `currentAppId` for per-app attribution
|
|
10. Registers idle callback, Ctrl+Esc handler, and title change handler
|
|
11. Installs the crash handler (before loading apps, so init crashes are caught)
|
|
12. Loads the desktop app (default: `apps/progman/progman.app`)
|
|
13. Dismisses the splash screen
|
|
14. Enters the main loop
|
|
|
|
|
|
## Main Loop
|
|
|
|
Each iteration of the main loop does four things:
|
|
|
|
1. `dvxUpdate()` -- process input events, dispatch callbacks, composite
|
|
dirty rects, flush to LFB
|
|
2. `tsYield()` -- give CPU time to app tasks (if any are active)
|
|
3. `shellReapApps()` -- clean up any apps that terminated this frame
|
|
4. `shellDesktopUpdate()` -- notify desktop app if apps were reaped
|
|
|
|
An idle callback (`idleYield`) is also registered so that `dvxUpdate()`
|
|
yields to app tasks during quiet frames.
|
|
|
|
|
|
## App Lifecycle
|
|
|
|
### DXE App Contract
|
|
|
|
Every DXE app exports two symbols:
|
|
|
|
* `appDescriptor` (`AppDescriptorT`) -- metadata:
|
|
- `name` -- display name (max 64 chars)
|
|
- `hasMainLoop` -- true for main-loop apps, false for callback-only
|
|
- `multiInstance` -- true to allow multiple instances via temp copy
|
|
- `stackSize` -- `SHELL_STACK_DEFAULT` (32KB) or explicit byte count
|
|
- `priority` -- `TS_PRIORITY_NORMAL` or custom
|
|
|
|
* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point
|
|
|
|
Optional export: `appShutdown` (`void appShutdown(void)`) -- called
|
|
during graceful shutdown.
|
|
|
|
### Callback-Only Apps (hasMainLoop = false)
|
|
|
|
`appMain()` is called directly in the shell's task 0. It creates
|
|
windows, registers callbacks, and returns immediately. The app lives
|
|
entirely through GUI callbacks. The shell reaps callback-only apps
|
|
automatically when their last window closes -- `shellReapApps()` checks
|
|
each frame for running callback apps with zero remaining windows.
|
|
|
|
### Main-Loop Apps (hasMainLoop = true)
|
|
|
|
A dedicated cooperative task is created. `appMain()` runs in that task
|
|
and can do its own polling/processing loop, calling `tsYield()` to
|
|
share CPU. Lifecycle ends when `appMain()` returns or the task is
|
|
killed.
|
|
|
|
### App States
|
|
|
|
```
|
|
Free -> Loaded -> Running -> Terminating -> Free
|
|
```
|
|
|
|
| State | Description |
|
|
|-------|-------------|
|
|
| `AppStateFreeE` | Slot available for reuse |
|
|
| `AppStateLoadedE` | DXE loaded, not yet started (transient) |
|
|
| `AppStateRunningE` | Entry point called, active |
|
|
| `AppStateTerminatingE` | Shutdown in progress, awaiting reap |
|
|
|
|
### App Slots
|
|
|
|
App slots are managed as a stb_ds dynamic array (no fixed max). Each
|
|
slot tracks: app ID, name, path, DXE handle, state, task ID, entry/
|
|
shutdown function pointers, and a pointer to the `DxeAppContextT`
|
|
passed to the app.
|
|
|
|
`DxeAppContextT` is heap-allocated (via `calloc`) so its address is
|
|
stable across `sApps` array reallocs -- apps save this pointer in their
|
|
static globals and it must not move. The shell frees it during reap.
|
|
|
|
The `DxeAppContextT` gives each app:
|
|
- `shellCtx` -- pointer to the shell's `AppContextT`
|
|
- `appId` -- this app's unique ID
|
|
- `appDir` -- directory containing the `.app` file (for resources)
|
|
- `configDir` -- writable config directory (`CONFIG/<apppath>/`)
|
|
|
|
### App ID Tracking
|
|
|
|
`ctx->currentAppId` on AppContextT tracks which app is currently
|
|
executing. The shell sets this before calling app code.
|
|
`dvxCreateWindow()` stamps `win->appId` directly so the shell can
|
|
associate windows with apps for cleanup.
|
|
|
|
For main-loop apps, `appTaskWrapper` receives the app ID (as an int
|
|
cast to `void *`), not a direct pointer to `ShellAppT`. This is because
|
|
the `sApps` dynamic array may reallocate between `tsCreate` and the
|
|
first time the task runs, which would invalidate a direct pointer.
|
|
|
|
The shell calls `dvxSetBusy()` before `dlopen` to show the hourglass
|
|
cursor during app loading, and clears it after `appMain` returns (for
|
|
callback apps) or after task creation (for main-loop apps).
|
|
|
|
|
|
## Crash Recovery
|
|
|
|
The platform layer installs signal handlers for SIGSEGV, SIGFPE, and
|
|
SIGILL via `platformInstallCrashHandler()`. If a crash occurs:
|
|
|
|
1. Platform handler logs signal name and register dump (DJGPP)
|
|
2. Handler `longjmp`s to the `setjmp` point in `shellMain()`
|
|
3. `tsRecoverToMain()` fixes the scheduler's bookkeeping
|
|
4. Shell logs app-specific info (name, path, task ID)
|
|
5. Crashed app is force-killed (`shellForceKillApp()`)
|
|
6. Error dialog is shown to the user
|
|
7. Desktop is notified to refresh
|
|
8. Main loop continues normally
|
|
|
|
This gives Windows 3.1-style fault tolerance -- one bad app does not
|
|
take down the whole system.
|
|
|
|
|
|
## Task Manager Integration
|
|
|
|
The Task Manager is a separate DXE (`taskmgr.lib` in `taskmgr/`), not
|
|
built into the shell. It registers itself at load time via a DXE
|
|
constructor that sets the `shellCtrlEscFn` function pointer. The shell
|
|
calls this pointer on Ctrl+Esc. If `taskmgr.lib` is not loaded,
|
|
`shellCtrlEscFn` is NULL and Ctrl+Esc does nothing.
|
|
|
|
See `taskmgr/README.md` for full Task Manager documentation.
|
|
|
|
|
|
## Desktop Update Notifications
|
|
|
|
Apps (especially the desktop app) register callbacks via
|
|
`shellRegisterDesktopUpdate()` to be notified when app state changes
|
|
(load, reap, crash, title change). Multiple callbacks are supported.
|
|
|
|
|
|
## Files
|
|
|
|
| File | Description |
|
|
|------|-------------|
|
|
| `shellMain.c` | Entry point, main loop, crash recovery, splash screen, idle callback |
|
|
| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE`; `shellCtrlEscFn` extern |
|
|
| `shellApp.c` | App loading, reaping, task creation, DXE management, per-app memory tracking |
|
|
| `shellInfo.h` | System information wrapper |
|
|
| `shellInfo.c` | Gathers and caches hardware info via platform layer |
|
|
| `Makefile` | Builds `bin/libs/dvxshell.lib` + config/themes/wallpapers |
|
|
|
|
|
|
## Build
|
|
|
|
```
|
|
make # builds dvxshell.lib + dvxshell.dep + config files
|
|
make clean # removes objects, library, and config output
|
|
```
|
|
|
|
Depends on: `libtasks.lib`, `libdvx.lib`, `texthelp.lib`, `listhelp.lib`
|
|
(via dvxshell.dep).
|