This commit is contained in:
Scott Duensing 2026-03-25 22:42:07 -05:00
parent 67872c6b98
commit 56816eedd8
12 changed files with 1582 additions and 2872 deletions

477
README.md
View file

@ -1,125 +1,140 @@
# DVX -- DOS Visual eXecutive # DVX -- DOS Visual eXecutive
A windowed GUI compositor and desktop shell for DOS, built with A windowed GUI compositor and widget toolkit targeting DJGPP/DPMI on
DJGPP/DPMI. DVX combines a Motif-style window manager, dirty-rectangle 486+ hardware. VESA VBE 2.0+ LFB only. Motif-inspired visual style
compositor, cooperative task switcher, and a modular DXE3 architecture with 2px bevels, fixed bitmap fonts, and dirty-rect compositing.
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 The system runs on 86Box (primary target) and real DOS hardware.
framebuffer. No bank switching -- LFB or fail.
## Features
- Motif/GEOS-style beveled window chrome with drag, resize, minimize,
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 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,
keyboard accelerators, and context menus
- Modal dialogs: message box (OK/Cancel/Yes/No/Retry) and file
open/save with directory navigation and filter dropdown
- 20-color theme system with live preview and INI-based theme files
- Wallpaper support: stretch, tile, or center with bilinear scaling and
16bpp ordered dithering
- Live video mode switching without restart
- Mouse wheel support (CuteMouse Wheel API)
- Screenshots (full screen or per-window) saved as PNG
- Clipboard (copy/cut/paste within DVX)
- Timer widget for periodic callbacks
- Cooperative task switcher for apps that need their own main loop
- DXE3 dynamic application loading with crash recovery (SIGSEGV,
SIGFPE, SIGILL caught and isolated per-app)
- INI-based preferences system with typed read/write accessors
- 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
- **CPU**: 486 baseline, Pentium-optimized paths where significant
- **Video**: VESA VBE 2.0+ with linear framebuffer
- **OS**: DOS with DPMI (CWSDPMI or equivalent)
- **Supported depths**: 8, 15, 16, 24, 32 bpp
- **Test platform**: 86Box with PCI video cards
## Architecture ## Architecture
DVX uses a modular DXE3 architecture. The bootstrap loader (`dvx.exe`) DVX is built as a set of DXE3 dynamic modules loaded by a bootstrap
is a small executable that discovers, dependency-sorts, and loads all executable. The loader resolves dependencies and loads modules in
modules at startup. Core libraries, widget plugins, and applications topological order, then hands control to the shell.
are separate `.lib`, `.wgt`, and `.app` DXE shared libraries.
``` ```
dvx.exe (bootstrap loader) dvx.exe (loader)
| |
+-- platformRegisterDxeExports() -- platform + libc/libm symbols +-- libs/libtasks.lib cooperative task switcher
+-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra)
+-- libs/texthelp.lib shared text editing helpers
+-- libs/listhelp.lib shared list/dropdown helpers
+-- libs/dvxshell.lib shell (app lifecycle, desktop, task manager)
| |
+-- libs/ (scanned, dependency-ordered) +-- widgets/*.wgt 26 widget type plugins
| 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) +-- apps/*/*.app DXE applications
| 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 ## Directory Structure
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 | Directory | Output | Description |
|--------------|-------------------------|--------------------------------------------|
| `loader/` | `bin/dvx.exe` | Bootstrap loader, platform layer, stb_ds |
| `core/` | `bin/libs/libdvx.lib` | Core GUI library (5 layers + widget infra) |
| `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching |
| `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers |
| `listhelp/` | `bin/libs/listhelp.lib` | Shared list/dropdown helpers |
| `shell/` | `bin/libs/dvxshell.lib` | DVX Shell (app lifecycle, task manager) |
| `widgets/` | `bin/widgets/*.wgt` | 26 individual widget DXE modules |
| `apps/` | `bin/apps/*/*.app` | Application DXE modules |
| `config/` | `bin/config/`, `bin/libs/`, `bin/widgets/` | INI config, themes, wallpapers, dep files |
| `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver |
| `packet/` | `lib/libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
| `security/` | `lib/libsecurity.a` | DH key exchange, XTEA-CTR cipher, RNG |
| `seclink/` | `lib/libseclink.a` | Secure serial link (channels, encryption) |
| `proxy/` | `bin/secproxy` | Linux proxy: 86Box <-> telnet BBS |
| `termdemo/` | `bin/termdemo.exe` | Standalone encrypted terminal demo |
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 ## Build
The 32 widget types in the `WidgetTypeE` enum are implemented across 26 Requires the DJGPP cross-compiler at `~/djgpp/djgpp`.
`.wgt` DXE modules. Some modules register multiple widget types:
| Module | Widget Types | ```
|--------|-------------| make # build everything
| `box.wgt` | VBox, HBox, Frame | make clean # remove all build artifacts
| `radio.wgt` | RadioGroup, Radio | ./mkcd.sh # build + create ISO for 86Box
| `textinpt.wgt` | TextInput, TextArea | ```
| `tabctrl.wgt` | TabControl, TabPage |
| `treeview.wgt` | TreeView, TreeItem |
Each widget DXE exports a standard `wgtRegister()` function that The top-level Makefile builds in dependency order:
registers its widget class(es) with the core widget infrastructure.
Widget DXEs are discovered by directory scanning, not hardcoded.
### App Types ```
core -> tasks -> loader -> texthelp -> listhelp -> widgets -> shell -> apps
```
Build output goes to `bin/` (executables, DXE modules, config) and
`obj/` (intermediate object files).
## Runtime Directory Layout (bin/)
```
bin/
dvx.exe bootstrap loader (entry point)
libs/
libtasks.lib task switching library
libdvx.lib core GUI library
texthelp.lib text editing helpers
listhelp.lib list/dropdown helpers
dvxshell.lib DVX shell
*.dep dependency files for load ordering
widgets/
box.wgt VBox/HBox/Frame containers
button.wgt push button
... (26 widget modules total)
*.dep dependency files
apps/
progman/progman.app Program Manager (desktop)
notepad/notepad.app text editor
clock/clock.app clock display
dvxdemo/dvxdemo.app widget showcase
cpanel/cpanel.app control panel
imgview/imgview.app image viewer
config/
dvx.ini system configuration
themes/ color theme files (.thm)
wpaper/ wallpaper images
```
## Core Architecture (5 Layers)
1. **dvxVideo** -- VESA VBE init, LFB mapping, backbuffer, pixel format, color packing
2. **dvxDraw** -- Rectangle fills, bevels, text rendering, cursor/icon drawing
3. **dvxComp** -- Dirty rectangle tracking, merge, compositor, LFB flush
4. **dvxWm** -- Window stack, chrome, drag/resize, menus, scrollbars, hit testing
5. **dvxApp** -- Event loop, input polling, public API, color schemes, wallpaper
## Widget System
Widgets are isolated DXE modules. Core knows nothing about individual
widget types -- no compile-time enum, no union, no per-widget structs in
dvxWidget.h.
* **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time
* **void *data**: Each widget allocates its own private data struct
* **Per-widget API registry**: `wgtRegisterApi("name", &api)` replaces the monolithic API
* **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros
* **Shared helpers**: texthelp.lib (text editing) and listhelp.lib (dropdown/list)
## DXE Module System
All modules are DXE3 dynamic libraries loaded by dvx.exe at startup.
The loader scans `libs/` for `.lib` files and `widgets/` for `.wgt`
files, reads `.dep` files for dependencies, and loads in topological
order. Widget modules that export `wgtRegister()` have it called after
loading.
Each `.dep` file lists base names of modules that must load first, one
per line. Comments start with `#`.
## App Types
**Callback-only** (`hasMainLoop = false`): `appMain` creates windows, **Callback-only** (`hasMainLoop = false`): `appMain` creates windows,
registers callbacks, and returns. The app lives through event callbacks registers callbacks, and returns. The app lives through event callbacks
@ -130,7 +145,8 @@ the app. `appMain` runs its own loop calling `tsYield()` to share CPU.
Used for apps that need continuous processing (clocks, terminal Used for apps that need continuous processing (clocks, terminal
emulators, games). 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. If
an app crashes, the handler `longjmp`s back to the shell's main loop, an app crashes, the handler `longjmp`s back to the shell's main loop,
@ -138,221 +154,16 @@ the crashed app is force-killed, and the shell continues running.
Diagnostic information (registers, faulting EIP) is logged to `dvx.log`. Diagnostic information (registers, faulting EIP) is logged to `dvx.log`.
## Directory Structure
### Source Tree
```
dvxgui/
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 (-> libs/libtasks.lib)
thirdparty/ stb_ds.h
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 serial-to-telnet proxy (secproxy)
termdemo/ Encrypted ANSI terminal demo (termdemo.exe)
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 (loader, core, tasks, shell, widgets, all apps)
make
# Build individual components
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
```
Set `DJGPP_PREFIX` in the component Makefiles if your toolchain is
installed somewhere other than `~/djgpp/djgpp`.
## Deployment
### CD-ROM ISO (86Box)
The primary deployment method is an ISO image mounted as a CD-ROM in
86Box:
```bash
./mkcd.sh
```
This builds everything, then creates an ISO 9660 image with 8.3
filenames at `~/.var/app/net._86box._86Box/data/86Box/dvx.iso`. Mount
it in 86Box as a CD-ROM drive and run `D:\DVX.EXE` (or whatever drive
letter is assigned).
## Configuration
DVX reads its configuration from `CONFIG\DVX.INI` on the target
filesystem. The INI file uses a standard `[section]` / `key = value`
format. Settings are applied at startup and can be changed live from the
Control Panel app.
```ini
[video]
width = 640
height = 480
bpp = 16
[mouse]
wheel = normal
doubleclick = 500
acceleration = medium
[colors]
desktop = 0,128,128
windowFace = 192,192,192
windowHighlight = 255,255,255
windowShadow = 128,128,128
activeTitleBg = 48,48,48
activeTitleFg = 255,255,255
inactiveTitleBg = 160,160,160
inactiveTitleFg = 64,64,64
contentBg = 192,192,192
contentFg = 0,0,0
menuBg = 192,192,192
menuFg = 0,0,0
menuHighlightBg = 48,48,48
menuHighlightFg = 255,255,255
buttonFace = 192,192,192
scrollbarBg = 192,192,192
scrollbarFg = 128,128,128
scrollbarTrough = 160,160,160
cursorColor = 255,255,255
cursorOutline = 0,0,0
[desktop]
wallpaper = C:\DVX\WPAPER\SWOOP.JPG
mode = stretch
```
### Video Section
- `width`, `height` -- requested resolution (closest VESA mode is used)
- `bpp` -- preferred color depth (8, 15, 16, 24, or 32)
### Mouse Section
- `wheel` -- `normal` or `reversed`
- `doubleclick` -- double-click speed in milliseconds (200--900)
- `acceleration` -- `off`, `low`, `medium`, or `high`
### Colors Section
All 20 system colors as `R,G,B` triplets (0--255). Key names match the
`ColorIdE` enum: `desktop`, `windowFace`, `windowHighlight`, `windowShadow`,
`activeTitleBg`, `activeTitleFg`, `inactiveTitleBg`, `inactiveTitleFg`,
`contentBg`, `contentFg`, `menuBg`, `menuFg`, `menuHighlightBg`,
`menuHighlightFg`, `buttonFace`, `scrollbarBg`, `scrollbarFg`,
`scrollbarTrough`, `cursorColor`, `cursorOutline`. Missing keys fall back to
compiled-in defaults (GEOS Ensemble palette). Colors can also be loaded from
standalone `.thm` theme files via the Control Panel.
### Desktop Section
- `wallpaper` -- path to wallpaper image (BMP, PNG, JPEG, GIF)
- `mode` -- `stretch`, `tile`, or `center`
## Bundled Applications ## Bundled Applications
| App | File | Type | Description | | App | File | Type | Description |
|-----|------|------|-------------| |-----------------|---------------|-----------|------------------------------------------------------------|
| Program Manager | `progman.app` | Callback | App launcher grid with icons; Help menu opens the shell's Task Manager (Ctrl+Esc) | | Program Manager | `progman.app` | Callback | App launcher grid, system info dialog |
| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save dialogs, clipboard, and undo | | Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save, clipboard |
| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable | | Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable |
| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating all 32 widget types | | DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating 31 widget types |
| Control Panel | `cpanel.app` | Callback | System settings: color themes with live preview, wallpaper selection, video mode switching, mouse configuration | | Control Panel | `cpanel.app` | Callback | Themes, wallpaper, video mode, mouse configuration |
| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, and GIF images with file dialog | | Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, GIF images with aspect-ratio zoom |
## Serial / Networking Stack ## Serial / Networking Stack
@ -361,37 +172,31 @@ A layered encrypted serial communications stack for connecting DVX to
remote systems (BBS, etc.) through 86Box's emulated UART: remote systems (BBS, etc.) through 86Box's emulated UART:
| Layer | Library | Description | | Layer | Library | Description |
|-------|---------|-------------| |----------|------------------|---------------------------------------------------|
| rs232 | `librs232.a` | ISR-driven UART driver with FIFO support, automatic UART type detection (8250 through 16750), configurable baud rate | | rs232 | `librs232.a` | ISR-driven UART driver, ring buffers, flow control |
| packet | `libpacket.a` | HDLC framing with byte stuffing, CRC-16 integrity checks, Go-Back-N sliding window for reliable delivery, 255-byte max payload | | packet | `libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
| security | `libsecurity.a` | 1024-bit Diffie-Hellman key exchange (RFC 2409 Group 2), XTEA-CTR stream cipher, XTEA-CTR DRBG random number generator | | security | `libsecurity.a` | 1024-bit DH, XTEA-CTR cipher, DRBG RNG |
| seclink | `libseclink.a` | Convenience wrapper: multiplexed channels (0--127), per-packet encryption flag, bulk send helper | | seclink | `libseclink.a` | Channel multiplexing, per-packet encryption |
| proxy | `secproxy` | Linux-side bridge: 86Box emulated serial port <-> secLink <-> telnet BBS; socket shim replaces rs232 API | | proxy | `secproxy` | Linux bridge: 86Box serial <-> telnet BBS |
## Third-Party Dependencies ## Third-Party Dependencies
All third-party code is vendored as single-header libraries with no All third-party code is vendored as single-header libraries:
external dependencies:
| Library | Location | Purpose | | Library | Location | Purpose |
|---------|----------|---------| |-------------------|--------------------|---------------------------------|
| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) | | stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) |
| stb_image_write.h | `core/thirdparty/` | Image writing (PNG export for screenshots) | | stb_image_write.h | `core/thirdparty/` | PNG export for screenshots |
| stb_ds.h | `tasks/thirdparty/` | Dynamic array and hash map (used by task manager) | | stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps |
stb_ds implementation is compiled into dvx.exe (the loader) and
exported via `dlregsym` to all DXE modules.
## Component Documentation ## Target Hardware
Each component directory has its own README with detailed API reference: * CPU: 486 baseline, Pentium-optimized paths where significant
* Video: VESA VBE 2.0+ with LFB (no bank switching)
- [`core/README.md`](core/README.md) -- Core GUI library architecture and API * Platform: 86Box emulator (trusted reference), real DOS hardware
- [`tasks/README.md`](tasks/README.md) -- Task switcher API * Resolutions: 640x480, 800x600, 1024x768 at 8/15/16/32 bpp
- [`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
- [`security/README.md`](security/README.md) -- Cryptographic primitives
- [`seclink/README.md`](seclink/README.md) -- Secure serial link
- [`proxy/README.md`](proxy/README.md) -- Linux SecLink proxy
- [`termdemo/README.md`](termdemo/README.md) -- Encrypted terminal demo

View file

@ -1,443 +1,178 @@
# DVX Shell Applications # DVX Shell Applications
DXE3 application modules for the DVX Shell. Each app is a `.app` file (DXE3 DXE3 application modules for the DVX Shell. Each app is a `.app` file
shared object format) placed in a subdirectory under `apps/`. The Program (DXE3 shared object) in a subdirectory under `apps/`. The Program
Manager scans this directory recursively at startup and displays all discovered Manager scans this directory at startup and displays all discovered
apps in a launcher grid. apps as launchable icons.
## App Contract
Every DXE app must export two symbols and may optionally export a third: ## DXE App Contract
```c Every app exports two symbols:
// Required: app metadata
AppDescriptorT appDescriptor = {
.name = "My App",
.hasMainLoop = false,
.multiInstance = false,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
// Required: entry point -- called once by the shell after dlopen * `appDescriptor` (`AppDescriptorT`) -- name, hasMainLoop, multiInstance, stackSize, priority
int32_t appMain(DxeAppContextT *ctx); * `appMain` (`int appMain(DxeAppContextT *)`) -- entry point
// Optional: graceful shutdown hook -- called before force-kill Optional: `appShutdown` (`void appShutdown(void)`) -- called during
void appShutdown(void); graceful shutdown.
```
`appMain` receives a `DxeAppContextT` with: ### Callback-Only Apps (hasMainLoop = false)
| Field | Type | Description | `appMain()` creates windows, registers callbacks, and returns 0. The
|-------|------|-------------| shell drives everything through event callbacks. No dedicated task or
| `shellCtx` | `AppContextT *` | The shell's GUI context -- pass to all `dvx*`/`wgt*` calls | stack is allocated. Lifecycle ends when the last window closes.
| `appId` | `int32_t` | This app's unique ID (1-based slot index; 0 = shell) |
| `appDir` | `char[260]` | Directory containing the `.app` file (for relative resource paths) |
| `configDir` | `char[260]` | Writable config directory (`CONFIG/<apppath>/`) |
Return 0 from `appMain` on success, non-zero on failure (shell will unload). ### Main-Loop Apps (hasMainLoop = true)
### AppDescriptorT Fields A cooperative task is created for the app. `appMain()` runs its own
loop calling `tsYield()` to share CPU. Lifecycle ends when `appMain()`
returns.
| Field | Type | Description |
|-------|------|-------------|
| `name` | `char[64]` | Display name shown in Task Manager and title bars |
| `hasMainLoop` | `bool` | `false` = callback-only (runs in task 0); `true` = gets own cooperative task |
| `multiInstance` | `bool` | `true` = allow multiple instances via temp file copy |
| `stackSize` | `int32_t` | Task stack in bytes; `SHELL_STACK_DEFAULT` (0) for the default |
| `priority` | `int32_t` | `TS_PRIORITY_LOW`, `TS_PRIORITY_NORMAL`, or `TS_PRIORITY_HIGH` |
## Building ## Applications
### Program Manager (progman)
| | |
|---|---|
| File | `apps/progman/progman.app` |
| Type | Callback-only |
| Multi-instance | No |
The desktop app. Scans `apps/` recursively for `.app` files and
displays them in a grid. Double-click or Enter launches an app.
Includes a Help menu with system information dialog and About box.
Registers with `shellRegisterDesktopUpdate()` to refresh when apps
are loaded, crash, or terminate.
Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetLabel.h`,
`widgetStatusBar.h`, `widgetTextInput.h`.
### Notepad (notepad)
| | |
|---|---|
| File | `apps/notepad/notepad.app` |
| Type | Callback-only |
| Multi-instance | Yes |
Text editor with File menu (New, Open, Save, Save As) and Edit menu
(Cut, Copy, Paste, Select All). Uses the TextArea widget for all
editing. 32KB text buffer limit. Tracks dirty state for save prompts.
Keyboard accelerators for all menu commands.
Widget headers used: `widgetTextInput.h`.
### Clock (clock)
| | |
|---|---|
| File | `apps/clock/clock.app` |
| Type | Main-loop |
| Multi-instance | Yes |
Digital clock display showing time and date. Demonstrates the main-loop
app pattern -- polls the system clock every second and invalidates the
window to trigger a repaint. Uses raw `onPaint` callbacks (no widgets)
to draw centered text.
Widget headers used: none (raw paint callbacks).
### DVX Demo (dvxdemo)
| | |
|---|---|
| File | `apps/dvxdemo/dvxdemo.app` |
| Type | Callback-only |
| Multi-instance | No |
Comprehensive widget system showcase. Opens multiple windows
demonstrating 31 of the 32 widget types (all except Timer):
* Main window: raw paint callbacks (gradients, patterns, text)
* Widget demo: form widgets (TextInput, Checkbox, Radio, ListBox)
* Controls window: TabControl with tabs for advanced widgets
(Dropdown, ProgressBar, Slider, Spinner, TreeView, ListView,
ScrollPane, Toolbar, Canvas, Splitter, Image)
* Terminal window: AnsiTerm widget
Widget headers used: 25 of the 26 widget headers (all except widgetTimer.h).
Resources: `logo.bmp`, `new.bmp`, `open.bmp`, `sample.bmp`, `save.bmp`.
### Control Panel (cpanel)
| | |
|---|---|
| File | `apps/cpanel/cpanel.app` |
| Type | Callback-only |
| Multi-instance | No |
System configuration with four tabs:
* **Mouse** -- scroll direction, double-click speed, acceleration
* **Colors** -- all 20 system colors with live preview, theme load/save
* **Desktop** -- wallpaper image selection and display mode
* **Video** -- resolution and color depth switching
Changes preview live. OK saves to `DVX.INI`, Cancel reverts to the
state captured when the control panel was opened.
Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetCanvas.h`,
`widgetDropdown.h`, `widgetLabel.h`, `widgetListBox.h`, `widgetSlider.h`,
`widgetSpacer.h`, `widgetTabControl.h`.
### Image Viewer (imgview)
| | |
|---|---|
| File | `apps/imgview/imgview.app` |
| Type | Callback-only |
| Multi-instance | Yes |
Displays BMP, PNG, JPEG, and GIF images. The image is scaled to fit
the window while preserving aspect ratio. Resize the window to zoom.
Open files via the File menu or by launching with Run in the Task
Manager.
Widget headers used: none (raw paint callbacks with `dvxLoadImage()`).
## Build
``` ```
make # builds all .app files into ../bin/apps/<name>/ make # builds all 6 app DXE modules
make clean # removes objects and binaries make clean # removes objects and app files
``` ```
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 compiles to a single `.o`, then is packaged via `dxe3gen`
into a `.app` DXE exporting `appDescriptor` and `appMain` (plus
`appShutdown` for clock).
Each app is compiled to an object file with the DJGPP cross-compiler, then Output goes to `bin/apps/<name>/<name>.app`.
packaged into a `.app` via `dxe3gen`:
```makefile
$(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
```
- `-E _appDescriptor` and `-E _appMain` export the required symbols (COFF ## Files
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 at
`dlopen` time from the loader and all `RTLD_GLOBAL` modules (libdvx,
libtasks, widgets, and the shell's wrapper overrides).
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
``` ```
apps/ apps/
Makefile -- build rules for all apps Makefile top-level build for all apps
README.md -- this file
progman/ progman/
progman.c -- Program Manager (desktop shell) progman.c Program Manager
notepad/ notepad/
notepad.c -- text editor notepad.c text editor
clock/ clock/
clock.c -- digital clock clock.c digital clock
cpanel/
cpanel.c -- Control Panel (system settings)
imgview/
imgview.c -- image viewer
dvxdemo/ dvxdemo/
dvxdemo.c -- widget showcase dvxdemo.c widget demo
*.bmp -- toolbar icons and sample images logo.bmp DVX logo bitmap
new.bmp toolbar icon
open.bmp toolbar icon
sample.bmp sample image
save.bmp toolbar icon
cpanel/
cpanel.c control panel
imgview/
imgview.c image viewer
``` ```
Each app lives in its own subdirectory. The subdirectory name becomes the
output path under `bin/apps/` (e.g., `bin/apps/progman/progman.app`).
## Bundled Applications
### Program Manager (`progman/`)
The desktop shell and default app launcher. Loaded automatically by the shell
at startup as the "desktop app" -- closing it prompts to exit the entire DVX
Shell.
- **App launcher grid**: scans `apps/` recursively for `.app` files (skipping
itself), displays them as a grid of buttons. Click or double-click to
launch.
- **Menu bar**: File (Run..., Exit Shell), Options (Minimize on Run), Window
(Cascade, Tile, Tile H, Tile V), Help (About, System Information, Task
Manager). The Task Manager menu item opens the shell-level Task Manager
via `shellTaskMgrOpen()`.
- **Minimize on Run**: optional preference -- when enabled, Program Manager
minimizes itself after launching an app, getting out of the way.
- **Status bar**: shows the count of running applications, updated in
real-time via `shellRegisterDesktopUpdate()`.
- **System Information**: opens a read-only text area showing CPU, memory,
drive, and video details gathered by the platform layer.
- **Preferences**: saved to `CONFIG/PROGMAN/progman.ini` via the standard
prefs system. Currently stores the "Minimize on Run" setting.
Type: callback-only. Single instance.
### Notepad (`notepad/`)
A basic text editor with file I/O and dirty-change tracking.
- **TextArea widget**: handles all editing -- keyboard input, cursor movement,
selection, scrolling, word wrap, copy/paste, undo (Ctrl+Z). Notepad only
wires up menus and file I/O around it.
- **File menu**: New, Open, Save, Save As, Exit.
- **Edit menu**: Cut, Copy, Paste, Select All (Ctrl+X/C/V/A).
- **CR/LF handling**: files are opened in binary mode to avoid DJGPP's
translation. `platformStripLineEndings()` normalizes on load;
`platformLineEnding()` writes platform-native line endings on save.
- **Dirty tracking**: uses djb2-xor hash of the text content. Cheap detection
without storing a full shadow buffer. Prompts "Save changes?" on close/new/
open if dirty.
- **32 KB text buffer**: keeps memory bounded on DOS. Larger files are
silently truncated on load.
- **Multi-instance**: each instance gets its own DXE code+data via temp file
copy. Window positions are offset +20px so instances cascade naturally.
Type: callback-only. Multi-instance.
### Clock (`clock/`)
A digital clock displaying 12-hour time and date, centered in a small
non-resizable window.
- **Main-loop app**: polls `time()` each iteration, repaints when the second
changes, then calls `tsYield()`. CPU usage is near zero because the check
is cheap and yields immediately when nothing changes.
- **Raw paint callback**: renders directly into the window's content buffer
using `rectFill` and `drawText` -- no widget tree. Demonstrates the
lower-level alternative to the widget system for custom rendering.
- **Shutdown hook**: exports `appShutdown()` so the shell can signal a clean
exit when force-killing via Task Manager or during shell shutdown.
- **Low priority**: uses `TS_PRIORITY_LOW` since clock updates are cosmetic
and should never preempt interactive apps.
Type: main-loop. Multi-instance.
### Control Panel (`cpanel/`)
System configuration with four tabs, all changes previewing live. OK saves to
`CONFIG/DVX.INI`; Cancel reverts to the state captured when the panel opened.
**Mouse tab:**
- Scroll wheel direction (Normal / Reversed) via dropdown.
- Double-click speed (200-900 ms) via slider with numeric label and test
button.
- Mouse acceleration (Off / Low / Medium / High) via dropdown.
- All changes apply immediately via `dvxSetMouseConfig()`.
**Colors tab:**
- List of all 20 system colors (`ColorCountE` entries from `dvxColorName`).
- RGB sliders (0-255) for the selected color, with numeric labels and a
canvas swatch preview.
- Changes apply live via `dvxSetColor()` -- the entire desktop updates in
real time.
- **Themes**: dropdown of `.thm` files from `CONFIG/THEMES/`, with Apply,
Load..., Save As..., and Reset buttons. Themes are loaded/saved via
`dvxLoadTheme()`/`dvxSaveTheme()`.
- Reset restores the compiled-in default color scheme.
**Desktop tab:**
- Wallpaper list: scans `CONFIG/WPAPER/` for BMP/JPG/PNG files.
- Apply, Browse..., and Clear buttons.
- Mode dropdown: Stretch, Tile, Center. Changes apply live via
`dvxSetWallpaperMode()`.
**Video tab:**
- List of all enumerated VESA modes with human-readable depth names (e.g.,
"800x600 65 thousand colors").
- Apply Mode button or double-click to switch.
- **10-second confirmation dialog**: after switching, a modal "Keep this
mode?" dialog counts down. If the user clicks Yes, the mode is kept.
Clicking No, closing the dialog, or letting the timer expire reverts to
the previous mode. Prevents being stuck in an unsupported mode.
Type: callback-only. Single instance.
### Image Viewer (`imgview/`)
Displays BMP, PNG, JPG, and GIF images loaded via stb_image.
- **Bilinear scaling**: images are scaled to fit the window while preserving
aspect ratio. The scaler converts from RGB to the native pixel format
(8/16/32 bpp) during the scale pass.
- **Deferred resize**: during a window drag-resize, the old scaled image is
shown. The expensive bilinear rescale only runs after the drag ends
(`sAc->stack.resizeWindow < 0`), avoiding per-frame scaling lag.
- **Responsive scaling**: for large images, `dvxUpdate()` is called every 32
scanlines during the scale loop to keep the UI responsive.
- **File menu**: Open (Ctrl+O), Close. Keyboard accelerator table registered.
- **Multi-instance**: multiple viewers can be open simultaneously, each with
its own image.
- **Raw paint callback**: renders directly into the content buffer with a dark
gray background and centered blit of the scaled image.
Type: callback-only. Multi-instance.
### DVX Demo (`dvxdemo/`)
A comprehensive widget showcase and integration test. Opens several windows
demonstrating the full DVX widget system:
- **Main window**: three raw-paint windows -- text rendering with full menu
bar/accelerators/context menu, vertical gradient, and checkerboard pattern
with scrollbars.
- **Widget Demo window**: form pattern with labeled inputs (text, password,
masked phone number), checkboxes, radio groups, single and multi-select
list boxes with context menus and drag reorder.
- **Advanced Widgets window**: nine tab pages covering every widget type --
dropdown, combo box, progress bar (horizontal and vertical), slider,
spinner, tree view (with drag reorder), multi-column list view (with
multi-select and drag reorder), scroll pane, toolbar (with image buttons
and text fallback), image from file, text area, canvas (with mouse
drawing), splitter (nested horizontal+vertical for explorer-style layout),
and a disabled-state comparison of all widget types.
- **ANSI Terminal window**: terminal emulator widget with sample output
demonstrating bold, reverse, blink, all 16 colors, background colors,
CP437 box-drawing characters, and 500-line scrollback.
Type: callback-only. Single instance.
## App Preferences
Apps that need persistent settings use the shell's config directory system:
```c
// In appMain:
shellEnsureConfigDir(ctx); // create CONFIG/<apppath>/ if needed
char path[260];
shellConfigPath(ctx, "settings.ini", path, sizeof(path));
prefsLoad(path);
// Read/write:
int32_t val = prefsGetInt("section", "key", defaultVal);
prefsSetInt("section", "key", newVal);
prefsSave();
```
The preferences system handles INI file format with `[section]` headers and
`key=value` pairs. Missing files or keys silently return defaults.
## Event Model
DVX apps receive events through two mechanisms:
**Widget callbacks** (high-level):
- `onClick`, `onDblClick`, `onChange` on individual widgets.
- The widget system handles focus, tab order, mouse hit testing, keyboard
dispatch, and repainting automatically.
- Used by most apps for standard UI (buttons, inputs, lists, sliders, etc.).
**Window callbacks** (low-level):
- `onPaint(win, dirtyRect)` -- render directly into the window's content
buffer. Used by Clock, Image Viewer, and the DVX Demo paint windows.
- `onClose(win)` -- window close requested (close gadget, Alt+F4).
- `onResize(win, contentW, contentH)` -- window was resized.
- `onMenu(win, menuId)` -- menu item selected or keyboard accelerator fired.
Both mechanisms can be mixed in the same app. For example, DVX Demo uses
widgets in some windows and raw paint callbacks in others.
## Writing a New App
### Minimal callback-only app
```c
#include "dvxApp.h"
#include "dvxWidget.h"
#include "shellApp.h"
AppDescriptorT appDescriptor = {
.name = "My App",
.hasMainLoop = false,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
static void onClose(WindowT *win) {
dvxDestroyWindow(sCtx->shellCtx, win);
sWin = NULL;
}
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
AppContextT *ac = ctx->shellCtx;
sWin = dvxCreateWindow(ac, "My App", 100, 100, 300, 200, true);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
WidgetT *root = wgtInitWindow(ac, sWin);
wgtLabel(root, "Hello, DVX!");
wgtInvalidate(root);
return 0;
}
```
### Minimal main-loop app
```c
#include "dvxApp.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "shellApp.h"
#include "taskswitch.h"
AppDescriptorT appDescriptor = {
.name = "My Task App",
.hasMainLoop = true,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
static bool sQuit = false;
static void onClose(WindowT *win) {
(void)win;
sQuit = true;
}
void appShutdown(void) {
sQuit = true;
}
int32_t appMain(DxeAppContextT *ctx) {
AppContextT *ac = ctx->shellCtx;
WindowT *win = dvxCreateWindow(ac, "My Task App", 100, 100, 200, 100, false);
if (!win) {
return -1;
}
win->onClose = onClose;
while (!sQuit) {
// Do work, update window content
tsYield();
}
dvxDestroyWindow(ac, win);
return 0;
}
```
### Adding to the build
Add your app directory and source to `apps/Makefile`:
```makefile
APPS = ... myapp
myapp: $(BINDIR)/myapp/myapp.app
$(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(OBJDIR)/myapp.o: myapp/myapp.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(BINDIR)/myapp:
mkdir -p $(BINDIR)/myapp
```
Add `-E _appShutdown` to the `dxe3gen` line if the app exports a shutdown
hook.
## App Guidelines
- Include `shellApp.h` for `AppDescriptorT`, `DxeAppContextT`, and
`SHELL_STACK_DEFAULT`.
- Use `ctx->shellCtx` (the `AppContextT *`) for all DVX API calls.
- Callback-only apps must destroy their own windows in `onClose` via
`dvxDestroyWindow()`. The shell detects the last window closing and reaps
the app automatically.
- Main-loop apps must call `tsYield()` regularly. A task that never yields
blocks the entire system (cooperative multitasking -- no preemption).
- Export `appShutdown()` for main-loop apps so the shell can signal a clean
exit on force-kill or shell shutdown.
- Use file-scoped `static` variables for app state. Each DXE has its own
data segment, so there is no collision between apps even with identical
variable names.
- Set `multiInstance = true` only if the app can safely run multiple copies
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 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.
Never write to the app's own directory -- use `CONFIG/<apppath>/` instead.

View file

@ -2,92 +2,152 @@
Runtime configuration, theme files, wallpaper images, and module Runtime configuration, theme files, wallpaper images, and module
dependency files. These are copied into `bin/config/` (INI, themes, dependency files. These are copied into `bin/config/` (INI, themes,
wallpapers) or `bin/libs/` (dep files) during the build. wallpapers) or `bin/libs/` and `bin/widgets/` (dep files) during the
build. Text files are converted to DOS line endings (CR+LF) via sed.
## Files ## Files
| File | Destination | Purpose | | Source File | Build Output | Description |
|------|-------------|---------| |-------------|-------------|-------------|
| `dvx.ini` | `bin/config/dvx.ini` | Main configuration (video, mouse, colors, desktop) | | `dvx.ini` | `bin/config/dvx.ini` | System configuration |
| `themes/*.thm` | `bin/config/themes/` | Color theme files (INI format with `[colors]` section) | | `themes/cde.thm` | `bin/config/themes/cde.thm` | CDE color theme |
| `wpaper/*.jpg` | `bin/config/wpaper/` | Bundled wallpaper images | | `themes/geos.thm` | `bin/config/themes/geos.thm` | GEOS Ensemble theme |
| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx.lib dependency list | | `themes/win31.thm` | `bin/config/themes/win31.thm` | Windows 3.1 theme |
| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell.lib dependency list | | `wpaper/blueglow.jpg` | `bin/config/wpaper/blueglow.jpg` | Wallpaper image |
| `wpaper/swoop.jpg` | `bin/config/wpaper/swoop.jpg` | Wallpaper image |
| `wpaper/triangle.jpg` | `bin/config/wpaper/triangle.jpg` | Wallpaper image |
| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx dependency file |
| `texthelp.dep` | `bin/libs/texthelp.dep` | texthelp dependency file |
| `listhelp.dep` | `bin/libs/listhelp.dep` | listhelp dependency file |
| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell dependency file |
| `textinpt.dep` | `bin/widgets/textinpt.dep` | TextInput widget dep file |
| `combobox.dep` | `bin/widgets/combobox.dep` | ComboBox widget dep file |
| `spinner.dep` | `bin/widgets/spinner.dep` | Spinner widget dep file |
| `terminal.dep` | `bin/widgets/terminal.dep` | AnsiTerm widget dep file |
| `dropdown.dep` | `bin/widgets/dropdown.dep` | Dropdown widget dep file |
| `listbox.dep` | `bin/widgets/listbox.dep` | ListBox widget dep file |
| `listview.dep` | `bin/widgets/listview.dep` | ListView widget dep file |
| `treeview.dep` | `bin/widgets/treeview.dep` | TreeView widget dep file |
## DVX.INI Format ## dvx.ini Format
Standard `[section]` / `key = value` INI format. Converted to DOS Standard INI format with `[section]` headers and `key = value` pairs.
line endings (`\r\n`) during the build. Comments start with `;`. The shell loads this at startup via
`prefsLoad("CONFIG/DVX.INI")`.
```ini ### [video] Section
[video]
width = 640
height = 480
bpp = 16
[mouse] | Key | Default | Description |
wheel = normal # normal | reversed |-----|---------|-------------|
doubleclick = 500 # milliseconds (200-900) | `width` | 640 | Requested horizontal resolution |
acceleration = medium # off | low | medium | high | `height` | 480 | Requested vertical resolution |
| `bpp` | 16 | Preferred color depth (8, 15, 16, 24, 32) |
[colors] The system picks the closest available VESA mode.
desktop = 0,128,128
windowFace = 192,192,192
# ... 20 color keys total (R,G,B triplets 0-255)
[desktop] ### [mouse] Section
wallpaper = CONFIG\WPAPER\SWOOP.JPG
mode = stretch # stretch | tile | center
```
| Key | Default | Values | Description |
|-----|---------|--------|-------------|
| `wheel` | normal | normal, reversed | Scroll wheel direction |
| `doubleclick` | 500 | 200-900 | Double-click speed (ms) |
| `acceleration` | medium | off, low, medium, high | Mouse acceleration |
## Dependency Files (.dep) ### [shell] Section
Plain text, one dependency per line. Each line is the base name | Key | Default | Description |
(without extension) of a module that must be loaded before this one. |-----|---------|-------------|
Lines starting with `#` are comments. Empty lines are ignored. | `desktop` | apps/progman/progman.app | Path to the desktop app loaded at startup |
Example -- `dvxshell.dep`: ### [colors] Section
```
# Core libraries
libtasks
libdvx
# Widget modules used by the shell All 20 system colors as `R,G,B` triplets (0-255). Missing keys use
box compiled-in defaults.
button
checkbox
dropdown
label
listbox
listview
radio
separator
spacer
statbar
textinpt
```
Dep files are read by the loader during startup to determine the | Key | Description |
correct load order via topological sort. |-----|-------------|
| `desktop` | Desktop background |
| `windowFace` | Window frame and widget face |
| `windowHighlight` | Bevel highlight (top/left) |
| `windowShadow` | Bevel shadow (bottom/right) |
| `activeTitleBg` | Focused window title bar background |
| `activeTitleFg` | Focused window title bar text |
| `inactiveTitleBg` | Unfocused window title bar background |
| `inactiveTitleFg` | Unfocused window title bar text |
| `contentBg` | Content area background |
| `contentFg` | Content area text |
| `menuBg` | Menu background |
| `menuFg` | Menu text |
| `menuHighlightBg` | Menu selection background |
| `menuHighlightFg` | Menu selection text |
| `buttonFace` | Button face color |
| `scrollbarBg` | Scrollbar background |
| `scrollbarFg` | Scrollbar foreground |
| `scrollbarTrough` | Scrollbar track color |
| `cursorColor` | Mouse cursor foreground |
| `cursorOutline` | Mouse cursor outline |
### [desktop] Section
| Key | Default | Values | Description |
|-----|---------|--------|-------------|
| `wallpaper` | (none) | file path | Path to wallpaper image |
| `mode` | stretch | stretch, tile, center | Wallpaper display mode |
## Theme Files (.thm) ## Theme Files (.thm)
INI format with a `[colors]` section containing the same 20 color Theme files use the same INI format as dvx.ini but contain only a
keys as `dvx.ini`. Loaded via the Control Panel app or `[colors]` section with the 20 color keys. The Control Panel can
`dvxLoadTheme()`. load and save themes via `dvxLoadTheme()` / `dvxSaveTheme()`.
```ini Bundled themes:
[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` | File | Description |
(Windows 3.1), `cde.thm` (CDE/Motif). |------|-------------|
| `cde.thm` | CDE (Common Desktop Environment) -- warm tan/blue palette |
| `geos.thm` | GEOS Ensemble -- cyan/grey palette |
| `win31.thm` | Windows 3.1 -- grey/navy palette |
## Dependency Files (.dep)
Plain text files listing module base names that must be loaded before
this module. One name per line. Empty lines and `#` comments are
ignored. Names are case-insensitive.
### Library Dependencies
| Dep File | Module | Dependencies |
|----------|--------|--------------|
| `libdvx.dep` | libdvx.lib | libtasks |
| `texthelp.dep` | texthelp.lib | libtasks, libdvx |
| `listhelp.dep` | listhelp.lib | libtasks, libdvx |
| `dvxshell.dep` | dvxshell.lib | libtasks, libdvx, texthelp, listhelp |
### Widget Dependencies
| Dep File | Widget | Dependencies |
|----------|--------|--------------|
| `textinpt.dep` | textinpt.wgt | texthelp |
| `combobox.dep` | combobox.wgt | texthelp, listhelp |
| `spinner.dep` | spinner.wgt | texthelp |
| `terminal.dep` | terminal.wgt | texthelp |
| `dropdown.dep` | dropdown.wgt | listhelp |
| `listbox.dep` | listbox.wgt | listhelp |
| `listview.dep` | listview.wgt | listhelp |
| `treeview.dep` | treeview.wgt | listhelp |
## Wallpaper Images
| File | Description |
|------|-------------|
| `wpaper/blueglow.jpg` | Blue gradient glow |
| `wpaper/swoop.jpg` | Curved swoosh pattern |
| `wpaper/triangle.jpg` | Geometric triangle pattern |
Wallpapers support BMP, PNG, JPEG, and GIF formats. They are
pre-rendered to screen dimensions in native pixel format at load time.

File diff suppressed because it is too large Load diff

100
listhelp/README.md Normal file
View file

@ -0,0 +1,100 @@
# listhelp -- Shared List/Dropdown Helper Library
Shared infrastructure for list and dropdown widgets, built as
`listhelp.lib` (DXE3 module). Provides dropdown arrow drawing, item
measurement, keyboard navigation, popup rectangle calculation, and
popup list painting.
Used by: Dropdown, ComboBox, ListBox, ListView, TreeView.
## API Reference
### Dropdown Arrow Glyph
```c
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops,
int32_t centerX, int32_t centerY, uint32_t color);
```
Draws the small downward-pointing triangle glyph used on dropdown
buttons.
### Item Measurement
```c
int32_t widgetMaxItemLen(const char **items, int32_t count);
```
Scans an array of item strings and returns the length of the longest
one. Used to size dropdown popups and list columns to fit their
content.
### Keyboard Navigation
```c
int32_t widgetNavigateIndex(int32_t key, int32_t current,
int32_t count, int32_t pageSize);
```
Maps arrow key presses to index changes for list navigation. Handles
Up, Down, Home, End, PageUp, and PageDown. Returns the new selected
index, clamped to valid range.
### Popup Rectangle Calculation
```c
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font,
int32_t contentH, int32_t *popX, int32_t *popY,
int32_t *popW, int32_t *popH);
```
Computes the screen rectangle for a dropdown popup overlay. Positions
the popup below the widget (or above if there is not enough room
below). Limits height to `DROPDOWN_MAX_VISIBLE` items.
### Popup List Painting
```c
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops,
const BitmapFontT *font, const ColorSchemeT *colors,
int32_t popX, int32_t popY, int32_t popW, int32_t popH,
const char **items, int32_t itemCount,
int32_t hoverIdx, int32_t scrollPos);
```
Renders the popup overlay list with items, selection highlight, scroll
position, and beveled border. Used by Dropdown and ComboBox for their
popup overlays.
### Constants
| Name | Value | Description |
|------|-------|-------------|
| `DROPDOWN_BTN_WIDTH` | 16 | Width of dropdown arrow button area |
| `DROPDOWN_MAX_VISIBLE` | 8 | Maximum visible items in popup list |
## Exported Symbols
Matches prefixes: `_widgetDraw*`, `_widgetDropdown*`, `_widgetMax*`,
`_widgetNavigate*`, `_widgetPaint*`.
## Files
| File | Description |
|------|-------------|
| `listHelp.h` | Public API header |
| `listHelp.c` | Complete implementation |
| `Makefile` | Builds `bin/libs/listhelp.lib` + dep file |
## Build
```
make # builds bin/libs/listhelp.lib + listhelp.dep
make clean # removes objects, library, and dep file
```
Depends on: `libtasks.lib`, `libdvx.lib` (via listhelp.dep).

View file

@ -1,73 +1,110 @@
# DVX Loader # DVX Loader
Bootstrap loader for the DVX desktop environment. Builds as Bootstrap loader for the DVX desktop environment. Builds as `dvx.exe`
`dvx.exe` -- the only native executable in the system. Everything -- the only native executable in the system. Everything else is a
else is a dynamically loaded DXE module. dynamically loaded DXE3 module.
## What It Does ## What It Does
1. Changes to the executable's directory so relative paths resolve 1. Changes working directory to the directory containing `dvx.exe`
2. Calls `platformRegisterDxeExports()` to register platform functions 2. Truncates `dvx.log` and initializes logging
and C runtime symbols (libc, libm, libgcc, DJGPP internals) with 3. Calls `platformInit()` to suppress Ctrl+C and install signal handlers
the DXE3 loader via `dlregsym()` 4. Calls `platformRegisterDxeExports()` to register platform and C
3. Recursively scans `LIBS/` for `*.lib` modules and `WIDGETS/` for runtime symbols (libc, libm, libgcc) for DXE module resolution
`*.wgt` modules 5. Scans and loads all modules in two phases (see below)
4. Reads `.dep` files to build a dependency graph 6. Finds `shellMain()` via `dlsym` across loaded modules
5. Topologically sorts all modules and loads them in order with 7. Calls `shellMain()` -- the shell takes over from here
`RTLD_GLOBAL` (each module's exports become available to 8. On return, closes all module handles in reverse load order
subsequent modules)
6. Calls `wgtRegister()` on any module that exports it (widget
registration) ## Two-Phase Module Loading
7. Finds `shellMain()` across all loaded modules and calls it
8. On return, closes all modules in reverse order ### Phase 1: Libraries (libs/*.lib)
Scans the `LIBS/` directory for `.lib` files. Each module may have a
`.dep` file (same base name, `.dep` extension) listing base names of
modules that must load first. The loader resolves the dependency graph
and loads in topological order.
Load order (via dep files):
```
libtasks.lib (no deps)
libdvx.lib (deps: libtasks)
texthelp.lib (deps: libtasks, libdvx)
listhelp.lib (deps: libtasks, libdvx)
dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp)
```
### Phase 2: Widgets (widgets/*.wgt)
Scans the `WIDGETS/` directory for `.wgt` files. Widget modules may
also have `.dep` files (e.g., `textinpt.dep` lists `texthelp`).
Loaded in topological order, same as libs.
After loading each module, the loader checks for a `wgtRegister`
export. If present, it is called immediately so the widget registers
its class(es) and API with the core.
## Dependency File Format
Plain text, one dependency base name per line. Empty lines and lines
starting with `#` are ignored. Names are case-insensitive.
Example (`combobox.dep`):
```
texthelp
listhelp
```
## Hosted Components
### dvxLog()
The global logging function is defined in `loaderMain.c` and exported
to all DXE modules. It appends a line to `dvx.log`, opening and
closing the file per write so it is never held open.
```c
void dvxLog(const char *fmt, ...);
```
### stb_ds
The `STB_DS_IMPLEMENTATION` is compiled into the loader. stb_ds
functions are exported to all DXE modules via the platform export
table.
### Platform Layer
`dvxPlatformDos.c` is compiled into the loader (not into libdvx.lib).
All platform functions are exported to DXE modules. This includes:
* Video: VESA VBE init, LFB mapping, mode enumeration
* Input: INT 33h mouse, INT 16h keyboard, CuteMouse wheel API
* Spans: `rep stosl`/`rep movsd` asm inner loops (8/16/32 bpp)
* DXE: symbol registration, symbol overrides
* Crash: signal handler installation, register dump logging
* System: memory info, directory creation, path utilities
## Files ## Files
| File | Purpose | | File | Description |
|------|---------| |------|-------------|
| `loaderMain.c` | Entry point, directory scanning, dependency resolution, module loading | | `loaderMain.c` | Entry point, module scanner, dependency resolver, logger |
| `Makefile` | Cross-compilation rules; links `dvxPlatformDos.c` directly | | `Makefile` | Builds `bin/dvx.exe` |
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 ## Build
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 # builds bin/dvx.exe
make clean make clean # removes objects and binary
``` ```
The Makefile compiles `loaderMain.c` and links it with The loader links `loaderMain.c` + `dvxPlatformDos.c` into a native
`dvxPlatformDos.o` (compiled from `../core/platform/dvxPlatformDos.c`) DJGPP executable. The CWSDPMI stub is prepended via exe2coff +
and `-lm`. The result goes through `exe2coff` + `CWSDSTUB` to CWSDSTUB.EXE for standalone execution.
produce a DOS executable.

View file

@ -61,7 +61,6 @@ $(WPAPERDIR)/%.jpg: ../config/wpaper/%.jpg | $(WPAPERDIR)
# Dependencies # Dependencies
$(OBJDIR)/shellMain.o: shellMain.c shellApp.h $(OBJDIR)/shellMain.o: shellMain.c shellApp.h
$(OBJDIR)/shellApp.o: shellApp.c shellApp.h $(OBJDIR)/shellApp.o: shellApp.c shellApp.h
$(OBJDIR)/shellExport.o: shellExport.c shellApp.h
$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h $(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h
$(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h $(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h

View file

@ -1,387 +1,162 @@
# DVX Shell # DVX Shell (dvxshell.lib)
The DVX Shell (`dvxshell.lib`) is a DXE module loaded by the DVX loader at The DVX Shell is a DXE3 module loaded by the DVX loader at startup.
startup. It initializes the GUI subsystem, loads DXE3 application modules at It initializes the GUI subsystem, loads DXE3 application modules on
runtime, runs the cooperative main loop, and provides crash recovery so a demand, runs the cooperative main loop, and provides crash recovery so
faulting app does not take down the entire system. a faulting app does not bring 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 ## Entry Point
| File | Purpose | The loader finds and calls `shellMain()` after all libs and widgets
|------|---------| are loaded. `shellMain()`:
| `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 |
| `shellTaskMgr.h` | `shellTaskMgrOpen()`, `shellTaskMgrRefresh()` |
| `Makefile` | Cross-compile rules, produces `dvxshell.lib` and `dvxshell.dep` |
## Building 1. Loads preferences from `CONFIG/DVX.INI`
2. Initializes the GUI via `dvxInit()` with configured video mode
3. Applies saved mouse, color, and wallpaper settings from INI
4. Initializes the cooperative task system (`tsInit()`)
5. Sets shell task (task 0) to `TS_PRIORITY_HIGH`
6. Gathers system information via the platform layer
7. Initializes the app slot table
8. Registers idle callback, Ctrl+Esc handler, and title change handler
9. Loads the desktop app (default: `apps/progman/progman.app`)
10. Installs the crash handler (after init, so init crashes are fatal)
11. Enters the main loop
```
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
`shellMain()` in `shellMain.c` is called by the loader after all dependencies
are loaded. It performs initialization in this order:
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).
2. **Load preferences** -- `prefsLoad("CONFIG/DVX.INI")`. Missing file or
keys silently fall back to compiled-in defaults.
3. **dvxInit** -- initialize VESA video (LFB), backbuffer, compositor, window
manager, font, cursor, input subsystems. Reads video width/height/bpp from
preferences (default 640x480x16).
4. **Mouse config** -- read wheel direction, double-click speed, acceleration
from `[mouse]` section and call `dvxSetMouseConfig()`.
5. **Color scheme** -- read `[colors]` section (20 RGB triplets), apply via
`dvxApplyColorScheme()`.
6. **Wallpaper** -- read `[desktop]` section for wallpaper path and mode
(stretch/tile/center), load via `dvxSetWallpaper()`.
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.
9. **System info** -- `shellInfoInit()` gathers CPU, memory, drive info via
the platform layer and logs it.
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`.
13. **Desktop app** -- `shellLoadApp(ctx, "apps/progman/progman.app")`. If
this fails, the shell exits.
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.
## Main Loop ## Main Loop
The main loop runs until `dvxQuit()` sets `ctx->running = false`: Each iteration of the main loop does four things:
``` 1. `dvxUpdate()` -- process input events, dispatch callbacks, composite
while (ctx->running) { dirty rects, flush to LFB
dvxUpdate(ctx); // (1) 2. `tsYield()` -- give CPU time to app tasks (if any are active)
tsYield(); // (2) 3. `shellReapApps()` -- clean up any apps that terminated this frame
shellReapApps(ctx); // (3) 4. `shellDesktopUpdate()` -- notify desktop app if apps were reaped
shellDesktopUpdate(); // (4)
}
```
1. **dvxUpdate** -- processes mouse/keyboard input, dispatches widget and An idle callback (`idleYield`) is also registered so that `dvxUpdate()`
window callbacks, composites dirty rects, flushes to the LFB. This is the yields to app tasks during quiet frames.
shell's primary job. When idle (no events, no dirty rects), dvxUpdate
calls the registered `idleCallback` which yields to app tasks.
2. **tsYield** -- explicit yield to give app tasks CPU time even during busy
frames with many repaints. Without this, a flurry of mouse-move events
could starve app tasks because dvxUpdate would keep finding work.
3. **shellReapApps** -- scans the 32-slot app table for apps in
`AppStateTerminatingE`. Calls the shutdown hook (if present), destroys
all windows owned by the app, kills the task, closes the DXE handle,
and cleans up temp files. Returns true if anything was reaped.
4. **shellDesktopUpdate** -- if apps were reaped, iterates the registered
desktop update callback list so listeners (Program Manager, Task Manager)
can refresh their UI.
## Crash Recovery
The shell provides Windows 3.1-style fault tolerance using `setjmp`/`longjmp`:
1. `installCrashHandler()` registers `crashHandler` for SIGSEGV, SIGFPE,
SIGILL.
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
`SIG_DFL` after each delivery), then `longjmp(sCrashJmp, 1)`.
4. `longjmp` restores the main task's stack frame to the `setjmp` point.
This is safe because cooperative switching means the main task's stack
is always intact -- it was cleanly suspended at a yield point.
5. `tsRecoverToMain()` fixes the task scheduler's `currentIdx` so it points
back to task 0 instead of the crashed task.
6. The crashed app is force-killed via `shellForceKillApp()`, and a message
box is displayed: "'AppName' has caused a fault and will be terminated."
7. The desktop update callbacks are notified so the UI refreshes.
The register dump logged includes EIP, CS, all GPRs (EAX-EDI), segment
registers, and EFLAGS -- invaluable for post-mortem debugging.
## App Lifecycle ## App Lifecycle
### DXE3 Loading ### DXE App Contract
DXE3 is DJGPP's dynamic linking mechanism. Each `.app` file is a DXE3 shared Every DXE app exports two symbols:
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). * `appDescriptor` (`AppDescriptorT`) -- metadata:
2. Check if this DXE path is already loaded (`findLoadedPath`). - `name` -- display name (max 64 chars)
- If the existing app's descriptor says `multiInstance = true`, copy - `hasMainLoop` -- true for main-loop apps, false for callback-only
the `.app` to a unique temp file (using `TEMP`/`TMP` env var) so - `multiInstance` -- true to allow multiple instances via temp copy
`dlopen` gets an independent code+data image. - `stackSize` -- `SHELL_STACK_DEFAULT` (32KB) or explicit byte count
- If `multiInstance = false`, block with an error message. - `priority` -- `TS_PRIORITY_NORMAL` or custom
3. `dlopen(loadPath, RTLD_GLOBAL)` -- load the DXE.
4. `dlsym(handle, "_appDescriptor")` -- resolve the metadata struct.
5. `dlsym(handle, "_appMain")` -- resolve the entry point.
6. `dlsym(handle, "_appShutdown")` -- resolve the optional shutdown hook.
7. Fill in the `ShellAppT` slot (name, path, handle, state, etc.).
8. Derive `appDir` (directory containing the `.app` file) and `configDir`
(`CONFIG/<apppath>/` -- mirrors the app's directory structure under
`CONFIG/`).
9. Set `sCurrentAppId` to the slot index.
10. Launch:
- **Callback-only** (`hasMainLoop = false`): call `entryFn()` directly
in task 0. The app creates windows, registers callbacks, and returns.
- **Main-loop** (`hasMainLoop = true`): call `tsCreate()` to make a
cooperative task with the descriptor's stack size and priority. The
task wrapper sets `sCurrentAppId`, calls `entryFn()`, and marks the
app `AppStateTerminatingE` when it returns.
11. Reset `sCurrentAppId` to 0. Set state to `AppStateRunningE`. Notify
desktop update callbacks.
### AppDescriptorT * `appMain` (`int appMain(DxeAppContextT *)`) -- entry point
Every DXE app exports a global `AppDescriptorT`: Optional export: `appShutdown` (`void appShutdown(void)`) -- called
during graceful shutdown.
| Field | Type | Description | ### Callback-Only Apps (hasMainLoop = false)
|-------|------|-------------|
| `name` | `char[64]` | Display name (Task Manager, title bars) |
| `hasMainLoop` | `bool` | `true` = dedicated cooperative task; `false` = callback-only |
| `multiInstance` | `bool` | `true` = allow multiple simultaneous instances via temp file copy |
| `stackSize` | `int32_t` | Task stack in bytes (`SHELL_STACK_DEFAULT` = use default) |
| `priority` | `int32_t` | `TS_PRIORITY_LOW` / `TS_PRIORITY_NORMAL` / `TS_PRIORITY_HIGH` |
### Callback vs Main-Loop Apps `appMain()` is called directly in the shell's task 0. It creates
windows, registers callbacks, and returns immediately. The app lives
entirely through GUI callbacks. Lifecycle ends when the last window
closes.
**Callback-only** (`hasMainLoop = false`): ### Main-Loop Apps (hasMainLoop = true)
- `appMain()` runs in the shell's task 0, creates windows, registers event
callbacks, and returns immediately.
- All subsequent work happens through callbacks dispatched by `dvxUpdate()`.
- Lifecycle ends when the last window is closed (detected by the
`shellWrapDestroyWindow` wrapper).
- No task stack allocated -- simpler and cheaper.
- Examples: Program Manager, Notepad, Control Panel, DVX Demo, Image Viewer.
**Main-loop** (`hasMainLoop = true`): A dedicated cooperative task is created. `appMain()` runs in that task
- A cooperative task is created via `tsCreate()`. and can do its own polling/processing loop, calling `tsYield()` to
- `appMain()` runs in that task with its own loop calling `tsYield()`. share CPU. Lifecycle ends when `appMain()` returns or the task is
- Needed when the app has ongoing work that cannot be expressed purely as killed.
event callbacks (polling, computation, animation).
- Lifecycle ends when `appMain()` returns.
- Example: Clock (polls `time()` each second).
### Multi-Instance Support ### App States
DXE3's `dlopen` is reference-counted per path -- loading the same `.app`
twice returns the same handle, sharing all globals and statics. For apps
that set `multiInstance = true`, the shell copies the `.app` to a temp file
(e.g., `C:\TEMP\_dvx02.app`) before `dlopen`, giving each instance its own
code and data segment. Temp files are cleaned up on app termination.
### App State Machine
``` ```
Free -> Loaded -> Running -> Terminating -> Free Free -> Loaded -> Running -> Terminating -> Free
``` ```
- **Free**: slot available. | State | Description |
- **Loaded**: DXE loaded, entry point not yet called (transient). |-------|-------------|
- **Running**: entry point called, app is active. | `AppStateFreeE` | Slot available for reuse |
- **Terminating**: app's task returned or last window closed; awaiting reap. | `AppStateLoadedE` | DXE loaded, not yet started (transient) |
| `AppStateRunningE` | Entry point called, active |
| `AppStateTerminatingE` | Shutdown in progress, awaiting reap |
## Resource Tracking ### App Slots
`sCurrentAppId` is a global set before calling any app code (entry, shutdown, App slots are managed as a stb_ds dynamic array (no fixed max). Each
callbacks). The shell's `dvxCreateWindow` wrapper stamps every new window with slot tracks: app ID, name, path, DXE handle, state, task ID, entry/
`win->appId = sCurrentAppId`. This is how the shell knows which windows belong shutdown function pointers, and the `DxeAppContextT` passed to the app.
to which app, enabling:
- Per-app window cleanup on crash or termination (walk the window stack, The `DxeAppContextT` gives each app:
destroy all windows with matching `appId`). - `shellCtx` -- pointer to the shell's `AppContextT`
- The last-window-closes-app rule for callback-only apps: when - `appId` -- this app's unique ID
`shellWrapDestroyWindow` detects that a callback-only app has no remaining - `appDir` -- directory containing the `.app` file (for resources)
windows, it marks the app `AppStateTerminatingE`. - `configDir` -- writable config directory (`CONFIG/<apppath>/`)
`sCurrentAppId` is a simple global (not thread-local) because cooperative ### App ID Tracking
multitasking means only one task runs at a time.
## DXE Export Table `ctx->currentAppId` on AppContextT tracks which app is currently
executing. The shell sets this before calling app code.
`dvxCreateWindow()` stamps `win->appId` directly so the shell can
associate windows with apps for cleanup.
`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. ## Crash Recovery
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. The platform layer installs signal handlers for SIGSEGV, SIGFPE, and
Since libdvx (which has the real functions) was loaded before SIGILL via `platformInstallCrashHandler()`. If a crash occurs:
`shellExportInit()` registers these wrappers, libdvx keeps the real
implementations. But any app DXE loaded afterward gets the wrappers, which 1. Platform handler logs signal name and register dump (DJGPP)
add resource tracking transparently. 2. Handler `longjmp`s to the `setjmp` point in `shellMain()`
3. `tsRecoverToMain()` fixes the scheduler's bookkeeping
4. Shell logs app-specific info (name, path, task ID)
5. Crashed app is force-killed (`shellForceKillApp()`)
6. Error dialog is shown to the user
7. Desktop is notified to refresh
8. Main loop continues normally
This gives Windows 3.1-style fault tolerance -- one bad app does not
take down the whole system.
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 ## Task Manager
`shellTaskMgr.c` implements a shell-level Task Manager accessible via The built-in Task Manager is always available via Ctrl+Esc. It shows
Ctrl+Esc regardless of which app is focused or whether the desktop app all running apps with their names and provides an "End Task" button
is running. It is owned by the shell (appId = 0), not by any DXE app. for force-killing hung apps. The Task Manager is a shell-level
component, not tied to any app -- it persists even if the desktop app
is terminated.
Features:
- **ListView** with 5 columns: Name, Title, File, Type (Task/Callback), Status.
- **Switch To** -- find the app's topmost window, restore if minimized, raise
and focus it.
- **End Task** -- force-kill the selected app via `shellForceKillApp()`.
- **Run...** -- open a file dialog filtered to `*.app`, load the selected file.
- **Status bar** -- shows running app count and memory usage (total/free MB).
- Registers with `shellRegisterDesktopUpdate` so the list auto-refreshes when
apps load, terminate, or crash.
## Desktop Update Callbacks ## Desktop Update Notifications
`shellRegisterDesktopUpdate(fn)` adds a function pointer to a dynamic array Apps (especially the desktop app) register callbacks via
(managed via stb_ds `arrput`/`arrdel`). `shellDesktopUpdate()` iterates the `shellRegisterDesktopUpdate()` to be notified when app state changes
array and calls each registered function. (load, reap, crash, title change). Multiple callbacks are supported.
This is the mechanism by which the shell notifies interested parties (Program
Manager status bar, Task Manager list) when app state changes -- without
polling. Multiple listeners are supported. Listeners should call
`shellUnregisterDesktopUpdate()` before they are destroyed.
## App Config Storage ## Files
Each app gets a dedicated writable config directory under `CONFIG/`. The path | File | Description |
mirrors the app's location under `APPS/`: |------|-------------|
| `shellMain.c` | Entry point, main loop, crash recovery, idle callback |
| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE` |
| `shellApp.c` | App loading, reaping, task creation, DXE management |
| `shellInfo.h` | System information wrapper |
| `shellInfo.c` | Gathers and caches hardware info via platform layer |
| `shellTaskMgr.h` | Task Manager API |
| `shellTaskMgr.c` | Task Manager window (list of running apps, End Task) |
| `Makefile` | Builds `bin/libs/dvxshell.lib` + config/themes/wallpapers |
## Build
``` ```
App path: APPS/PROGMAN/progman.app make # builds dvxshell.lib + dvxshell.dep + config files
Config dir: CONFIG/PROGMAN/ make clean # removes objects, library, and config output
``` ```
API: Depends on: `libtasks.lib`, `libdvx.lib`, `texthelp.lib`, `listhelp.lib`
- `shellEnsureConfigDir(ctx)` -- create the directory tree via (via dvxshell.dep).
`platformMkdirRecursive()`. Returns 0 on success.
- `shellConfigPath(ctx, "settings.ini", buf, sizeof(buf))` -- build a full
path to a file in the config directory.
Apps use the standard preferences system (`prefsLoad`/`prefsSave`) pointed at
their config directory for persistent settings.
## System Hotkeys
These are always active regardless of which app is focused:
| Hotkey | Action |
|--------|--------|
| Alt+Tab | Cycle windows forward (rotate top to bottom of stack) |
| Shift+Alt+Tab | Cycle windows backward (pull bottom to top) |
| Alt+F4 | Close the focused window (calls its onClose callback) |
| Ctrl+F12 | Full screen screenshot -- prompts for save path (PNG/BMP/JPG) |
| Ctrl+Shift+F12 | Focused window screenshot -- prompts for save path |
| Ctrl+Esc | Open/raise the Task Manager (shell-level, always available) |
| F10 | Activate/toggle the focused window's menu bar |
| Alt+Space | Open/close the system menu on the focused window |
## Screenshot System
Two screenshot functions are available, both accessible from the system menu
(Alt+Space) or via hotkeys:
- `interactiveScreenshot(ctx)` -- captures the full screen (composited
backbuffer), then opens a Save As file dialog filtered to PNG/BMP/JPG/TGA.
Called by Ctrl+F12 or the "Screenshot..." system menu item.
- `interactiveWindowScreenshot(ctx, win)` -- captures just the focused
window's content buffer. Called by Ctrl+Shift+F12 or the "Window Shot..."
system menu item.
The system menu also includes standard window operations: Restore, Move, Size,
Minimize, Maximize, and Close.
## Logging
`shellLog(fmt, ...)` appends a printf-formatted line to `dvx.log`. The file
is opened, written, and closed on each call (append-per-write) so:
- The file is never held open, allowing Notepad to read it while the shell
runs.
- Writes are flushed immediately, important for crash diagnostics.
- The file is truncated once at startup.
Log output includes: startup sequence, preferences applied, video modes
enumerated, system information, app load/reap events, crash details with
full register dumps.
## Shell API Summary
| 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 |
| `shellReapApp(ctx, app)` | Gracefully shut down one app (calls shutdown hook) |
| `shellForceKillApp(ctx, app)` | Forcibly kill an app -- skips shutdown hook |
| `shellTerminateAllApps(ctx)` | Kill all running apps (shell shutdown) |
| `shellGetApp(appId)` | Get app slot by ID (NULL if invalid/free) |
| `shellRunningAppCount()` | Count running apps (excluding the shell) |
| `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 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 |
| `shellTaskMgrOpen(ctx)` | Open or raise the Task Manager window |
| `shellTaskMgrRefresh()` | Refresh the Task Manager list and status |
| `shellInfoInit(ctx)` | Gather and log system hardware information |
| `shellGetSystemInfo()` | Return cached system info text |

View file

@ -1,341 +1,118 @@
# taskswitch -- Cooperative Task Switching Library # taskswitch -- Cooperative Task Switching Library
Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS
protected mode). Part of the DVX GUI project. protected mode). Built as `libtasks.lib` -- a DXE3 module loaded by
the DVX loader.
Tasks voluntarily yield the CPU by calling `tsYield()`. A credit-based
weighted round-robin scheduler gives higher-priority tasks proportionally
more CPU time while guaranteeing that low-priority tasks are never
starved. A priority-10 task gets 11 turns per scheduling round; a
priority-0 task gets 1 -- but it always runs eventually.
The task array is backed by stb_ds and grows dynamically. Terminated
task slots are recycled, so there is no fixed upper limit on the number
of tasks created over the lifetime of the application.
## Why Cooperative? ## Why Cooperative
DOS is single-threaded. DPMI provides no timer-based preemption. The DOS is single-threaded with no kernel scheduler. DPMI provides no
DVX GUI event model is inherently single-threaded: one compositor, one thread or timer-based preemption. The DVX GUI event model is
input queue, one window stack. Preemptive switching would require inherently single-threaded (one compositor, one input queue, one
locking around every GUI call for no benefit. Cooperative switching window stack). Cooperative switching lets each task yield at safe
lets each task yield at safe points, avoiding synchronization entirely. points, avoiding the need for synchronization primitives entirely.
## Files ## Scheduling Algorithm
| File | Purpose | Credit-based weighted fair-share, round-robin within a credit epoch.
|------|---------|
| `taskswitch.h` | Public API -- types, constants, function prototypes | Each task receives `(priority + 1)` credits per scheduling round.
| `taskswitch.c` | Implementation (scheduler, context switch, slots, includes stb_ds implementation) | Tasks run round-robin, consuming one credit per turn. When all
| `thirdparty/stb_ds.h` | stb dynamic array/hashmap library (third-party) | credits are spent, every ready task is refilled.
| `Makefile` | DJGPP cross-compilation build rules |
| Priority | Constant | Credits/Round | Relative Share |
|----------|----------|---------------|----------------|
| 0 | `TS_PRIORITY_LOW` | 1 | ~4% |
| 5 | `TS_PRIORITY_NORMAL` | 6 | ~22% |
| 10 | `TS_PRIORITY_HIGH` | 11 | ~41% |
Higher-priority tasks run proportionally more often but never starve
lower ones. A priority-10 task gets 11 turns per round while a
priority-0 task gets 1.
## Building ## Task States
Cross-compile from Linux: | State | Description |
|-------|-------------|
``` | `TaskStateReady` | Eligible for scheduling |
make # builds ../bin/libs/libtasks.lib | `TaskStateRunning` | Currently executing (cosmetic marker) |
make clean # removes objects and library | `TaskStatePaused` | Skipped until resumed |
``` | `TaskStateTerminated` | Slot available for reuse |
Output:
| Path | Description |
|------|-------------|
| `../bin/libs/libtasks.lib` | DXE module |
| `../obj/tasks/` | Object files |
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
```c
#include <stdio.h>
#include "taskswitch.h"
void myTask(void *arg) {
const char *name = (const char *)arg;
for (int i = 0; i < 3; i++) {
printf("[%s] working...\n", name);
tsYield();
}
}
int main(void) {
tsInit();
tsCreate("alpha", myTask, "alpha", 0, TS_PRIORITY_NORMAL);
tsCreate("beta", myTask, "beta", 0, TS_PRIORITY_HIGH);
while (tsActiveCount() > 1) {
tsYield();
}
tsShutdown();
return 0;
}
```
## Lifecycle
1. `tsInit()` -- Initialize the task system. The calling context
(typically `main`) becomes task 0 with `TS_PRIORITY_NORMAL`. No
separate stack is allocated for task 0 -- it uses the process stack.
2. `tsCreate(...)` -- Create tasks. Each gets a name, entry function,
argument pointer, stack size (0 for the default), and a priority.
Returns the task ID (>= 0) or a negative error code. Terminated
task slots are reused automatically.
3. `tsYield()` -- Call from any task (including main) to hand the CPU
to the next eligible task. This is the sole mechanism for task
switching.
4. `tsShutdown()` -- Free all task stacks and the task array.
Tasks terminate by returning from their entry function or by calling
`tsExit()`. The main task (id 0) must never call `tsExit()`. When a
task terminates, its stack is freed immediately and its slot becomes
available for reuse by the next `tsCreate()` call.
## API Reference ## API Reference
### Initialization and Teardown | Function | Description |
|----------|-------------|
| Function | Signature | Description | | `tsInit()` | Initialize task system; calling context becomes task 0 (main) |
|----------|-----------|-------------| | `tsShutdown()` | Shut down and free all resources |
| `tsInit` | `int32_t tsInit(void)` | Initialize the library. Returns `TS_OK` or a negative error code. | | `tsCreate(name, entry, arg, stackSize, priority)` | Create a new task; returns task ID |
| `tsShutdown` | `void tsShutdown(void)` | Free all resources. Safe to call even if `tsInit` was never called. | | `tsYield()` | Yield CPU to next eligible task (credit-based round-robin) |
| `tsPause(taskId)` | Pause a task (implicit yield if self) |
### Task Creation and Termination | `tsResume(taskId)` | Resume a paused task (refills credits) |
| `tsSetPriority(taskId, priority)` | Set priority (refills credits immediately) |
| Function | Signature | Description | | `tsGetPriority(taskId)` | Get task priority |
|----------|-----------|-------------| | `tsGetState(taskId)` | Get task state |
| `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. | | `tsCurrentId()` | Get currently running task's ID |
| `tsExit` | `void tsExit(void)` | Terminate the calling task. Must not be called from the main task. Never returns. | | `tsGetName(taskId)` | Get task name |
| `tsKill` | `int32_t tsKill(uint32_t taskId)` | Forcibly terminate another task. Cannot kill main (id 0) or self (use `tsExit` instead). | | `tsExit()` | Terminate calling task (never returns) |
| `tsKill(taskId)` | Forcibly terminate another task |
### Scheduling | `tsRecoverToMain()` | Force scheduler back to task 0 after crash longjmp |
| `tsActiveCount()` | Count of non-terminated tasks |
| 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. |
| `tsActiveCount` | `uint32_t tsActiveCount(void)` | Return the number of non-terminated tasks. |
## Constants
### Error Codes ### Error Codes
| Name | Value | Meaning | | Constant | Value | Description |
|------|-------|---------| |----------|-------|-------------|
| `TS_OK` | 0 | Success | | `TS_OK` | 0 | Success |
| `TS_ERR_INIT` | -1 | Library not initialized | | `TS_ERR_INIT` | -1 | Initialization failure |
| `TS_ERR_PARAM` | -2 | Invalid parameter | | `TS_ERR_PARAM` | -2 | Invalid parameter |
| `TS_ERR_FULL` | -3 | Task table full (unused, kept for compatibility) | | `TS_ERR_FULL` | -3 | No available task slots |
| `TS_ERR_NOMEM` | -4 | Memory allocation failed | | `TS_ERR_NOMEM` | -4 | Stack allocation failed |
| `TS_ERR_STATE` | -5 | Invalid state transition | | `TS_ERR_STATE` | -5 | Invalid state transition |
### Priority Presets ### Constants
| Name | Value | Credits per Round | | Constant | Value | Description |
|------|-------|-------------------| |----------|-------|-------------|
| `TS_PRIORITY_LOW` | 0 | 1 | | `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack size (32KB) |
| `TS_PRIORITY_NORMAL` | 5 | 6 | | `TS_NAME_MAX` | 32 | Maximum task name length |
| `TS_PRIORITY_HIGH` | 10 | 11 |
Any non-negative `int32_t` may be used as a priority. The presets are
provided for convenience. In the DVX Shell, the main task runs at
`TS_PRIORITY_HIGH` to keep the UI responsive; app tasks default to
`TS_PRIORITY_NORMAL`.
### Defaults
| Name | Value | Description |
|------|-------|-------------|
| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack per task |
| `TS_NAME_MAX` | 32 | Max task name length |
## Types ## Task 0 (Main Task)
### TaskStateE Task 0 is special:
* Cannot be killed or paused
* Uses the process stack directly (no separate allocation)
* `tsRecoverToMain()` always returns control here after a crash
* The DVX shell runs as task 0 with `TS_PRIORITY_HIGH`
```c
typedef enum { ## Crash Recovery
TaskStateReady = 0, // Eligible for scheduling
TaskStateRunning = 1, // Currently executing `tsRecoverToMain()` is called after `longjmp` from a signal handler
TaskStatePaused = 2, // Suspended until tsResume() that fired in a non-main task. It fixes the scheduler's `currentIdx`
TaskStateTerminated = 3 // Finished; slot will be recycled to point back to task 0. The crashed task is NOT cleaned up by this
} TaskStateE; call -- `tsKill()` must be called separately afterward.
## Files
| File | Description |
|------|-------------|
| `taskswitch.h` | Public API header |
| `taskswitch.c` | Complete implementation (context switch, scheduler, stack management) |
| `Makefile` | Builds `bin/libs/libtasks.lib` |
## Build
```
make # builds bin/libs/libtasks.lib
make clean # removes objects and library
``` ```
Only Ready tasks participate in scheduling. Running is cosmetic (marks No dependencies. Exports all symbols matching `_ts*`.
the currently executing task). Paused tasks are skipped until explicitly
resumed. Terminated slots are recycled by `tsCreate`.
### TaskEntryT
```c
typedef void (*TaskEntryT)(void *arg);
```
The signature every task entry function must follow. The `arg` parameter
is the pointer passed to `tsCreate`.
## Scheduler Details
The scheduler is a credit-based weighted round-robin, a variant of the
Linux 2.4 goodness() scheduler.
1. Every ready task holds a credit counter initialized to `priority + 1`.
2. When `tsYield()` is called, the scheduler scans tasks starting one
past the current task (wrapping around) looking for a ready task with
credits > 0. When found, that task's credits are decremented and it
becomes the running task.
3. When no ready task has credits remaining, every ready task is
refilled to `priority + 1` (one "epoch") and the scan repeats.
This means a priority-10 task receives 11 turns for every 1 turn a
priority-0 task receives, but the low-priority task still runs -- it
is never starved.
Credits are also refilled when:
- A task is created (`tsCreate`) -- starts with `priority + 1`.
- A task is resumed (`tsResume`) -- refilled so it runs promptly.
- A task's priority changes (`tsSetPriority`) -- reset to `new + 1`.
## Task Slot Management
The task array is a stb_ds dynamic array that grows automatically.
Each slot has an `allocated` flag:
- `tsCreate()` scans for the first unallocated slot (starting at index
1, since slot 0 is always the main task). If no free slot exists, the
array is extended with `arrput()`.
- `tsExit()` and `tsKill()` free the terminated task's stack immediately
and mark the slot as unallocated, making it available for the next
`tsCreate()` call.
- Task IDs are stable array indices. Slots are never removed or
reordered, so a task ID remains valid for queries until the slot is
recycled.
This supports long-running applications (like the DVX Shell) that
create and destroy many tasks over their lifetime without unbounded
memory growth.
## Context Switch Internals
Context switching uses inline assembly with both i386 and x86_64 code
paths. The `contextSwitch` function is marked `noinline` to preserve
callee-saved register assumptions.
Why inline asm instead of setjmp/longjmp: setjmp/longjmp only save
callee-saved registers and do not give control over the stack pointer
in a portable way. New tasks need a fresh stack with the instruction
pointer set to a trampoline -- setjmp cannot bootstrap that. The asm
approach also avoids ABI differences in jmp_buf layout across DJGPP
versions.
### i386 (DJGPP target)
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 |
| EBP | 12 | Frame pointer |
| ESP | 16 | Stack pointer |
| EIP | 20 | Resume address (captured as local label) |
### x86_64 (for native Linux testing)
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 |
| R14 | 24 | Callee-saved general purpose |
| R15 | 32 | Callee-saved general purpose |
| RBP | 40 | Frame pointer |
| RSP | 48 | Stack pointer |
| RIP | 56 | Resume address (RIP-relative lea) |
Segment registers are not saved because DJGPP runs in a flat
protected-mode environment where CS, DS, ES, and SS share the same
base.
New tasks have their initial stack pointer set to a 16-byte-aligned
region at the top of a malloc'd stack, with the instruction pointer
set to an internal trampoline that calls the user's entry function
and then `tsExit()`.
## Limitations
- **Cooperative only** -- tasks must call `tsYield()` (or
`tsPause`/`tsExit`) to allow other tasks to run. A task that never
yields blocks everything.
- **Not interrupt-safe** -- no locking or volatile module state. Do not
call library functions from interrupt handlers.
- **Single-threaded** -- designed for one CPU under DOS protected mode.
- **Stack overflow is not detected** -- size the stack appropriately for
each task's needs.
## 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. 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.

View file

@ -12,6 +12,10 @@ the DVX widget system outside the shell framework.
Connects to a remote BBS through the SecLink proxy, providing a full Connects to a remote BBS through the SecLink proxy, providing a full
ANSI terminal in a DVX-style window with encrypted transport. ANSI terminal in a DVX-style window with encrypted transport.
NOTE: This program predates the DXE module refactoring and links
against static libraries from `lib/`. It may need Makefile updates
to build against the current source tree layout.
## Architecture ## Architecture
@ -77,19 +81,6 @@ The handshake completes in text mode before the GUI starts, so the
DOS console shows progress messages during connection setup. DOS console shows progress messages during connection setup.
## Main Loop
Each iteration:
1. `secLinkPoll()` -- read serial data, decrypt, deliver to ring buffer.
2. `dvxUpdate()` -- process mouse, keyboard, paint, and window events.
During paint, the terminal widget calls `commRead` to drain the ring
buffer and render new data.
An idle callback also calls `secLinkPoll()` so incoming data is
processed even when the user is not interacting with the terminal.
## Data Flow ## Data Flow
``` ```
@ -100,25 +91,10 @@ Keyboard -> widgetAnsiTermOnKey() -> commWrite()
-> secLinkSend() -> serial -> proxy -> BBS -> secLinkSend() -> serial -> proxy -> BBS
``` ```
A 4KB ring buffer (`RECV_BUF_SIZE`) bridges the SecLink receive A 4KB ring buffer bridges the SecLink receive callback (which fires
callback (which fires asynchronously during `secLinkPoll()`) and the asynchronously during `secLinkPoll()`) and the terminal widget's comm
terminal widget's comm read interface (which is polled synchronously read interface (which is polled synchronously during the widget paint
during the widget paint cycle). This decoupling is necessary because cycle).
the callback can fire at any time during polling, but the terminal
widget expects to read data synchronously.
## GUI
- **Window**: resizable, titled "SecLink Terminal"
- **Menu bar**: File -> Quit
- **Terminal**: 80x25 ANSI terminal widget with 1000-line scrollback
- **Status bar**: shows COM port, baud rate, and encryption status
The ANSI terminal widget supports standard escape sequences including
cursor control, SGR colors (16-color CGA palette), erase, scroll,
insert/delete lines, and DEC private modes (cursor visibility, line
wrap).
## Test Setup ## Test Setup
@ -138,27 +114,10 @@ wrap).
termdemo termdemo
``` ```
4. The handshake completes, the GUI appears, and BBS output is
displayed in the terminal window.
## Building
```
make # builds ../bin/termdemo.exe (and all dependency libs)
make clean # removes objects and binary
```
The Makefile automatically builds all dependency libraries before
linking. Objects are placed in `../obj/termdemo/`, the binary in
`../bin/`.
Target: DJGPP cross-compiler, 486+ CPU, VESA VBE 2.0+ video.
## Dependencies ## Dependencies
All libraries are built into `../lib/`: All libraries are built into `lib/`:
| Library | Purpose | | Library | Purpose |
|------------------|--------------------------------------| |------------------|--------------------------------------|
@ -176,3 +135,11 @@ termdemo/
termdemo.c terminal emulator program termdemo.c terminal emulator program
Makefile DJGPP cross-compilation build Makefile DJGPP cross-compilation build
``` ```
## Build
```
make # builds bin/termdemo.exe (and dependency libs)
make clean # removes objects and binary
```

132
texthelp/README.md Normal file
View file

@ -0,0 +1,132 @@
# texthelp -- Shared Text Editing Helper Library
Shared infrastructure for text editing widgets, built as
`texthelp.lib` (DXE3 module). Provides clipboard operations, cursor
blink management, word boundary detection, cross-widget selection
clearing, and a complete single-line text editing engine.
Used by: TextInput, TextArea, Spinner, ComboBox, AnsiTerm.
## API Reference
### Cursor Blink
```c
void wgtUpdateCursorBlink(void);
```
Toggles the global cursor blink state (`sCursorBlinkOn`) based on
`clock()` timing. Called from the event dispatch layer during paint.
Widgets that show a text cursor check `sCursorBlinkOn` to decide
whether to draw or hide the cursor.
### Selection Management
```c
void clearOtherSelections(WidgetT *except);
```
Clears text/item selections in all widgets except the specified one.
Called when a widget gains focus or starts a new selection, ensuring
only one widget has an active selection at a time.
### Word / Character Helpers
```c
bool isWordChar(char c);
```
Returns true if `c` is a word character (alphanumeric or underscore).
Used for double-click word selection and Ctrl+arrow word navigation.
```c
int32_t wordStart(const char *buf, int32_t pos);
```
Scans backward from `pos` to find the start of the current word.
```c
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
```
Scans forward from `pos` to find the end of the current word.
### Single-Line Text Editing Engine
Four functions implement the complete editing behavior shared across
TextInput, Spinner, ComboBox, and other single-line text widgets:
```c
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod,
char *buf, int32_t bufSize, int32_t *pLen,
int32_t *pCursor, int32_t *pScrollOff,
int32_t *pSelStart, int32_t *pSelEnd,
char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
```
Handles all keyboard input: character insertion, deletion (Backspace,
Delete), cursor movement (arrows, Home, End), word navigation
(Ctrl+arrows), selection (Shift+arrows, Ctrl+Shift+arrows, Ctrl+A),
clipboard (Ctrl+C/X/V), and undo (Ctrl+Z).
```c
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy,
int32_t textLeftX, const BitmapFontT *font,
const char *buf, int32_t len, int32_t scrollOff,
int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd,
bool wordSelect, bool dragSelect);
```
Handles mouse click positioning: converts pixel coordinates to cursor
position, handles word selection (double-click), and drag-select.
```c
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge,
int32_t maxChars, const BitmapFontT *font, int32_t len,
int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
```
Updates cursor and selection during mouse drag operations.
```c
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops,
const BitmapFontT *font, const ColorSchemeT *colors,
int32_t textX, int32_t textY, const char *buf,
int32_t visLen, int32_t scrollOff, int32_t cursorPos,
int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg,
bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
```
Renders a single line of text with selection highlighting and cursor.
### Constants
| Name | Value | Description |
|------|-------|-------------|
| `TEXT_INPUT_PAD` | 3 | Padding inside text input border |
## Exported Symbols
Matches prefixes: `_clipboard*`, `_clearOther*`, `_isWordChar*`,
`_multiClick*`, `_widgetText*`, `_wordStart*`, `_wordEnd*`.
## Files
| File | Description |
|------|-------------|
| `textHelp.h` | Public API header |
| `textHelp.c` | Complete implementation |
| `Makefile` | Builds `bin/libs/texthelp.lib` + dep file |
## Build
```
make # builds bin/libs/texthelp.lib + texthelp.dep
make clean # removes objects, library, and dep file
```
Depends on: `libtasks.lib`, `libdvx.lib` (via texthelp.dep).

View file

@ -1,95 +1,391 @@
# DVX Widget Modules # DVX Widget Modules
Individual widget type implementations for the DVX GUI, each built Individual widget type implementations, each built as a separate `.wgt`
as a separate `.wgt` DXE module. The loader scans the `WIDGETS/` DXE module. The loader scans the `WIDGETS/` directory at startup and
directory recursively at startup and loads every `.wgt` file it loads every `.wgt` file it finds. Each module exports `wgtRegister()`,
finds, calling `wgtRegister()` on each to fill the widget class which registers its widget class(es) and API struct with the core.
table.
Drop a new `.wgt` file in the directory and it is automatically Core knows nothing about individual widgets. All per-widget state lives
available -- no loader or core library changes needed. in `w->data` (allocated by the widget DXE). All per-widget behavior is
dispatched through the WidgetClassT vtable. Each widget provides a
public header with its API struct, typed accessor, and convenience
macros.
## How Widget DXEs Work ## Widget Summary
Each `.wgt` module contains: 26 source files produce 26 `.wgt` modules containing 32 widget types:
1. The widget implementation (paint, layout, mouse/key handlers) | Source File | .wgt File | Types | Public Header | Dependencies |
2. A `static const WidgetClassT` vtable definition |-------------|-----------|-------|---------------|--------------|
3. A `wgtRegister()` function that writes the vtable pointer into | `widgetBox.c` | `box.wgt` | VBox, HBox, Frame | `widgetBox.h` | -- |
`widgetClassTable[]` at the widget's enum slot | `widgetButton.c` | `button.wgt` | Button | `widgetButton.h` | -- |
4. The public API functions (e.g. `wgtTreeView()`, `wgtTreeViewGetSelected()`) | `widgetLabel.c` | `label.wgt` | Label | `widgetLabel.h` | -- |
| `widgetTextInput.c` | `textinpt.wgt` | TextInput, TextArea | `widgetTextInput.h` | texthelp |
At load time, the module resolves its dependencies against: | `widgetCheckbox.c` | `checkbox.wgt` | Checkbox | `widgetCheckbox.h` | -- |
- The loader's `dlregsym` table (platform functions, libc) | `widgetRadio.c` | `radio.wgt` | RadioGroup, Radio | `widgetRadio.h` | -- |
- `libtasks.lib` exports (stb_ds dynamic arrays) | `widgetDropdown.c` | `dropdown.wgt` | Dropdown | `widgetDropdown.h` | listhelp |
- `libdvx.lib` exports (widget infrastructure: `widgetAlloc`, | `widgetComboBox.c` | `combobox.wgt` | ComboBox | `widgetComboBox.h` | texthelp, listhelp |
`widgetClassTable`, `widgetScrollbarThumb`, shared state, etc.) | `widgetListBox.c` | `listbox.wgt` | ListBox | `widgetListBox.h` | listhelp |
- Other widget modules loaded earlier (via `RTLD_GLOBAL`) | `widgetListView.c` | `listview.wgt` | ListView | `widgetListView.h` | listhelp |
| `widgetTreeView.c` | `treeview.wgt` | TreeView, TreeItem | `widgetTreeView.h` | listhelp |
| `widgetSpinner.c` | `spinner.wgt` | Spinner | `widgetSpinner.h` | texthelp |
| `widgetSlider.c` | `slider.wgt` | Slider | `widgetSlider.h` | -- |
| `widgetProgressBar.c` | `progress.wgt` | ProgressBar | `widgetProgressBar.h` | -- |
| `widgetImage.c` | `image.wgt` | Image | `widgetImage.h` | -- |
| `widgetImageButton.c` | `imgbtn.wgt` | ImageButton | `widgetImageButton.h` | -- |
| `widgetCanvas.c` | `canvas.wgt` | Canvas | `widgetCanvas.h` | -- |
| `widgetSeparator.c` | `separatr.wgt` | Separator | `widgetSeparator.h` | -- |
| `widgetSpacer.c` | `spacer.wgt` | Spacer | `widgetSpacer.h` | -- |
| `widgetTabControl.c` | `tabctrl.wgt` | TabControl, TabPage | `widgetTabControl.h` | -- |
| `widgetScrollPane.c` | `scrlpane.wgt` | ScrollPane | `widgetScrollPane.h` | -- |
| `widgetSplitter.c` | `splitter.wgt` | Splitter | `widgetSplitter.h` | -- |
| `widgetToolbar.c` | `toolbar.wgt` | Toolbar | `widgetToolbar.h` | -- |
| `widgetStatusBar.c` | `statbar.wgt` | StatusBar | `widgetStatusBar.h` | -- |
| `widgetTimer.c` | `timer.wgt` | Timer | `widgetTimer.h` | -- |
| `widgetAnsiTerm.c` | `terminal.wgt` | AnsiTerm | `widgetAnsiTerm.h` | texthelp |
## Widget Catalog ## Dependency Files (.dep)
| Module | Widget Types | Description | Widgets that use shared helper libraries need a `.dep` file so the
|--------|-------------|-------------| loader loads the helper first. Dep files are in `config/` and copied
| `box.wgt` | VBox, HBox, Frame | Containers: vertical/horizontal box layout, titled frame | to `bin/widgets/` during build:
| `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 | Widget | Dep File | Dependencies |
multiple related types). |--------|----------|--------------|
| textinpt | `textinpt.dep` | texthelp |
| combobox | `combobox.dep` | texthelp, listhelp |
| spinner | `spinner.dep` | texthelp |
| terminal | `terminal.dep` | texthelp |
| dropdown | `dropdown.dep` | listhelp |
| listbox | `listbox.dep` | listhelp |
| listview | `listview.dep` | listhelp |
| treeview | `treeview.dep` | listhelp |
## Writing a New Widget ## Per-Widget API Details
1. Create `widgets/myWidget.c` ### Box (VBox, HBox, Frame)
2. Include `"dvxWidgetPlugin.h"` for infrastructure access
3. Implement paint, calcMinSize, and event handlers as needed Container widgets that arrange children vertically or horizontally.
4. Define a `static const WidgetClassT` with your vtable Frame adds a titled border around its children.
5. Add a `wgtRegister()` function:
```c | API Function | Macro | Description |
void wgtRegister(void) { |--------------|-------|-------------|
widgetClassTable[MyWidgetTypeE] = &sClassMyWidget; | `vBox(parent)` | `wgtVBox(parent)` | Create vertical container |
} | `hBox(parent)` | `wgtHBox(parent)` | Create horizontal container |
``` | `frame(parent, title)` | `wgtFrame(parent, title)` | Create titled frame container |
6. Add the new enum value to `WidgetTypeE` in `core/dvxWidget.h`
(or use `wgtRegisterClass()` for a fully dynamic type ID) ### Button
7. Add public API functions (e.g. `wgtMyWidget()`) that call
`widgetAlloc(parent, MyWidgetTypeE)` Push button with text label and accelerator key support.
8. Add a build rule in `widgets/Makefile`
9. Build -- the loader discovers and loads it automatically | API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, text)` | `wgtButton(parent, text)` | Create a button |
### Label
Static text label with optional alignment.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, text)` | `wgtLabel(parent, text)` | Create a label |
| `setAlign(w, align)` | `wgtLabelSetAlign(w, align)` | Set text alignment |
### TextInput / TextArea
Single-line text input, password input, masked input, and multiline
text area. Uses texthelp.lib for editing engine, cursor blink, word
boundaries, clipboard, and selection.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, maxLen)` | `wgtTextInput(parent, maxLen)` | Single-line text input |
| `password(parent, maxLen)` | `wgtPasswordInput(parent, maxLen)` | Password input (masked display) |
| `masked(parent, mask)` | `wgtMaskedInput(parent, mask)` | Masked input (e.g. phone number) |
| `textArea(parent, maxLen)` | `wgtTextArea(parent, maxLen)` | Multiline text editor |
### Checkbox
Toggle checkbox with text label.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, text)` | `wgtCheckbox(parent, text)` | Create a checkbox |
| `isChecked(w)` | `wgtCheckboxIsChecked(w)` | Get checked state |
| `setChecked(w, checked)` | `wgtCheckboxSetChecked(w, checked)` | Set checked state |
### Radio (RadioGroup, Radio)
Mutually exclusive radio buttons within a group container.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `group(parent)` | `wgtRadioGroup(parent)` | Create radio group container |
| `create(parent, text)` | `wgtRadio(parent, text)` | Create radio button in group |
| `groupSetSelected(w, idx)` | `wgtRadioGroupSetSelected(w, idx)` | Select radio by index |
| `getIndex(w)` | `wgtRadioGetIndex(w)` | Get selected index |
### Dropdown
Drop-down selection list with popup overlay.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtDropdown(parent)` | Create a dropdown |
| `setItems(w, items, count)` | `wgtDropdownSetItems(w, items, count)` | Set item list |
| `getSelected(w)` | `wgtDropdownGetSelected(w)` | Get selected index |
| `setSelected(w, idx)` | `wgtDropdownSetSelected(w, idx)` | Set selected index |
### ComboBox
Editable text input with dropdown suggestion list. Combines TextInput
and Dropdown behavior.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, maxLen)` | `wgtComboBox(parent, maxLen)` | Create a combo box |
| `setItems(w, items, count)` | `wgtComboBoxSetItems(w, items, count)` | Set suggestion list |
| `getSelected(w)` | `wgtComboBoxGetSelected(w)` | Get selected index (-1 if typed) |
| `setSelected(w, idx)` | `wgtComboBoxSetSelected(w, idx)` | Select item by index |
### ListBox
Scrollable list of text items with single or multi-select and optional
drag-reorder.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtListBox(parent)` | Create a list box |
| `setItems(w, items, count)` | `wgtListBoxSetItems(w, items, count)` | Set item list |
| `getSelected(w)` | `wgtListBoxGetSelected(w)` | Get selected index |
| `setSelected(w, idx)` | `wgtListBoxSetSelected(w, idx)` | Set selected index |
| `setMultiSelect(w, multi)` | `wgtListBoxSetMultiSelect(w, multi)` | Enable multi-select |
| `isItemSelected(w, idx)` | `wgtListBoxIsItemSelected(w, idx)` | Check if item is selected |
| `setItemSelected(w, idx, sel)` | `wgtListBoxSetItemSelected(w, idx, sel)` | Select/deselect item |
| `selectAll(w)` | `wgtListBoxSelectAll(w)` | Select all items |
| `clearSelection(w)` | `wgtListBoxClearSelection(w)` | Deselect all items |
| `setReorderable(w, r)` | `wgtListBoxSetReorderable(w, r)` | Enable drag reorder |
### ListView
Multi-column list with sortable headers, resizable columns, single or
multi-select, and optional drag-reorder.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtListView(parent)` | Create a list view |
| `setColumns(w, cols, count)` | `wgtListViewSetColumns(w, cols, count)` | Define columns |
| `setData(w, cellData, rowCount)` | `wgtListViewSetData(w, cellData, rowCount)` | Set row data |
| `getSelected(w)` | `wgtListViewGetSelected(w)` | Get selected row |
| `setSelected(w, idx)` | `wgtListViewSetSelected(w, idx)` | Set selected row |
| `setSort(w, col, dir)` | `wgtListViewSetSort(w, col, dir)` | Set sort column/direction |
| `setHeaderClickCallback(w, cb)` | `wgtListViewSetHeaderClickCallback(w, cb)` | Set header click handler |
| `setMultiSelect(w, multi)` | `wgtListViewSetMultiSelect(w, multi)` | Enable multi-select |
| `isItemSelected(w, idx)` | `wgtListViewIsItemSelected(w, idx)` | Check if row is selected |
| `setItemSelected(w, idx, sel)` | `wgtListViewSetItemSelected(w, idx, sel)` | Select/deselect row |
| `selectAll(w)` | `wgtListViewSelectAll(w)` | Select all rows |
| `clearSelection(w)` | `wgtListViewClearSelection(w)` | Deselect all rows |
| `setReorderable(w, r)` | `wgtListViewSetReorderable(w, r)` | Enable drag reorder |
Column definition uses `ListViewColT` with title, tagged width, and alignment
(`ListViewAlignLeftE`, `ListViewAlignCenterE`, `ListViewAlignRightE`).
Sort direction: `ListViewSortNoneE`, `ListViewSortAscE`, `ListViewSortDescE`.
### TreeView (TreeView, TreeItem)
Hierarchical tree with expand/collapse, single or multi-select, and
optional drag-reorder.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtTreeView(parent)` | Create a tree view |
| `getSelected(w)` | `wgtTreeViewGetSelected(w)` | Get selected item widget |
| `setSelected(w, item)` | `wgtTreeViewSetSelected(w, item)` | Set selected item |
| `setMultiSelect(w, multi)` | `wgtTreeViewSetMultiSelect(w, multi)` | Enable multi-select |
| `setReorderable(w, r)` | `wgtTreeViewSetReorderable(w, r)` | Enable drag reorder |
| `item(parent, text)` | `wgtTreeItem(parent, text)` | Create tree item |
| `itemSetExpanded(w, exp)` | `wgtTreeItemSetExpanded(w, exp)` | Expand/collapse item |
| `itemIsExpanded(w)` | `wgtTreeItemIsExpanded(w)` | Check if expanded |
| `itemIsSelected(w)` | `wgtTreeItemIsSelected(w)` | Check if selected |
| `itemSetSelected(w, sel)` | `wgtTreeItemSetSelected(w, sel)` | Select/deselect item |
### Spinner
Numeric input with up/down buttons.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, min, max, step)` | `wgtSpinner(parent, min, max, step)` | Create a spinner |
| `setValue(w, value)` | `wgtSpinnerSetValue(w, value)` | Set current value |
| `getValue(w)` | `wgtSpinnerGetValue(w)` | Get current value |
| `setRange(w, min, max)` | `wgtSpinnerSetRange(w, min, max)` | Set value range |
| `setStep(w, step)` | `wgtSpinnerSetStep(w, step)` | Set increment step |
### Slider
Horizontal track with draggable thumb for value selection.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, min, max)` | `wgtSlider(parent, min, max)` | Create a slider |
| `setValue(w, value)` | `wgtSliderSetValue(w, value)` | Set current value |
| `getValue(w)` | `wgtSliderGetValue(w)` | Get current value |
### ProgressBar
Horizontal or vertical progress indicator (0-100).
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtProgressBar(parent)` | Create horizontal progress bar |
| `createV(parent)` | `wgtProgressBarV(parent)` | Create vertical progress bar |
| `setValue(w, value)` | `wgtProgressBarSetValue(w, value)` | Set value (0-100) |
| `getValue(w)` | `wgtProgressBarGetValue(w)` | Get current value |
### Image
Displays a pixel buffer or image file in native display format.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, data, w, h, pitch)` | `wgtImage(parent, data, w, h, pitch)` | Create from pixel data |
| `fromFile(parent, path)` | `wgtImageFromFile(parent, path)` | Create from image file |
| `setData(w, data, imgW, imgH, pitch)` | `wgtImageSetData(w, data, imgW, imgH, pitch)` | Replace image data |
### ImageButton
Clickable button displaying an image instead of text.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, data, w, h, pitch)` | `wgtImageButton(parent, data, w, h, pitch)` | Create from pixel data |
| `fromFile(parent, path)` | `wgtImageButtonFromFile(parent, path)` | Create from image file |
| `setData(w, data, imgW, imgH, pitch)` | `wgtImageButtonSetData(w, data, imgW, imgH, pitch)` | Replace image data |
### Canvas
Drawable surface with pen tools, shapes, pixel access, and file I/O.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, w, h)` | `wgtCanvas(parent, w, h)` | Create a canvas |
| `clear(w, color)` | `wgtCanvasClear(w, color)` | Fill with solid color |
| `setPenColor(w, color)` | `wgtCanvasSetPenColor(w, color)` | Set drawing color |
| `setPenSize(w, size)` | `wgtCanvasSetPenSize(w, size)` | Set pen width |
| `setMouseCallback(w, cb)` | `wgtCanvasSetMouseCallback(w, cb)` | Set mouse event handler |
| `save(w, path)` | `wgtCanvasSave(w, path)` | Save canvas to file |
| `load(w, path)` | `wgtCanvasLoad(w, path)` | Load image into canvas |
| `drawLine(w, x0, y0, x1, y1)` | `wgtCanvasDrawLine(w, ...)` | Draw line |
| `drawRect(w, x, y, w, h)` | `wgtCanvasDrawRect(w, ...)` | Draw rectangle outline |
| `fillRect(w, x, y, w, h)` | `wgtCanvasFillRect(w, ...)` | Fill rectangle |
| `fillCircle(w, cx, cy, r)` | `wgtCanvasFillCircle(w, ...)` | Fill circle |
| `setPixel(w, x, y, color)` | `wgtCanvasSetPixel(w, x, y, color)` | Set individual pixel |
| `getPixel(w, x, y)` | `wgtCanvasGetPixel(w, x, y)` | Read pixel color |
### Separator (HSeparator, VSeparator)
Thin decorative line (horizontal or vertical).
| API Function | Macro | Description |
|--------------|-------|-------------|
| `hSeparator(parent)` | `wgtHSeparator(parent)` | Create horizontal separator |
| `vSeparator(parent)` | `wgtVSeparator(parent)` | Create vertical separator |
### Spacer
Invisible flexible space for layout padding.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtSpacer(parent)` | Create a spacer |
### TabControl (TabControl, TabPage)
Tabbed container with clickable tab headers.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtTabControl(parent)` | Create tab control |
| `page(parent, title)` | `wgtTabPage(parent, title)` | Add a tab page |
| `setActive(w, idx)` | `wgtTabControlSetActive(w, idx)` | Switch to tab by index |
| `getActive(w)` | `wgtTabControlGetActive(w)` | Get active tab index |
### ScrollPane
Scrollable container with internal scrollbars. Child content can
exceed the visible area; the ScrollPane handles clipping and scroll.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtScrollPane(parent)` | Create a scroll pane |
### Splitter
Resizable split between two child regions with a draggable divider.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, vertical)` | `wgtSplitter(parent, vertical)` | Create splitter |
| `setPos(w, pos)` | `wgtSplitterSetPos(w, pos)` | Set divider position (pixels) |
| `getPos(w)` | `wgtSplitterGetPos(w)` | Get divider position |
### Toolbar
Horizontal container for toolbar buttons (typically ImageButtons).
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtToolbar(parent)` | Create a toolbar |
### StatusBar
Single-line text display at the bottom of a window.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent)` | `wgtStatusBar(parent)` | Create a status bar |
Set text via `wgtSetText(statusBar, "text")`.
### Timer
Non-visual widget that fires callbacks at a specified interval.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, intervalMs, repeat)` | `wgtTimer(parent, intervalMs, repeat)` | Create a timer |
| `start(w)` | `wgtTimerStart(w)` | Start the timer |
| `stop(w)` | `wgtTimerStop(w)` | Stop the timer |
| `setInterval(w, intervalMs)` | `wgtTimerSetInterval(w, intervalMs)` | Change interval |
| `isRunning(w)` | `wgtTimerIsRunning(w)` | Check if running |
| `updateTimers()` | `wgtUpdateTimers()` | Process all pending timers |
### AnsiTerm
ANSI terminal emulator widget with scrollback buffer. Supports standard
escape sequences (cursor control, SGR colors, erase, scroll, DEC
private modes). Optionally connects to a comm channel for serial I/O.
| API Function | Macro | Description |
|--------------|-------|-------------|
| `create(parent, cols, rows)` | `wgtAnsiTerm(parent, cols, rows)` | Create terminal |
| `write(w, data, len)` | `wgtAnsiTermWrite(w, data, len)` | Write data to terminal |
| `clear(w)` | `wgtAnsiTermClear(w)` | Clear terminal screen |
| `setComm(w, ctx, readFn, writeFn)` | `wgtAnsiTermSetComm(w, ctx, readFn, writeFn)` | Set comm channel |
| `setScrollback(w, maxLines)` | `wgtAnsiTermSetScrollback(w, maxLines)` | Set scrollback depth |
| `poll(w)` | `wgtAnsiTermPoll(w)` | Poll comm channel |
| `repaint(w, outY, outH)` | `wgtAnsiTermRepaint(w, outY, outH)` | Fast incremental repaint |
## Building ## Build
``` ```
make # compiles all widget .c files, creates .wgt modules in ../bin/widgets/ make # builds all 26 .wgt modules + dep files
make clean make clean # removes objects, .wgt files, and dep files
``` ```
Each `.wgt` is created with `dxe3gen -E _wgt -U`, which exports all Each widget is compiled to a single `.o` file, then packaged via
symbols starting with `wgt` (public API + `wgtRegister`) and allows `dxe3gen` into a `.wgt` DXE module exporting only `wgtRegister`.
unresolved symbols (resolved at load time from other modules).