diff --git a/README.md b/README.md index bfa78c8..cde52a3 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,140 @@ # DVX -- DOS Visual eXecutive -A windowed GUI compositor and desktop shell for DOS, built with -DJGPP/DPMI. DVX combines a Motif-style window manager, dirty-rectangle -compositor, cooperative task switcher, and a modular DXE3 architecture -into a multitasking desktop environment. The bootstrap loader -dynamically discovers and loads core libraries, widget plugins, and -applications at runtime. +A windowed GUI compositor and widget toolkit targeting DJGPP/DPMI on +486+ hardware. VESA VBE 2.0+ LFB only. Motif-inspired visual style +with 2px bevels, fixed bitmap fonts, and dirty-rect compositing. -Targets real and emulated 486+ hardware with VESA VBE 2.0+ linear -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 +The system runs on 86Box (primary target) and real DOS hardware. ## Architecture -DVX uses a modular DXE3 architecture. The bootstrap loader (`dvx.exe`) -is a small executable that discovers, dependency-sorts, and loads all -modules at startup. Core libraries, widget plugins, and applications -are separate `.lib`, `.wgt`, and `.app` DXE shared libraries. +DVX is built as a set of DXE3 dynamic modules loaded by a bootstrap +executable. The loader resolves dependencies and loads modules in +topological order, then hands control to the shell. ``` -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) - | libtasks.lib -- cooperative task switching, stb_ds - | libdvx.lib -- draw, comp, wm, app, widget infrastructure - | dvxshell.lib -- shell, app lifecycle, crash recovery + +-- widgets/*.wgt 26 widget type plugins | - +-- widgets/ (scanned, each calls wgtRegister) - | box.wgt, button.wgt, label.wgt, ... (26 modules) - | - +-- shellMain() -- found via dlsym, enters main loop - | - +-- apps/ (loaded on demand by shell) - progman.app, notepad.app, clock.app, ... + +-- apps/*/*.app DXE applications ``` -### Boot Sequence -1. `dvx.exe` starts, calls `platformRegisterDxeExports()` to register - platform and libc/libm symbols for DXE modules -2. Scans `libs/` recursively for `*.lib` files, reads `.dep` files, - topologically sorts, and loads in dependency order -3. Scans `widgets/` recursively for `*.wgt` files, loads each, and - calls `wgtRegister()` on any module that exports it -4. Finds `shellMain()` across loaded modules via `dlsym` -5. Calls `shellMain()`, which initializes the GUI, loads the desktop - app, and enters the main event loop +## Directory Structure -### 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 -`.wgt` DXE modules. Some modules register multiple widget types: +Requires the DJGPP cross-compiler at `~/djgpp/djgpp`. -| Module | Widget Types | -|--------|-------------| -| `box.wgt` | VBox, HBox, Frame | -| `radio.wgt` | RadioGroup, Radio | -| `textinpt.wgt` | TextInput, TextArea | -| `tabctrl.wgt` | TabControl, TabPage | -| `treeview.wgt` | TreeView, TreeItem | +``` +make # build everything +make clean # remove all build artifacts +./mkcd.sh # build + create ISO for 86Box +``` -Each widget DXE exports a standard `wgtRegister()` function that -registers its widget class(es) with the core widget infrastructure. -Widget DXEs are discovered by directory scanning, not hardcoded. +The top-level Makefile builds in dependency order: -### 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, 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 emulators, games). -### Crash Recovery + +## Crash Recovery The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL. If an app crashes, the handler `longjmp`s back to the shell's main loop, @@ -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`. -## 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 -| App | File | Type | Description | -|-----|------|------|-------------| -| Program Manager | `progman.app` | Callback | App launcher grid with icons; Help menu opens the shell's Task Manager (Ctrl+Esc) | -| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save dialogs, clipboard, and undo | -| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable | -| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating all 32 widget types | -| Control Panel | `cpanel.app` | Callback | System settings: color themes with live preview, wallpaper selection, video mode switching, mouse configuration | -| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, and GIF images with file dialog | +| App | File | Type | Description | +|-----------------|---------------|-----------|------------------------------------------------------------| +| Program Manager | `progman.app` | Callback | App launcher grid, system info dialog | +| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save, clipboard | +| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable | +| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating 31 widget types | +| Control Panel | `cpanel.app` | Callback | Themes, wallpaper, video mode, mouse configuration | +| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, GIF images with aspect-ratio zoom | ## Serial / Networking Stack @@ -360,38 +171,32 @@ standalone `.thm` theme files via the Control Panel. A layered encrypted serial communications stack for connecting DVX to remote systems (BBS, etc.) through 86Box's emulated UART: -| Layer | Library | Description | -|-------|---------|-------------| -| rs232 | `librs232.a` | ISR-driven UART driver with FIFO support, automatic UART type detection (8250 through 16750), configurable baud rate | -| packet | `libpacket.a` | HDLC framing with byte stuffing, CRC-16 integrity checks, Go-Back-N sliding window for reliable delivery, 255-byte max payload | -| security | `libsecurity.a` | 1024-bit Diffie-Hellman key exchange (RFC 2409 Group 2), XTEA-CTR stream cipher, XTEA-CTR DRBG random number generator | -| seclink | `libseclink.a` | Convenience wrapper: multiplexed channels (0--127), per-packet encryption flag, bulk send helper | -| proxy | `secproxy` | Linux-side bridge: 86Box emulated serial port <-> secLink <-> telnet BBS; socket shim replaces rs232 API | +| Layer | Library | Description | +|----------|------------------|---------------------------------------------------| +| rs232 | `librs232.a` | ISR-driven UART driver, ring buffers, flow control | +| packet | `libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ | +| security | `libsecurity.a` | 1024-bit DH, XTEA-CTR cipher, DRBG RNG | +| seclink | `libseclink.a` | Channel multiplexing, per-packet encryption | +| proxy | `secproxy` | Linux bridge: 86Box serial <-> telnet BBS | ## Third-Party Dependencies -All third-party code is vendored as single-header libraries with no -external dependencies: +All third-party code is vendored as single-header libraries: -| Library | Location | Purpose | -|---------|----------|---------| -| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) | -| stb_image_write.h | `core/thirdparty/` | Image writing (PNG export for screenshots) | -| stb_ds.h | `tasks/thirdparty/` | Dynamic array and hash map (used by task manager) | +| Library | Location | Purpose | +|-------------------|--------------------|---------------------------------| +| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) | +| stb_image_write.h | `core/thirdparty/` | PNG export for screenshots | +| stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps | + +stb_ds implementation is compiled into dvx.exe (the loader) and +exported via `dlregsym` to all DXE modules. -## Component Documentation +## Target Hardware -Each component directory has its own README with detailed API reference: - -- [`core/README.md`](core/README.md) -- Core GUI library architecture and API -- [`tasks/README.md`](tasks/README.md) -- Task switcher API -- [`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 +* CPU: 486 baseline, Pentium-optimized paths where significant +* Video: VESA VBE 2.0+ with LFB (no bank switching) +* Platform: 86Box emulator (trusted reference), real DOS hardware +* Resolutions: 640x480, 800x600, 1024x768 at 8/15/16/32 bpp diff --git a/apps/README.md b/apps/README.md index b08954d..6682f09 100644 --- a/apps/README.md +++ b/apps/README.md @@ -1,443 +1,178 @@ # DVX Shell Applications -DXE3 application modules for the DVX Shell. Each app is a `.app` file (DXE3 -shared object format) placed in a subdirectory under `apps/`. The Program -Manager scans this directory recursively at startup and displays all discovered -apps in a launcher grid. +DXE3 application modules for the DVX Shell. Each app is a `.app` file +(DXE3 shared object) in a subdirectory under `apps/`. The Program +Manager scans this directory at startup and displays all discovered +apps as launchable icons. -## App Contract -Every DXE app must export two symbols and may optionally export a third: +## DXE App Contract -```c -// Required: app metadata -AppDescriptorT appDescriptor = { - .name = "My App", - .hasMainLoop = false, - .multiInstance = false, - .stackSize = SHELL_STACK_DEFAULT, - .priority = TS_PRIORITY_NORMAL -}; +Every app exports two symbols: -// Required: entry point -- called once by the shell after dlopen -int32_t appMain(DxeAppContextT *ctx); +* `appDescriptor` (`AppDescriptorT`) -- name, hasMainLoop, multiInstance, stackSize, priority +* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point -// Optional: graceful shutdown hook -- called before force-kill -void appShutdown(void); -``` +Optional: `appShutdown` (`void appShutdown(void)`) -- called during +graceful shutdown. -`appMain` receives a `DxeAppContextT` with: +### Callback-Only Apps (hasMainLoop = false) -| Field | Type | Description | -|-------|------|-------------| -| `shellCtx` | `AppContextT *` | The shell's GUI context -- pass to all `dvx*`/`wgt*` calls | -| `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//`) | +`appMain()` creates windows, registers callbacks, and returns 0. The +shell drives everything through event callbacks. No dedicated task or +stack is allocated. Lifecycle ends when the last window closes. -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// -make clean # removes objects and binaries +make # builds all 6 app DXE modules +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 -packaged into a `.app` via `dxe3gen`: +Output goes to `bin/apps//.app`. -```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 - 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 +## Files ``` apps/ - Makefile -- build rules for all apps - README.md -- this file - progman/ - progman.c -- Program Manager (desktop shell) - notepad/ - notepad.c -- text editor - clock/ - clock.c -- digital clock - cpanel/ - cpanel.c -- Control Panel (system settings) - imgview/ - imgview.c -- image viewer - dvxdemo/ - dvxdemo.c -- widget showcase - *.bmp -- toolbar icons and sample images + Makefile top-level build for all apps + progman/ + progman.c Program Manager + notepad/ + notepad.c text editor + clock/ + clock.c digital clock + dvxdemo/ + dvxdemo.c widget demo + 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// 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//` instead. diff --git a/config/README.md b/config/README.md index 54ff61f..97fb39b 100644 --- a/config/README.md +++ b/config/README.md @@ -1,93 +1,153 @@ # DVX Configuration Files Runtime configuration, theme files, wallpaper images, and module -dependency files. These are copied into `bin/config/` (INI, themes, -wallpapers) or `bin/libs/` (dep files) during the build. +dependency files. These are copied into `bin/config/` (INI, themes, +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 -| File | Destination | Purpose | -|------|-------------|---------| -| `dvx.ini` | `bin/config/dvx.ini` | Main configuration (video, mouse, colors, desktop) | -| `themes/*.thm` | `bin/config/themes/` | Color theme files (INI format with `[colors]` section) | -| `wpaper/*.jpg` | `bin/config/wpaper/` | Bundled wallpaper images | -| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx.lib dependency list | -| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell.lib dependency list | +| Source File | Build Output | Description | +|-------------|-------------|-------------| +| `dvx.ini` | `bin/config/dvx.ini` | System configuration | +| `themes/cde.thm` | `bin/config/themes/cde.thm` | CDE color theme | +| `themes/geos.thm` | `bin/config/themes/geos.thm` | GEOS Ensemble theme | +| `themes/win31.thm` | `bin/config/themes/win31.thm` | Windows 3.1 theme | +| `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 -line endings (`\r\n`) during the build. +Standard INI format with `[section]` headers and `key = value` pairs. +Comments start with `;`. The shell loads this at startup via +`prefsLoad("CONFIG/DVX.INI")`. -```ini -[video] -width = 640 -height = 480 -bpp = 16 +### [video] Section -[mouse] -wheel = normal # normal | reversed -doubleclick = 500 # milliseconds (200-900) -acceleration = medium # off | low | medium | high +| Key | Default | Description | +|-----|---------|-------------| +| `width` | 640 | Requested horizontal resolution | +| `height` | 480 | Requested vertical resolution | +| `bpp` | 16 | Preferred color depth (8, 15, 16, 24, 32) | -[colors] -desktop = 0,128,128 -windowFace = 192,192,192 -# ... 20 color keys total (R,G,B triplets 0-255) +The system picks the closest available VESA mode. -[desktop] -wallpaper = CONFIG\WPAPER\SWOOP.JPG -mode = stretch # stretch | tile | center -``` +### [mouse] Section +| 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 -(without extension) of a module that must be loaded before this one. -Lines starting with `#` are comments. Empty lines are ignored. +| Key | Default | Description | +|-----|---------|-------------| +| `desktop` | apps/progman/progman.app | Path to the desktop app loaded at startup | -Example -- `dvxshell.dep`: -``` -# Core libraries -libtasks -libdvx +### [colors] Section -# Widget modules used by the shell -box -button -checkbox -dropdown -label -listbox -listview -radio -separator -spacer -statbar -textinpt -``` +All 20 system colors as `R,G,B` triplets (0-255). Missing keys use +compiled-in defaults. -Dep files are read by the loader during startup to determine the -correct load order via topological sort. +| Key | Description | +|-----|-------------| +| `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) -INI format with a `[colors]` section containing the same 20 color -keys as `dvx.ini`. Loaded via the Control Panel app or -`dvxLoadTheme()`. +Theme files use the same INI format as dvx.ini but contain only a +`[colors]` section with the 20 color keys. The Control Panel can +load and save themes via `dvxLoadTheme()` / `dvxSaveTheme()`. -```ini -[colors] -desktop = 0,128,128 -windowFace = 192,192,192 -windowHighlight = 255,255,255 -windowShadow = 128,128,128 -; ... remaining colors -``` +Bundled themes: -Three themes are bundled: `geos.thm` (GEOS Ensemble), `win31.thm` -(Windows 3.1), `cde.thm` (CDE/Motif). +| File | Description | +|------|-------------| +| `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. diff --git a/core/README.md b/core/README.md index f3638d9..57eb162 100644 --- a/core/README.md +++ b/core/README.md @@ -1,1271 +1,298 @@ # DVX Core Library (libdvx.lib) -The core GUI compositor library for DVX, built as a DXE module -(libdvx.lib). Provides VESA video setup, 2D drawing primitives, -dirty-rectangle compositing, a full window manager with Motif-style -chrome, and the widget infrastructure (layout engine, event dispatch, -class registration). Individual widget type implementations live in -`../widgets/` as separate `.wgt` DXE modules that register themselves -at runtime via `wgtRegisterClass()`. +The core GUI infrastructure for DVX, built as a DXE3 module. Provides +VESA video setup, 2D drawing primitives, dirty-rectangle compositing, +a window manager with Motif-style chrome, and the widget infrastructure +(layout engine, event dispatch, class registration). Individual widget +type implementations live in `../widgets/` as separate `.wgt` DXE +modules that register themselves at runtime via `wgtRegisterClass()`. -Applications include `dvxApp.h` (which pulls in all lower layers) and -optionally `dvxWidget.h` for the widget system. In a DXE app context, -headers are found via `-I` flags set by the build system. +Core knows nothing about individual widget types. There is no +WidgetTypeE enum, no widget union, and no per-widget structs in +dvxWidget.h. All widget-specific behavior is dispatched through the +WidgetClassT vtable. -## Architecture +## 5-Layer Architecture -The library is organized in five layers, each a `.h`/`.c` pair. Higher -layers depend on lower ones but never the reverse. +| Layer | Header | Source | Description | +|-------|--------|--------|-------------| +| 1. Video | `dvxVideo.h` | `dvxVideo.c` | VESA VBE init, LFB mapping, backbuffer, pixel format, `packColor()` | +| 2. Draw | `dvxDraw.h` | `dvxDraw.c` | Rect fills, bevels, text, bitmap cursors, focus rects, lines | +| 3. Compositor | `dvxComp.h` | `dvxComp.c` | Dirty rect tracking, merge, clip, LFB flush | +| 4. Window Manager | `dvxWm.h` | `dvxWm.c` | Window stack, chrome, drag/resize, menus, scrollbars, hit test | +| 5. Application | `dvxApp.h` | `dvxApp.c` | Event loop, input polling, color schemes, wallpaper, public API | -``` -Layer 5 dvxApp Event loop, mouse/keyboard input, public API -Layer 4 dvxWm Window stack, chrome, drag, resize, focus, menus -Layer 3 dvxComp Dirty rectangle list, merge, LFB flush -Layer 2 dvxDraw Spans, rects, bevels, text, bitmaps (asm inner loops) -Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format - dvxWidget Widget infrastructure (layout, events, class table) -``` +Additional modules built into libdvx.lib: -The widget class table (`widgetClassTable[]`) starts empty. Widget DXE -modules (`.wgt` files in `../widgets/`) fill it at runtime by calling -registration functions (e.g. `wgtBoxRegister()`, `wgtButtonRegister()`). -The DVX shell loader loads each widget DXE and calls its register -function before starting the application. - -Dynamic widget registration (Phase 2): external DXE plugins can call -`wgtRegisterClass()` to add entirely new widget types at runtime. The -returned type ID is >= `WGT_TYPE_DYNAMIC_BASE` (32). The maximum number -of widget types (built-in + dynamic) is `WGT_MAX_TYPES` (64). +| Header | Source | Description | +|--------|--------|-------------| +| `dvxDialog.h` | `dvxDialog.c` | Modal message box and file open/save dialogs | +| `dvxPrefs.h` | `dvxPrefs.c` | INI-based preferences (read/write with typed accessors) | +| `dvxWidget.h` | `widgetClass.c`, `widgetCore.c`, `widgetEvent.c`, `widgetLayout.c`, `widgetOps.c`, `widgetScrollbar.c` | Widget infrastructure | +| `dvxWidgetPlugin.h` | (header only) | Plugin API for widget DXE modules | +| -- | `dvxImage.c` | Image loading via stb_image (BMP, PNG, JPEG, GIF) | +| -- | `dvxImageWrite.c` | PNG export via stb_image_write | -## File Structure +## Source Files -### Core Layers - -| File | Purpose | -|------|---------| -| `dvxVideo.h/.c` | Layer 1: VESA VBE mode negotiation, LFB mapping, system RAM backbuffer, pixel format discovery, color packing | -| `dvxDraw.h/.c` | Layer 2: Rectangle fills, bitmap blits, text rendering, bevels, lines, cursor/icon rendering | -| `dvxComp.h/.c` | Layer 3: Dirty rectangle tracking, merge, backbuffer-to-LFB flush | -| `dvxWm.h/.c` | Layer 4: Window lifecycle, Z-order stack, chrome drawing, hit testing, drag/resize/scroll | -| `dvxApp.h/.c` | Layer 5: AppContextT, event loop, window creation, color scheme, wallpaper, screenshots | - -### Supporting Files - -| File | Purpose | -|------|---------| -| `dvxTypes.h` | Shared type definitions used by all layers (PixelFormatT, DisplayT, WindowT, ColorSchemeT, etc.) | -| `dvxWidget.h` | Widget system public API, WidgetClassT vtable struct, WCLASS_* flags (for DXE plugins) | -| `dvxDialog.h/.c` | Modal dialogs (message box, file open/save) | -| `dvxPrefs.h/.c` | INI-based preferences system (read/write with typed accessors) | -| `dvxFont.h` | Embedded 8x16 VGA bitmap font glyph data (CP437, 256 glyphs) | -| `dvxCursor.h` | Mouse cursor bitmask data (5 shapes: arrow, resize H/V/NWSE/NESW) | -| `dvxPalette.h` | Default VGA palette for 8-bit mode | -| `dvxIcon.c` | stb_image implementation unit (BMP/PNG/JPEG/GIF loading) | -| `dvxImageWrite.c` | stb_image_write implementation unit (PNG export) | - -### Widget Infrastructure - -The widget infrastructure lives in this directory. It provides the -layout engine, event dispatch, class table, scrollbar primitives, and -shared helpers that all widget types depend on. The actual widget type -implementations (button, checkbox, listbox, etc.) are individual `.wgt` -DXE modules in `../widgets/`. - -| File | Purpose | -|------|---------| -| `dvxWidgetPlugin.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes | -| `widgetClass.c` | Widget class table (`widgetClassTable[]`, starts empty) and `wgtRegisterClass()` dynamic registration | -| `widgetCore.c` | Widget allocation, tree ops, hit testing, focus management, shared helpers | -| `widgetScrollbar.c` | Widget-internal scrollbar drawing and hit testing (shared by ListBox, TreeView, etc.) | -| `widgetLayout.c` | Two-pass layout engine: measure (bottom-up) + arrange (top-down) | -| `widgetEvent.c` | Mouse, keyboard, scroll, resize, and paint event dispatch to widget tree | -| `widgetOps.c` | Paint dispatch, overlay rendering, public widget operations (wgtFind, wgtDestroy, etc.) | - -### Platform Abstraction - -| File | Purpose | -|------|---------| -| `platform/dvxPlatform.h` | OS/CPU-neutral interface: video, input, span ops, filesystem | -| `platform/dvxPlatformDos.c` | DOS/DJGPP: VESA, DPMI, INT 16h/33h, rep stosl/movsd asm spans, DXE export table | - -To port DVX to a new platform, implement a new `dvxPlatformXxx.c` -against `platform/dvxPlatform.h` and swap it in the Makefile. No other -files need modification. - -### Third Party - -| File | Purpose | -|------|---------| -| `thirdparty/stb_image.h` | Single-header image loader (BMP, PNG, JPEG, GIF) | -| `thirdparty/stb_image_write.h` | Single-header image writer (PNG) | +| File | Description | +|------|-------------| +| `dvxVideo.c` | VESA mode negotiation, LFB mapping via DPMI, backbuffer alloc, `packColor()` | +| `dvxDraw.c` | `rectFill()`, `rectCopy()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, cursor rendering | +| `dvxComp.c` | `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` | +| `dvxWm.c` | Window create/destroy, Z-order, chrome drawing, drag/resize, menu bar, scrollbars, minimize/maximize | +| `dvxApp.c` | `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, screenshots | +| `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops | +| `dvxPrefs.c` | `prefsLoad()`, `prefsSave()`, typed get/set for string/int/bool | +| `dvxImage.c` | `dvxLoadImage()` -- stb_image loader, converts to native pixel format | +| `dvxImageWrite.c` | `dvxSaveImage()` -- PNG writer for screenshots | +| `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table | +| `widgetCore.c` | Widget allocation, tree ops, focus management, clipboard, hit testing, cursor blink | +| `widgetEvent.c` | `widgetOnMouse()`, `widgetOnKey()`, `widgetOnPaint()`, `widgetOnResize()`, scrollbar management | +| `widgetLayout.c` | Two-pass flexbox layout: bottom-up `calcMinSize`, top-down space allocation with weights | +| `widgetOps.c` | `wgtPaint()`, `wgtLayout()`, `wgtInitWindow()`, text get/set, invalidation | +| `widgetScrollbar.c` | Scrollbar drawing (H/V), thumb calculation, hit testing, drag update | -## Building +## Public Headers -Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`). - -```bash -make # builds ../bin/libs/libdvx.lib and ../bin/libs/libdvx.dep -make clean -``` - -The Makefile uses `dxe3gen` to produce the DXE module. Internal symbols -are exported with `-E` prefixes so that widget DXE modules can link -against them at runtime (`-E _widget`, `-E _sCursor`, `-E _dvx`, -`-E _wgt`, `-E _wm`, `-E _draw`, etc.). The dependency file -`libdvx.dep` is copied from `../config/libdvx.dep` to `../bin/libs/`. - -Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed -somewhere other than `~/djgpp/djgpp`. +| Header | Purpose | +|--------|---------| +| `dvxTypes.h` | All shared types: DisplayT, RectT, BlitOpsT, BevelStyleT, BitmapFontT, ColorSchemeT, WindowT, MenuT, ScrollbarT, CursorT, PopupStateT | +| `dvxVideo.h` | `videoInit()`, `videoShutdown()`, `packColor()`, `setClipRect()`, `resetClipRect()` | +| `dvxDraw.h` | All drawing functions: `rectFill()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, etc. | +| `dvxComp.h` | Dirty list operations: `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` | +| `dvxWm.h` | Window management: `wmCreateWindow()`, `wmDestroyWindow()`, `wmRaiseWindow()`, menus, scrollbars, chrome | +| `dvxApp.h` | Application API: `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, image I/O | +| `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` | +| `dvxPrefs.h` | INI preferences: `prefsLoad()`, `prefsSave()`, typed accessors | +| `dvxWidget.h` | Widget system public API: WidgetT, WidgetClassT, size tags, layout, API registry | +| `dvxWidgetPlugin.h` | Plugin API for widget DXE authors: tree ops, focus, scrollbar helpers, shared state | +| `dvxFont.h` | Embedded 8x14 and 8x16 bitmap font data (CP437) | +| `dvxCursor.h` | Mouse cursor AND/XOR mask data (arrow, resize H/V/diag, busy) | +| `dvxPalette.h` | Default 256-color VGA palette for 8-bit mode | ---- +## Platform Layer + +| File | Description | +|------|-------------| +| `platform/dvxPlatform.h` | Platform abstraction API (video, input, spans, DXE, crash recovery) | +| `platform/dvxPlatformDos.c` | DJGPP/DPMI implementation (VESA VBE, INT 33h mouse, INT 16h keyboard, asm spans) | + +The platform layer is compiled into dvx.exe (the loader), not into +libdvx.lib. Platform functions are exported to all DXE modules via +`platformRegisterDxeExports()`. -## Quick Start +## Third-Party Libraries -### Minimal window (raw drawing) +| File | Description | +|------|-------------| +| `thirdparty/stb_image.h` | Image loading (implementation compiled into dvxImage.c) | +| `thirdparty/stb_image_write.h` | PNG writing (implementation compiled into dvxImageWrite.c) | +| `thirdparty/stb_ds.h` | Dynamic arrays/hash maps (implementation in loader, exported to all DXEs) | + + +## WidgetT Structure + +The WidgetT struct is generic -- no widget-specific fields or union: ```c -#include "dvxApp.h" +typedef struct WidgetT { + int32_t type; // assigned by wgtRegisterClass() + const struct WidgetClassT *wclass; // vtable pointer + char name[32]; -static void onPaint(WindowT *win, RectT *dirty) { - AppContextT *ctx = (AppContextT *)win->userData; - const BlitOpsT *ops = dvxGetBlitOps(ctx); - const DisplayT *d = dvxGetDisplay(ctx); - uint32_t blue = packColor(d, 0, 0, 200); + // Tree linkage + struct WidgetT *parent, *firstChild, *lastChild, *nextSibling; + WindowT *window; - for (int32_t y = 0; y < win->contentH; y++) { - ops->spanFill(win->contentBuf + y * win->contentPitch, - blue, win->contentW); - } -} + // Geometry (relative to window content area) + int32_t x, y, w, h; + int32_t calcMinW, calcMinH; // computed minimum size -int main(void) { - AppContextT ctx; - if (dvxInit(&ctx, 1024, 768, 16) != 0) { - return 1; - } + // Size hints (tagged: wgtPixels/wgtChars/wgtPercent, 0 = auto) + int32_t minW, minH, maxW, maxH; + int32_t prefW, prefH; + int32_t weight; // extra-space distribution (0 = fixed) - WindowT *win = dvxCreateWindow(&ctx, "Hello", 100, 100, 300, 200, true); - win->userData = &ctx; - win->onPaint = onPaint; + // Container properties + WidgetAlignE align; + int32_t spacing, padding; // tagged sizes - RectT r = {0, 0, win->contentW, win->contentH}; - win->onPaint(win, &r); + // Colors (0 = use color scheme defaults) + uint32_t fgColor, bgColor; - dvxRun(&ctx); - dvxShutdown(&ctx); - return 0; -} + // State + bool visible, enabled, readOnly, focused; + char accelKey; + + // User data and callbacks + void *userData; + void *data; // widget-private data (allocated by widget DXE) + const char *tooltip; + MenuT *contextMenu; + void (*onClick)(struct WidgetT *w); + void (*onDblClick)(struct WidgetT *w); + void (*onChange)(struct WidgetT *w); + void (*onFocus)(struct WidgetT *w); + void (*onBlur)(struct WidgetT *w); +} WidgetT; ``` -### Widget-based window + +## WidgetClassT Vtable + +Each widget type defines a static WidgetClassT with flags and function pointers. +The vtable has 26 function slots plus a flags field: + +| Slot | Signature | Purpose | +|------|-----------|---------| +| `flags` | `uint32_t` | Static properties (see Flags below) | +| `paint` | `(w, d, ops, font, colors)` | Render the widget | +| `paintOverlay` | `(w, d, ops, font, colors)` | Render overlay (dropdown popup) | +| `calcMinSize` | `(w, font)` | Compute minimum size (bottom-up pass) | +| `layout` | `(w, font)` | Position children (top-down pass) | +| `getLayoutMetrics` | `(w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout | +| `onMouse` | `(w, root, vx, vy)` | Handle mouse click | +| `onKey` | `(w, key, mod)` | Handle keyboard input | +| `onAccelActivate` | `(w, root)` | Handle accelerator key match | +| `destroy` | `(w)` | Free widget-private data | +| `onChildChanged` | `(parent, child)` | Notification when a child changes | +| `getText` | `(w)` | Return widget text | +| `setText` | `(w, text)` | Set widget text | +| `clearSelection` | `(w)` | Clear text/item selection | +| `dragSelect` | `(w, root, vx, vy)` | Handle drag selection | +| `openPopup` | `(w)` | Open dropdown popup | +| `closePopup` | `(w)` | Close dropdown popup | +| `getPopupItemCount` | `(w)` | Return number of popup items | +| `getPopupRect` | `(w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle | +| `setPressed` | `(w, pressed)` | Set button press visual state | +| `reorderDrop` | `(w)` | Complete drag-reorder operation | +| `reorderUpdate` | `(w, root, x, y)` | Update drag-reorder position | +| `getCursorShape` | `(w, vx, vy)` | Return cursor ID for this position | +| `poll` | `(w, win)` | Periodic polling (AnsiTerm comms) | +| `quickRepaint` | `(w, outY, outH)` | Fast incremental repaint | +| `getTextFieldWidth` | `(w)` | Text field width for scroll calc | +| `scrollDragUpdate` | `(w, orient, dragOff, mouseX, mouseY)` | Scrollbar drag update | + +### WidgetClassT Flags + +| Flag | Value | Description | +|------|-------|-------------| +| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus | +| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses VBox/HBox layout algorithm | +| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally | +| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering | +| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here | +| `WCLASS_FOCUS_FORWARD` | 0x0020 | Accel hit forwards focus to next focusable | +| `WCLASS_HAS_POPUP` | 0x0040 | Has dropdown popup overlay | +| `WCLASS_SCROLLABLE` | 0x0080 | Accepts mouse wheel events | +| `WCLASS_SCROLL_CONTAINER` | 0x0100 | Scroll container (ScrollPane) | +| `WCLASS_NEEDS_POLL` | 0x0200 | Needs periodic polling | +| `WCLASS_SWALLOWS_TAB` | 0x0400 | Tab key goes to widget, not focus nav | +| `WCLASS_RELAYOUT_ON_SCROLL` | 0x0800 | Full relayout on scrollbar drag | +| `WCLASS_PRESS_RELEASE` | 0x1000 | Click = press+release (buttons) | +| `WCLASS_ACCEL_WHEN_HIDDEN` | 0x2000 | Accel matching works when invisible | + + +## Widget Registration + +Each widget DXE exports `wgtRegister()`, called by the loader after +`dlopen`. A typical registration: ```c -#include "dvxApp.h" -#include "dvxWidget.h" - -static void onButtonClick(WidgetT *w) { - WidgetT *root = w; - while (root->parent) root = root->parent; - - WidgetT *lbl = wgtFind(root, "status"); - if (lbl) { - wgtSetText(lbl, "Clicked!"); - wgtInvalidate(lbl); - } -} - -int main(void) { - AppContextT ctx; - dvxInit(&ctx, 1024, 768, 16); - - WindowT *win = dvxCreateWindow(&ctx, "Widgets", 100, 100, 260, 200, true); - win->userData = &ctx; - - WidgetT *root = wgtInitWindow(&ctx, win); - - WidgetT *lbl = wgtLabel(root, "Ready."); - wgtSetName(lbl, "status"); - - wgtHSeparator(root); - - WidgetT *row = wgtHBox(root); - wgtLabel(row, "Name:"); - wgtTextInput(row, 64); - - WidgetT *btn = wgtButton(root, "Go"); - btn->onClick = onButtonClick; - - wgtInvalidate(root); - - dvxRun(&ctx); - dvxShutdown(&ctx); - return 0; -} -``` - - ---- - - -## Application API (dvxApp.h) - -### Lifecycle - -```c -int32_t dvxInit(AppContextT *ctx, int32_t requestedW, - int32_t requestedH, int32_t preferredBpp); -``` -Initialize VESA video, input, fonts, color scheme, and cursors. Finds -a mode matching the requested resolution and bit depth. Returns 0 on -success, -1 on failure. - -```c -void dvxRun(AppContextT *ctx); -``` -Enter the main event loop. Handles mouse, keyboard, window management, -compositing, and LFB flush. Returns when `dvxQuit()` is called. - -```c -bool dvxUpdate(AppContextT *ctx); -``` -Process one iteration of the event loop. Returns `true` if still -running, `false` when exit requested. Use instead of `dvxRun()` when -embedding the GUI in an existing main loop. - -```c -void dvxShutdown(AppContextT *ctx); -``` -Restore text mode, release LFB mapping, free the backbuffer. - -```c -void dvxQuit(AppContextT *ctx); -``` -Request exit from the main loop. - -### Video Mode - -```c -int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, - int32_t requestedH, int32_t preferredBpp); -``` -Switch to a new video mode live. Reallocates the backbuffer, all window -content buffers, repacks colors, rescales wallpaper, and repositions -windows. Returns 0 on success, -1 on failure (old mode restored). - -```c -const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx, - int32_t *count); -``` -Return the list of available VESA modes (enumerated at init). - -### Windows - -```c -WindowT *dvxCreateWindow(AppContextT *ctx, const char *title, - int32_t x, int32_t y, int32_t w, int32_t h, - bool resizable); -WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, - int32_t w, int32_t h, bool resizable); -void dvxDestroyWindow(AppContextT *ctx, WindowT *win); -``` - -After creation, set `win->userData` and install callbacks: - -| Callback | Signature | When Called | -|----------|-----------|------------| -| `onPaint` | `void (WindowT *win, RectT *dirtyArea)` | Content needs redrawing | -| `onKey` | `void (WindowT *win, int32_t key, int32_t mod)` | Key press (focused window) | -| `onMouse` | `void (WindowT *win, int32_t x, int32_t y, int32_t buttons)` | Mouse event in content area | -| `onResize` | `void (WindowT *win, int32_t newW, int32_t newH)` | Window resized by user | -| `onClose` | `void (WindowT *win)` | Close button double-clicked | -| `onMenu` | `void (WindowT *win, int32_t menuId)` | Menu item selected or accelerator triggered | -| `onScroll` | `void (WindowT *win, ScrollbarOrientE orient, int32_t value)` | Scrollbar moved | - -Mouse/key coordinates are relative to the content area. `buttons` is -a bitmask: `MOUSE_LEFT` (1), `MOUSE_RIGHT` (2), `MOUSE_MIDDLE` (4). - -Example: - -```c -static void onClose(WindowT *win) { - AppContextT *ctx = (AppContextT *)win->userData; - dvxDestroyWindow(ctx, win); -} - -WindowT *win = dvxCreateWindow(&ctx, "My Window", 50, 50, 400, 300, true); -win->userData = &ctx; -win->onClose = onClose; -win->onPaint = myPaintHandler; -``` - -### Window Properties - -Set directly on WindowT after creation: - -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `maxW` | `int32_t` | -1 | Maximum width when maximized (-1 = screen width) | -| `maxH` | `int32_t` | -1 | Maximum height when maximized (-1 = screen height) | -| `modal` | `bool` | false | When true, only this window receives input | -| `userData` | `void *` | NULL | Application data pointer | - -### Window Operations - -```c -void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title); -int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path); -void dvxMinimizeWindow(AppContextT *ctx, WindowT *win); -void dvxMaximizeWindow(AppContextT *ctx, WindowT *win); -void dvxFitWindow(AppContextT *ctx, WindowT *win); -``` - -`dvxFitWindow` resizes the window to exactly fit its widget tree's -computed minimum size (plus chrome). Useful for dialog boxes. - -### Window Arrangement - -```c -dvxCascadeWindows(&ctx); // staggered diagonal cascade -dvxTileWindows(&ctx); // NxM grid fill -dvxTileWindowsH(&ctx); // side-by-side, equal width -dvxTileWindowsV(&ctx); // stacked, equal height -``` - -### Invalidation - -```c -void dvxInvalidateRect(AppContextT *ctx, WindowT *win, - int32_t x, int32_t y, int32_t w, int32_t h); -void dvxInvalidateWindow(AppContextT *ctx, WindowT *win); -``` - -Mark a region (or the entire content area) as needing repaint. The -compositor flushes dirty rectangles to the LFB on the next frame. - -### Content Buffer - -Each window has a persistent content backbuffer in display pixel format: - -| Field | Description | -|-------|-------------| -| `contentBuf` | Pixel data in native display format | -| `contentPitch` | Bytes per scanline | -| `contentW` | Width in pixels | -| `contentH` | Height in pixels | - -Paint callbacks write directly into `contentBuf`. The compositor copies -visible portions to the screen backbuffer, then flushes dirty rects to -the LFB. - -### Clipboard - -```c -dvxClipboardCopy("Hello", 5); - -int32_t len; -const char *text = dvxClipboardGet(&len); -``` - -Process-wide static buffer. Adequate for copy/paste within DVX on -single-tasking DOS. - -### Screenshots - -```c -dvxScreenshot(&ctx, "screen.png"); // entire screen -dvxWindowScreenshot(&ctx, win, "window.png"); // single window content -``` - -Converts from native pixel format to RGB for the PNG encoder. - -### Image Loading - -```c -uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, - int32_t *outW, int32_t *outH, int32_t *outPitch); -void dvxFreeImage(uint8_t *data); -int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data, - int32_t w, int32_t h, int32_t pitch, const char *path); -``` - -Load BMP/PNG/JPEG/GIF files and convert to the display's native pixel -format. Save native-format pixel data to PNG. - -### Accessors - -```c -DisplayT *dvxGetDisplay(AppContextT *ctx); -const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx); -const BitmapFontT *dvxGetFont(const AppContextT *ctx); -const ColorSchemeT *dvxGetColors(const AppContextT *ctx); -``` - -### Mouse Configuration - -```c -void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, - int32_t dblClickMs, int32_t accelThreshold); -``` - -`wheelDir`: 1 = normal, -1 = reversed. -`dblClickMs`: double-click speed in milliseconds. -`accelThreshold`: double-speed threshold in mickeys/sec (0 = unchanged). - - ---- - - -## Color System - -DVX uses a 20-color scheme that controls the entire UI appearance. All -colors are pre-packed into native pixel format at init time for -zero-cost per-pixel rendering. - -### ColorSchemeT Fields - -| Field | Usage | -|-------|-------| -| `desktop` | Desktop background fill | -| `windowFace` | Window frame fill | -| `windowHighlight` | Bevel light edge (top/left) | -| `windowShadow` | Bevel dark edge (bottom/right) | -| `activeTitleBg` / `activeTitleFg` | Focused window title bar | -| `inactiveTitleBg` / `inactiveTitleFg` | Unfocused window title bar | -| `contentBg` / `contentFg` | Window content area defaults | -| `menuBg` / `menuFg` | Menu bar and popup menus | -| `menuHighlightBg` / `menuHighlightFg` | Highlighted menu item | -| `buttonFace` | Push button interior | -| `scrollbarBg` / `scrollbarFg` / `scrollbarTrough` | Scrollbar elements | -| `cursorFg` / `cursorBg` | Mouse cursor colors | - -### ColorIdE Enum - -Each color has an integer ID for programmatic access: - -``` -ColorDesktopE, ColorWindowFaceE, ColorWindowHighlightE, -ColorWindowShadowE, ColorActiveTitleBgE, ColorActiveTitleFgE, -ColorInactiveTitleBgE, ColorInactiveTitleFgE, ColorContentBgE, -ColorContentFgE, ColorMenuBgE, ColorMenuFgE, ColorMenuHighlightBgE, -ColorMenuHighlightFgE, ColorButtonFaceE, ColorScrollbarBgE, -ColorScrollbarFgE, ColorScrollbarTroughE, ColorCursorFgE, -ColorCursorBgE, ColorCountE -``` - -### Color API - -```c -void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b); -void dvxGetColor(const AppContextT *ctx, ColorIdE id, uint8_t *r, uint8_t *g, uint8_t *b); -void dvxApplyColorScheme(AppContextT *ctx); -void dvxResetColorScheme(AppContextT *ctx); -``` - -### Theme Files - -Theme files are INI-format with a `[colors]` section containing RGB -values for each of the 20 color IDs: - -```c -bool dvxLoadTheme(AppContextT *ctx, const char *filename); -bool dvxSaveTheme(const AppContextT *ctx, const char *filename); -const char *dvxColorName(ColorIdE id); // INI key name -const char *dvxColorLabel(ColorIdE id); // human-readable label -``` - -Bundled themes: `cde.thm`, `geos.thm`, `win31.thm`. - - ---- - - -## Wallpaper System - -```c -bool dvxSetWallpaper(AppContextT *ctx, const char *path); -void dvxSetWallpaperMode(AppContextT *ctx, WallpaperModeE mode); -``` - -Three display modes: - -| Mode | Enum | Description | -|------|------|-------------| -| Stretch | `WallpaperStretchE` | Bilinear scaling to fill screen; ordered dithering for 16bpp | -| Tile | `WallpaperTileE` | Repeating pattern from top-left | -| Center | `WallpaperCenterE` | Centered with desktop color fill around edges | - -The wallpaper is pre-rendered to screen dimensions in native pixel -format. Pass NULL to `dvxSetWallpaper` to clear. The wallpaper path is -stored so the image can be reloaded after a video mode change. - -Supported formats: BMP, PNG, JPEG, GIF (via stb_image). - - ---- - - -## Preferences System (dvxPrefs.h) - -INI-based configuration with typed read/write accessors and -caller-supplied defaults. - -```c -bool prefsLoad(const char *filename); -bool prefsSave(void); -bool prefsSaveAs(const char *filename); -void prefsFree(void); - -const char *prefsGetString(const char *section, const char *key, const char *defaultVal); -int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal); -bool prefsGetBool(const char *section, const char *key, bool defaultVal); - -void prefsSetString(const char *section, const char *key, const char *value); -void prefsSetInt(const char *section, const char *key, int32_t value); -void prefsSetBool(const char *section, const char *key, bool value); -void prefsRemove(const char *section, const char *key); -``` - -Only one file may be loaded at a time. If the file is missing or a key -is absent, getters return the caller-supplied default silently. Boolean -values recognize `true`/`yes`/`1` and `false`/`no`/`0`. - - ---- - - -## Menu System - -### Menu Bars - -```c -MenuBarT *wmAddMenuBar(WindowT *win); -MenuT *wmAddMenu(MenuBarT *bar, const char *label); -void wmAddMenuItem(MenuT *menu, const char *label, int32_t id); -void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked); -void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked); -void wmAddMenuSeparator(MenuT *menu); -MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label); -``` - -The `&` prefix marks accelerator keys -- `"&File"` means Alt+F opens -the menu. Up to 8 menus per bar, 16 items per menu, submenus nested up -to 4 levels deep. - -After adding menus, call `wmUpdateContentRect(win)` and -`wmReallocContentBuf(win, &ctx.display)` to adjust the content area. - -Example: - -```c -MenuBarT *bar = wmAddMenuBar(win); - -MenuT *fileMenu = wmAddMenu(bar, "&File"); -wmAddMenuItem(fileMenu, "&New", CMD_NEW); -wmAddMenuItem(fileMenu, "&Open...", CMD_OPEN); -wmAddMenuSeparator(fileMenu); -wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT); - -MenuT *viewMenu = wmAddMenu(bar, "&View"); -wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_TOOLBAR, true); -wmAddMenuRadioItem(viewMenu, "&Small", CMD_SMALL, false); -wmAddMenuRadioItem(viewMenu, "&Large", CMD_LARGE, true); - -MenuT *zoomMenu = wmAddSubMenu(viewMenu, "&Zoom"); -wmAddMenuItem(zoomMenu, "Zoom &In", CMD_ZOOM_IN); -wmAddMenuItem(zoomMenu, "Zoom &Out", CMD_ZOOM_OUT); - -wmUpdateContentRect(win); -wmReallocContentBuf(win, &ctx.display); -``` - -### Context Menus - -Right-click menus attached to windows or widgets: - -```c -MenuT *ctxMenu = wmCreateMenu(); -wmAddMenuItem(ctxMenu, "Cu&t", CMD_CUT); -wmAddMenuItem(ctxMenu, "&Copy", CMD_COPY); -wmAddMenuItem(ctxMenu, "&Paste", CMD_PASTE); -win->contextMenu = ctxMenu; - -// Widget-level context menu -WidgetT *lb = wgtListBox(root); -lb->contextMenu = wmCreateMenu(); -wmAddMenuItem(lb->contextMenu, "&Delete", CMD_DELETE); -``` - -Context menus route through the window's `onMenu` callback. The caller -owns the MenuT allocation (free with `wmFreeMenu()`). - -### Accelerator Tables - -Keyboard shortcuts routed through `onMenu`: - -```c -AccelTableT *accel = dvxCreateAccelTable(); -dvxAddAccel(accel, 'N', ACCEL_CTRL, CMD_NEW); -dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN); -dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_SAVE); -dvxAddAccel(accel, KEY_F1, 0, CMD_HELP); -win->accelTable = accel; -``` - -Modifier constants: `ACCEL_SHIFT` (0x03), `ACCEL_CTRL` (0x04), -`ACCEL_ALT` (0x08). - -Key constants for extended keys: `KEY_F1`--`KEY_F12`, `KEY_INSERT`, -`KEY_DELETE`, `KEY_HOME`, `KEY_END`, `KEY_PGUP`, `KEY_PGDN`. - -Up to 32 entries per table. Free with `dvxFreeAccelTable()`. - - ---- - - -## Scrollbars - -```c -ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t pageSize); -ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t pageSize); -``` - -After adding scrollbars, call `wmUpdateContentRect(win)` and -`wmReallocContentBuf(win, &ctx.display)`. The `onScroll` callback fires -when the user drags the thumb or clicks arrow buttons / trough. - -Widget-managed windows handle their own scrollbars automatically -- do -not add them manually to widget windows. - - ---- - - -## Dialogs (dvxDialog.h) - -### Message Box - -```c -int32_t result = dvxMessageBox(&ctx, "Confirm", - "Are you sure?", MB_YESNO | MB_ICONQUESTION); - -if (result == ID_YES) { - dvxQuit(&ctx); -} -``` - -Button flags: `MB_OK`, `MB_OKCANCEL`, `MB_YESNO`, `MB_YESNOCANCEL`, -`MB_RETRYCANCEL`. - -Icon flags: `MB_ICONINFO`, `MB_ICONWARNING`, `MB_ICONERROR`, -`MB_ICONQUESTION`. - -Return values: `ID_OK`, `ID_CANCEL`, `ID_YES`, `ID_NO`, `ID_RETRY`. - -### File Dialog - -```c -static const FileFilterT filters[] = { - {"All Files (*.*)", "*.*"}, - {"Text Files (*.txt)", "*.txt"}, - {"Bitmap Files (*.bmp)", "*.bmp"} +static int32_t sButtonType; + +static const WidgetClassT sButtonClass = { + .flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE, + .paint = buttonPaint, + .calcMinSize = buttonCalcMinSize, + .onMouse = buttonOnMouse, + .onKey = buttonOnKey, + .destroy = buttonDestroy, + .getText = buttonGetText, + .setText = buttonSetText, + .setPressed = buttonSetPressed, + .onAccelActivate = buttonAccelActivate, }; -char path[260]; -if (dvxFileDialog(&ctx, "Open File", FD_OPEN, NULL, - filters, 3, path, sizeof(path))) { - // path contains the selected filename +static const ButtonApiT sApi = { .create = buttonCreate }; + +void wgtRegister(void) { + sButtonType = wgtRegisterClass(&sButtonClass); + wgtRegisterApi("button", &sApi); } ``` -Flags: `FD_OPEN`, `FD_SAVE`. Pass NULL for `initialDir` to use the -current directory. Filename validation is platform-specific (8.3 on DOS). +## Per-Widget API Registry ---- - - -## Video and Drawing (dvxVideo.h, dvxDraw.h) - -Lower-level APIs. Application code typically only needs `packColor`. - -### Color Packing +The monolithic WidgetApiT is gone. Each widget registers a small API +struct under a name via `wgtRegisterApi()`. Callers retrieve it via +`wgtGetApi()` and cast to the widget-specific type. Per-widget headers +(e.g. `widgetButton.h`) provide typed accessors and convenience macros: ```c -uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b); -``` - -Pack RGB into the display's native pixel format. For 8-bit mode, -returns the nearest palette index. - -### Clip Rectangle - -```c -void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h); -void resetClipRect(DisplayT *d); -``` - -### Rectangle Operations - -```c -void rectFill(DisplayT *d, const BlitOpsT *ops, - int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color); -void rectCopy(DisplayT *d, const BlitOpsT *ops, - int32_t dstX, int32_t dstY, const uint8_t *srcBuf, - int32_t srcPitch, int32_t srcX, int32_t srcY, - int32_t w, int32_t h); -``` - -### Bevel Drawing - -```c -void drawBevel(DisplayT *d, const BlitOpsT *ops, - int32_t x, int32_t y, int32_t w, int32_t h, - const BevelStyleT *style); -``` - -Convenience macros for common bevel styles: -- `BEVEL_RAISED(cs, bw)` -- standard raised (buttons, window frames) -- `BEVEL_SUNKEN(cs, face, bw)` -- sunken (text fields, list boxes) -- `BEVEL_TROUGH(cs)` -- scrollbar trough (1px sunken) -- `BEVEL_SB_BUTTON(cs)` -- scrollbar arrow button (1px raised) - -### Text Rendering - -```c -void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, - int32_t x, int32_t y, const char *text, - uint32_t fg, uint32_t bg, bool opaque); -void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, - int32_t x, int32_t y, const char *text, int32_t count, - uint32_t fg, uint32_t bg, bool opaque); -int32_t textWidth(const BitmapFontT *font, const char *text); -``` - -`drawTextN` is the fast path for bulk rendering -- computes clip bounds -once and fills the background in a single pass. - -```c -void drawTextAccel(DisplayT *d, const BlitOpsT *ops, - const BitmapFontT *font, int32_t x, int32_t y, - const char *text, uint32_t fg, uint32_t bg, bool opaque); -int32_t textWidthAccel(const BitmapFontT *font, const char *text); -char accelParse(const char *text); -``` - -Text with `&` accelerator markers. The character after `&` is -underlined. `&&` produces a literal `&`. - -### Terminal Row Rendering - -```c -void drawTermRow(DisplayT *d, const BlitOpsT *ops, - const BitmapFontT *font, int32_t x, int32_t y, - int32_t cols, const uint8_t *lineData, - const uint32_t *palette, bool blinkVisible, - int32_t cursorCol); -``` - -Renders an entire 80-column terminal row in one call. `lineData` points -to `(char, attr)` byte pairs. Much faster than per-character rendering. - -### Lines and Focus - -```c -void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color); -void drawVLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t h, uint32_t color); -void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color); -``` - -`drawFocusRect` draws a dotted rectangle (alternating pixels) for -keyboard focus indicators. - - ---- - - -## Compositor (dvxComp.h) - -```c -void dirtyListInit(DirtyListT *dl); -void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h); -void dirtyListMerge(DirtyListT *dl); -void dirtyListClear(DirtyListT *dl); -void flushRect(DisplayT *d, const RectT *r); -bool rectIntersect(const RectT *a, const RectT *b, RectT *result); -bool rectIsEmpty(const RectT *r); -``` - -The compositing pipeline each frame: -1. Layers above call `dirtyListAdd()` for changed regions -2. `dirtyListMerge()` consolidates overlapping/adjacent rects -3. For each merged dirty rect, redraw desktop then each window - (back-to-front, painter's algorithm) -4. `flushRect()` copies each dirty rect from backBuf to the LFB - -Up to 128 dirty rects per frame (`MAX_DIRTY_RECTS`). - - ---- - - -## Window Manager (dvxWm.h) - -### Window Stack - -Windows are stored in an array of pointers ordered front-to-back: index -`count-1` is the topmost window. The compositor iterates back-to-front -for painting and front-to-back for hit testing. - -```c -void wmInit(WindowStackT *stack); -WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, - int32_t x, int32_t y, int32_t w, int32_t h, bool resizable); -void wmDestroyWindow(WindowStackT *stack, WindowT *win); -void wmRaiseWindow(WindowStackT *stack, DirtyListT *dl, int32_t idx); -void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx); -``` - -### Hit Testing - -```c -int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hitPart); -``` - -Returns the stack index and hit region: -`HIT_CONTENT`, `HIT_TITLE`, `HIT_CLOSE`, `HIT_RESIZE`, `HIT_MENU`, -`HIT_VSCROLL`, `HIT_HSCROLL`, `HIT_MINIMIZE`, `HIT_MAXIMIZE`, -`HIT_NONE`. - -### Window Operations - -```c -void wmMaximize(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win); -void wmMinimize(WindowStackT *stack, DirtyListT *dl, WindowT *win); -void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win); -void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win); -``` - -### System Menu - -The system menu (control menu) appears on single-click of the close -gadget. Commands: Restore, Move, Size, Minimize, Maximize, Close, -Screenshot, Window Screenshot. Matches Windows 3.x behavior. - -### Keyboard Window Management - -| Key | Action | -|-----|--------| -| Alt+Tab | Cycle windows forward | -| Shift+Alt+Tab | Cycle windows backward | -| Alt+F4 | Close focused window | -| Alt+Space | Open system menu | -| F10 | Activate menu bar | - - ---- - - -## Widget System (dvxWidget.h) - -A retained-mode widget toolkit layered on top of the window manager. -Widgets form a tree rooted at a per-window VBox container. Layout is -automatic via a flexbox-like algorithm with weighted space distribution. - -Widget type implementations are NOT compiled into libdvx. Each widget -type is a separate `.wgt` DXE module in `../widgets/` that registers -its `WidgetClassT` vtable into `widgetClassTable[]` at load time. The -`WidgetClassT` struct and `WCLASS_*` flags are defined in `dvxWidget.h` -(the public API header) so that DXE plugins can create and register new -widget types. - -### Widget Catalog (32 built-in types) - -Each type below is implemented in its own `.wgt` DXE module and loaded -by the DVX shell at startup. - -#### Containers - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| VBox | `wgtVBox(parent)` | Vertical stack layout | -| HBox | `wgtHBox(parent)` | Horizontal stack layout | -| Frame | `wgtFrame(parent, title)` | Titled groupbox with bevel border | -| TabControl | `wgtTabControl(parent)` | Tabbed page container with scrollable headers | -| TabPage | `wgtTabPage(tabs, title)` | Single page within a TabControl | -| ScrollPane | `wgtScrollPane(parent)` | Scrollable container with automatic scrollbars | -| Splitter | `wgtSplitter(parent, vertical)` | Draggable divider between two child regions | -| Toolbar | `wgtToolbar(parent)` | Horizontal raised container for toolbar buttons | -| StatusBar | `wgtStatusBar(parent)` | Horizontal sunken container for status text | - -#### Basic Widgets - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| Label | `wgtLabel(parent, text)` | Static text display with optional alignment | -| Button | `wgtButton(parent, text)` | Beveled push button with press animation | -| Checkbox | `wgtCheckbox(parent, text)` | Toggle checkbox with checkmark glyph | -| RadioGroup | `wgtRadioGroup(parent)` | Container for mutually exclusive radio buttons | -| Radio | `wgtRadio(group, text)` | Individual radio button with diamond indicator | - -#### Text Input Widgets - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| TextInput | `wgtTextInput(parent, maxLen)` | Single-line text field with selection, undo, clipboard | -| PasswordInput | `wgtPasswordInput(parent, maxLen)` | Bullet-masked text field (no copy/cut) | -| MaskedInput | `wgtMaskedInput(parent, mask)` | Formatted input (e.g. `"(###) ###-####"`) | -| TextArea | `wgtTextArea(parent, maxLen)` | Multi-line text editor with scrollbars | - -#### Selection Widgets - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| ListBox | `wgtListBox(parent)` | Scrollable item list with optional multi-select and drag reorder | -| Dropdown | `wgtDropdown(parent)` | Non-editable selection with popup list | -| ComboBox | `wgtComboBox(parent, maxLen)` | Editable text field with dropdown suggestions | -| TreeView | `wgtTreeView(parent)` | Hierarchical tree with expand/collapse, multi-select, drag reorder | -| TreeItem | `wgtTreeItem(parent, text)` | Item node within a TreeView | -| ListView | `wgtListView(parent)` | Multi-column list with sortable/resizable headers, multi-select | - -#### Value Widgets - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| Slider | `wgtSlider(parent, min, max)` | Draggable track bar (horizontal or vertical) | -| Spinner | `wgtSpinner(parent, min, max, step)` | Numeric up/down input with increment buttons | -| ProgressBar | `wgtProgressBar(parent)` | Percentage display bar (horizontal or vertical) | - -#### Visual Widgets - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| Image | `wgtImage(parent, data, w, h, pitch)` | Static image display (BMP/PNG/JPEG/GIF) | -| ImageButton | `wgtImageButton(parent, data, w, h, pitch)` | Clickable image button with press animation | -| Canvas | `wgtCanvas(parent, w, h)` | Drawable bitmap surface with programmatic drawing API | -| Spacer | `wgtSpacer(parent)` | Invisible flexible space (weight=100) | -| Separator | `wgtHSeparator(parent)` / `wgtVSeparator(parent)` | Beveled divider line | - -#### Special Widgets - -| Widget | Constructor | Description | -|--------|-------------|-------------| -| AnsiTerm | `wgtAnsiTerm(parent, cols, rows)` | VT100/ANSI terminal emulator with 16-color CGA palette, scrollback, blink | -| Timer | `wgtTimer(parent, intervalMs, repeat)` | Invisible callback timer (one-shot or repeating) | - - -### WidgetClassT Vtable - -Each widget type has a `WidgetClassT` vtable that defines its behavior. -This struct is defined in `dvxWidget.h` so that DXE plugins can create -their own widget classes. - -```c -typedef struct WidgetClassT { - uint32_t flags; - void (*paint)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); - void (*paintOverlay)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); - void (*calcMinSize)(WidgetT *w, const BitmapFontT *font); - void (*layout)(WidgetT *w, const BitmapFontT *font); - void (*onMouse)(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy); - void (*onKey)(WidgetT *w, int32_t key, int32_t mod); - void (*destroy)(WidgetT *w); - const char *(*getText)(const WidgetT *w); - void (*setText)(WidgetT *w, const char *text); -} WidgetClassT; -``` - -Class flags (`WCLASS_*`): - -| Flag | Value | Meaning | -|------|-------|---------| -| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus (Tab navigation) | -| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses the generic VBox/HBox layout algorithm | -| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally (vs. vertical) | -| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering itself | -| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here, no child recursion | - -### Dynamic Widget Registration - -```c -int32_t wgtRegisterClass(const WidgetClassT *wclass); -``` - -Register a new widget class at runtime. Returns the assigned type ID -(>= `WGT_TYPE_DYNAMIC_BASE`, which is 32) on success, or -1 if the -table is full. The `WidgetClassT` must remain valid for the lifetime -of the process (typically a `static const` in the registering DXE). - -The `customData` field on `WidgetT` is available for DXE widget plugins -to store private per-widget data (NULL for built-in types). - -### Widget Event Model - -All widget types share a universal set of event callbacks set directly -on the WidgetT struct: - -| Callback | Signature | Description | -|----------|-----------|-------------| -| `onClick` | `void (WidgetT *w)` | Mouse click completed on the widget | -| `onDblClick` | `void (WidgetT *w)` | Double-click on the widget | -| `onChange` | `void (WidgetT *w)` | Value or state changed (checkbox toggle, text edit, slider move, timer fire) | -| `onFocus` | `void (WidgetT *w)` | Widget received keyboard focus | -| `onBlur` | `void (WidgetT *w)` | Widget lost keyboard focus | - -Type-specific handlers (button press animation, listbox selection -highlight) run first, then these universal callbacks fire. - -Additional per-widget fields: `userData` (void pointer), `customData` -(DXE plugin private data), `tooltip` (hover text), `contextMenu` -(right-click menu). - -### Initialization - -```c -WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win); -``` - -Attach the widget system to a window. Returns the root VBox container. -Installs `onPaint`, `onMouse`, `onKey`, and `onResize` handlers. Build -the UI by adding child widgets to the returned root. - -Call `wgtInvalidate(root)` after the tree is fully built to trigger the -initial layout and paint. - -### Layout System - -The layout engine runs in two passes: -1. **Measure** (bottom-up): compute minimum sizes for every widget -2. **Arrange** (top-down): distribute available space according to - weights, respecting min/max constraints - -#### Size Specifications - -Size fields (`minW`, `minH`, `maxW`, `maxH`, `prefW`, `prefH`, -`spacing`, `padding`) accept tagged values: - -```c -wgtPixels(120) // 120 pixels -wgtChars(15) // 15 character widths (15 * 8 = 120 pixels) -wgtPercent(50) // 50% of parent dimension -``` - -A raw `0` means auto (use the widget's natural/computed size). - -#### Weight - -The `weight` field controls extra-space distribution among siblings: -- `weight = 0` -- fixed size, does not stretch (default for most widgets) -- `weight = 100` -- normal stretch (default for spacers, text inputs) -- Relative ratios work: weights of 100, 200, 100 give a 1:2:1 split - -When all children have weight 0, the container's `align` property -(`AlignStartE`, `AlignCenterE`, `AlignEndE`) positions children within -the extra space. - -#### Container Properties - -| Field | Type | Default | Description | -|-------|------|---------|-------------| -| `align` | `WidgetAlignE` | `AlignStartE` | Main-axis alignment | -| `spacing` | `int32_t` | 0 (4px) | Tagged gap between children | -| `padding` | `int32_t` | 0 (4px) | Tagged internal padding | - -### Widget Operations - -```c -void wgtSetText(WidgetT *w, const char *text); -const char *wgtGetText(const WidgetT *w); -void wgtSetEnabled(WidgetT *w, bool enabled); -void wgtSetReadOnly(WidgetT *w, bool readOnly); -void wgtSetVisible(WidgetT *w, bool visible); -void wgtSetFocused(WidgetT *w); -WidgetT *wgtGetFocused(void); -void wgtSetName(WidgetT *w, const char *name); -WidgetT *wgtFind(WidgetT *root, const char *name); -void wgtDestroy(WidgetT *w); -void wgtInvalidate(WidgetT *w); -void wgtInvalidatePaint(WidgetT *w); -void wgtSetTooltip(WidgetT *w, const char *text); -void wgtSetDebugLayout(AppContextT *ctx, bool enabled); -AppContextT *wgtGetContext(const WidgetT *w); -``` - -- `wgtInvalidate` triggers full relayout + repaint (use after - structural changes) -- `wgtInvalidatePaint` triggers repaint only (use for visual-only - changes like checkbox toggle) -- `wgtFind` searches by name using djb2 hash for fast rejection -- `wgtGetContext` walks up the tree to retrieve the AppContextT - -### Timer Widget - -```c -WidgetT *wgtTimer(WidgetT *parent, int32_t intervalMs, bool repeat); -void wgtTimerStart(WidgetT *w); -void wgtTimerStop(WidgetT *w); -void wgtTimerSetInterval(WidgetT *w, int32_t intervalMs); -bool wgtTimerIsRunning(const WidgetT *w); -``` - -Invisible widget that fires `onChange` at the specified interval. Does -not participate in layout. Set `repeat = false` for one-shot timers -that auto-stop after the first fire. `wgtUpdateTimers()` is called -automatically by `dvxUpdate()` each frame. - -### Layout and Paint Internals - -Available for manual use when embedding in a custom event loop: - -```c -int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth); -void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT *font); -void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, - const BitmapFontT *font, const ColorSchemeT *colors); +// widgetButton.h +typedef struct { + WidgetT *(*create)(WidgetT *parent, const char *text); +} ButtonApiT; + +static inline const ButtonApiT *dvxButtonApi(void) { + static const ButtonApiT *sApi; + if (!sApi) { sApi = (const ButtonApiT *)wgtGetApi("button"); } + return sApi; +} + +#define wgtButton(parent, text) dvxButtonApi()->create(parent, text) ``` ---- +## Tagged Size Values + +Size hints encode both unit type and numeric value in a single int32_t: + +| Macro | Encoding | Example | +|-------|----------|---------| +| `wgtPixels(v)` | Bits 31:30 = 00 | `w->minW = wgtPixels(200);` | +| `wgtChars(v)` | Bits 31:30 = 01 | `w->minW = wgtChars(40);` | +| `wgtPercent(v)` | Bits 31:30 = 10 | `w->minW = wgtPercent(50);` | +| `0` | -- | Auto (use computed minimum) | -## Font System +## Layout Algorithm -DVX uses a fixed-width 8x16 bitmap font covering the full IBM Code Page -437 character set (256 glyphs). The font data is compiled in as a static -array -- no external font files. +Two-pass flexbox-like layout: -| Property | Value | -|----------|-------| -| Width | 8 pixels (fixed) | -| Height | 16 pixels (8x14 also available) | -| Encoding | CP437 (full 256 glyphs) | -| Format | 1 bit per pixel, MSB = leftmost, 16 bytes per glyph | -| Box drawing | Characters 176--223 (scrollbar arrows, window gadgets) | - -Character positions are pure multiplication (`x = col * 8`), and each -glyph scanline is exactly one byte, enabling simple per-scanline -rendering without bit shifting across byte boundaries. +1. **Bottom-up** (`calcMinSize`): Each widget computes its minimum size. + Containers sum children along the main axis, max across the cross axis. +2. **Top-down** (`layout`): Available space is allocated to children. + Extra space beyond minimum is distributed proportionally to weights. + `weight=0` means fixed size, `weight=100` is the default flexible weight. -## Cursor System +## Exported Symbols -Five software-rendered cursor shapes, stored as 16x16 AND/XOR bitmask -pairs (the standard IBM VGA hardware cursor format): - -| ID | Constant | Shape | -|----|----------|-------| -| 0 | `CURSOR_ARROW` | Standard arrow pointer | -| 1 | `CURSOR_RESIZE_H` | Horizontal resize (left-right) | -| 2 | `CURSOR_RESIZE_V` | Vertical resize (up-down) | -| 3 | `CURSOR_RESIZE_DIAG_NWSE` | NW-SE diagonal resize | -| 4 | `CURSOR_RESIZE_DIAG_NESW` | NE-SW diagonal resize | - -Cursors are painted in software into the backbuffer because VESA VBE -does not provide a hardware sprite. The affected region is flushed to -the LFB each frame. - - -## Window Chrome - -Windows use a Motif/GEOS-style frame: - -- 4px beveled outer border with perpendicular groove breaks -- 20px title bar (dark background when focused) -- Close button on the left edge (single-click opens system menu, - double-click closes) -- Minimize button on the right edge (always present) -- Maximize button to the left of minimize (resizable windows only) -- Optional menu bar below the title bar (20px) -- Optional scrollbars along the right and bottom edges (16px) - -Minimized windows appear as 64x64 beveled icons along the bottom of the -screen. If a window icon is set via `dvxSetWindowIcon()`, that image is -shown; otherwise a scaled thumbnail of the window's content buffer is -used. Thumbnails refresh automatically one per frame (staggered). -Double-click an icon to restore. - - -## Platform Abstraction (platform/dvxPlatform.h) - -All OS-specific code is behind a single interface. To port DVX, -implement these functions in a new `dvxPlatformXxx.c`: - -| Function | Purpose | -|----------|---------| -| `platformInit()` | One-time setup (signal handlers, etc.) | -| `platformYield()` | Cooperative multitasking yield | -| `platformVideoInit()` | Set up video mode, LFB, backbuffer | -| `platformVideoShutdown()` | Restore text mode, free resources | -| `platformVideoEnumModes()` | Enumerate available video modes | -| `platformVideoSetPalette()` | Set 8-bit palette entries | -| `platformVideoFreeBuffers()` | Free backbuffer without restoring text mode | -| `platformFlushRect()` | Copy rectangle from backbuffer to LFB | -| `platformSpanFill8/16/32()` | Optimized pixel fill (rep stosl on DOS) | -| `platformSpanCopy8/16/32()` | Optimized pixel copy (rep movsd on DOS) | -| `platformMouseInit()` | Initialize mouse driver | -| `platformMousePoll()` | Read mouse position and buttons | -| `platformMouseWheelInit()` | Detect and activate mouse wheel | -| `platformMouseWheelPoll()` | Read wheel delta | -| `platformMouseWarp()` | Move cursor to absolute position | -| `platformMouseSetAccel()` | Set double-speed threshold | -| `platformKeyboardGetModifiers()` | Read shift/ctrl/alt state | -| `platformKeyboardRead()` | Non-blocking key read | -| `platformAltScanToChar()` | Map Alt+scancode to ASCII | -| `platformValidateFilename()` | Check filename for OS constraints | -| `platformGetSystemInfo()` | Gather hardware info string | -| `platformGetMemoryInfo()` | Query total/free RAM | -| `platformMkdirRecursive()` | Create directory tree | -| `platformChdir()` | Change directory (with drive support on DOS) | -| `platformPathDirEnd()` | Find last path separator | -| `platformLineEnding()` | Native line ending string | -| `platformStripLineEndings()` | Remove CR from CR+LF pairs | - - -## Key Constants (dvxTypes.h) - -### Extended Key Codes - -Non-ASCII keys encoded as `(scancode | 0x100)`: +libdvx.lib exports symbols matching these prefixes: ``` -KEY_F1 through KEY_F12 -KEY_INSERT, KEY_DELETE -KEY_HOME, KEY_END -KEY_PGUP, KEY_PGDN -``` - -### Chrome Constants - -``` -CHROME_BORDER_WIDTH 4 Border thickness -CHROME_TITLE_HEIGHT 20 Title bar height -CHROME_MENU_HEIGHT 20 Menu bar height -CHROME_CLOSE_BTN_SIZE 16 Close gadget size -SCROLLBAR_WIDTH 16 Scrollbar track width -MAX_WINDOWS 64 Maximum simultaneous windows -MAX_DIRTY_RECTS 128 Dirty rects per frame -``` - -### Mouse Constants - -``` -MOUSE_LEFT 1 Left button bitmask -MOUSE_RIGHT 2 Right button bitmask -MOUSE_MIDDLE 4 Middle button bitmask -MOUSE_WHEEL_STEP 3 Lines per wheel notch +dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*, +resetClip*, stbi*, dirtyList*, widget*, accelParse*, clipboard*, +multiClick*, sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*, +sOpen*, sPressed*, sDrag*, sDrawing*, sResize*, sListView*, +sSplitter*, sTreeView* ``` -## Hardware Requirements +## Build -- 486 or later CPU -- VESA VBE 2.0+ compatible video card with linear framebuffer -- PS/2 mouse (or compatible driver) -- DPMI host (CWSDPMI, Windows DOS box, DOSBox, 86Box) +``` +make # builds bin/libs/libdvx.lib + bin/libs/libdvx.dep +make clean # removes objects and library +``` -Tested on 86Box with PCI video cards. +Depends on: `libtasks.lib` (via libdvx.dep). diff --git a/listhelp/README.md b/listhelp/README.md new file mode 100644 index 0000000..02dfcb2 --- /dev/null +++ b/listhelp/README.md @@ -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). diff --git a/loader/README.md b/loader/README.md index d343c64..6d7757f 100644 --- a/loader/README.md +++ b/loader/README.md @@ -1,73 +1,110 @@ # DVX Loader -Bootstrap loader for the DVX desktop environment. Builds as -`dvx.exe` -- the only native executable in the system. Everything -else is a dynamically loaded DXE module. +Bootstrap loader for the DVX desktop environment. Builds as `dvx.exe` +-- the only native executable in the system. Everything else is a +dynamically loaded DXE3 module. ## What It Does -1. Changes to the executable's directory so relative paths resolve -2. Calls `platformRegisterDxeExports()` to register platform functions - and C runtime symbols (libc, libm, libgcc, DJGPP internals) with - the DXE3 loader via `dlregsym()` -3. Recursively scans `LIBS/` for `*.lib` modules and `WIDGETS/` for - `*.wgt` modules -4. Reads `.dep` files to build a dependency graph -5. Topologically sorts all modules and loads them in order with - `RTLD_GLOBAL` (each module's exports become available to - subsequent modules) -6. Calls `wgtRegister()` on any module that exports it (widget - registration) -7. Finds `shellMain()` across all loaded modules and calls it -8. On return, closes all modules in reverse order +1. Changes working directory to the directory containing `dvx.exe` +2. Truncates `dvx.log` and initializes logging +3. Calls `platformInit()` to suppress Ctrl+C and install signal handlers +4. Calls `platformRegisterDxeExports()` to register platform and C + runtime symbols (libc, libm, libgcc) for DXE module resolution +5. Scans and loads all modules in two phases (see below) +6. Finds `shellMain()` via `dlsym` across loaded modules +7. Calls `shellMain()` -- the shell takes over from here +8. On return, closes all module handles in reverse load order + + +## Two-Phase Module Loading + +### 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 -| File | Purpose | -|------|---------| -| `loaderMain.c` | Entry point, directory scanning, dependency resolution, module loading | -| `Makefile` | Cross-compilation rules; links `dvxPlatformDos.c` directly | - -The DXE export table lives in `core/platform/dvxPlatformDos.c`, not -in the loader. The loader is platform-agnostic -- all DJGPP/DXE3 -knowledge is in the platform layer. +| File | Description | +|------|-------------| +| `loaderMain.c` | Entry point, module scanner, dependency resolver, logger | +| `Makefile` | Builds `bin/dvx.exe` | -## Dependency Resolution - -Each module may have a `.dep` file alongside it (same base name, -`.dep` extension). The file lists base names of modules that must -be loaded first, one per line. Lines starting with `#` are comments. - -Example -- `dvxshell.dep`: -``` -libtasks -libdvx -box -button -label -listview -``` - -A dependency is satisfied when either: -- A module with that base name has been loaded, or -- No module with that base name exists in the scan (external, assumed OK) - -The loader iterates until all modules are loaded or no further -progress can be made (circular dependency). Unloadable modules are -reported to stderr. - - -## Building +## Build ``` -make # builds ../bin/dvx.exe -make clean +make # builds bin/dvx.exe +make clean # removes objects and binary ``` -The Makefile compiles `loaderMain.c` and links it with -`dvxPlatformDos.o` (compiled from `../core/platform/dvxPlatformDos.c`) -and `-lm`. The result goes through `exe2coff` + `CWSDSTUB` to -produce a DOS executable. +The loader links `loaderMain.c` + `dvxPlatformDos.c` into a native +DJGPP executable. The CWSDPMI stub is prepended via exe2coff + +CWSDSTUB.EXE for standalone execution. diff --git a/shell/Makefile b/shell/Makefile index 2c76bda..e03b6f3 100644 --- a/shell/Makefile +++ b/shell/Makefile @@ -61,7 +61,6 @@ $(WPAPERDIR)/%.jpg: ../config/wpaper/%.jpg | $(WPAPERDIR) # Dependencies $(OBJDIR)/shellMain.o: shellMain.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)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h diff --git a/shell/README.md b/shell/README.md index 1ac7ca4..c3ed6c1 100644 --- a/shell/README.md +++ b/shell/README.md @@ -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 -startup. It initializes the GUI subsystem, loads DXE3 application modules at -runtime, runs the cooperative main loop, and provides crash recovery so a -faulting app does not take down the entire system. +The DVX Shell is a DXE3 module loaded by the DVX loader at startup. +It initializes the GUI subsystem, loads DXE3 application modules on +demand, runs the cooperative main loop, and provides crash recovery so +a faulting app does not bring down the entire system. -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 | -|------|---------| -| `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` | +The loader finds and calls `shellMain()` after all libs and widgets +are loaded. `shellMain()`: -## 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 -The main loop runs until `dvxQuit()` sets `ctx->running = false`: +Each iteration of the main loop does four things: -``` -while (ctx->running) { - dvxUpdate(ctx); // (1) - tsYield(); // (2) - shellReapApps(ctx); // (3) - shellDesktopUpdate(); // (4) -} -``` +1. `dvxUpdate()` -- process input events, dispatch callbacks, composite + dirty rects, flush to LFB +2. `tsYield()` -- give CPU time to app tasks (if any are active) +3. `shellReapApps()` -- clean up any apps that terminated this frame +4. `shellDesktopUpdate()` -- notify desktop app if apps were reaped -1. **dvxUpdate** -- processes mouse/keyboard input, dispatches widget and - window callbacks, composites dirty rects, flushes to the LFB. This is the - 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. +An idle callback (`idleYield`) is also registered so that `dvxUpdate()` +yields to app tasks during quiet frames. -## 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 -### DXE3 Loading +### DXE App Contract -DXE3 is DJGPP's dynamic linking mechanism. Each `.app` file is a DXE3 shared -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()`: +Every DXE app exports two symbols: -1. Allocate a slot from the 32-entry fixed array (slot 0 is the shell). -2. Check if this DXE path is already loaded (`findLoadedPath`). - - If the existing app's descriptor says `multiInstance = true`, copy - the `.app` to a unique temp file (using `TEMP`/`TMP` env var) so - `dlopen` gets an independent code+data image. - - If `multiInstance = false`, block with an error message. -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//` -- 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. +* `appDescriptor` (`AppDescriptorT`) -- metadata: + - `name` -- display name (max 64 chars) + - `hasMainLoop` -- true for main-loop apps, false for callback-only + - `multiInstance` -- true to allow multiple instances via temp copy + - `stackSize` -- `SHELL_STACK_DEFAULT` (32KB) or explicit byte count + - `priority` -- `TS_PRIORITY_NORMAL` or custom -### 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 | -|-------|------|-------------| -| `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-Only Apps (hasMainLoop = false) -### 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`): -- `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 Apps (hasMainLoop = true) -**Main-loop** (`hasMainLoop = true`): -- A cooperative task is created via `tsCreate()`. -- `appMain()` runs in that task with its own loop calling `tsYield()`. -- Needed when the app has ongoing work that cannot be expressed purely as - event callbacks (polling, computation, animation). -- Lifecycle ends when `appMain()` returns. -- Example: Clock (polls `time()` each second). +A dedicated cooperative task is created. `appMain()` runs in that task +and can do its own polling/processing loop, calling `tsYield()` to +share CPU. Lifecycle ends when `appMain()` returns or the task is +killed. -### Multi-Instance Support - -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 +### App States ``` Free -> Loaded -> Running -> Terminating -> Free ``` -- **Free**: slot available. -- **Loaded**: DXE loaded, entry point not yet called (transient). -- **Running**: entry point called, app is active. -- **Terminating**: app's task returned or last window closed; awaiting reap. +| State | Description | +|-------|-------------| +| `AppStateFreeE` | Slot available for reuse | +| `AppStateLoadedE` | DXE loaded, not yet started (transient) | +| `AppStateRunningE` | Entry point called, active | +| `AppStateTerminatingE` | Shutdown in progress, awaiting reap | -## Resource Tracking +### App Slots -`sCurrentAppId` is a global set before calling any app code (entry, shutdown, -callbacks). The shell's `dvxCreateWindow` wrapper stamps every new window with -`win->appId = sCurrentAppId`. This is how the shell knows which windows belong -to which app, enabling: +App slots are managed as a stb_ds dynamic array (no fixed max). Each +slot tracks: app ID, name, path, DXE handle, state, task ID, entry/ +shutdown function pointers, and the `DxeAppContextT` passed to the app. -- Per-app window cleanup on crash or termination (walk the window stack, - destroy all windows with matching `appId`). -- The last-window-closes-app rule for callback-only apps: when - `shellWrapDestroyWindow` detects that a callback-only app has no remaining - windows, it marks the app `AppStateTerminatingE`. +The `DxeAppContextT` gives each app: +- `shellCtx` -- pointer to the shell's `AppContextT` +- `appId` -- this app's unique ID +- `appDir` -- directory containing the `.app` file (for resources) +- `configDir` -- writable config directory (`CONFIG//`) -`sCurrentAppId` is a simple global (not thread-local) because cooperative -multitasking means only one task runs at a time. +### App ID Tracking -## 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. -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. +## Crash Recovery -The key mechanic: `dlregsym` takes precedence over `RTLD_GLOBAL` exports. -Since libdvx (which has the real functions) was loaded before -`shellExportInit()` registers these wrappers, libdvx keeps the real -implementations. But any app DXE loaded afterward gets the wrappers, which -add resource tracking transparently. +The platform layer installs signal handlers for SIGSEGV, SIGFPE, and +SIGILL via `platformInstallCrashHandler()`. If a crash occurs: + +1. Platform handler logs signal name and register dump (DJGPP) +2. Handler `longjmp`s to the `setjmp` point in `shellMain()` +3. `tsRecoverToMain()` fixes the scheduler's bookkeeping +4. Shell logs app-specific info (name, path, task ID) +5. Crashed app is force-killed (`shellForceKillApp()`) +6. Error dialog is shown to the user +7. Desktop is notified to refresh +8. Main loop continues normally + +This gives Windows 3.1-style fault tolerance -- one bad app does not +take down the whole system. -All other symbol exports -- dvx*, wgt*, ts*, platform*, libc, libm -- come -from the DXE modules loaded with `RTLD_GLOBAL` by the loader. They no longer -need to be listed in the shell's export table. ## Task Manager -`shellTaskMgr.c` implements a shell-level Task Manager accessible via -Ctrl+Esc regardless of which app is focused or whether the desktop app -is running. It is owned by the shell (appId = 0), not by any DXE app. +The built-in Task Manager is always available via Ctrl+Esc. It shows +all running apps with their names and provides an "End Task" button +for force-killing hung apps. The Task Manager is a shell-level +component, not tied to any app -- it persists even if the desktop app +is terminated. -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 -(managed via stb_ds `arrput`/`arrdel`). `shellDesktopUpdate()` iterates the -array and calls each registered function. +Apps (especially the desktop app) register callbacks via +`shellRegisterDesktopUpdate()` to be notified when app state changes +(load, reap, crash, title change). Multiple callbacks are supported. -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 -mirrors the app's location under `APPS/`: +| File | Description | +|------|-------------| +| `shellMain.c` | Entry point, main loop, crash recovery, idle callback | +| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE` | +| `shellApp.c` | App loading, reaping, task creation, DXE management | +| `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 -Config dir: CONFIG/PROGMAN/ +make # builds dvxshell.lib + dvxshell.dep + config files +make clean # removes objects, library, and config output ``` -API: -- `shellEnsureConfigDir(ctx)` -- create the directory tree via - `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 | +Depends on: `libtasks.lib`, `libdvx.lib`, `texthelp.lib`, `listhelp.lib` +(via dvxshell.dep). diff --git a/tasks/README.md b/tasks/README.md index 1b640a8..ee5d411 100644 --- a/tasks/README.md +++ b/tasks/README.md @@ -1,341 +1,118 @@ # taskswitch -- Cooperative Task Switching Library Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS -protected mode). Part of the DVX GUI project. - -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. +protected mode). Built as `libtasks.lib` -- a DXE3 module loaded by +the DVX loader. -## Why Cooperative? +## Why Cooperative -DOS is single-threaded. DPMI provides no timer-based preemption. The -DVX GUI event model is inherently single-threaded: one compositor, one -input queue, one window stack. Preemptive switching would require -locking around every GUI call for no benefit. Cooperative switching -lets each task yield at safe points, avoiding synchronization entirely. +DOS is single-threaded with no kernel scheduler. DPMI provides no +thread or timer-based preemption. The DVX GUI event model is +inherently single-threaded (one compositor, one input queue, one +window stack). Cooperative switching lets each task yield at safe +points, avoiding the need for synchronization primitives entirely. -## Files +## Scheduling Algorithm -| File | Purpose | -|------|---------| -| `taskswitch.h` | Public API -- types, constants, function prototypes | -| `taskswitch.c` | Implementation (scheduler, context switch, slots, includes stb_ds implementation) | -| `thirdparty/stb_ds.h` | stb dynamic array/hashmap library (third-party) | -| `Makefile` | DJGPP cross-compilation build rules | +Credit-based weighted fair-share, round-robin within a credit epoch. + +Each task receives `(priority + 1)` credits per scheduling round. +Tasks run round-robin, consuming one credit per turn. When all +credits are spent, every ready task is refilled. + +| 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: - -``` -make # builds ../bin/libs/libtasks.lib -make clean # removes objects and library -``` - -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 -#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. +| State | Description | +|-------|-------------| +| `TaskStateReady` | Eligible for scheduling | +| `TaskStateRunning` | Currently executing (cosmetic marker) | +| `TaskStatePaused` | Skipped until resumed | +| `TaskStateTerminated` | Slot available for reuse | ## API Reference -### Initialization and Teardown - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsInit` | `int32_t tsInit(void)` | Initialize the library. Returns `TS_OK` or a negative error code. | -| `tsShutdown` | `void tsShutdown(void)` | Free all resources. Safe to call even if `tsInit` was never called. | - -### Task Creation and Termination - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsCreate` | `int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t ss, int32_t pri)` | Create a ready task. Returns the task ID (>= 0) or a negative error code. Pass 0 for `ss` to use `TS_DEFAULT_STACK_SIZE`. Reuses terminated slots. | -| `tsExit` | `void tsExit(void)` | Terminate the calling task. Must not be called from the main task. Never returns. | -| `tsKill` | `int32_t tsKill(uint32_t taskId)` | Forcibly terminate another task. Cannot kill main (id 0) or self (use `tsExit` instead). | - -### Scheduling - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsYield` | `void tsYield(void)` | Voluntarily relinquish the CPU to the next eligible ready task. | - -### Pausing and Resuming - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsPause` | `int32_t tsPause(uint32_t id)` | Pause a task. Main task (id 0) cannot be paused. Self-pause triggers an implicit yield. | -| `tsResume` | `int32_t tsResume(uint32_t id)` | Resume a paused task. Credits are refilled so it is not penalized for having been paused. | - -### Priority - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsSetPriority` | `int32_t tsSetPriority(uint32_t id, int32_t pri)` | Change a task's priority. Credits are reset so the change takes effect immediately. | -| `tsGetPriority` | `int32_t tsGetPriority(uint32_t id)` | Return the task's priority, or `TS_ERR_PARAM` on an invalid ID. | - -### Crash Recovery - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsRecoverToMain` | `void tsRecoverToMain(void)` | Reset scheduler state to task 0 after a `longjmp` from a signal handler. Call before `tsKill` on the crashed task. The crashed task's slot is NOT freed -- call `tsKill` afterward. | - -### Query - -| Function | Signature | Description | -|----------|-----------|-------------| -| `tsGetState` | `TaskStateE tsGetState(uint32_t id)` | Return the task's state enum value. | -| `tsCurrentId` | `uint32_t tsCurrentId(void)` | Return the ID of the currently running task. | -| `tsGetName` | `const char *tsGetName(uint32_t id)` | Return the task's name string, or `NULL` on invalid ID. | -| `tsActiveCount` | `uint32_t tsActiveCount(void)` | Return the number of non-terminated tasks. | - - -## Constants +| Function | Description | +|----------|-------------| +| `tsInit()` | Initialize task system; calling context becomes task 0 (main) | +| `tsShutdown()` | Shut down and free all resources | +| `tsCreate(name, entry, arg, stackSize, priority)` | Create a new task; returns task ID | +| `tsYield()` | Yield CPU to next eligible task (credit-based round-robin) | +| `tsPause(taskId)` | Pause a task (implicit yield if self) | +| `tsResume(taskId)` | Resume a paused task (refills credits) | +| `tsSetPriority(taskId, priority)` | Set priority (refills credits immediately) | +| `tsGetPriority(taskId)` | Get task priority | +| `tsGetState(taskId)` | Get task state | +| `tsCurrentId()` | Get currently running task's ID | +| `tsGetName(taskId)` | Get task name | +| `tsExit()` | Terminate calling task (never returns) | +| `tsKill(taskId)` | Forcibly terminate another task | +| `tsRecoverToMain()` | Force scheduler back to task 0 after crash longjmp | +| `tsActiveCount()` | Count of non-terminated tasks | ### Error Codes -| Name | Value | Meaning | -|------|-------|---------| -| `TS_OK` | 0 | Success | -| `TS_ERR_INIT` | -1 | Library not initialized | +| Constant | Value | Description | +|----------|-------|-------------| +| `TS_OK` | 0 | Success | +| `TS_ERR_INIT` | -1 | Initialization failure | | `TS_ERR_PARAM` | -2 | Invalid parameter | -| `TS_ERR_FULL` | -3 | Task table full (unused, kept for compatibility) | -| `TS_ERR_NOMEM` | -4 | Memory allocation failed | +| `TS_ERR_FULL` | -3 | No available task slots | +| `TS_ERR_NOMEM` | -4 | Stack allocation failed | | `TS_ERR_STATE` | -5 | Invalid state transition | -### Priority Presets +### Constants -| Name | Value | Credits per Round | -|------|-------|-------------------| -| `TS_PRIORITY_LOW` | 0 | 1 | -| `TS_PRIORITY_NORMAL` | 5 | 6 | -| `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 | +| Constant | Value | Description | +|----------|-------|-------------| +| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack size (32KB) | +| `TS_NAME_MAX` | 32 | Maximum 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 { - TaskStateReady = 0, // Eligible for scheduling - TaskStateRunning = 1, // Currently executing - TaskStatePaused = 2, // Suspended until tsResume() - TaskStateTerminated = 3 // Finished; slot will be recycled -} TaskStateE; + +## Crash Recovery + +`tsRecoverToMain()` is called after `longjmp` from a signal handler +that fired in a non-main task. It fixes the scheduler's `currentIdx` +to point back to task 0. The crashed task is NOT cleaned up by this +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 -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. +No dependencies. Exports all symbols matching `_ts*`. diff --git a/termdemo/README.md b/termdemo/README.md index da8a680..d45f1e8 100644 --- a/termdemo/README.md +++ b/termdemo/README.md @@ -12,6 +12,10 @@ the DVX widget system outside the shell framework. Connects to a remote BBS through the SecLink proxy, providing a full 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 @@ -77,19 +81,6 @@ The handshake completes in text mode before the GUI starts, so the 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 ``` @@ -100,25 +91,10 @@ Keyboard -> widgetAnsiTermOnKey() -> commWrite() -> secLinkSend() -> serial -> proxy -> BBS ``` -A 4KB ring buffer (`RECV_BUF_SIZE`) bridges the SecLink receive -callback (which fires asynchronously during `secLinkPoll()`) and the -terminal widget's comm read interface (which is polled synchronously -during the widget paint cycle). This decoupling is necessary because -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). +A 4KB ring buffer bridges the SecLink receive callback (which fires +asynchronously during `secLinkPoll()`) and the terminal widget's comm +read interface (which is polled synchronously during the widget paint +cycle). ## Test Setup @@ -138,27 +114,10 @@ wrap). 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 -All libraries are built into `../lib/`: +All libraries are built into `lib/`: | Library | Purpose | |------------------|--------------------------------------| @@ -176,3 +135,11 @@ termdemo/ termdemo.c terminal emulator program Makefile DJGPP cross-compilation build ``` + + +## Build + +``` +make # builds bin/termdemo.exe (and dependency libs) +make clean # removes objects and binary +``` diff --git a/texthelp/README.md b/texthelp/README.md new file mode 100644 index 0000000..12be228 --- /dev/null +++ b/texthelp/README.md @@ -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). diff --git a/widgets/README.md b/widgets/README.md index f2262c3..bac9071 100644 --- a/widgets/README.md +++ b/widgets/README.md @@ -1,95 +1,391 @@ # DVX Widget Modules -Individual widget type implementations for the DVX GUI, each built -as a separate `.wgt` DXE module. The loader scans the `WIDGETS/` -directory recursively at startup and loads every `.wgt` file it -finds, calling `wgtRegister()` on each to fill the widget class -table. +Individual widget type implementations, each built as a separate `.wgt` +DXE module. The loader scans the `WIDGETS/` directory at startup and +loads every `.wgt` file it finds. Each module exports `wgtRegister()`, +which registers its widget class(es) and API struct with the core. -Drop a new `.wgt` file in the directory and it is automatically -available -- no loader or core library changes needed. +Core knows nothing about individual widgets. All per-widget state lives +in `w->data` (allocated by the widget DXE). All per-widget behavior is +dispatched through the WidgetClassT vtable. Each widget provides a +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) -2. A `static const WidgetClassT` vtable definition -3. A `wgtRegister()` function that writes the vtable pointer into - `widgetClassTable[]` at the widget's enum slot -4. The public API functions (e.g. `wgtTreeView()`, `wgtTreeViewGetSelected()`) - -At load time, the module resolves its dependencies against: -- The loader's `dlregsym` table (platform functions, libc) -- `libtasks.lib` exports (stb_ds dynamic arrays) -- `libdvx.lib` exports (widget infrastructure: `widgetAlloc`, - `widgetClassTable`, `widgetScrollbarThumb`, shared state, etc.) -- Other widget modules loaded earlier (via `RTLD_GLOBAL`) +| Source File | .wgt File | Types | Public Header | Dependencies | +|-------------|-----------|-------|---------------|--------------| +| `widgetBox.c` | `box.wgt` | VBox, HBox, Frame | `widgetBox.h` | -- | +| `widgetButton.c` | `button.wgt` | Button | `widgetButton.h` | -- | +| `widgetLabel.c` | `label.wgt` | Label | `widgetLabel.h` | -- | +| `widgetTextInput.c` | `textinpt.wgt` | TextInput, TextArea | `widgetTextInput.h` | texthelp | +| `widgetCheckbox.c` | `checkbox.wgt` | Checkbox | `widgetCheckbox.h` | -- | +| `widgetRadio.c` | `radio.wgt` | RadioGroup, Radio | `widgetRadio.h` | -- | +| `widgetDropdown.c` | `dropdown.wgt` | Dropdown | `widgetDropdown.h` | listhelp | +| `widgetComboBox.c` | `combobox.wgt` | ComboBox | `widgetComboBox.h` | texthelp, listhelp | +| `widgetListBox.c` | `listbox.wgt` | ListBox | `widgetListBox.h` | listhelp | +| `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 | -|--------|-------------|-------------| -| `box.wgt` | VBox, HBox, Frame | Containers: vertical/horizontal box layout, titled frame | -| `button.wgt` | Button | Push button with text and keyboard accelerator | -| `canvas.wgt` | Canvas | Freeform drawing surface with mouse callbacks | -| `checkbox.wgt` | Checkbox | Toggle checkbox with label | -| `combobox.wgt` | ComboBox | Editable text input with dropdown list | -| `dropdown.wgt` | Dropdown | Non-editable dropdown selector | -| `image.wgt` | Image | Static image display (BMP/PNG/JPEG/GIF) | -| `imgbtn.wgt` | ImageButton | Button with icon image | -| `label.wgt` | Label | Static text display with optional accelerator | -| `listbox.wgt` | ListBox | Scrollable string list with single/multi select | -| `listview.wgt` | ListView | Multi-column list with headers, sorting, column resize | -| `progress.wgt` | ProgressBar | Horizontal or vertical progress indicator | -| `radio.wgt` | RadioGroup, Radio | Mutually exclusive option buttons | -| `scrlpane.wgt` | ScrollPane | Scrollable viewport for oversized content | -| `separatr.wgt` | Separator | Horizontal or vertical divider line | -| `slider.wgt` | Slider | Horizontal value slider with thumb | -| `spacer.wgt` | Spacer | Invisible layout spacer | -| `spinner.wgt` | Spinner | Numeric input with up/down buttons | -| `splitter.wgt` | Splitter | Resizable two-pane divider | -| `statbar.wgt` | StatusBar | Horizontal bar at window bottom | -| `tabctrl.wgt` | TabControl, TabPage | Tabbed page container | -| `terminal.wgt` | AnsiTerm | VT100 terminal emulator with scrollback | -| `textinpt.wgt` | TextInput, TextArea | Single-line and multi-line text editing | -| `timer.wgt` | Timer | Periodic callback (no visual) | -| `toolbar.wgt` | Toolbar | Horizontal button/widget bar | -| `treeview.wgt` | TreeView, TreeItem | Hierarchical tree with expand/collapse | +Widgets that use shared helper libraries need a `.dep` file so the +loader loads the helper first. Dep files are in `config/` and copied +to `bin/widgets/` during build: -26 modules implementing 32 widget types (some modules register -multiple related types). +| Widget | Dep File | Dependencies | +|--------|----------|--------------| +| 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` -2. Include `"dvxWidgetPlugin.h"` for infrastructure access -3. Implement paint, calcMinSize, and event handlers as needed -4. Define a `static const WidgetClassT` with your vtable -5. Add a `wgtRegister()` function: - ```c - void wgtRegister(void) { - widgetClassTable[MyWidgetTypeE] = &sClassMyWidget; - } - ``` -6. Add the new enum value to `WidgetTypeE` in `core/dvxWidget.h` - (or use `wgtRegisterClass()` for a fully dynamic type ID) -7. Add public API functions (e.g. `wgtMyWidget()`) that call - `widgetAlloc(parent, MyWidgetTypeE)` -8. Add a build rule in `widgets/Makefile` -9. Build -- the loader discovers and loads it automatically +### Box (VBox, HBox, Frame) + +Container widgets that arrange children vertically or horizontally. +Frame adds a titled border around its children. + +| API Function | Macro | Description | +|--------------|-------|-------------| +| `vBox(parent)` | `wgtVBox(parent)` | Create vertical container | +| `hBox(parent)` | `wgtHBox(parent)` | Create horizontal container | +| `frame(parent, title)` | `wgtFrame(parent, title)` | Create titled frame container | + +### Button + +Push button with text label and accelerator key support. + +| 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 clean +make # builds all 26 .wgt modules + dep files +make clean # removes objects, .wgt files, and dep files ``` -Each `.wgt` is created with `dxe3gen -E _wgt -U`, which exports all -symbols starting with `wgt` (public API + `wgtRegister`) and allows -unresolved symbols (resolved at load time from other modules). +Each widget is compiled to a single `.o` file, then packaged via +`dxe3gen` into a `.wgt` DXE module exporting only `wgtRegister`.