Code and docs cleanup.

This commit is contained in:
Scott Duensing 2026-03-26 21:15:20 -05:00
parent be7473ff27
commit bf610ba95b
9 changed files with 2372 additions and 1917 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ lib/
.gitignore~
.gitattributes~
*.SWP
.claude/

View file

@ -20,7 +20,8 @@ dvx.exe (loader)
+-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra)
+-- libs/texthelp.lib shared text editing helpers
+-- libs/listhelp.lib shared list/dropdown helpers
+-- libs/dvxshell.lib shell (app lifecycle, desktop, task manager)
+-- libs/dvxshell.lib shell (app lifecycle, desktop)
+-- libs/taskmgr.lib task manager (Ctrl+Esc, separate DXE)
|
+-- widgets/*.wgt 26 widget type plugins
|
@ -37,9 +38,11 @@ dvx.exe (loader)
| `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching |
| `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers |
| `listhelp/` | `bin/libs/listhelp.lib` | Shared list/dropdown helpers |
| `shell/` | `bin/libs/dvxshell.lib` | DVX Shell (app lifecycle, task manager) |
| `shell/` | `bin/libs/dvxshell.lib` | DVX Shell (app lifecycle, desktop) |
| `taskmgr/` | `bin/libs/taskmgr.lib` | Task Manager (separate DXE, Ctrl+Esc) |
| `widgets/` | `bin/widgets/*.wgt` | 26 individual widget DXE modules |
| `apps/` | `bin/apps/*/*.app` | Application DXE modules |
| `tools/` | `bin/dvxres` | Resource tool (host native, not DXE) |
| `config/` | `bin/config/`, `bin/libs/`, `bin/widgets/` | INI config, themes, wallpapers, dep files |
| `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver |
| `packet/` | `lib/libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
@ -62,7 +65,8 @@ make clean # remove all build artifacts
The top-level Makefile builds in dependency order:
```
core -> tasks -> loader -> texthelp -> listhelp -> widgets -> shell -> apps
core -> tasks -> loader -> texthelp -> listhelp -> widgets -> shell -> taskmgr -> apps
tools (host native, parallel)
```
Build output goes to `bin/` (executables, DXE modules, config) and
@ -80,6 +84,7 @@ bin/
texthelp.lib text editing helpers
listhelp.lib list/dropdown helpers
dvxshell.lib DVX shell
taskmgr.lib task manager (Ctrl+Esc)
*.dep dependency files for load ordering
widgets/
box.wgt VBox/HBox/Frame containers
@ -117,9 +122,17 @@ dvxWidget.h.
* **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time
* **void *data**: Each widget allocates its own private data struct
* **ABI-stable dispatch**: `WidgetClassT.handlers[]` is an array of function
pointers indexed by `WGT_METHOD_*` constants (0-20, room for 32). Core
dispatches via `w->wclass->handlers[WGT_METHOD_PAINT]` etc., so adding
new methods does not break existing widget DXE binaries
* **Generic drag**: `WGT_METHOD_ON_DRAG_UPDATE` and `WGT_METHOD_ON_DRAG_END`
provide widget-level drag support without per-widget hacks in core
* **Per-widget API registry**: `wgtRegisterApi("name", &api)` replaces the monolithic API
* **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros
* **Shared helpers**: texthelp.lib (text editing) and listhelp.lib (dropdown/list)
* **All limits dynamic**: widget child arrays, app slots, and desktop callbacks
are stb_ds dynamic arrays with no fixed maximums
## DXE Module System
@ -148,10 +161,12 @@ 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`.
The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL
before loading any apps, so even crashes during app initialization are
caught. 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`.
## Bundled Applications
@ -190,8 +205,10 @@ All third-party code is vendored as single-header libraries:
| stb_image_write.h | `core/thirdparty/` | PNG export for screenshots |
| stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps |
stb_ds implementation is compiled into dvx.exe (the loader) and
exported via `dlregsym` to all DXE modules.
stb_ds implementation is compiled into dvx.exe (the loader) with
`STBDS_REALLOC`/`STBDS_FREE` overridden to use `dvxRealloc`/`dvxFree`,
so all `arrput`/`arrfree` calls in DXE code are tracked per-app. The
functions are exported via `dlregsym` to all DXE modules.
## Target Hardware

View file

@ -10,7 +10,7 @@ modules that register themselves at runtime via `wgtRegisterClass()`.
Core knows nothing about individual widget types. There is no
WidgetTypeE enum, no widget union, and no per-widget structs in
dvxWidget.h. All widget-specific behavior is dispatched through the
WidgetClassT vtable.
WidgetClassT dispatch table.
## 5-Layer Architecture
@ -29,6 +29,8 @@ Additional modules built into libdvx.lib:
|--------|--------|-------------|
| `dvxDialog.h` | `dvxDialog.c` | Modal message box and file open/save dialogs |
| `dvxPrefs.h` | `dvxPrefs.c` | INI-based preferences (read/write with typed accessors) |
| `dvxResource.h` | `dvxResource.c` | Resource system -- icons, text, and binary data appended to DXE files |
| `dvxMem.h` | (header only) | Per-app memory tracking API declarations |
| `dvxWidget.h` | `widgetClass.c`, `widgetCore.c`, `widgetEvent.c`, `widgetLayout.c`, `widgetOps.c`, `widgetScrollbar.c` | Widget infrastructure |
| `dvxWidgetPlugin.h` | (header only) | Plugin API for widget DXE modules |
| -- | `dvxImage.c` | Image loading via stb_image (BMP, PNG, JPEG, GIF) |
@ -46,7 +48,8 @@ Additional modules built into libdvx.lib:
| `dvxApp.c` | `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, screenshots |
| `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops |
| `dvxPrefs.c` | `prefsLoad()`, `prefsSave()`, typed get/set for string/int/bool |
| `dvxImage.c` | `dvxLoadImage()` -- stb_image loader, converts to native pixel format |
| `dvxResource.c` | `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` -- resource system |
| `dvxImage.c` | `dvxLoadImage()`, `dvxLoadImageFromMemory()` -- stb_image loader, converts to native pixel format |
| `dvxImageWrite.c` | `dvxSaveImage()` -- PNG writer for screenshots |
| `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table |
| `widgetCore.c` | Widget allocation, tree ops, focus management, clipboard, hit testing, cursor blink |
@ -68,7 +71,9 @@ Additional modules built into libdvx.lib:
| `dvxApp.h` | Application API: `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, image I/O |
| `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` |
| `dvxPrefs.h` | INI preferences: `prefsLoad()`, `prefsSave()`, typed accessors |
| `dvxWidget.h` | Widget system public API: WidgetT, WidgetClassT, size tags, layout, API registry |
| `dvxResource.h` | Resource system: `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` |
| `dvxMem.h` | Per-app memory tracking: `dvxMalloc()`, `dvxFree()`, `dvxMemGetAppUsage()`, etc. |
| `dvxWidget.h` | Widget system public API: WidgetT, WidgetClassT, size tags, layout, API registry, `wclsFoo()` dispatch helpers |
| `dvxWidgetPlugin.h` | Plugin API for widget DXE authors: tree ops, focus, scrollbar helpers, shared state |
| `dvxFont.h` | Embedded 8x14 and 8x16 bitmap font data (CP437) |
| `dvxCursor.h` | Mouse cursor AND/XOR mask data (arrow, resize H/V/diag, busy) |
@ -79,7 +84,7 @@ Additional modules built into libdvx.lib:
| File | Description |
|------|-------------|
| `platform/dvxPlatform.h` | Platform abstraction API (video, input, spans, DXE, crash recovery) |
| `platform/dvxPlatform.h` | Platform abstraction API (video, input, spans, DXE, crash recovery, memory tracking) |
| `platform/dvxPlatformDos.c` | DJGPP/DPMI implementation (VESA VBE, INT 33h mouse, INT 16h keyboard, asm spans) |
The platform layer is compiled into dvx.exe (the loader), not into
@ -96,6 +101,83 @@ libdvx.lib. Platform functions are exported to all DXE modules via
| `thirdparty/stb_ds.h` | Dynamic arrays/hash maps (implementation in loader, exported to all DXEs) |
## Dynamic Limits
All major data structures grow dynamically via realloc. There are no
fixed-size limits for:
- **Windows** -- `WindowStackT.windows` is a dynamic array
- **Menus** -- `MenuBarT.menus` and `MenuT.items` are dynamic arrays
- **Accelerator entries** -- `AccelTableT.entries` is a dynamic array
- **Dirty rectangles** -- `DirtyListT.rects` is a dynamic array
- **Submenu depth** -- `PopupStateT.parentStack` is a dynamic array
The only fixed-size buffers remaining are per-element string fields
(`MAX_TITLE_LEN = 128`, `MAX_MENU_LABEL = 32`, `MAX_WIDGET_NAME = 32`)
and the system menu (`SYS_MENU_MAX_ITEMS = 10`).
## Resource System
Resources are appended to DXE3 files (.app, .wgt, .lib) after the
normal DXE content. The DXE loader never reads past the DXE header,
so appended data is invisible to dlopen.
File layout:
[DXE3 content]
[resource data entries] -- sequential, variable length
[resource directory] -- fixed-size entries (48 bytes each)
[footer] -- magic + directory offset + count (16 bytes)
### Resource Types
| Define | Value | Description |
|--------|-------|-------------|
| `DVX_RES_ICON` | 1 | Image data (BMP icon: 16x16, 32x32, etc.) |
| `DVX_RES_TEXT` | 2 | Null-terminated string (author, copyright, etc.) |
| `DVX_RES_BINARY` | 3 | Arbitrary binary data (app-specific) |
### Resource API
| Function | Description |
|----------|-------------|
| `dvxResOpen(path)` | Open a resource handle by reading the footer and directory. Returns NULL if no resources. |
| `dvxResRead(h, name, outSize)` | Find a resource by name and read its data into a malloc'd buffer. Caller frees. |
| `dvxResFind(h, name)` | Find a resource by name and return its directory entry pointer. |
| `dvxResClose(h)` | Close the handle and free associated memory. |
### Key Types
| Type | Description |
|------|-------------|
| `DvxResDirEntryT` | Directory entry: name[32], type, offset, size, reserved (48 bytes) |
| `DvxResFooterT` | Footer: magic (`0x52585644` = "DVXR"), dirOffset, entryCount, reserved (16 bytes) |
| `DvxResHandleT` | Runtime handle: path, entries array, entry count |
## Memory Tracking (dvxMem.h)
Per-app memory tracking wraps malloc/free/calloc/realloc/strdup with a
small header per allocation that records the owning app ID and size.
DXE code does not need to include dvxMem.h -- the DXE export table maps
the standard allocator names to these wrappers transparently.
| Function | Description |
|----------|-------------|
| `dvxMalloc(size)` | Tracked malloc |
| `dvxCalloc(nmemb, size)` | Tracked calloc |
| `dvxRealloc(ptr, size)` | Tracked realloc |
| `dvxFree(ptr)` | Tracked free (falls through to real free on non-tracked pointers) |
| `dvxStrdup(s)` | Tracked strdup |
| `dvxMemSnapshotLoad(appId)` | Record baseline memory for leak detection |
| `dvxMemGetAppUsage(appId)` | Query current memory usage for an app (bytes) |
| `dvxMemResetApp(appId)` | Free all allocations charged to an app |
The global `dvxMemAppIdPtr` pointer is set by the shell to
`&ctx->currentAppId` so the allocator knows which app to charge.
## WidgetT Structure
The WidgetT struct is generic -- no widget-specific fields or union:
@ -103,8 +185,8 @@ The WidgetT struct is generic -- no widget-specific fields or union:
```c
typedef struct WidgetT {
int32_t type; // assigned by wgtRegisterClass()
const struct WidgetClassT *wclass; // vtable pointer
char name[32];
const struct WidgetClassT *wclass; // dispatch table pointer
char name[MAX_WIDGET_NAME];
// Tree linkage
struct WidgetT *parent, *firstChild, *lastChild, *nextSibling;
@ -144,34 +226,84 @@ typedef struct WidgetT {
```
## WidgetClassT Vtable
## WidgetClassT Dispatch Table
Each widget type defines a static WidgetClassT with flags and function pointers.
The vtable has 26 function slots plus a flags field:
WidgetClassT is an ABI-stable dispatch table. Method IDs are fixed
constants that never change -- adding new methods appends new IDs
without shifting existing ones. Widget DXEs compiled against an older
DVX version continue to work unmodified.
| Slot | Signature | Purpose |
|------|-----------|---------|
| `flags` | `uint32_t` | Static properties (see Flags below) |
| `paint` | `(w, d, ops, font, colors)` | Render the widget |
| `paintOverlay` | `(w, d, ops, font, colors)` | Render overlay (dropdown popup) |
| `calcMinSize` | `(w, font)` | Compute minimum size (bottom-up pass) |
| `layout` | `(w, font)` | Position children (top-down pass) |
| `getLayoutMetrics` | `(w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout |
| `onMouse` | `(w, root, vx, vy)` | Handle mouse click |
| `onKey` | `(w, key, mod)` | Handle keyboard input |
| `onAccelActivate` | `(w, root)` | Handle accelerator key match |
| `destroy` | `(w)` | Free widget-private data |
| `onChildChanged` | `(parent, child)` | Notification when a child changes |
| `getText` | `(w)` | Return widget text |
| `setText` | `(w, text)` | Set widget text |
| `clearSelection` | `(w)` | Clear text/item selection |
| `closePopup` | `(w)` | Close dropdown popup |
| `getPopupRect` | `(w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle |
| `onDragUpdate` | `(w, root, x, y)` | Mouse move during drag |
| `onDragEnd` | `(w, root, x, y)` | Mouse release after drag |
| `getCursorShape` | `(w, vx, vy)` | Return cursor ID for this position |
| `poll` | `(w, win)` | Periodic polling (AnsiTerm comms) |
| `quickRepaint` | `(w, outY, outH)` | Fast incremental repaint |
```c
#define WGT_CLASS_VERSION 1 // bump on breaking ABI change
#define WGT_METHOD_MAX 32 // room for future methods
typedef struct WidgetClassT {
uint32_t version;
uint32_t flags;
void *handlers[WGT_METHOD_MAX];
} WidgetClassT;
```
### Method ID Table
21 methods are currently defined (IDs 0--20). WGT_METHOD_MAX is 32,
leaving room for 11 future methods without a version bump.
| ID | Method ID | Signature | Purpose |
|----|-----------|-----------|---------|
| 0 | `WGT_METHOD_PAINT` | `void (w, d, ops, font, colors)` | Render the widget |
| 1 | `WGT_METHOD_PAINT_OVERLAY` | `void (w, d, ops, font, colors)` | Render overlay (dropdown popup) |
| 2 | `WGT_METHOD_CALC_MIN_SIZE` | `void (w, font)` | Compute minimum size (bottom-up pass) |
| 3 | `WGT_METHOD_LAYOUT` | `void (w, font)` | Position children (top-down pass) |
| 4 | `WGT_METHOD_GET_LAYOUT_METRICS` | `void (w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout |
| 5 | `WGT_METHOD_ON_MOUSE` | `void (w, root, vx, vy)` | Handle mouse click |
| 6 | `WGT_METHOD_ON_KEY` | `void (w, key, mod)` | Handle keyboard input |
| 7 | `WGT_METHOD_ON_ACCEL_ACTIVATE` | `void (w, root)` | Handle accelerator key match |
| 8 | `WGT_METHOD_DESTROY` | `void (w)` | Free widget-private data |
| 9 | `WGT_METHOD_ON_CHILD_CHANGED` | `void (parent, child)` | Notification when a child changes |
| 10 | `WGT_METHOD_GET_TEXT` | `const char *(w)` | Return widget text |
| 11 | `WGT_METHOD_SET_TEXT` | `void (w, text)` | Set widget text |
| 12 | `WGT_METHOD_CLEAR_SELECTION` | `bool (w)` | Clear text/item selection |
| 13 | `WGT_METHOD_CLOSE_POPUP` | `void (w)` | Close dropdown popup |
| 14 | `WGT_METHOD_GET_POPUP_RECT` | `void (w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle |
| 15 | `WGT_METHOD_ON_DRAG_UPDATE` | `void (w, root, x, y)` | Mouse move during drag |
| 16 | `WGT_METHOD_ON_DRAG_END` | `void (w, root, x, y)` | Mouse release after drag |
| 17 | `WGT_METHOD_GET_CURSOR_SHAPE` | `int32_t (w, vx, vy)` | Return cursor ID for this position |
| 18 | `WGT_METHOD_POLL` | `void (w, win)` | Periodic polling (AnsiTerm comms) |
| 19 | `WGT_METHOD_QUICK_REPAINT` | `int32_t (w, outY, outH)` | Fast incremental repaint |
| 20 | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` | `void (parent, child)` | Scroll to make a child visible |
### Typed Dispatch Helpers
Each `wclsFoo()` inline function extracts a handler by stable method
ID, casts it to the correct function pointer type, and calls it with
a NULL check. This gives callers type-safe dispatch with the same
codegen as a direct struct field call.
| Helper | Wraps Method ID |
|--------|-----------------|
| `wclsHas(w, methodId)` | Check if a method is implemented (non-NULL) |
| `wclsPaint(w, d, ops, font, colors)` | `WGT_METHOD_PAINT` |
| `wclsPaintOverlay(w, d, ops, font, colors)` | `WGT_METHOD_PAINT_OVERLAY` |
| `wclsCalcMinSize(w, font)` | `WGT_METHOD_CALC_MIN_SIZE` |
| `wclsLayout(w, font)` | `WGT_METHOD_LAYOUT` |
| `wclsGetLayoutMetrics(w, font, pad, gap, extraTop, borderW)` | `WGT_METHOD_GET_LAYOUT_METRICS` |
| `wclsOnMouse(w, root, vx, vy)` | `WGT_METHOD_ON_MOUSE` |
| `wclsOnKey(w, key, mod)` | `WGT_METHOD_ON_KEY` |
| `wclsOnAccelActivate(w, root)` | `WGT_METHOD_ON_ACCEL_ACTIVATE` |
| `wclsDestroy(w)` | `WGT_METHOD_DESTROY` |
| `wclsOnChildChanged(parent, child)` | `WGT_METHOD_ON_CHILD_CHANGED` |
| `wclsGetText(w)` | `WGT_METHOD_GET_TEXT` |
| `wclsSetText(w, text)` | `WGT_METHOD_SET_TEXT` |
| `wclsClearSelection(w)` | `WGT_METHOD_CLEAR_SELECTION` |
| `wclsClosePopup(w)` | `WGT_METHOD_CLOSE_POPUP` |
| `wclsGetPopupRect(w, font, contentH, popX, popY, popW, popH)` | `WGT_METHOD_GET_POPUP_RECT` |
| `wclsOnDragUpdate(w, root, x, y)` | `WGT_METHOD_ON_DRAG_UPDATE` |
| `wclsOnDragEnd(w, root, x, y)` | `WGT_METHOD_ON_DRAG_END` |
| `wclsGetCursorShape(w, vx, vy)` | `WGT_METHOD_GET_CURSOR_SHAPE` |
| `wclsPoll(w, win)` | `WGT_METHOD_POLL` |
| `wclsQuickRepaint(w, outY, outH)` | `WGT_METHOD_QUICK_REPAINT` |
| `wclsScrollChildIntoView(parent, child)` | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` |
### WidgetClassT Flags
@ -196,22 +328,25 @@ The vtable has 26 function slots plus a flags field:
## Widget Registration
Each widget DXE exports `wgtRegister()`, called by the loader after
`dlopen`. A typical registration:
`dlopen`. The WidgetClassT uses the `handlers[]` array indexed by
method IDs:
```c
static int32_t sButtonType;
static const WidgetClassT sButtonClass = {
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.paint = buttonPaint,
.calcMinSize = buttonCalcMinSize,
.onMouse = buttonOnMouse,
.onKey = buttonOnKey,
.destroy = buttonDestroy,
.getText = buttonGetText,
.setText = buttonSetText,
.setPressed = buttonSetPressed,
.onAccelActivate = buttonAccelActivate,
.version = WGT_CLASS_VERSION,
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.handlers = {
[WGT_METHOD_PAINT] = buttonPaint,
[WGT_METHOD_CALC_MIN_SIZE] = buttonCalcMinSize,
[WGT_METHOD_ON_MOUSE] = buttonOnMouse,
[WGT_METHOD_ON_KEY] = buttonOnKey,
[WGT_METHOD_DESTROY] = buttonDestroy,
[WGT_METHOD_GET_TEXT] = buttonGetText,
[WGT_METHOD_SET_TEXT] = buttonSetText,
[WGT_METHOD_ON_ACCEL_ACTIVATE] = buttonAccelActivate,
}
};
static const ButtonApiT sApi = { .create = buttonCreate };
@ -268,6 +403,28 @@ Two-pass flexbox-like layout:
Extra space beyond minimum is distributed proportionally to weights.
`weight=0` means fixed size, `weight=100` is the default flexible weight.
### Cross-Axis Centering
When a child widget has a `maxW` (in a VBox) or `maxH` (in an HBox)
that constrains it smaller than the available cross-axis space, the
layout engine automatically centers the child on the cross axis. This
means setting `maxW` or `maxH` on a child inside a container will both
cap its size and center it within the remaining space.
## Image Loading
Two image loading functions are available:
| Function | Description |
|----------|-------------|
| `dvxLoadImage(ctx, path, outW, outH, outPitch)` | Load from a file path |
| `dvxLoadImageFromMemory(ctx, data, dataLen, outW, outH, outPitch)` | Load from a memory buffer (e.g. resource data) |
Both convert to the display's native pixel format. Caller frees the
returned buffer with `dvxFreeImage()`. Supported formats: BMP, PNG,
JPEG, GIF (via stb_image).
## Exported Symbols
@ -275,10 +432,10 @@ libdvx.lib exports symbols matching these prefixes:
```
dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*,
resetClip*, stbi*, dirtyList*, widget*, accelParse*, clipboard*,
multiClick*, sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*,
sOpen*, sPressed*, sDrag*, sDrawing*, sResize*, sListView*,
sSplitter*, sTreeView*
resetClip*, stbi*, stbi_write*, dirtyList*, widget*,
accelParse*, clipboard*, multiClick*,
sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*,
sOpen*, sDrag*, sClosed*, sKey*
```

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,7 @@ libdvx.lib (deps: libtasks)
texthelp.lib (deps: libtasks, libdvx)
listhelp.lib (deps: libtasks, libdvx)
dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp)
taskmgr.lib (deps: dvxshell, libtasks, libdvx, texthelp, listhelp)
```
### Phase 2: Widgets (widgets/*.wgt)
@ -73,7 +74,11 @@ void dvxLog(const char *fmt, ...);
### stb_ds
The `STB_DS_IMPLEMENTATION` is compiled into the loader. stb_ds
The `STB_DS_IMPLEMENTATION` is compiled into the loader with
`STBDS_REALLOC` and `STBDS_FREE` overridden to use `dvxRealloc` and
`dvxFree`. This means all `arrput`/`hmput`/`arrfree` calls in DXE
code route through the per-app memory tracker, so stb_ds memory is
attributed correctly in the Task Manager's memory column. stb_ds
functions are exported to all DXE modules via the platform export
table.
@ -89,6 +94,24 @@ All platform functions are exported to DXE modules. This includes:
* Crash: signal handler installation, register dump logging
* System: memory info, directory creation, path utilities
### Per-App Memory Tracking
The DXE export table maps standard C allocation symbols to tracked
wrappers:
| C Symbol | Mapped To | Effect |
|----------|-----------|--------|
| `malloc` | `dvxMalloc` | Allocations attributed to `currentAppId` |
| `calloc` | `dvxCalloc` | Same |
| `realloc` | `dvxRealloc` | Transfers attribution on resize |
| `free` | `dvxFree` | Decrements app's tracked usage |
| `strdup` | `dvxStrdup` | Tracks the duplicated string |
This is transparent to DXE code -- apps call `malloc` normally and the
tracked wrapper runs instead. The Task Manager reads per-app usage via
`dvxMemGetAppUsage()`. When an app is reaped, `dvxMemResetApp()` zeroes
its counter.
## Files

View file

@ -14,14 +14,17 @@ 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. Initializes the cooperative task system (`tsInit()`)
5. Sets shell task (task 0) to `TS_PRIORITY_HIGH`
6. Gathers system information via the platform layer
7. Initializes the app slot table
8. Registers idle callback, Ctrl+Esc handler, and title change handler
9. Loads the desktop app (default: `apps/progman/progman.app`)
10. Installs the crash handler (after init, so init crashes are fatal)
11. Enters the main loop
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
@ -60,8 +63,9 @@ during graceful shutdown.
`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. Lifecycle ends when the last window
closes.
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)
@ -87,7 +91,12 @@ Free -> Loaded -> Running -> Terminating -> Free
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 the `DxeAppContextT` passed to the app.
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`
@ -102,6 +111,15 @@ 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
@ -121,13 +139,15 @@ This gives Windows 3.1-style fault tolerance -- one bad app does not
take down the whole system.
## Task Manager
## Task Manager Integration
The built-in Task Manager is always available via Ctrl+Esc. It shows
all running apps with their names and provides an "End Task" button
for force-killing hung apps. The Task Manager is a shell-level
component, not tied to any app -- it persists even if the desktop app
is terminated.
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
@ -141,13 +161,11 @@ Apps (especially the desktop app) register callbacks via
| File | Description |
|------|-------------|
| `shellMain.c` | Entry point, main loop, crash recovery, idle callback |
| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE` |
| `shellApp.c` | App loading, reaping, task creation, DXE management |
| `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 |
| `shellTaskMgr.h` | Task Manager API |
| `shellTaskMgr.c` | Task Manager window (list of running apps, End Task) |
| `Makefile` | Builds `bin/libs/dvxshell.lib` + config/themes/wallpapers |

75
taskmgr/README.md Normal file
View file

@ -0,0 +1,75 @@
# DVX Task Manager (taskmgr.lib)
A separate DXE3 module that provides the system Task Manager window.
Built as `taskmgr.lib` and loaded alongside the other libs at startup.
The Task Manager is a shell-level component, not tied to any app -- it
persists even if the desktop app (Program Manager) is terminated.
## Registration
The module uses a GCC `__attribute__((constructor))` function to
register itself with the shell at load time. The constructor sets the
`shellCtrlEscFn` function pointer (declared in `shellApp.h`) to
`shellTaskMgrOpen`. The shell calls this pointer when Ctrl+Esc is
pressed. If `taskmgr.lib` is not loaded, the pointer remains NULL and
Ctrl+Esc does nothing.
## Features
- **App list**: 6-column ListView showing Name, Title, File, Type,
Memory, and Status for all running apps
- **Memory column**: Per-app memory usage from `dvxMemGetAppUsage()`,
displayed in KB or MB
- **Switch To**: Raises and focuses the selected app's topmost window;
restores it if minimized. Also triggered by double-clicking a row.
- **End Task**: Force-kills the selected app via `shellForceKillApp()`
- **Run...**: Opens a file dialog to browse for and launch a `.app` file
- **Accelerator keys**: `&Switch To` (Alt+S), `&End Task` (Alt+E),
`&Run...` (Alt+R)
- **Status bar**: Shows running app count and system memory usage
- **Live refresh**: Registers a desktop update callback so the list
refreshes automatically when apps are loaded, reaped, or crash
## Window Behavior
- Owned by the shell (appId = 0), not by any app
- Single-instance: if already open, Ctrl+Esc raises and focuses it
- Closing the window unregisters the desktop update callback and
cleans up dynamic arrays
## API
```c
// Open or raise the Task Manager window.
void shellTaskMgrOpen(AppContextT *ctx);
// Refresh the task list (called by desktop update notification).
void shellTaskMgrRefresh(void);
```
## Dependencies
Loaded after: `dvxshell`, `libtasks`, `libdvx`, `texthelp`, `listhelp`
(via `taskmgr.dep`).
## Files
| File | Description |
|------|-------------|
| `shellTaskMgr.h` | Public API (open, refresh) |
| `shellTaskMgr.c` | Task Manager window, list, buttons, DXE constructor |
| `Makefile` | Builds `bin/libs/taskmgr.lib` + dep file |
## Build
```
make # builds bin/libs/taskmgr.lib + taskmgr.dep
make clean # removes objects, library, and dep file
```

117
tools/README.md Normal file
View file

@ -0,0 +1,117 @@
# DVX Tools
Host-native utilities that run on the development machine (Linux or
DOS). These are not DXE modules and do not cross-compile with DJGPP --
they build with the system GCC.
## dvxres -- Resource Tool
Command-line tool for managing resource blocks appended to DXE3 files
(`.app`, `.wgt`, `.lib`). Resources are invisible to `dlopen` because
they are appended after the DXE3 content.
### Commands
```
dvxres add <file> <name> <type> <data|@file>
dvxres build <file> <manifest.res>
dvxres list <file>
dvxres get <file> <name> [outfile]
dvxres strip <file>
```
| Command | Description |
|---------|-------------|
| `add` | Add or replace a single resource in a DXE file |
| `build` | Add all resources listed in a manifest file (replaces any existing resources) |
| `list` | List all resources in a DXE file |
| `get` | Extract a resource to a file or stdout |
| `strip` | Remove all appended resources, leaving only the DXE content |
### Resource Types
| Type | Keyword | Description |
|------|---------|-------------|
| `DVX_RES_ICON` | `icon` or `image` | Image data (BMP icons, etc.) |
| `DVX_RES_TEXT` | `text` | Null-terminated string (author, copyright) |
| `DVX_RES_BINARY` | `binary` | Arbitrary binary data (app-specific) |
For `add` with type `text`, the data argument is the string value
directly. For `icon` or `binary`, the data argument is a file path.
### Resource File Format
Resources are appended after the normal DXE3 content:
```
[DXE3 content] -- untouched, loaded by dlopen
[resource data entries] -- sequential, variable length
[resource directory] -- fixed-size 48-byte entries
[footer] -- 16 bytes: magic + dir offset + count
```
The footer is at the very end of the file. Reading starts from
`EOF - 16` bytes. The magic value is `0x52585644` ("DVXR" in
little-endian). The directory offset points to the start of the
directory entries, and the entry count gives the number of resources.
Each directory entry (48 bytes) contains:
- `name[32]` -- resource name (null-terminated)
- `type` (uint32) -- DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY
- `offset` (uint32) -- absolute file offset of data
- `size` (uint32) -- data size in bytes
- `reserved` (uint32) -- padding
### Manifest File Format (.res)
Plain text, one resource per line:
```
# Comment lines start with #
name type data
# Examples:
icon32 icon icons/myapp32.bmp
icon16 icon icons/myapp16.bmp
author text "John Doe"
appdata binary data/config.bin
```
Each line has three fields: name, type, and data. Text data can be
quoted. Empty lines and lines starting with `#` are ignored.
## mkicon -- Icon Generator
Generates simple 32x32 24-bit BMP pixel-art icons for DVX apps.
```
mkicon <output.bmp> <type>
```
Available icon types: `clock`, `notepad`, `cpanel`, `dvxdemo`, `imgview`.
Note: mkicon is defined in `mkicon.c` but is not currently built by the
Makefile (it was used during initial development).
## Files
| File | Description |
|------|-------------|
| `dvxres.c` | Resource tool implementation |
| `mkicon.c` | Icon generator (not built by default) |
| `Makefile` | Builds `bin/dvxres` (host native) |
## Build
```
make # builds bin/dvxres
make clean # removes bin/dvxres
```
Uses the system GCC, not the DJGPP cross-compiler. Links against
`core/dvxResource.c` for the runtime resource API (`dvxResOpen`,
`dvxResRead`, `dvxResClose`).

View file

@ -7,11 +7,50 @@ which registers its widget class(es) and API struct with the core.
Core knows nothing about individual widgets. All per-widget state lives
in `w->data` (allocated by the widget DXE). All per-widget behavior is
dispatched through the WidgetClassT vtable. Each widget provides a
dispatched through the `WidgetClassT` vtable. Each widget provides a
public header with its API struct, typed accessor, and convenience
macros.
## ABI-Stable Handler Dispatch
`WidgetClassT` contains a `handlers[WGT_METHOD_MAX]` array of function
pointers. Core dispatches via integer index:
`w->wclass->handlers[WGT_METHOD_PAINT]`, etc. Currently 21 methods are
defined (`WGT_METHOD_COUNT`), with room for 32 (`WGT_METHOD_MAX`).
Adding new methods does not break existing widget DXE binaries -- old
widgets simply have NULL in the new slots.
Key method constants:
| Constant | Index | Signature |
|----------|-------|-----------|
| `WGT_METHOD_PAINT` | 0 | `(w, d, ops, font, colors)` |
| `WGT_METHOD_PAINT_OVERLAY` | 1 | `(w, d, ops, font, colors)` |
| `WGT_METHOD_CALC_MIN_SIZE` | 2 | `(w, font)` |
| `WGT_METHOD_LAYOUT` | 3 | `(w, font)` |
| `WGT_METHOD_ON_MOUSE` | 5 | `(w, root, vx, vy)` |
| `WGT_METHOD_ON_KEY` | 6 | `(w, key, mod)` |
| `WGT_METHOD_DESTROY` | 8 | `(w)` |
| `WGT_METHOD_ON_DRAG_UPDATE` | 15 | `(w, root, x, y)` |
| `WGT_METHOD_ON_DRAG_END` | 16 | `(w, root, x, y)` |
| `WGT_METHOD_QUICK_REPAINT` | 19 | `(w, outY, outH)` |
## Generic Drag Support
Widgets that need drag behavior implement `WGT_METHOD_ON_DRAG_UPDATE`
and `WGT_METHOD_ON_DRAG_END`. Core tracks the drag widget and calls
these methods on mouse move/release during a drag. This replaces
per-widget drag hacks. Used by Slider, Splitter, ListBox (reorder),
ListView (reorder, column resize), and TreeView (reorder).
## Dynamic Limits
All widget child arrays, app slot tables, and callback lists use stb_ds
dynamic arrays. There are no fixed maximums for child count, app count,
or registered callbacks.
## Widget Summary
26 source files produce 26 `.wgt` modules containing 32 widget types: