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~ .gitignore~
.gitattributes~ .gitattributes~
*.SWP *.SWP
.claude/

View file

@ -20,7 +20,8 @@ dvx.exe (loader)
+-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra) +-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra)
+-- libs/texthelp.lib shared text editing helpers +-- libs/texthelp.lib shared text editing helpers
+-- libs/listhelp.lib shared list/dropdown 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 +-- widgets/*.wgt 26 widget type plugins
| |
@ -37,9 +38,11 @@ dvx.exe (loader)
| `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching | | `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching |
| `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers | | `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers |
| `listhelp/` | `bin/libs/listhelp.lib` | Shared list/dropdown 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 | | `widgets/` | `bin/widgets/*.wgt` | 26 individual widget DXE modules |
| `apps/` | `bin/apps/*/*.app` | Application 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 | | `config/` | `bin/config/`, `bin/libs/`, `bin/widgets/` | INI config, themes, wallpapers, dep files |
| `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver | | `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver |
| `packet/` | `lib/libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ | | `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: 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 Build output goes to `bin/` (executables, DXE modules, config) and
@ -80,6 +84,7 @@ bin/
texthelp.lib text editing helpers texthelp.lib text editing helpers
listhelp.lib list/dropdown helpers listhelp.lib list/dropdown helpers
dvxshell.lib DVX shell dvxshell.lib DVX shell
taskmgr.lib task manager (Ctrl+Esc)
*.dep dependency files for load ordering *.dep dependency files for load ordering
widgets/ widgets/
box.wgt VBox/HBox/Frame containers box.wgt VBox/HBox/Frame containers
@ -117,9 +122,17 @@ dvxWidget.h.
* **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time * **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time
* **void *data**: Each widget allocates its own private data struct * **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 API registry**: `wgtRegisterApi("name", &api)` replaces the monolithic API
* **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros * **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros
* **Shared helpers**: texthelp.lib (text editing) and listhelp.lib (dropdown/list) * **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 ## DXE Module System
@ -148,10 +161,12 @@ emulators, games).
## Crash Recovery ## Crash Recovery
The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL. If The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL
an app crashes, the handler `longjmp`s back to the shell's main loop, before loading any apps, so even crashes during app initialization are
the crashed app is force-killed, and the shell continues running. caught. If an app crashes, the handler `longjmp`s back to the shell's
Diagnostic information (registers, faulting EIP) is logged to `dvx.log`. 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 ## 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_image_write.h | `core/thirdparty/` | PNG export for screenshots |
| stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps | | stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps |
stb_ds implementation is compiled into dvx.exe (the loader) and stb_ds implementation is compiled into dvx.exe (the loader) with
exported via `dlregsym` to all DXE modules. `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 ## 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 Core knows nothing about individual widget types. There is no
WidgetTypeE enum, no widget union, and no per-widget structs in WidgetTypeE enum, no widget union, and no per-widget structs in
dvxWidget.h. All widget-specific behavior is dispatched through the dvxWidget.h. All widget-specific behavior is dispatched through the
WidgetClassT vtable. WidgetClassT dispatch table.
## 5-Layer Architecture ## 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 | | `dvxDialog.h` | `dvxDialog.c` | Modal message box and file open/save dialogs |
| `dvxPrefs.h` | `dvxPrefs.c` | INI-based preferences (read/write with typed accessors) | | `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 | | `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 | | `dvxWidgetPlugin.h` | (header only) | Plugin API for widget DXE modules |
| -- | `dvxImage.c` | Image loading via stb_image (BMP, PNG, JPEG, GIF) | | -- | `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 | | `dvxApp.c` | `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, screenshots |
| `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops | | `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops |
| `dvxPrefs.c` | `prefsLoad()`, `prefsSave()`, typed get/set for string/int/bool | | `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 | | `dvxImageWrite.c` | `dvxSaveImage()` -- PNG writer for screenshots |
| `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table | | `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table |
| `widgetCore.c` | Widget allocation, tree ops, focus management, clipboard, hit testing, cursor blink | | `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 | | `dvxApp.h` | Application API: `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, image I/O |
| `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` | | `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` |
| `dvxPrefs.h` | INI preferences: `prefsLoad()`, `prefsSave()`, typed accessors | | `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 | | `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) | | `dvxFont.h` | Embedded 8x14 and 8x16 bitmap font data (CP437) |
| `dvxCursor.h` | Mouse cursor AND/XOR mask data (arrow, resize H/V/diag, busy) | | `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 | | 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) | | `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 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) | | `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 ## WidgetT Structure
The WidgetT struct is generic -- no widget-specific fields or union: 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 ```c
typedef struct WidgetT { typedef struct WidgetT {
int32_t type; // assigned by wgtRegisterClass() int32_t type; // assigned by wgtRegisterClass()
const struct WidgetClassT *wclass; // vtable pointer const struct WidgetClassT *wclass; // dispatch table pointer
char name[32]; char name[MAX_WIDGET_NAME];
// Tree linkage // Tree linkage
struct WidgetT *parent, *firstChild, *lastChild, *nextSibling; 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. WidgetClassT is an ABI-stable dispatch table. Method IDs are fixed
The vtable has 26 function slots plus a flags field: 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 | ```c
|------|-----------|---------| #define WGT_CLASS_VERSION 1 // bump on breaking ABI change
| `flags` | `uint32_t` | Static properties (see Flags below) | #define WGT_METHOD_MAX 32 // room for future methods
| `paint` | `(w, d, ops, font, colors)` | Render the widget |
| `paintOverlay` | `(w, d, ops, font, colors)` | Render overlay (dropdown popup) | typedef struct WidgetClassT {
| `calcMinSize` | `(w, font)` | Compute minimum size (bottom-up pass) | uint32_t version;
| `layout` | `(w, font)` | Position children (top-down pass) | uint32_t flags;
| `getLayoutMetrics` | `(w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout | void *handlers[WGT_METHOD_MAX];
| `onMouse` | `(w, root, vx, vy)` | Handle mouse click | } WidgetClassT;
| `onKey` | `(w, key, mod)` | Handle keyboard input | ```
| `onAccelActivate` | `(w, root)` | Handle accelerator key match |
| `destroy` | `(w)` | Free widget-private data | ### Method ID Table
| `onChildChanged` | `(parent, child)` | Notification when a child changes |
| `getText` | `(w)` | Return widget text | 21 methods are currently defined (IDs 0--20). WGT_METHOD_MAX is 32,
| `setText` | `(w, text)` | Set widget text | leaving room for 11 future methods without a version bump.
| `clearSelection` | `(w)` | Clear text/item selection |
| `closePopup` | `(w)` | Close dropdown popup | | ID | Method ID | Signature | Purpose |
| `getPopupRect` | `(w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle | |----|-----------|-----------|---------|
| `onDragUpdate` | `(w, root, x, y)` | Mouse move during drag | | 0 | `WGT_METHOD_PAINT` | `void (w, d, ops, font, colors)` | Render the widget |
| `onDragEnd` | `(w, root, x, y)` | Mouse release after drag | | 1 | `WGT_METHOD_PAINT_OVERLAY` | `void (w, d, ops, font, colors)` | Render overlay (dropdown popup) |
| `getCursorShape` | `(w, vx, vy)` | Return cursor ID for this position | | 2 | `WGT_METHOD_CALC_MIN_SIZE` | `void (w, font)` | Compute minimum size (bottom-up pass) |
| `poll` | `(w, win)` | Periodic polling (AnsiTerm comms) | | 3 | `WGT_METHOD_LAYOUT` | `void (w, font)` | Position children (top-down pass) |
| `quickRepaint` | `(w, outY, outH)` | Fast incremental repaint | | 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 ### WidgetClassT Flags
@ -196,22 +328,25 @@ The vtable has 26 function slots plus a flags field:
## Widget Registration ## Widget Registration
Each widget DXE exports `wgtRegister()`, called by the loader after 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 ```c
static int32_t sButtonType; static int32_t sButtonType;
static const WidgetClassT sButtonClass = { static const WidgetClassT sButtonClass = {
.version = WGT_CLASS_VERSION,
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE, .flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.paint = buttonPaint, .handlers = {
.calcMinSize = buttonCalcMinSize, [WGT_METHOD_PAINT] = buttonPaint,
.onMouse = buttonOnMouse, [WGT_METHOD_CALC_MIN_SIZE] = buttonCalcMinSize,
.onKey = buttonOnKey, [WGT_METHOD_ON_MOUSE] = buttonOnMouse,
.destroy = buttonDestroy, [WGT_METHOD_ON_KEY] = buttonOnKey,
.getText = buttonGetText, [WGT_METHOD_DESTROY] = buttonDestroy,
.setText = buttonSetText, [WGT_METHOD_GET_TEXT] = buttonGetText,
.setPressed = buttonSetPressed, [WGT_METHOD_SET_TEXT] = buttonSetText,
.onAccelActivate = buttonAccelActivate, [WGT_METHOD_ON_ACCEL_ACTIVATE] = buttonAccelActivate,
}
}; };
static const ButtonApiT sApi = { .create = buttonCreate }; static const ButtonApiT sApi = { .create = buttonCreate };
@ -268,6 +403,28 @@ Two-pass flexbox-like layout:
Extra space beyond minimum is distributed proportionally to weights. Extra space beyond minimum is distributed proportionally to weights.
`weight=0` means fixed size, `weight=100` is the default flexible weight. `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 ## Exported Symbols
@ -275,10 +432,10 @@ libdvx.lib exports symbols matching these prefixes:
``` ```
dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*, dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*,
resetClip*, stbi*, dirtyList*, widget*, accelParse*, clipboard*, resetClip*, stbi*, stbi_write*, dirtyList*, widget*,
multiClick*, sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*, accelParse*, clipboard*, multiClick*,
sOpen*, sPressed*, sDrag*, sDrawing*, sResize*, sListView*, sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*,
sSplitter*, sTreeView* 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) texthelp.lib (deps: libtasks, libdvx)
listhelp.lib (deps: libtasks, libdvx) listhelp.lib (deps: libtasks, libdvx)
dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp) dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp)
taskmgr.lib (deps: dvxshell, libtasks, libdvx, texthelp, listhelp)
``` ```
### Phase 2: Widgets (widgets/*.wgt) ### Phase 2: Widgets (widgets/*.wgt)
@ -73,7 +74,11 @@ void dvxLog(const char *fmt, ...);
### stb_ds ### 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 functions are exported to all DXE modules via the platform export
table. table.
@ -89,6 +94,24 @@ All platform functions are exported to DXE modules. This includes:
* Crash: signal handler installation, register dump logging * Crash: signal handler installation, register dump logging
* System: memory info, directory creation, path utilities * 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 ## Files

View file

@ -14,14 +14,17 @@ are loaded. `shellMain()`:
1. Loads preferences from `CONFIG/DVX.INI` 1. Loads preferences from `CONFIG/DVX.INI`
2. Initializes the GUI via `dvxInit()` with configured video mode 2. Initializes the GUI via `dvxInit()` with configured video mode
3. Applies saved mouse, color, and wallpaper settings from INI 3. Applies saved mouse, color, and wallpaper settings from INI
4. Initializes the cooperative task system (`tsInit()`) 4. Shows a splash screen ("DVX - DOS Visual eXecutive / Loading...")
5. Sets shell task (task 0) to `TS_PRIORITY_HIGH` 5. Initializes the cooperative task system (`tsInit()`)
6. Gathers system information via the platform layer 6. Sets shell task (task 0) to `TS_PRIORITY_HIGH`
7. Initializes the app slot table 7. Gathers system information via the platform layer
8. Registers idle callback, Ctrl+Esc handler, and title change handler 8. Initializes the app slot table
9. Loads the desktop app (default: `apps/progman/progman.app`) 9. Points the memory tracker at `currentAppId` for per-app attribution
10. Installs the crash handler (after init, so init crashes are fatal) 10. Registers idle callback, Ctrl+Esc handler, and title change handler
11. Enters the main loop 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 ## Main Loop
@ -60,8 +63,9 @@ during graceful shutdown.
`appMain()` is called directly in the shell's task 0. It creates `appMain()` is called directly in the shell's task 0. It creates
windows, registers callbacks, and returns immediately. The app lives windows, registers callbacks, and returns immediately. The app lives
entirely through GUI callbacks. Lifecycle ends when the last window entirely through GUI callbacks. The shell reaps callback-only apps
closes. automatically when their last window closes -- `shellReapApps()` checks
each frame for running callback apps with zero remaining windows.
### Main-Loop Apps (hasMainLoop = true) ### 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 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/ 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: The `DxeAppContextT` gives each app:
- `shellCtx` -- pointer to the shell's `AppContextT` - `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 `dvxCreateWindow()` stamps `win->appId` directly so the shell can
associate windows with apps for cleanup. 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 ## Crash Recovery
@ -121,13 +139,15 @@ This gives Windows 3.1-style fault tolerance -- one bad app does not
take down the whole system. take down the whole system.
## Task Manager ## Task Manager Integration
The built-in Task Manager is always available via Ctrl+Esc. It shows The Task Manager is a separate DXE (`taskmgr.lib` in `taskmgr/`), not
all running apps with their names and provides an "End Task" button built into the shell. It registers itself at load time via a DXE
for force-killing hung apps. The Task Manager is a shell-level constructor that sets the `shellCtrlEscFn` function pointer. The shell
component, not tied to any app -- it persists even if the desktop app calls this pointer on Ctrl+Esc. If `taskmgr.lib` is not loaded,
is terminated. `shellCtrlEscFn` is NULL and Ctrl+Esc does nothing.
See `taskmgr/README.md` for full Task Manager documentation.
## Desktop Update Notifications ## Desktop Update Notifications
@ -141,13 +161,11 @@ Apps (especially the desktop app) register callbacks via
| File | Description | | File | Description |
|------|-------------| |------|-------------|
| `shellMain.c` | Entry point, main loop, crash recovery, idle callback | | `shellMain.c` | Entry point, main loop, crash recovery, splash screen, idle callback |
| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE` | | `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE`; `shellCtrlEscFn` extern |
| `shellApp.c` | App loading, reaping, task creation, DXE management | | `shellApp.c` | App loading, reaping, task creation, DXE management, per-app memory tracking |
| `shellInfo.h` | System information wrapper | | `shellInfo.h` | System information wrapper |
| `shellInfo.c` | Gathers and caches hardware info via platform layer | | `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 | | `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 Core knows nothing about individual widgets. All per-widget state lives
in `w->data` (allocated by the widget DXE). All per-widget behavior is 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 public header with its API struct, typed accessor, and convenience
macros. 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 ## Widget Summary
26 source files produce 26 `.wgt` modules containing 32 widget types: 26 source files produce 26 `.wgt` modules containing 32 widget types: