This commit is contained in:
Scott Duensing 2026-03-22 21:03:10 -05:00
parent 163959a192
commit 3b2d87845e
8 changed files with 736 additions and 266 deletions

194
README.md
View file

@ -2,9 +2,10 @@
A windowed GUI compositor and desktop shell for DOS, built with
DJGPP/DPMI. DVX combines a Motif-style window manager, dirty-rectangle
compositor, cooperative task switcher, and DXE3 dynamic application
loading into a multitasking desktop environment where applications are
`.app` shared libraries loaded at runtime.
compositor, cooperative task switcher, and a modular DXE3 architecture
into a multitasking desktop environment. The bootstrap loader
dynamically discovers and loads core libraries, widget plugins, and
applications at runtime.
Targets real and emulated 486+ hardware with VESA VBE 2.0+ linear
framebuffer. No bank switching -- LFB or fail.
@ -16,9 +17,10 @@ framebuffer. No bank switching -- LFB or fail.
maximize, and restore
- Dirty-rectangle compositor -- only changed regions are flushed to video
memory, critical for acceptable frame rates on 486/Pentium hardware
- 32 widget types: buttons, text inputs, list boxes, tree views, list
views, tab controls, sliders, spinners, progress bars, dropdowns,
combo boxes, splitters, scroll panes, ANSI terminal emulator, and more
- 32 widget types across 26 DXE modules: buttons, text inputs, list
boxes, tree views, list views, tab controls, sliders, spinners,
progress bars, dropdowns, combo boxes, splitters, scroll panes, ANSI
terminal emulator, and more
- Flexbox-style automatic layout engine (VBox/HBox containers with
weighted space distribution)
- Dropdown menus with cascading submenus, checkbox and radio items,
@ -40,6 +42,8 @@ framebuffer. No bank switching -- LFB or fail.
- Encrypted serial networking stack (DH key exchange, XTEA-CTR cipher)
- Platform abstraction layer -- DOS/DJGPP production target, Linux/SDL2
development target
- Modular DXE architecture with dependency resolution -- core libraries,
widget plugins, and applications are all separate loadable modules
## Target Hardware
@ -53,30 +57,68 @@ framebuffer. No bank switching -- LFB or fail.
## Architecture
The shell runs as a single DOS executable (`dvx.exe`) that loads
applications dynamically via DJGPP's DXE3 shared library system.
DVX uses a modular DXE3 architecture. The bootstrap loader (`dvx.exe`)
is a small executable that discovers, dependency-sorts, and loads all
modules at startup. Core libraries, widget plugins, and applications
are separate `.lib`, `.wgt`, and `.app` DXE shared libraries.
```
+-------------------------------------------------------------------+
| dvx.exe (Task 0) |
| +-------------+ +-----------+ +------------+ |
| | shellMain | | shellApp | | shellExport| |
| | (event loop)| | (lifecycle| | (DXE symbol| |
| | | | + reaper)| | export) | |
| +-------------+ +-----------+ +------------+ |
| | | | |
| +------+-------+ +----+-----+ +----+----+ |
| | libdvx.a | |libtasks.a| | libdxe | |
| | (GUI/widgets)| |(scheduler)| | (DJGPP) | |
| +--------------+ +----------+ +---------+ |
+-------------------------------------------------------------------+
| |
+---------+ +---------+
| app.app | | app.app |
| (Task N)| | (Task M)|
+---------+ +---------+
dvx.exe (bootstrap loader)
|
+-- platformRegisterDxeExports() -- platform + libc/libm symbols
|
+-- libs/ (scanned, dependency-ordered)
| libtasks.lib -- cooperative task switching, stb_ds
| libdvx.lib -- draw, comp, wm, app, widget infrastructure
| dvxshell.lib -- shell, app lifecycle, crash recovery
|
+-- widgets/ (scanned, each calls wgtRegister)
| box.wgt, button.wgt, label.wgt, ... (26 modules)
|
+-- shellMain() -- found via dlsym, enters main loop
|
+-- apps/ (loaded on demand by shell)
progman.app, notepad.app, clock.app, ...
```
### Boot Sequence
1. `dvx.exe` starts, calls `platformRegisterDxeExports()` to register
platform and libc/libm symbols for DXE modules
2. Scans `libs/` recursively for `*.lib` files, reads `.dep` files,
topologically sorts, and loads in dependency order
3. Scans `widgets/` recursively for `*.wgt` files, loads each, and
calls `wgtRegister()` on any module that exports it
4. Finds `shellMain()` across loaded modules via `dlsym`
5. Calls `shellMain()`, which initializes the GUI, loads the desktop
app, and enters the main event loop
### Dependency Resolution
Each DXE module may have a `.dep` file (same base name, `.dep`
extension) listing the base names of modules that must be loaded before
it. The loader reads all `.dep` files, builds a dependency graph, and
loads modules in topological order. For example, `libdvx.dep` contains
`libtasks`, and `dvxshell.dep` lists `libtasks`, `libdvx`, and all
widget modules it requires.
### Widget DXE Modules
The 32 widget types in the `WidgetTypeE` enum are implemented across 26
`.wgt` DXE modules. Some modules register multiple widget types:
| Module | Widget Types |
|--------|-------------|
| `box.wgt` | VBox, HBox, Frame |
| `radio.wgt` | RadioGroup, Radio |
| `textinpt.wgt` | TextInput, TextArea |
| `tabctrl.wgt` | TabControl, TabPage |
| `treeview.wgt` | TreeView, TreeItem |
Each widget DXE exports a standard `wgtRegister()` function that
registers its widget class(es) with the core widget infrastructure.
Widget DXEs are discovered by directory scanning, not hardcoded.
### App Types
**Callback-only** (`hasMainLoop = false`): `appMain` creates windows,
@ -98,49 +140,111 @@ Diagnostic information (registers, faulting EIP) is logged to `dvx.log`.
## Directory Structure
### Source Tree
```
dvxgui/
dvx/ GUI compositor library (libdvx.a)
platform/ Platform abstraction (DOS/DJGPP, Linux/SDL2)
widgets/ Widget system (32 types, one file per type)
core/ Core GUI infrastructure (-> libs/libdvx.lib)
platform/ Platform abstraction (dvxPlatform.h, dvxPlatformDos.c)
thirdparty/ stb_image.h, stb_image_write.h
tasks/ Cooperative task switcher (libtasks.a)
tasks/ Cooperative task switcher (-> libs/libtasks.lib)
thirdparty/ stb_ds.h
dvxshell/ Desktop shell (dvx.exe)
apps/ DXE app modules (.app files)
shell/ Desktop shell (-> libs/dvxshell.lib)
widgets/ Widget modules (-> widgets/*.wgt, 26 files)
loader/ Bootstrap loader (-> dvx.exe)
apps/ Application modules (-> apps/*.app)
progman/ Program Manager -- app launcher grid
notepad/ Text editor with file I/O
clock/ Digital clock (multi-instance, main-loop)
dvxdemo/ Widget system showcase / demo app
cpanel/ Control Panel -- themes, wallpaper, video, mouse
imgview/ Image Viewer -- BMP/PNG/JPEG/GIF display
config/ Themes, wallpapers, dvx.ini, dependency files
rs232/ ISR-driven UART serial driver (librs232.a)
packet/ HDLC framing, CRC-16, Go-Back-N (libpacket.a)
security/ DH key exchange, XTEA-CTR cipher (libsecurity.a)
seclink/ Secure serial link wrapper (libseclink.a)
proxy/ Linux SecLink-to-telnet proxy (secproxy)
proxy/ Linux serial-to-telnet proxy (secproxy)
termdemo/ Encrypted ANSI terminal demo (termdemo.exe)
themes/ Color theme files (.thm)
wpaper/ Bundled wallpaper images
bin/ Build output (dvx.exe, apps/, config/)
bin/ Build output
lib/ Build output (static libraries)
releases/ Release archives
```
### Build Output (`bin/`)
```
bin/
dvx.exe Bootstrap loader
dvx.map Linker map
libs/
libtasks.lib Task switching DXE
libdvx.lib Core GUI DXE
libdvx.dep Dependencies: libtasks
dvxshell.lib Shell DXE
dvxshell.dep Dependencies: libtasks, libdvx, box, button, etc.
widgets/
box.wgt VBox/HBox/Frame
button.wgt Button
canvas.wgt Canvas
checkbox.wgt Checkbox
combobox.wgt ComboBox
dropdown.wgt Dropdown
image.wgt Image
imgbtn.wgt ImageButton
label.wgt Label
listbox.wgt ListBox
listview.wgt ListView
progress.wgt ProgressBar
radio.wgt RadioGroup/Radio
scrlpane.wgt ScrollPane
separatr.wgt Separator
slider.wgt Slider
spacer.wgt Spacer
spinner.wgt Spinner
splitter.wgt Splitter
statbar.wgt StatusBar
tabctrl.wgt TabControl/TabPage
terminal.wgt AnsiTerm
textinpt.wgt TextInput/TextArea
timer.wgt Timer
toolbar.wgt Toolbar
treeview.wgt TreeView/TreeItem
apps/
progman/progman.app
notepad/notepad.app
clock/clock.app
dvxdemo/dvxdemo.app
cpanel/cpanel.app
imgview/imgview.app
config/
dvx.ini
themes/
cde.thm
geos.thm
win31.thm
wpaper/
blueglow.jpg
swoop.jpg
triangle.jpg
```
## Building
Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`).
```bash
# Build everything (dvx lib, tasks lib, shell, all apps)
# Build everything (loader, core, tasks, shell, widgets, all apps)
make
# Build individual components
make -C dvx # builds lib/libdvx.a
make -C tasks # builds lib/libtasks.a
make -C dvxshell # builds bin/dvx.exe
make -C apps # builds bin/apps/*/*.app
make -C loader # builds dvx.exe
make -C core # builds libs/libdvx.lib + widgets/*.wgt
make -C tasks # builds libs/libtasks.lib
make -C shell # builds libs/dvxshell.lib + config
make -C widgets # builds widgets/*.wgt
make -C apps # builds apps/*/*.app
# Clean all build artifacts
make clean
@ -272,8 +376,8 @@ external dependencies:
| Library | Location | Purpose |
|---------|----------|---------|
| stb_image.h | `dvx/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) |
| stb_image_write.h | `dvx/thirdparty/` | Image writing (PNG export for screenshots) |
| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) |
| stb_image_write.h | `core/thirdparty/` | Image writing (PNG export for screenshots) |
| stb_ds.h | `tasks/thirdparty/` | Dynamic array and hash map (used by task manager) |
@ -281,9 +385,9 @@ external dependencies:
Each component directory has its own README with detailed API reference:
- [`dvx/README.md`](dvx/README.md) -- GUI library architecture and API
- [`core/README.md`](core/README.md) -- Core GUI library architecture and API
- [`tasks/README.md`](tasks/README.md) -- Task switcher API
- [`dvxshell/README.md`](dvxshell/README.md) -- Shell internals and DXE app contract
- [`shell/README.md`](shell/README.md) -- Shell internals and DXE app contract
- [`apps/README.md`](apps/README.md) -- Writing DXE applications
- [`rs232/README.md`](rs232/README.md) -- Serial port driver
- [`packet/README.md`](packet/README.md) -- Packet transport protocol

View file

@ -54,6 +54,8 @@ make # builds all .app files into ../bin/apps/<name>/
make clean # removes objects and binaries
```
CFLAGS: `-O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../tasks -I../tasks/thirdparty -I../shell`
Each app is compiled to an object file with the DJGPP cross-compiler, then
packaged into a `.app` via `dxe3gen`:
@ -66,11 +68,26 @@ $(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp
underscore prefix).
- `-E _appShutdown` is added for apps that export a shutdown hook (e.g.,
Clock).
- `-U` marks all other symbols as unresolved imports to be resolved from the
shell's export table at dlopen time.
- `-U` marks all other symbols as unresolved imports to be resolved at
`dlopen` time from the loader and all `RTLD_GLOBAL` modules (libdvx,
libtasks, widgets, and the shell's wrapper overrides).
Requires `lib/libdvx.a`, `lib/libtasks.a`, and the DXE3 tools from the DJGPP
toolchain.
Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`).
## Symbol Resolution
Apps do not link against static libraries. All symbols are resolved at load
time via the DXE3 dynamic linker:
1. The **loader** registers libc/libm/runtime symbols via `dlregsym()`.
2. **DXE modules** loaded with `RTLD_GLOBAL` (libdvx, libtasks, widgets)
export their public APIs to the global symbol namespace.
3. The **shell** registers 3 wrapper overrides (`dvxCreateWindow`,
`dvxCreateWindowCentered`, `dvxDestroyWindow`) via `dlregsym()` for
resource tracking.
When an app DXE is loaded, all its unresolved symbols are matched against
this combined symbol table.
## Directory Structure
@ -418,8 +435,8 @@ hook.
simultaneously. Each instance gets independent globals and statics.
- Avoid `static inline` functions in shared headers. Code inlined into the
DXE binary cannot be updated without recompiling the app. Use macros for
trivial expressions or regular functions exported through the shell's DXE
table.
trivial expressions or regular functions exported through the DXE module
system.
- Use `ctx->appDir` for loading app-relative resources (icons, data files).
The working directory is shared by all apps and belongs to the shell.
- Use `shellEnsureConfigDir()` + `shellConfigPath()` for persistent settings.

93
config/README.md Normal file
View file

@ -0,0 +1,93 @@
# DVX Configuration Files
Runtime configuration, theme files, wallpaper images, and module
dependency files. These are copied into `bin/config/` (INI, themes,
wallpapers) or `bin/libs/` (dep files) during the build.
## Files
| File | Destination | Purpose |
|------|-------------|---------|
| `dvx.ini` | `bin/config/dvx.ini` | Main configuration (video, mouse, colors, desktop) |
| `themes/*.thm` | `bin/config/themes/` | Color theme files (INI format with `[colors]` section) |
| `wpaper/*.jpg` | `bin/config/wpaper/` | Bundled wallpaper images |
| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx.lib dependency list |
| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell.lib dependency list |
## DVX.INI Format
Standard `[section]` / `key = value` INI format. Converted to DOS
line endings (`\r\n`) during the build.
```ini
[video]
width = 640
height = 480
bpp = 16
[mouse]
wheel = normal # normal | reversed
doubleclick = 500 # milliseconds (200-900)
acceleration = medium # off | low | medium | high
[colors]
desktop = 0,128,128
windowFace = 192,192,192
# ... 20 color keys total (R,G,B triplets 0-255)
[desktop]
wallpaper = CONFIG\WPAPER\SWOOP.JPG
mode = stretch # stretch | tile | center
```
## Dependency Files (.dep)
Plain text, one dependency per line. Each line is the base name
(without extension) of a module that must be loaded before this one.
Lines starting with `#` are comments. Empty lines are ignored.
Example -- `dvxshell.dep`:
```
# Core libraries
libtasks
libdvx
# Widget modules used by the shell
box
button
checkbox
dropdown
label
listbox
listview
radio
separator
spacer
statbar
textinpt
```
Dep files are read by the loader during startup to determine the
correct load order via topological sort.
## Theme Files (.thm)
INI format with a `[colors]` section containing the same 20 color
keys as `dvx.ini`. Loaded via the Control Panel app or
`dvxLoadTheme()`.
```ini
[colors]
desktop = 0,128,128
windowFace = 192,192,192
windowHighlight = 255,255,255
windowShadow = 128,128,128
; ... remaining colors
```
Three themes are bundled: `geos.thm` (GEOS Ensemble), `win31.thm`
(Windows 3.1), `cde.thm` (CDE/Motif).

View file

@ -1,10 +1,16 @@
# DVX GUI Library (libdvx.a)
# DVX Core Library (libdvx.lib)
The core GUI compositor library for DVX. Provides VESA video setup,
2D drawing primitives, dirty-rectangle compositing, a full window
manager with Motif-style chrome, and a 32-type widget toolkit with
automatic layout. Applications include `dvxApp.h` (which pulls in all
lower layers) and optionally `dvxWidget.h` for the widget system.
The core GUI compositor library for DVX, built as a DXE module
(libdvx.lib). Provides VESA video setup, 2D drawing primitives,
dirty-rectangle compositing, a full window manager with Motif-style
chrome, and the widget infrastructure (layout engine, event dispatch,
class registration). Individual widget type implementations live in
`../widgets/` as separate `.wgt` DXE modules that register themselves
at runtime via `wgtRegisterClass()`.
Applications include `dvxApp.h` (which pulls in all lower layers) and
optionally `dvxWidget.h` for the widget system. In a DXE app context,
headers are found via `-I` flags set by the build system.
## Architecture
@ -18,9 +24,20 @@ Layer 4 dvxWm Window stack, chrome, drag, resize, focus, menus
Layer 3 dvxComp Dirty rectangle list, merge, LFB flush
Layer 2 dvxDraw Spans, rects, bevels, text, bitmaps (asm inner loops)
Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format
dvxWidget Widget/layout system (optional, standalone)
dvxWidget Widget infrastructure (layout, events, class table)
```
The widget class table (`widgetClassTable[]`) starts empty. Widget DXE
modules (`.wgt` files in `../widgets/`) fill it at runtime by calling
registration functions (e.g. `wgtBoxRegister()`, `wgtButtonRegister()`).
The DVX shell loader loads each widget DXE and calls its register
function before starting the application.
Dynamic widget registration (Phase 2): external DXE plugins can call
`wgtRegisterClass()` to add entirely new widget types at runtime. The
returned type ID is >= `WGT_TYPE_DYNAMIC_BASE` (32). The maximum number
of widget types (built-in + dynamic) is `WGT_MAX_TYPES` (64).
## File Structure
@ -39,7 +56,7 @@ Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format
| File | Purpose |
|------|---------|
| `dvxTypes.h` | Shared type definitions used by all layers (PixelFormatT, DisplayT, WindowT, ColorSchemeT, etc.) |
| `dvxWidget.h` | Widget system public API (32 widget types, layout, events) |
| `dvxWidget.h` | Widget system public API, WidgetClassT vtable struct, WCLASS_* flags (for DXE plugins) |
| `dvxDialog.h/.c` | Modal dialogs (message box, file open/save) |
| `dvxPrefs.h/.c` | INI-based preferences system (read/write with typed accessors) |
| `dvxFont.h` | Embedded 8x16 VGA bitmap font glyph data (CP437, 256 glyphs) |
@ -48,35 +65,35 @@ Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format
| `dvxIcon.c` | stb_image implementation unit (BMP/PNG/JPEG/GIF loading) |
| `dvxImageWrite.c` | stb_image_write implementation unit (PNG export) |
### Widget Infrastructure
The widget infrastructure lives in this directory. It provides the
layout engine, event dispatch, class table, scrollbar primitives, and
shared helpers that all widget types depend on. The actual widget type
implementations (button, checkbox, listbox, etc.) are individual `.wgt`
DXE modules in `../widgets/`.
| File | Purpose |
|------|---------|
| `widgetInternal.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes |
| `widgetClass.c` | Widget class table (`widgetClassTable[]`, starts empty) and `wgtRegisterClass()` dynamic registration |
| `widgetCore.c` | Widget allocation, tree ops, hit testing, focus management, shared helpers |
| `widgetScrollbar.c` | Widget-internal scrollbar drawing and hit testing (shared by ListBox, TreeView, etc.) |
| `widgetLayout.c` | Two-pass layout engine: measure (bottom-up) + arrange (top-down) |
| `widgetEvent.c` | Mouse, keyboard, scroll, resize, and paint event dispatch to widget tree |
| `widgetOps.c` | Paint dispatch, overlay rendering, public widget operations (wgtFind, wgtDestroy, etc.) |
### Platform Abstraction
| File | Purpose |
|------|---------|
| `platform/dvxPlatform.h` | OS/CPU-neutral interface: video, input, span ops, filesystem |
| `platform/dvxPlatformDos.c` | DOS/DJGPP: VESA, DPMI, INT 16h/33h, rep stosl/movsd asm spans |
| `platform/dvxPlatformDos.c` | DOS/DJGPP: VESA, DPMI, INT 16h/33h, rep stosl/movsd asm spans, DXE export table |
To port DVX to a new platform, implement a new `dvxPlatformXxx.c`
against `platform/dvxPlatform.h` and swap it in the Makefile. No other
files need modification.
### Widget System
| File | Purpose |
|------|---------|
| `widgets/widgetInternal.h` | Shared internal header: vtable type, constants, cross-widget prototypes |
| `widgets/widgetClass.c` | Widget class table: one WidgetClassT vtable entry per type |
| `widgets/widgetCore.c` | Allocation, tree ops, hit testing, flag-based type queries |
| `widgets/widgetLayout.c` | Two-pass layout engine (measure + arrange) |
| `widgets/widgetEvent.c` | Mouse, keyboard, scroll, resize, and paint event dispatch |
| `widgets/widgetOps.c` | Paint dispatcher, public operations (wgtFind, wgtDestroy, etc.) |
| `widgets/widget*.c` | One file per widget type (button, checkbox, slider, etc.) |
Each widget type is self-contained in its own `.c` file. Dispatch for
paint, layout, mouse, keyboard, getText/setText, and destroy is driven
by a per-type vtable (WidgetClassT) rather than switch statements.
Adding a new widget type requires only a new `.c` file and an entry in
the class table.
### Third Party
| File | Purpose |
@ -90,10 +107,16 @@ the class table.
Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`).
```bash
make # builds ../lib/libdvx.a
make clean # removes obj/ and lib/
make # builds ../bin/libs/libdvx.lib and ../bin/libs/libdvx.dep
make clean
```
The Makefile uses `dxe3gen` to produce the DXE module. Internal symbols
are exported with `-E` prefixes so that widget DXE modules can link
against them at runtime (`-E _widget`, `-E _sCursor`, `-E _dvx`,
`-E _wgt`, `-E _wm`, `-E _draw`, etc.). The dependency file
`libdvx.dep` is copied from `../config/libdvx.dep` to `../bin/libs/`.
Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed
somewhere other than `~/djgpp/djgpp`.
@ -854,7 +877,17 @@ A retained-mode widget toolkit layered on top of the window manager.
Widgets form a tree rooted at a per-window VBox container. Layout is
automatic via a flexbox-like algorithm with weighted space distribution.
### Widget Catalog (32 types)
Widget type implementations are NOT compiled into libdvx. Each widget
type is a separate `.wgt` DXE module in `../widgets/` that registers
its `WidgetClassT` vtable into `widgetClassTable[]` at load time. The
`WidgetClassT` struct and `WCLASS_*` flags are defined in `dvxWidget.h`
(the public API header) so that DXE plugins can create and register new
widget types.
### Widget Catalog (32 built-in types)
Each type below is implemented in its own `.wgt` DXE module and loaded
by the DVX shell at startup.
#### Containers
@ -926,6 +959,51 @@ automatic via a flexbox-like algorithm with weighted space distribution.
| Timer | `wgtTimer(parent, intervalMs, repeat)` | Invisible callback timer (one-shot or repeating) |
### WidgetClassT Vtable
Each widget type has a `WidgetClassT` vtable that defines its behavior.
This struct is defined in `dvxWidget.h` so that DXE plugins can create
their own widget classes.
```c
typedef struct WidgetClassT {
uint32_t flags;
void (*paint)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void (*paintOverlay)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void (*calcMinSize)(WidgetT *w, const BitmapFontT *font);
void (*layout)(WidgetT *w, const BitmapFontT *font);
void (*onMouse)(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void (*onKey)(WidgetT *w, int32_t key, int32_t mod);
void (*destroy)(WidgetT *w);
const char *(*getText)(const WidgetT *w);
void (*setText)(WidgetT *w, const char *text);
} WidgetClassT;
```
Class flags (`WCLASS_*`):
| Flag | Value | Meaning |
|------|-------|---------|
| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus (Tab navigation) |
| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses the generic VBox/HBox layout algorithm |
| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally (vs. vertical) |
| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering itself |
| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here, no child recursion |
### Dynamic Widget Registration
```c
int32_t wgtRegisterClass(const WidgetClassT *wclass);
```
Register a new widget class at runtime. Returns the assigned type ID
(>= `WGT_TYPE_DYNAMIC_BASE`, which is 32) on success, or -1 if the
table is full. The `WidgetClassT` must remain valid for the lifetime
of the process (typically a `static const` in the registering DXE).
The `customData` field on `WidgetT` is available for DXE widget plugins
to store private per-widget data (NULL for built-in types).
### Widget Event Model
All widget types share a universal set of event callbacks set directly
@ -942,8 +1020,9 @@ on the WidgetT struct:
Type-specific handlers (button press animation, listbox selection
highlight) run first, then these universal callbacks fire.
Additional per-widget fields: `userData` (void pointer), `tooltip`
(hover text), `contextMenu` (right-click menu).
Additional per-widget fields: `userData` (void pointer), `customData`
(DXE plugin private data), `tooltip` (hover text), `contextMenu`
(right-click menu).
### Initialization

73
loader/README.md Normal file
View file

@ -0,0 +1,73 @@
# DVX Loader
Bootstrap loader for the DVX desktop environment. Builds as
`dvx.exe` -- the only native executable in the system. Everything
else is a dynamically loaded DXE module.
## What It Does
1. Changes to the executable's directory so relative paths resolve
2. Calls `platformRegisterDxeExports()` to register platform functions
and C runtime symbols (libc, libm, libgcc, DJGPP internals) with
the DXE3 loader via `dlregsym()`
3. Recursively scans `LIBS/` for `*.lib` modules and `WIDGETS/` for
`*.wgt` modules
4. Reads `.dep` files to build a dependency graph
5. Topologically sorts all modules and loads them in order with
`RTLD_GLOBAL` (each module's exports become available to
subsequent modules)
6. Calls `wgtRegister()` on any module that exports it (widget
registration)
7. Finds `shellMain()` across all loaded modules and calls it
8. On return, closes all modules in reverse order
## Files
| File | Purpose |
|------|---------|
| `loaderMain.c` | Entry point, directory scanning, dependency resolution, module loading |
| `Makefile` | Cross-compilation rules; links `dvxPlatformDos.c` directly |
The DXE export table lives in `core/platform/dvxPlatformDos.c`, not
in the loader. The loader is platform-agnostic -- all DJGPP/DXE3
knowledge is in the platform layer.
## Dependency Resolution
Each module may have a `.dep` file alongside it (same base name,
`.dep` extension). The file lists base names of modules that must
be loaded first, one per line. Lines starting with `#` are comments.
Example -- `dvxshell.dep`:
```
libtasks
libdvx
box
button
label
listview
```
A dependency is satisfied when either:
- A module with that base name has been loaded, or
- No module with that base name exists in the scan (external, assumed OK)
The loader iterates until all modules are loaded or no further
progress can be made (circular dependency). Unloadable modules are
reported to stderr.
## Building
```
make # builds ../bin/dvx.exe
make clean
```
The Makefile compiles `loaderMain.c` and links it with
`dvxPlatformDos.o` (compiled from `../core/platform/dvxPlatformDos.c`)
and `-lm`. The result goes through `exe2coff` + `CWSDSTUB` to
produce a DOS executable.

View file

@ -1,67 +1,101 @@
# DVX Shell
The DVX Shell (`dvx.exe`) is the host process for the DVX desktop environment.
It initializes the GUI subsystem, loads DXE3 application modules at runtime,
runs the cooperative main loop, and provides crash recovery so a faulting app
does not take down the entire system. Think of it as the Windows 3.1 Program
Manager and kernel combined into one executable.
The DVX Shell (`dvxshell.lib`) is a DXE module loaded by the DVX loader at
startup. It initializes the GUI subsystem, loads DXE3 application modules at
runtime, runs the cooperative main loop, and provides crash recovery so a
faulting app does not take down the entire system.
The shell is not a standalone executable. The loader maps it into memory via
`dlopen`, resolves its entry point (`shellMain`), and calls it. All symbols
the shell needs -- libdvx, libtasks, widgets, libc -- are resolved at load
time from DXE modules already loaded by the loader and the loader's own
`dlregsym` table.
## Files
| File | Purpose |
|------|---------|
| `shellMain.c` | Entry point, main loop, crash recovery, logging, desktop update callbacks |
| `shellApp.c` | App loading (dlopen/dlsym), lifecycle state machine, reaping, resource tracking, config dirs |
| `shellApp.h` | `ShellAppT`, `AppDescriptorT`, `AppStateE`, `DxeAppContextT`, public shell API |
| `shellExport.c` | DXE export table (400+ symbols), wrapper functions for resource tracking |
| `shellInfo.c` | System information gathering (delegates to platform layer), caches result |
| `shellMain.c` | Entry point (`shellMain`), main loop, crash recovery, logging |
| `shellApp.c` | App loading via `dlopen`/`dlsym`, lifecycle, reaping |
| `shellApp.h` | `ShellAppT`, `AppDescriptorT`, `AppStateE`, `DxeAppContextT`, API |
| `shellExport.c` | 3 DXE wrapper overrides for window resource tracking |
| `shellInfo.c` | System info gathering |
| `shellInfo.h` | `shellInfoInit()`, `shellGetSystemInfo()` |
| `shellTaskMgr.c` | Task Manager window -- list view, Switch To / End Task / Run buttons |
| `shellTaskMgr.c` | Task Manager window |
| `shellTaskMgr.h` | `shellTaskMgrOpen()`, `shellTaskMgrRefresh()` |
| `Makefile` | Cross-compile rules, links `-ldvx -ltasks -lm` |
| `Makefile` | Cross-compile rules, produces `dvxshell.lib` and `dvxshell.dep` |
## Building
```
make # builds ../bin/dvx.exe (also builds libdvx.a, libtasks.a)
make clean # removes objects and binary
make # builds ../bin/libs/dvxshell.lib and ../bin/libs/dvxshell.dep
make clean # removes objects, binary, and deployed config files
```
CFLAGS: `-O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty`
The shell is compiled to object files and packaged into a DXE via `dxe3gen`.
It exports `_shell` so the loader can resolve `shellMain`. All other symbols
are unresolved imports (`-U`) satisfied at load time.
Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`).
## Dependencies
`dvxshell.dep` lists the modules the loader must load before the shell:
```
libtasks
libdvx
box
button
checkbox
dropdown
label
listbox
listview
radio
separator
spacer
statbar
textinpt
```
The loader reads this file, loads each module via `dlopen` with `RTLD_GLOBAL`,
then loads the shell itself. This is how all symbols (dvx*, wgt*, ts*, etc.)
become available without the shell statically linking anything.
## Startup Sequence
`main()` in `shellMain.c` performs initialization in this order:
`shellMain()` in `shellMain.c` is called by the loader after all dependencies
are loaded. It performs initialization in this order:
1. **Change to exe directory** -- resolve the directory containing `dvx.exe`
via `argv[0]` and `platformChdir()` so that relative paths (`CONFIG/`,
`APPS/`, etc.) work regardless of where the user launched from.
2. **Truncate log** -- open `dvx.log` for write to clear it, then close.
1. **Truncate log** -- open `dvx.log` for write to clear it, then close.
All subsequent writes use append-per-write (the file is never held open).
3. **Load preferences** -- `prefsLoad("CONFIG/DVX.INI")`. Missing file or
2. **Load preferences** -- `prefsLoad("CONFIG/DVX.INI")`. Missing file or
keys silently fall back to compiled-in defaults.
4. **dvxInit** -- initialize VESA video (LFB), backbuffer, compositor, window
3. **dvxInit** -- initialize VESA video (LFB), backbuffer, compositor, window
manager, font, cursor, input subsystems. Reads video width/height/bpp from
preferences (default 640x480x16).
5. **Mouse config** -- read wheel direction, double-click speed, acceleration
4. **Mouse config** -- read wheel direction, double-click speed, acceleration
from `[mouse]` section and call `dvxSetMouseConfig()`.
6. **Color scheme** -- read `[colors]` section (20 RGB triplets), apply via
5. **Color scheme** -- read `[colors]` section (20 RGB triplets), apply via
`dvxApplyColorScheme()`.
7. **Wallpaper** -- read `[desktop]` section for wallpaper path and mode
6. **Wallpaper** -- read `[desktop]` section for wallpaper path and mode
(stretch/tile/center), load via `dvxSetWallpaper()`.
8. **Video mode log** -- enumerate all available VESA modes to `dvx.log`.
9. **Task system** -- `tsInit()`, set shell task (task 0) to
7. **Video mode log** -- enumerate all available VESA modes to `dvx.log`.
8. **Task system** -- `tsInit()`, set shell task (task 0) to
`TS_PRIORITY_HIGH` so the UI stays responsive under load.
10. **System info** -- `shellInfoInit()` gathers CPU, memory, drive info via
9. **System info** -- `shellInfoInit()` gathers CPU, memory, drive info via
the platform layer and logs it.
11. **DXE exports** -- `shellExportInit()` calls `dlregsym()` to register the
export table. Must happen before any `dlopen()`.
12. **App slot table** -- `shellAppInit()` zeroes the 32-slot fixed array.
13. **Idle/hotkey callbacks** -- wire up `idleYield`, `ctrlEscHandler`,
10. **DXE exports** -- `shellExportInit()` calls `dlregsym()` to register the
3 wrapper overrides. Must happen before any `dlopen()` of app DXEs.
11. **App slot table** -- `shellAppInit()` zeroes the 32-slot fixed array.
12. **Idle/hotkey callbacks** -- wire up `idleYield`, `ctrlEscHandler`,
`titleChangeHandler` on the `AppContextT`.
14. **Desktop app** -- `shellLoadApp(ctx, "apps/progman/progman.app")`. If
13. **Desktop app** -- `shellLoadApp(ctx, "apps/progman/progman.app")`. If
this fails, the shell exits.
15. **Crash handlers** -- `installCrashHandler()` registers signal handlers
14. **Crash handlers** -- `installCrashHandler()` registers signal handlers
for SIGSEGV, SIGFPE, SIGILL. Installed last so initialization crashes
get the default DJGPP abort-with-register-dump instead of our recovery
path.
@ -100,7 +134,7 @@ The shell provides Windows 3.1-style fault tolerance using `setjmp`/`longjmp`:
1. `installCrashHandler()` registers `crashHandler` for SIGSEGV, SIGFPE,
SIGILL.
2. `setjmp(sCrashJmp)` in `main()` establishes the recovery point.
2. `setjmp(sCrashJmp)` in `shellMain()` establishes the recovery point.
3. If a signal fires (in any task), `crashHandler` logs the crash details
(signal, app name, full register dump from `__djgpp_exception_state_ptr`),
re-installs the handler (DJGPP uses SysV semantics -- handler resets to
@ -122,7 +156,8 @@ registers, and EFLAGS -- invaluable for post-mortem debugging.
### DXE3 Loading
DXE3 is DJGPP's dynamic linking mechanism. Each `.app` file is a DXE3 shared
object. The shell resolves symbols from the export table registered via
object. Symbols are resolved from the loaded RTLD_GLOBAL modules (libdvx,
libtasks, widgets) and the shell's 3 wrapper overrides registered via
`dlregsym()`. The load sequence in `shellLoadApp()`:
1. Allocate a slot from the 32-entry fixed array (slot 0 is the shell).
@ -216,10 +251,31 @@ to which app, enabling:
`sCurrentAppId` is a simple global (not thread-local) because cooperative
multitasking means only one task runs at a time.
## DXE Export Table
`shellExport.c` registers 3 wrapper functions via `dlregsym()` that override
the real implementations for subsequently loaded app DXEs:
1. `dvxCreateWindow` -- stamps `win->appId` for resource ownership tracking.
2. `dvxCreateWindowCentered` -- stamps `win->appId` for resource ownership
tracking.
3. `dvxDestroyWindow` -- checks if the destroyed window was a callback-only
app's last window and marks it for reaping.
The key mechanic: `dlregsym` takes precedence over `RTLD_GLOBAL` exports.
Since libdvx (which has the real functions) was loaded before
`shellExportInit()` registers these wrappers, libdvx keeps the real
implementations. But any app DXE loaded afterward gets the wrappers, which
add resource tracking transparently.
All other symbol exports -- dvx*, wgt*, ts*, platform*, libc, libm -- come
from the DXE modules loaded with `RTLD_GLOBAL` by the loader. They no longer
need to be listed in the shell's export table.
## Task Manager
`shellTaskMgr.c` implements a shell-level Task Manager accessible via
**Ctrl+Esc** regardless of which app is focused or whether the desktop app
Ctrl+Esc regardless of which app is focused or whether the desktop app
is running. It is owned by the shell (appId = 0), not by any DXE app.
Features:
@ -262,38 +318,6 @@ API:
Apps use the standard preferences system (`prefsLoad`/`prefsSave`) pointed at
their config directory for persistent settings.
## DXE Export Table
`shellExport.c` contains the ABI contract between the shell and apps. Three
categories of exports:
1. **Wrapped functions** (3): `dvxCreateWindow`, `dvxCreateWindowCentered`,
`dvxDestroyWindow`. These are intercepted to stamp `win->appId` for
resource ownership tracking. Apps see them under their original names --
the wrapping is transparent.
2. **Direct exports** (200+): all other `dvx*`, `wgt*`, `wm*`, `ts*`,
drawing, preferences, platform, and shell API functions. Safe to call
without interception.
3. **libc / libm / runtime exports** (200+): DXE3 modules are relocatable
objects, not fully linked executables. Every C library function a DXE
calls must be explicitly listed so the loader can resolve it at dlopen
time. This includes:
- Memory (malloc, calloc, realloc, free)
- String operations (str*, mem*)
- Formatted I/O (printf, snprintf, fprintf, sscanf, etc.)
- File I/O (fopen, fread, fwrite, fclose, etc.)
- Directory operations (opendir, readdir, closedir, mkdir)
- Time (time, localtime, clock, strftime)
- Math (sin, cos, sqrt, pow, floor, ceil, etc.)
- stb_ds internals (arrput/arrfree/hm* macro backends)
- stb_image / stb_image_write
- libgcc 64-bit integer helpers (__divdi3, __moddi3, etc.)
- DJGPP stdio internals (__dj_stdin, __dj_stdout, __dj_stderr)
The table is registered once via `dlregsym()` before any `dlopen()`.
## System Hotkeys
These are always active regardless of which app is focused:
@ -341,6 +365,7 @@ full register dumps.
| Function | Description |
|----------|-------------|
| `shellMain(argc, argv)` | Entry point called by the DVX loader |
| `shellAppInit()` | Zero the 32-slot app table |
| `shellLoadApp(ctx, path)` | Load and start a DXE app. Returns app ID (>= 1) or -1 |
| `shellReapApps(ctx)` | Clean up terminated apps (call each frame). Returns true if any reaped |
@ -352,7 +377,7 @@ full register dumps.
| `shellLog(fmt, ...)` | Append to dvx.log |
| `shellEnsureConfigDir(ctx)` | Create an app's config directory tree |
| `shellConfigPath(ctx, name, buf, size)` | Build path to file in app's config dir |
| `shellExportInit()` | Register DXE symbol export table via dlregsym() |
| `shellExportInit()` | Register 3 DXE wrapper overrides via dlregsym() |
| `shellRegisterDesktopUpdate(fn)` | Register callback for app state changes |
| `shellUnregisterDesktopUpdate(fn)` | Remove a previously registered callback |
| `shellDesktopUpdate()` | Notify all registered listeners |

View file

@ -25,11 +25,10 @@ lets each task yield at safe points, avoiding synchronization entirely.
## Files
| File | Description |
|-----------------------|----------------------------------------------------|
| File | Purpose |
|------|---------|
| `taskswitch.h` | Public API -- types, constants, function prototypes |
| `taskswitch.c` | Implementation (scheduler, context switch, slots) |
| `demo.c` | Standalone test harness exercising all features |
| `taskswitch.c` | Implementation (scheduler, context switch, slots, includes stb_ds implementation) |
| `thirdparty/stb_ds.h` | stb dynamic array/hashmap library (third-party) |
| `Makefile` | DJGPP cross-compilation build rules |
@ -39,18 +38,25 @@ lets each task yield at safe points, avoiding synchronization entirely.
Cross-compile from Linux:
```
make # builds ../lib/libtasks.a
make demo # also builds ../bin/tsdemo.exe
make clean # removes objects, library, and demo binary
make # builds ../bin/libs/libtasks.lib
make clean # removes objects and library
```
Output:
| Path | Description |
|---------------------|----------------------|
| `../lib/libtasks.a` | Static library |
|------|-------------|
| `../bin/libs/libtasks.lib` | DXE module |
| `../obj/tasks/` | Object files |
| `../bin/tsdemo.exe` | Demo executable |
The library is compiled to an object file and packaged into a DXE via
`dxe3gen`. It exports all `ts*` symbols (`-E _ts`) and stb_ds internals
(`-E _stbds_`) so that other DXE modules loaded with `RTLD_GLOBAL` can
use both the task API and stb_ds data structures. All other symbols are
unresolved imports (`-U`) resolved at load time from the loader's
`dlregsym` table.
Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`).
## Quick Start
@ -110,14 +116,14 @@ available for reuse by the next `tsCreate()` call.
### Initialization and Teardown
| Function | Signature | Description |
|--------------|-------------------------|--------------------------------------------------------------------|
|----------|-----------|-------------|
| `tsInit` | `int32_t tsInit(void)` | Initialize the library. Returns `TS_OK` or a negative error code. |
| `tsShutdown` | `void tsShutdown(void)` | Free all resources. Safe to call even if `tsInit` was never called. |
### Task Creation and Termination
| Function | Signature | Description |
|------------|---------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|----------|-----------|-------------|
| `tsCreate` | `int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t ss, int32_t pri)` | Create a ready task. Returns the task ID (>= 0) or a negative error code. Pass 0 for `ss` to use `TS_DEFAULT_STACK_SIZE`. Reuses terminated slots. |
| `tsExit` | `void tsExit(void)` | Terminate the calling task. Must not be called from the main task. Never returns. |
| `tsKill` | `int32_t tsKill(uint32_t taskId)` | Forcibly terminate another task. Cannot kill main (id 0) or self (use `tsExit` instead). |
@ -125,33 +131,33 @@ available for reuse by the next `tsCreate()` call.
### Scheduling
| Function | Signature | Description |
|-----------|----------------------|-----------------------------------------------------------------|
|----------|-----------|-------------|
| `tsYield` | `void tsYield(void)` | Voluntarily relinquish the CPU to the next eligible ready task. |
### Pausing and Resuming
| Function | Signature | Description |
|------------|---------------------------------|------------------------------------------------------------------------------------------------------------|
|----------|-----------|-------------|
| `tsPause` | `int32_t tsPause(uint32_t id)` | Pause a task. Main task (id 0) cannot be paused. Self-pause triggers an implicit yield. |
| `tsResume` | `int32_t tsResume(uint32_t id)` | Resume a paused task. Credits are refilled so it is not penalized for having been paused. |
### Priority
| Function | Signature | Description |
|-----------------|---------------------------------------------------|-----------------------------------------------------------------------------------|
|----------|-----------|-------------|
| `tsSetPriority` | `int32_t tsSetPriority(uint32_t id, int32_t pri)` | Change a task's priority. Credits are reset so the change takes effect immediately. |
| `tsGetPriority` | `int32_t tsGetPriority(uint32_t id)` | Return the task's priority, or `TS_ERR_PARAM` on an invalid ID. |
### Crash Recovery
| Function | Signature | Description |
|-------------------|------------------------------|------------------------------------------------------------------------------------------------------------------|
|----------|-----------|-------------|
| `tsRecoverToMain` | `void tsRecoverToMain(void)` | Reset scheduler state to task 0 after a `longjmp` from a signal handler. Call before `tsKill` on the crashed task. The crashed task's slot is NOT freed -- call `tsKill` afterward. |
### Query
| Function | Signature | Description |
|-----------------|--------------------------------------|--------------------------------------------------------|
|----------|-----------|-------------|
| `tsGetState` | `TaskStateE tsGetState(uint32_t id)` | Return the task's state enum value. |
| `tsCurrentId` | `uint32_t tsCurrentId(void)` | Return the ID of the currently running task. |
| `tsGetName` | `const char *tsGetName(uint32_t id)` | Return the task's name string, or `NULL` on invalid ID. |
@ -163,7 +169,7 @@ available for reuse by the next `tsCreate()` call.
### Error Codes
| Name | Value | Meaning |
|----------------|-------|--------------------------------------------------|
|------|-------|---------|
| `TS_OK` | 0 | Success |
| `TS_ERR_INIT` | -1 | Library not initialized |
| `TS_ERR_PARAM` | -2 | Invalid parameter |
@ -174,7 +180,7 @@ available for reuse by the next `tsCreate()` call.
### Priority Presets
| Name | Value | Credits per Round |
|----------------------|-------|-------------------|
|------|-------|-------------------|
| `TS_PRIORITY_LOW` | 0 | 1 |
| `TS_PRIORITY_NORMAL` | 5 | 6 |
| `TS_PRIORITY_HIGH` | 10 | 11 |
@ -187,7 +193,7 @@ provided for convenience. In the DVX Shell, the main task runs at
### Defaults
| Name | Value | Description |
|-------------------------|-------|------------------------|
|------|-------|-------------|
| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack per task |
| `TS_NAME_MAX` | 32 | Max task name length |
@ -281,7 +287,7 @@ versions.
Six callee-saved values are saved and restored per switch:
| Register | Offset | Purpose |
|----------|--------|------------------------------------------|
|----------|--------|---------|
| EBX | 0 | Callee-saved general purpose |
| ESI | 4 | Callee-saved general purpose |
| EDI | 8 | Callee-saved general purpose |
@ -294,7 +300,7 @@ Six callee-saved values are saved and restored per switch:
Eight callee-saved values are saved and restored per switch:
| Register | Offset | Purpose |
|----------|--------|------------------------------------------|
|----------|--------|---------|
| RBX | 0 | Callee-saved general purpose |
| R12 | 8 | Callee-saved general purpose |
| R13 | 16 | Callee-saved general purpose |
@ -326,32 +332,10 @@ and then `tsExit()`.
each task's needs.
## Demo
`demo.c` exercises five phases:
1. **Priority scheduling** -- creates tasks at low, normal, and high
priority. All tasks run, but the high-priority task gets significantly
more turns.
2. **Pause** -- pauses one task mid-run and shows it stops being
scheduled.
3. **Resume** -- resumes the paused task and shows it picks up where it
left off.
4. **Priority boost** -- raises the low-priority task above all others
and shows it immediately gets more turns.
5. **Slot reuse** -- creates three waves of short-lived tasks that
terminate and shows subsequent waves reuse the same task IDs.
Build and run:
```
make demo
tsdemo
```
## Third-Party Dependencies
- **stb_ds.h** (Sean Barrett) -- dynamic array and hashmap library.
Located in `thirdparty/stb_ds.h`. Used for the task control block
array. Public domain / MIT licensed.
array. Included in the DXE build and exported via `-E _stbds_` so
other modules can use stb_ds without bundling their own copy.
Public domain / MIT licensed.

95
widgets/README.md Normal file
View file

@ -0,0 +1,95 @@
# DVX Widget Modules
Individual widget type implementations for the DVX GUI, each built
as a separate `.wgt` DXE module. The loader scans the `WIDGETS/`
directory recursively at startup and loads every `.wgt` file it
finds, calling `wgtRegister()` on each to fill the widget class
table.
Drop a new `.wgt` file in the directory and it is automatically
available -- no loader or core library changes needed.
## How Widget DXEs Work
Each `.wgt` module contains:
1. The widget implementation (paint, layout, mouse/key handlers)
2. A `static const WidgetClassT` vtable definition
3. A `wgtRegister()` function that writes the vtable pointer into
`widgetClassTable[]` at the widget's enum slot
4. The public API functions (e.g. `wgtTreeView()`, `wgtTreeViewGetSelected()`)
At load time, the module resolves its dependencies against:
- The loader's `dlregsym` table (platform functions, libc)
- `libtasks.lib` exports (stb_ds dynamic arrays)
- `libdvx.lib` exports (widget infrastructure: `widgetAlloc`,
`widgetClassTable`, `widgetScrollbarThumb`, shared state, etc.)
- Other widget modules loaded earlier (via `RTLD_GLOBAL`)
## Widget Catalog
| Module | Widget Types | Description |
|--------|-------------|-------------|
| `box.wgt` | VBox, HBox, Frame | Containers: vertical/horizontal box layout, titled frame |
| `button.wgt` | Button | Push button with text and keyboard accelerator |
| `canvas.wgt` | Canvas | Freeform drawing surface with mouse callbacks |
| `checkbox.wgt` | Checkbox | Toggle checkbox with label |
| `combobox.wgt` | ComboBox | Editable text input with dropdown list |
| `dropdown.wgt` | Dropdown | Non-editable dropdown selector |
| `image.wgt` | Image | Static image display (BMP/PNG/JPEG/GIF) |
| `imgbtn.wgt` | ImageButton | Button with icon image |
| `label.wgt` | Label | Static text display with optional accelerator |
| `listbox.wgt` | ListBox | Scrollable string list with single/multi select |
| `listview.wgt` | ListView | Multi-column list with headers, sorting, column resize |
| `progress.wgt` | ProgressBar | Horizontal or vertical progress indicator |
| `radio.wgt` | RadioGroup, Radio | Mutually exclusive option buttons |
| `scrlpane.wgt` | ScrollPane | Scrollable viewport for oversized content |
| `separatr.wgt` | Separator | Horizontal or vertical divider line |
| `slider.wgt` | Slider | Horizontal value slider with thumb |
| `spacer.wgt` | Spacer | Invisible layout spacer |
| `spinner.wgt` | Spinner | Numeric input with up/down buttons |
| `splitter.wgt` | Splitter | Resizable two-pane divider |
| `statbar.wgt` | StatusBar | Horizontal bar at window bottom |
| `tabctrl.wgt` | TabControl, TabPage | Tabbed page container |
| `terminal.wgt` | AnsiTerm | VT100 terminal emulator with scrollback |
| `textinpt.wgt` | TextInput, TextArea | Single-line and multi-line text editing |
| `timer.wgt` | Timer | Periodic callback (no visual) |
| `toolbar.wgt` | Toolbar | Horizontal button/widget bar |
| `treeview.wgt` | TreeView, TreeItem | Hierarchical tree with expand/collapse |
26 modules implementing 32 widget types (some modules register
multiple related types).
## Writing a New Widget
1. Create `widgets/myWidget.c`
2. Include `"widgetInternal.h"` for infrastructure access
3. Implement paint, calcMinSize, and event handlers as needed
4. Define a `static const WidgetClassT` with your vtable
5. Add a `wgtRegister()` function:
```c
void wgtRegister(void) {
widgetClassTable[MyWidgetTypeE] = &sClassMyWidget;
}
```
6. Add the new enum value to `WidgetTypeE` in `core/dvxWidget.h`
(or use `wgtRegisterClass()` for a fully dynamic type ID)
7. Add public API functions (e.g. `wgtMyWidget()`) that call
`widgetAlloc(parent, MyWidgetTypeE)`
8. Add a build rule in `widgets/Makefile`
9. Build -- the loader discovers and loads it automatically
## Building
```
make # compiles all widget .c files, creates .wgt modules in ../bin/widgets/
make clean
```
Each `.wgt` is created with `dxe3gen -E _wgt -U`, which exports all
symbols starting with `wgt` (public API + `wgtRegister`) and allows
unresolved symbols (resolved at load time from other modules).