diff --git a/README.md b/README.md index 3b5f154..bfa78c8 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ A windowed GUI compositor and desktop shell for DOS, built with DJGPP/DPMI. DVX combines a Motif-style window manager, dirty-rectangle -compositor, cooperative task switcher, and DXE3 dynamic application -loading into a multitasking desktop environment where applications are -`.app` shared libraries loaded at runtime. +compositor, cooperative task switcher, and a modular DXE3 architecture +into a multitasking desktop environment. The bootstrap loader +dynamically discovers and loads core libraries, widget plugins, and +applications at runtime. Targets real and emulated 486+ hardware with VESA VBE 2.0+ linear framebuffer. No bank switching -- LFB or fail. @@ -16,9 +17,10 @@ framebuffer. No bank switching -- LFB or fail. maximize, and restore - Dirty-rectangle compositor -- only changed regions are flushed to video memory, critical for acceptable frame rates on 486/Pentium hardware -- 32 widget types: buttons, text inputs, list boxes, tree views, list - views, tab controls, sliders, spinners, progress bars, dropdowns, - combo boxes, splitters, scroll panes, ANSI terminal emulator, and more +- 32 widget types across 26 DXE modules: buttons, text inputs, list + boxes, tree views, list views, tab controls, sliders, spinners, + progress bars, dropdowns, combo boxes, splitters, scroll panes, ANSI + terminal emulator, and more - Flexbox-style automatic layout engine (VBox/HBox containers with weighted space distribution) - Dropdown menus with cascading submenus, checkbox and radio items, @@ -40,6 +42,8 @@ framebuffer. No bank switching -- LFB or fail. - Encrypted serial networking stack (DH key exchange, XTEA-CTR cipher) - Platform abstraction layer -- DOS/DJGPP production target, Linux/SDL2 development target +- Modular DXE architecture with dependency resolution -- core libraries, + widget plugins, and applications are all separate loadable modules ## Target Hardware @@ -53,30 +57,68 @@ framebuffer. No bank switching -- LFB or fail. ## Architecture -The shell runs as a single DOS executable (`dvx.exe`) that loads -applications dynamically via DJGPP's DXE3 shared library system. +DVX uses a modular DXE3 architecture. The bootstrap loader (`dvx.exe`) +is a small executable that discovers, dependency-sorts, and loads all +modules at startup. Core libraries, widget plugins, and applications +are separate `.lib`, `.wgt`, and `.app` DXE shared libraries. ``` -+-------------------------------------------------------------------+ -| dvx.exe (Task 0) | -| +-------------+ +-----------+ +------------+ | -| | shellMain | | shellApp | | shellExport| | -| | (event loop)| | (lifecycle| | (DXE symbol| | -| | | | + reaper)| | export) | | -| +-------------+ +-----------+ +------------+ | -| | | | | -| +------+-------+ +----+-----+ +----+----+ | -| | libdvx.a | |libtasks.a| | libdxe | | -| | (GUI/widgets)| |(scheduler)| | (DJGPP) | | -| +--------------+ +----------+ +---------+ | -+-------------------------------------------------------------------+ - | | - +---------+ +---------+ - | app.app | | app.app | - | (Task N)| | (Task M)| - +---------+ +---------+ +dvx.exe (bootstrap loader) + | + +-- platformRegisterDxeExports() -- platform + libc/libm symbols + | + +-- libs/ (scanned, dependency-ordered) + | libtasks.lib -- cooperative task switching, stb_ds + | libdvx.lib -- draw, comp, wm, app, widget infrastructure + | dvxshell.lib -- shell, app lifecycle, crash recovery + | + +-- widgets/ (scanned, each calls wgtRegister) + | box.wgt, button.wgt, label.wgt, ... (26 modules) + | + +-- shellMain() -- found via dlsym, enters main loop + | + +-- apps/ (loaded on demand by shell) + progman.app, notepad.app, clock.app, ... ``` +### Boot Sequence + +1. `dvx.exe` starts, calls `platformRegisterDxeExports()` to register + platform and libc/libm symbols for DXE modules +2. Scans `libs/` recursively for `*.lib` files, reads `.dep` files, + topologically sorts, and loads in dependency order +3. Scans `widgets/` recursively for `*.wgt` files, loads each, and + calls `wgtRegister()` on any module that exports it +4. Finds `shellMain()` across loaded modules via `dlsym` +5. Calls `shellMain()`, which initializes the GUI, loads the desktop + app, and enters the main event loop + +### Dependency Resolution + +Each DXE module may have a `.dep` file (same base name, `.dep` +extension) listing the base names of modules that must be loaded before +it. The loader reads all `.dep` files, builds a dependency graph, and +loads modules in topological order. For example, `libdvx.dep` contains +`libtasks`, and `dvxshell.dep` lists `libtasks`, `libdvx`, and all +widget modules it requires. + +### Widget DXE Modules + +The 32 widget types in the `WidgetTypeE` enum are implemented across 26 +`.wgt` DXE modules. Some modules register multiple widget types: + +| Module | Widget Types | +|--------|-------------| +| `box.wgt` | VBox, HBox, Frame | +| `radio.wgt` | RadioGroup, Radio | +| `textinpt.wgt` | TextInput, TextArea | +| `tabctrl.wgt` | TabControl, TabPage | +| `treeview.wgt` | TreeView, TreeItem | + +Each widget DXE exports a standard `wgtRegister()` function that +registers its widget class(es) with the core widget infrastructure. +Widget DXEs are discovered by directory scanning, not hardcoded. + ### App Types **Callback-only** (`hasMainLoop = false`): `appMain` creates windows, @@ -98,33 +140,93 @@ Diagnostic information (registers, faulting EIP) is logged to `dvx.log`. ## Directory Structure +### Source Tree + ``` dvxgui/ - dvx/ GUI compositor library (libdvx.a) - platform/ Platform abstraction (DOS/DJGPP, Linux/SDL2) - widgets/ Widget system (32 types, one file per type) - thirdparty/ stb_image.h, stb_image_write.h - tasks/ Cooperative task switcher (libtasks.a) - thirdparty/ stb_ds.h - dvxshell/ Desktop shell (dvx.exe) - apps/ DXE app modules (.app files) - 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 - rs232/ ISR-driven UART serial driver (librs232.a) - packet/ HDLC framing, CRC-16, Go-Back-N (libpacket.a) - security/ DH key exchange, XTEA-CTR cipher (libsecurity.a) - seclink/ Secure serial link wrapper (libseclink.a) - proxy/ Linux SecLink-to-telnet proxy (secproxy) - termdemo/ Encrypted ANSI terminal demo (termdemo.exe) - themes/ Color theme files (.thm) - wpaper/ Bundled wallpaper images - bin/ Build output (dvx.exe, apps/, config/) - lib/ Build output (static libraries) - releases/ Release archives + 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 ``` @@ -133,14 +235,16 @@ dvxgui/ Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`). ```bash -# Build everything (dvx lib, tasks lib, shell, all apps) +# Build everything (loader, core, tasks, shell, widgets, all apps) make # Build individual components -make -C dvx # builds lib/libdvx.a -make -C tasks # builds lib/libtasks.a -make -C dvxshell # builds bin/dvx.exe -make -C apps # builds bin/apps/*/*.app +make -C loader # builds dvx.exe +make -C core # builds libs/libdvx.lib + widgets/*.wgt +make -C tasks # builds libs/libtasks.lib +make -C shell # builds libs/dvxshell.lib + config +make -C widgets # builds widgets/*.wgt +make -C apps # builds apps/*/*.app # Clean all build artifacts make clean @@ -272,8 +376,8 @@ external dependencies: | Library | Location | Purpose | |---------|----------|---------| -| stb_image.h | `dvx/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) | -| stb_image_write.h | `dvx/thirdparty/` | Image writing (PNG export for screenshots) | +| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) | +| stb_image_write.h | `core/thirdparty/` | Image writing (PNG export for screenshots) | | stb_ds.h | `tasks/thirdparty/` | Dynamic array and hash map (used by task manager) | @@ -281,9 +385,9 @@ external dependencies: Each component directory has its own README with detailed API reference: -- [`dvx/README.md`](dvx/README.md) -- GUI library architecture and API +- [`core/README.md`](core/README.md) -- Core GUI library architecture and API - [`tasks/README.md`](tasks/README.md) -- Task switcher API -- [`dvxshell/README.md`](dvxshell/README.md) -- Shell internals and DXE app contract +- [`shell/README.md`](shell/README.md) -- Shell internals and DXE app contract - [`apps/README.md`](apps/README.md) -- Writing DXE applications - [`rs232/README.md`](rs232/README.md) -- Serial port driver - [`packet/README.md`](packet/README.md) -- Packet transport protocol diff --git a/apps/README.md b/apps/README.md index 820ad34..b08954d 100644 --- a/apps/README.md +++ b/apps/README.md @@ -54,6 +54,8 @@ make # builds all .app files into ../bin/apps// make clean # removes objects and binaries ``` +CFLAGS: `-O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../tasks -I../tasks/thirdparty -I../shell` + Each app is compiled to an object file with the DJGPP cross-compiler, then packaged into a `.app` via `dxe3gen`: @@ -66,11 +68,26 @@ $(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp underscore prefix). - `-E _appShutdown` is added for apps that export a shutdown hook (e.g., Clock). -- `-U` marks all other symbols as unresolved imports to be resolved from the - shell's export table at dlopen time. +- `-U` marks all other symbols as unresolved imports to be resolved at + `dlopen` time from the loader and all `RTLD_GLOBAL` modules (libdvx, + libtasks, widgets, and the shell's wrapper overrides). -Requires `lib/libdvx.a`, `lib/libtasks.a`, and the DXE3 tools from the DJGPP -toolchain. +Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`). + +## Symbol Resolution + +Apps do not link against static libraries. All symbols are resolved at load +time via the DXE3 dynamic linker: + +1. The **loader** registers libc/libm/runtime symbols via `dlregsym()`. +2. **DXE modules** loaded with `RTLD_GLOBAL` (libdvx, libtasks, widgets) + export their public APIs to the global symbol namespace. +3. The **shell** registers 3 wrapper overrides (`dvxCreateWindow`, + `dvxCreateWindowCentered`, `dvxDestroyWindow`) via `dlregsym()` for + resource tracking. + +When an app DXE is loaded, all its unresolved symbols are matched against +this combined symbol table. ## Directory Structure @@ -418,8 +435,8 @@ hook. simultaneously. Each instance gets independent globals and statics. - Avoid `static inline` functions in shared headers. Code inlined into the DXE binary cannot be updated without recompiling the app. Use macros for - trivial expressions or regular functions exported through the shell's DXE - table. + trivial expressions or regular functions exported through the DXE module + system. - Use `ctx->appDir` for loading app-relative resources (icons, data files). The working directory is shared by all apps and belongs to the shell. - Use `shellEnsureConfigDir()` + `shellConfigPath()` for persistent settings. diff --git a/config/README.md b/config/README.md new file mode 100644 index 0000000..54ff61f --- /dev/null +++ b/config/README.md @@ -0,0 +1,93 @@ +# DVX Configuration Files + +Runtime configuration, theme files, wallpaper images, and module +dependency files. These are copied into `bin/config/` (INI, themes, +wallpapers) or `bin/libs/` (dep files) during the build. + + +## Files + +| File | Destination | Purpose | +|------|-------------|---------| +| `dvx.ini` | `bin/config/dvx.ini` | Main configuration (video, mouse, colors, desktop) | +| `themes/*.thm` | `bin/config/themes/` | Color theme files (INI format with `[colors]` section) | +| `wpaper/*.jpg` | `bin/config/wpaper/` | Bundled wallpaper images | +| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx.lib dependency list | +| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell.lib dependency list | + + +## DVX.INI Format + +Standard `[section]` / `key = value` INI format. Converted to DOS +line endings (`\r\n`) during the build. + +```ini +[video] +width = 640 +height = 480 +bpp = 16 + +[mouse] +wheel = normal # normal | reversed +doubleclick = 500 # milliseconds (200-900) +acceleration = medium # off | low | medium | high + +[colors] +desktop = 0,128,128 +windowFace = 192,192,192 +# ... 20 color keys total (R,G,B triplets 0-255) + +[desktop] +wallpaper = CONFIG\WPAPER\SWOOP.JPG +mode = stretch # stretch | tile | center +``` + + +## Dependency Files (.dep) + +Plain text, one dependency per line. Each line is the base name +(without extension) of a module that must be loaded before this one. +Lines starting with `#` are comments. Empty lines are ignored. + +Example -- `dvxshell.dep`: +``` +# Core libraries +libtasks +libdvx + +# Widget modules used by the shell +box +button +checkbox +dropdown +label +listbox +listview +radio +separator +spacer +statbar +textinpt +``` + +Dep files are read by the loader during startup to determine the +correct load order via topological sort. + + +## Theme Files (.thm) + +INI format with a `[colors]` section containing the same 20 color +keys as `dvx.ini`. Loaded via the Control Panel app or +`dvxLoadTheme()`. + +```ini +[colors] +desktop = 0,128,128 +windowFace = 192,192,192 +windowHighlight = 255,255,255 +windowShadow = 128,128,128 +; ... remaining colors +``` + +Three themes are bundled: `geos.thm` (GEOS Ensemble), `win31.thm` +(Windows 3.1), `cde.thm` (CDE/Motif). diff --git a/core/README.md b/core/README.md index 9a06715..29eca7b 100644 --- a/core/README.md +++ b/core/README.md @@ -1,10 +1,16 @@ -# DVX GUI Library (libdvx.a) +# DVX Core Library (libdvx.lib) -The core GUI compositor library for DVX. Provides VESA video setup, -2D drawing primitives, dirty-rectangle compositing, a full window -manager with Motif-style chrome, and a 32-type widget toolkit with -automatic layout. Applications include `dvxApp.h` (which pulls in all -lower layers) and optionally `dvxWidget.h` for the widget system. +The core GUI compositor library for DVX, built as a DXE module +(libdvx.lib). Provides VESA video setup, 2D drawing primitives, +dirty-rectangle compositing, a full window manager with Motif-style +chrome, and the widget infrastructure (layout engine, event dispatch, +class registration). Individual widget type implementations live in +`../widgets/` as separate `.wgt` DXE modules that register themselves +at runtime via `wgtRegisterClass()`. + +Applications include `dvxApp.h` (which pulls in all lower layers) and +optionally `dvxWidget.h` for the widget system. In a DXE app context, +headers are found via `-I` flags set by the build system. ## Architecture @@ -18,9 +24,20 @@ Layer 4 dvxWm Window stack, chrome, drag, resize, focus, menus Layer 3 dvxComp Dirty rectangle list, merge, LFB flush Layer 2 dvxDraw Spans, rects, bevels, text, bitmaps (asm inner loops) Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format - dvxWidget Widget/layout system (optional, standalone) + dvxWidget Widget infrastructure (layout, events, class table) ``` +The widget class table (`widgetClassTable[]`) starts empty. Widget DXE +modules (`.wgt` files in `../widgets/`) fill it at runtime by calling +registration functions (e.g. `wgtBoxRegister()`, `wgtButtonRegister()`). +The DVX shell loader loads each widget DXE and calls its register +function before starting the application. + +Dynamic widget registration (Phase 2): external DXE plugins can call +`wgtRegisterClass()` to add entirely new widget types at runtime. The +returned type ID is >= `WGT_TYPE_DYNAMIC_BASE` (32). The maximum number +of widget types (built-in + dynamic) is `WGT_MAX_TYPES` (64). + ## File Structure @@ -39,7 +56,7 @@ Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format | File | Purpose | |------|---------| | `dvxTypes.h` | Shared type definitions used by all layers (PixelFormatT, DisplayT, WindowT, ColorSchemeT, etc.) | -| `dvxWidget.h` | Widget system public API (32 widget types, layout, events) | +| `dvxWidget.h` | Widget system public API, WidgetClassT vtable struct, WCLASS_* flags (for DXE plugins) | | `dvxDialog.h/.c` | Modal dialogs (message box, file open/save) | | `dvxPrefs.h/.c` | INI-based preferences system (read/write with typed accessors) | | `dvxFont.h` | Embedded 8x16 VGA bitmap font glyph data (CP437, 256 glyphs) | @@ -48,35 +65,35 @@ Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format | `dvxIcon.c` | stb_image implementation unit (BMP/PNG/JPEG/GIF loading) | | `dvxImageWrite.c` | stb_image_write implementation unit (PNG export) | +### Widget Infrastructure + +The widget infrastructure lives in this directory. It provides the +layout engine, event dispatch, class table, scrollbar primitives, and +shared helpers that all widget types depend on. The actual widget type +implementations (button, checkbox, listbox, etc.) are individual `.wgt` +DXE modules in `../widgets/`. + +| File | Purpose | +|------|---------| +| `widgetInternal.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes | +| `widgetClass.c` | Widget class table (`widgetClassTable[]`, starts empty) and `wgtRegisterClass()` dynamic registration | +| `widgetCore.c` | Widget allocation, tree ops, hit testing, focus management, shared helpers | +| `widgetScrollbar.c` | Widget-internal scrollbar drawing and hit testing (shared by ListBox, TreeView, etc.) | +| `widgetLayout.c` | Two-pass layout engine: measure (bottom-up) + arrange (top-down) | +| `widgetEvent.c` | Mouse, keyboard, scroll, resize, and paint event dispatch to widget tree | +| `widgetOps.c` | Paint dispatch, overlay rendering, public widget operations (wgtFind, wgtDestroy, etc.) | + ### Platform Abstraction | File | Purpose | |------|---------| | `platform/dvxPlatform.h` | OS/CPU-neutral interface: video, input, span ops, filesystem | -| `platform/dvxPlatformDos.c` | DOS/DJGPP: VESA, DPMI, INT 16h/33h, rep stosl/movsd asm spans | +| `platform/dvxPlatformDos.c` | DOS/DJGPP: VESA, DPMI, INT 16h/33h, rep stosl/movsd asm spans, DXE export table | To port DVX to a new platform, implement a new `dvxPlatformXxx.c` against `platform/dvxPlatform.h` and swap it in the Makefile. No other files need modification. -### Widget System - -| File | Purpose | -|------|---------| -| `widgets/widgetInternal.h` | Shared internal header: vtable type, constants, cross-widget prototypes | -| `widgets/widgetClass.c` | Widget class table: one WidgetClassT vtable entry per type | -| `widgets/widgetCore.c` | Allocation, tree ops, hit testing, flag-based type queries | -| `widgets/widgetLayout.c` | Two-pass layout engine (measure + arrange) | -| `widgets/widgetEvent.c` | Mouse, keyboard, scroll, resize, and paint event dispatch | -| `widgets/widgetOps.c` | Paint dispatcher, public operations (wgtFind, wgtDestroy, etc.) | -| `widgets/widget*.c` | One file per widget type (button, checkbox, slider, etc.) | - -Each widget type is self-contained in its own `.c` file. Dispatch for -paint, layout, mouse, keyboard, getText/setText, and destroy is driven -by a per-type vtable (WidgetClassT) rather than switch statements. -Adding a new widget type requires only a new `.c` file and an entry in -the class table. - ### Third Party | File | Purpose | @@ -90,10 +107,16 @@ the class table. Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`). ```bash -make # builds ../lib/libdvx.a -make clean # removes obj/ and lib/ +make # builds ../bin/libs/libdvx.lib and ../bin/libs/libdvx.dep +make clean ``` +The Makefile uses `dxe3gen` to produce the DXE module. Internal symbols +are exported with `-E` prefixes so that widget DXE modules can link +against them at runtime (`-E _widget`, `-E _sCursor`, `-E _dvx`, +`-E _wgt`, `-E _wm`, `-E _draw`, etc.). The dependency file +`libdvx.dep` is copied from `../config/libdvx.dep` to `../bin/libs/`. + Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed somewhere other than `~/djgpp/djgpp`. @@ -854,7 +877,17 @@ A retained-mode widget toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container. Layout is automatic via a flexbox-like algorithm with weighted space distribution. -### Widget Catalog (32 types) +Widget type implementations are NOT compiled into libdvx. Each widget +type is a separate `.wgt` DXE module in `../widgets/` that registers +its `WidgetClassT` vtable into `widgetClassTable[]` at load time. The +`WidgetClassT` struct and `WCLASS_*` flags are defined in `dvxWidget.h` +(the public API header) so that DXE plugins can create and register new +widget types. + +### Widget Catalog (32 built-in types) + +Each type below is implemented in its own `.wgt` DXE module and loaded +by the DVX shell at startup. #### Containers @@ -926,6 +959,51 @@ automatic via a flexbox-like algorithm with weighted space distribution. | Timer | `wgtTimer(parent, intervalMs, repeat)` | Invisible callback timer (one-shot or repeating) | +### WidgetClassT Vtable + +Each widget type has a `WidgetClassT` vtable that defines its behavior. +This struct is defined in `dvxWidget.h` so that DXE plugins can create +their own widget classes. + +```c +typedef struct WidgetClassT { + uint32_t flags; + void (*paint)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); + void (*paintOverlay)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); + void (*calcMinSize)(WidgetT *w, const BitmapFontT *font); + void (*layout)(WidgetT *w, const BitmapFontT *font); + void (*onMouse)(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy); + void (*onKey)(WidgetT *w, int32_t key, int32_t mod); + void (*destroy)(WidgetT *w); + const char *(*getText)(const WidgetT *w); + void (*setText)(WidgetT *w, const char *text); +} WidgetClassT; +``` + +Class flags (`WCLASS_*`): + +| Flag | Value | Meaning | +|------|-------|---------| +| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus (Tab navigation) | +| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses the generic VBox/HBox layout algorithm | +| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally (vs. vertical) | +| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering itself | +| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here, no child recursion | + +### Dynamic Widget Registration + +```c +int32_t wgtRegisterClass(const WidgetClassT *wclass); +``` + +Register a new widget class at runtime. Returns the assigned type ID +(>= `WGT_TYPE_DYNAMIC_BASE`, which is 32) on success, or -1 if the +table is full. The `WidgetClassT` must remain valid for the lifetime +of the process (typically a `static const` in the registering DXE). + +The `customData` field on `WidgetT` is available for DXE widget plugins +to store private per-widget data (NULL for built-in types). + ### Widget Event Model All widget types share a universal set of event callbacks set directly @@ -942,8 +1020,9 @@ on the WidgetT struct: Type-specific handlers (button press animation, listbox selection highlight) run first, then these universal callbacks fire. -Additional per-widget fields: `userData` (void pointer), `tooltip` -(hover text), `contextMenu` (right-click menu). +Additional per-widget fields: `userData` (void pointer), `customData` +(DXE plugin private data), `tooltip` (hover text), `contextMenu` +(right-click menu). ### Initialization diff --git a/loader/README.md b/loader/README.md new file mode 100644 index 0000000..d343c64 --- /dev/null +++ b/loader/README.md @@ -0,0 +1,73 @@ +# DVX Loader + +Bootstrap loader for the DVX desktop environment. Builds as +`dvx.exe` -- the only native executable in the system. Everything +else is a dynamically loaded DXE module. + + +## What It Does + +1. Changes to the executable's directory so relative paths resolve +2. Calls `platformRegisterDxeExports()` to register platform functions + and C runtime symbols (libc, libm, libgcc, DJGPP internals) with + the DXE3 loader via `dlregsym()` +3. Recursively scans `LIBS/` for `*.lib` modules and `WIDGETS/` for + `*.wgt` modules +4. Reads `.dep` files to build a dependency graph +5. Topologically sorts all modules and loads them in order with + `RTLD_GLOBAL` (each module's exports become available to + subsequent modules) +6. Calls `wgtRegister()` on any module that exports it (widget + registration) +7. Finds `shellMain()` across all loaded modules and calls it +8. On return, closes all modules in reverse order + + +## Files + +| File | Purpose | +|------|---------| +| `loaderMain.c` | Entry point, directory scanning, dependency resolution, module loading | +| `Makefile` | Cross-compilation rules; links `dvxPlatformDos.c` directly | + +The DXE export table lives in `core/platform/dvxPlatformDos.c`, not +in the loader. The loader is platform-agnostic -- all DJGPP/DXE3 +knowledge is in the platform layer. + + +## Dependency Resolution + +Each module may have a `.dep` file alongside it (same base name, +`.dep` extension). The file lists base names of modules that must +be loaded first, one per line. Lines starting with `#` are comments. + +Example -- `dvxshell.dep`: +``` +libtasks +libdvx +box +button +label +listview +``` + +A dependency is satisfied when either: +- A module with that base name has been loaded, or +- No module with that base name exists in the scan (external, assumed OK) + +The loader iterates until all modules are loaded or no further +progress can be made (circular dependency). Unloadable modules are +reported to stderr. + + +## Building + +``` +make # builds ../bin/dvx.exe +make clean +``` + +The Makefile compiles `loaderMain.c` and links it with +`dvxPlatformDos.o` (compiled from `../core/platform/dvxPlatformDos.c`) +and `-lm`. The result goes through `exe2coff` + `CWSDSTUB` to +produce a DOS executable. diff --git a/shell/README.md b/shell/README.md index 9149fce..1ac7ca4 100644 --- a/shell/README.md +++ b/shell/README.md @@ -1,67 +1,101 @@ # DVX Shell -The DVX Shell (`dvx.exe`) is the host process for the DVX desktop environment. -It initializes the GUI subsystem, loads DXE3 application modules at runtime, -runs the cooperative main loop, and provides crash recovery so a faulting app -does not take down the entire system. Think of it as the Windows 3.1 Program -Manager and kernel combined into one executable. +The DVX Shell (`dvxshell.lib`) is a DXE module loaded by the DVX loader at +startup. It initializes the GUI subsystem, loads DXE3 application modules at +runtime, runs the cooperative main loop, and provides crash recovery so a +faulting app does not take down the entire system. + +The shell is not a standalone executable. The loader maps it into memory via +`dlopen`, resolves its entry point (`shellMain`), and calls it. All symbols +the shell needs -- libdvx, libtasks, widgets, libc -- are resolved at load +time from DXE modules already loaded by the loader and the loader's own +`dlregsym` table. ## Files | File | Purpose | |------|---------| -| `shellMain.c` | Entry point, main loop, crash recovery, logging, desktop update callbacks | -| `shellApp.c` | App loading (dlopen/dlsym), lifecycle state machine, reaping, resource tracking, config dirs | -| `shellApp.h` | `ShellAppT`, `AppDescriptorT`, `AppStateE`, `DxeAppContextT`, public shell API | -| `shellExport.c` | DXE export table (400+ symbols), wrapper functions for resource tracking | -| `shellInfo.c` | System information gathering (delegates to platform layer), caches result | +| `shellMain.c` | Entry point (`shellMain`), main loop, crash recovery, logging | +| `shellApp.c` | App loading via `dlopen`/`dlsym`, lifecycle, reaping | +| `shellApp.h` | `ShellAppT`, `AppDescriptorT`, `AppStateE`, `DxeAppContextT`, API | +| `shellExport.c` | 3 DXE wrapper overrides for window resource tracking | +| `shellInfo.c` | System info gathering | | `shellInfo.h` | `shellInfoInit()`, `shellGetSystemInfo()` | -| `shellTaskMgr.c` | Task Manager window -- list view, Switch To / End Task / Run buttons | +| `shellTaskMgr.c` | Task Manager window | | `shellTaskMgr.h` | `shellTaskMgrOpen()`, `shellTaskMgrRefresh()` | -| `Makefile` | Cross-compile rules, links `-ldvx -ltasks -lm` | +| `Makefile` | Cross-compile rules, produces `dvxshell.lib` and `dvxshell.dep` | ## Building ``` -make # builds ../bin/dvx.exe (also builds libdvx.a, libtasks.a) -make clean # removes objects and binary +make # builds ../bin/libs/dvxshell.lib and ../bin/libs/dvxshell.dep +make clean # removes objects, binary, and deployed config files ``` +CFLAGS: `-O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty` + +The shell is compiled to object files and packaged into a DXE via `dxe3gen`. +It exports `_shell` so the loader can resolve `shellMain`. All other symbols +are unresolved imports (`-U`) satisfied at load time. + Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`). +## Dependencies + +`dvxshell.dep` lists the modules the loader must load before the shell: + +``` +libtasks +libdvx +box +button +checkbox +dropdown +label +listbox +listview +radio +separator +spacer +statbar +textinpt +``` + +The loader reads this file, loads each module via `dlopen` with `RTLD_GLOBAL`, +then loads the shell itself. This is how all symbols (dvx*, wgt*, ts*, etc.) +become available without the shell statically linking anything. + ## Startup Sequence -`main()` in `shellMain.c` performs initialization in this order: +`shellMain()` in `shellMain.c` is called by the loader after all dependencies +are loaded. It performs initialization in this order: -1. **Change to exe directory** -- resolve the directory containing `dvx.exe` - via `argv[0]` and `platformChdir()` so that relative paths (`CONFIG/`, - `APPS/`, etc.) work regardless of where the user launched from. -2. **Truncate log** -- open `dvx.log` for write to clear it, then close. +1. **Truncate log** -- open `dvx.log` for write to clear it, then close. All subsequent writes use append-per-write (the file is never held open). -3. **Load preferences** -- `prefsLoad("CONFIG/DVX.INI")`. Missing file or +2. **Load preferences** -- `prefsLoad("CONFIG/DVX.INI")`. Missing file or keys silently fall back to compiled-in defaults. -4. **dvxInit** -- initialize VESA video (LFB), backbuffer, compositor, window +3. **dvxInit** -- initialize VESA video (LFB), backbuffer, compositor, window manager, font, cursor, input subsystems. Reads video width/height/bpp from preferences (default 640x480x16). -5. **Mouse config** -- read wheel direction, double-click speed, acceleration +4. **Mouse config** -- read wheel direction, double-click speed, acceleration from `[mouse]` section and call `dvxSetMouseConfig()`. -6. **Color scheme** -- read `[colors]` section (20 RGB triplets), apply via +5. **Color scheme** -- read `[colors]` section (20 RGB triplets), apply via `dvxApplyColorScheme()`. -7. **Wallpaper** -- read `[desktop]` section for wallpaper path and mode +6. **Wallpaper** -- read `[desktop]` section for wallpaper path and mode (stretch/tile/center), load via `dvxSetWallpaper()`. -8. **Video mode log** -- enumerate all available VESA modes to `dvx.log`. -9. **Task system** -- `tsInit()`, set shell task (task 0) to +7. **Video mode log** -- enumerate all available VESA modes to `dvx.log`. +8. **Task system** -- `tsInit()`, set shell task (task 0) to `TS_PRIORITY_HIGH` so the UI stays responsive under load. -10. **System info** -- `shellInfoInit()` gathers CPU, memory, drive info via - the platform layer and logs it. -11. **DXE exports** -- `shellExportInit()` calls `dlregsym()` to register the - export table. Must happen before any `dlopen()`. -12. **App slot table** -- `shellAppInit()` zeroes the 32-slot fixed array. -13. **Idle/hotkey callbacks** -- wire up `idleYield`, `ctrlEscHandler`, +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`. -14. **Desktop app** -- `shellLoadApp(ctx, "apps/progman/progman.app")`. If +13. **Desktop app** -- `shellLoadApp(ctx, "apps/progman/progman.app")`. If this fails, the shell exits. -15. **Crash handlers** -- `installCrashHandler()` registers signal handlers +14. **Crash handlers** -- `installCrashHandler()` registers signal handlers for SIGSEGV, SIGFPE, SIGILL. Installed last so initialization crashes get the default DJGPP abort-with-register-dump instead of our recovery path. @@ -100,7 +134,7 @@ The shell provides Windows 3.1-style fault tolerance using `setjmp`/`longjmp`: 1. `installCrashHandler()` registers `crashHandler` for SIGSEGV, SIGFPE, SIGILL. -2. `setjmp(sCrashJmp)` in `main()` establishes the recovery point. +2. `setjmp(sCrashJmp)` in `shellMain()` establishes the recovery point. 3. If a signal fires (in any task), `crashHandler` logs the crash details (signal, app name, full register dump from `__djgpp_exception_state_ptr`), re-installs the handler (DJGPP uses SysV semantics -- handler resets to @@ -122,7 +156,8 @@ registers, and EFLAGS -- invaluable for post-mortem debugging. ### DXE3 Loading DXE3 is DJGPP's dynamic linking mechanism. Each `.app` file is a DXE3 shared -object. The shell resolves symbols from the export table registered via +object. Symbols are resolved from the loaded RTLD_GLOBAL modules (libdvx, +libtasks, widgets) and the shell's 3 wrapper overrides registered via `dlregsym()`. The load sequence in `shellLoadApp()`: 1. Allocate a slot from the 32-entry fixed array (slot 0 is the shell). @@ -216,10 +251,31 @@ to which app, enabling: `sCurrentAppId` is a simple global (not thread-local) because cooperative multitasking means only one task runs at a time. +## DXE Export Table + +`shellExport.c` registers 3 wrapper functions via `dlregsym()` that override +the real implementations for subsequently loaded app DXEs: + +1. `dvxCreateWindow` -- stamps `win->appId` for resource ownership tracking. +2. `dvxCreateWindowCentered` -- stamps `win->appId` for resource ownership + tracking. +3. `dvxDestroyWindow` -- checks if the destroyed window was a callback-only + app's last window and marks it for reaping. + +The key mechanic: `dlregsym` takes precedence over `RTLD_GLOBAL` exports. +Since libdvx (which has the real functions) was loaded before +`shellExportInit()` registers these wrappers, libdvx keeps the real +implementations. But any app DXE loaded afterward gets the wrappers, which +add resource tracking transparently. + +All other symbol exports -- dvx*, wgt*, ts*, platform*, libc, libm -- come +from the DXE modules loaded with `RTLD_GLOBAL` by the loader. They no longer +need to be listed in the shell's export table. + ## Task Manager `shellTaskMgr.c` implements a shell-level Task Manager accessible via -**Ctrl+Esc** regardless of which app is focused or whether the desktop app +Ctrl+Esc regardless of which app is focused or whether the desktop app is running. It is owned by the shell (appId = 0), not by any DXE app. Features: @@ -262,38 +318,6 @@ API: Apps use the standard preferences system (`prefsLoad`/`prefsSave`) pointed at their config directory for persistent settings. -## DXE Export Table - -`shellExport.c` contains the ABI contract between the shell and apps. Three -categories of exports: - -1. **Wrapped functions** (3): `dvxCreateWindow`, `dvxCreateWindowCentered`, - `dvxDestroyWindow`. These are intercepted to stamp `win->appId` for - resource ownership tracking. Apps see them under their original names -- - the wrapping is transparent. - -2. **Direct exports** (200+): all other `dvx*`, `wgt*`, `wm*`, `ts*`, - drawing, preferences, platform, and shell API functions. Safe to call - without interception. - -3. **libc / libm / runtime exports** (200+): DXE3 modules are relocatable - objects, not fully linked executables. Every C library function a DXE - calls must be explicitly listed so the loader can resolve it at dlopen - time. This includes: - - Memory (malloc, calloc, realloc, free) - - String operations (str*, mem*) - - Formatted I/O (printf, snprintf, fprintf, sscanf, etc.) - - File I/O (fopen, fread, fwrite, fclose, etc.) - - Directory operations (opendir, readdir, closedir, mkdir) - - Time (time, localtime, clock, strftime) - - Math (sin, cos, sqrt, pow, floor, ceil, etc.) - - stb_ds internals (arrput/arrfree/hm* macro backends) - - stb_image / stb_image_write - - libgcc 64-bit integer helpers (__divdi3, __moddi3, etc.) - - DJGPP stdio internals (__dj_stdin, __dj_stdout, __dj_stderr) - -The table is registered once via `dlregsym()` before any `dlopen()`. - ## System Hotkeys These are always active regardless of which app is focused: @@ -341,6 +365,7 @@ full register dumps. | Function | Description | |----------|-------------| +| `shellMain(argc, argv)` | Entry point called by the DVX loader | | `shellAppInit()` | Zero the 32-slot app table | | `shellLoadApp(ctx, path)` | Load and start a DXE app. Returns app ID (>= 1) or -1 | | `shellReapApps(ctx)` | Clean up terminated apps (call each frame). Returns true if any reaped | @@ -352,7 +377,7 @@ full register dumps. | `shellLog(fmt, ...)` | Append to dvx.log | | `shellEnsureConfigDir(ctx)` | Create an app's config directory tree | | `shellConfigPath(ctx, name, buf, size)` | Build path to file in app's config dir | -| `shellExportInit()` | Register DXE symbol export table via dlregsym() | +| `shellExportInit()` | Register 3 DXE wrapper overrides via dlregsym() | | `shellRegisterDesktopUpdate(fn)` | Register callback for app state changes | | `shellUnregisterDesktopUpdate(fn)` | Remove a previously registered callback | | `shellDesktopUpdate()` | Notify all registered listeners | diff --git a/tasks/README.md b/tasks/README.md index 67d0752..1b640a8 100644 --- a/tasks/README.md +++ b/tasks/README.md @@ -25,13 +25,12 @@ lets each task yield at safe points, avoiding synchronization entirely. ## Files -| File | Description | -|-----------------------|----------------------------------------------------| -| `taskswitch.h` | Public API -- types, constants, function prototypes | -| `taskswitch.c` | Implementation (scheduler, context switch, slots) | -| `demo.c` | Standalone test harness exercising all features | -| `thirdparty/stb_ds.h` | stb dynamic array/hashmap library (third-party) | -| `Makefile` | DJGPP cross-compilation build rules | +| 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 | ## Building @@ -39,18 +38,25 @@ lets each task yield at safe points, avoiding synchronization entirely. Cross-compile from Linux: ``` -make # builds ../lib/libtasks.a -make demo # also builds ../bin/tsdemo.exe -make clean # removes objects, library, and demo binary +make # builds ../bin/libs/libtasks.lib +make clean # removes objects and library ``` Output: -| Path | Description | -|---------------------|----------------------| -| `../lib/libtasks.a` | Static library | -| `../obj/tasks/` | Object files | -| `../bin/tsdemo.exe` | Demo executable | +| 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 @@ -109,75 +115,75 @@ available for reuse by the next `tsCreate()` call. ### Initialization and Teardown -| Function | Signature | Description | -|--------------|-------------------------|--------------------------------------------------------------------| -| `tsInit` | `int32_t tsInit(void)` | Initialize the library. Returns `TS_OK` or a negative error code. | +| 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 | -|------------|---------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------| +| 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). | +| `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 | -|-----------|----------------------|-----------------------------------------------------------------| +| 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. | +| 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 | -|-----------------|---------------------------------------------------|-----------------------------------------------------------------------------------| +| 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. | +| `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 | -|-------------------|------------------------------|------------------------------------------------------------------------------------------------------------------| +| 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. | +| Function | Signature | Description | +|----------|-----------|-------------| +| `tsGetState` | `TaskStateE tsGetState(uint32_t id)` | Return the task's state enum value. | +| `tsCurrentId` | `uint32_t tsCurrentId(void)` | Return the ID of the currently running task. | +| `tsGetName` | `const char *tsGetName(uint32_t id)` | Return the task's name string, or `NULL` on invalid ID. | +| `tsActiveCount` | `uint32_t tsActiveCount(void)` | Return the number of non-terminated tasks. | ## Constants ### Error Codes -| Name | Value | Meaning | -|----------------|-------|--------------------------------------------------| -| `TS_OK` | 0 | Success | -| `TS_ERR_INIT` | -1 | Library not initialized | -| `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_STATE` | -5 | Invalid state transition | +| Name | Value | Meaning | +|------|-------|---------| +| `TS_OK` | 0 | Success | +| `TS_ERR_INIT` | -1 | Library not initialized | +| `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_STATE` | -5 | Invalid state transition | ### Priority Presets -| Name | Value | Credits per Round | -|----------------------|-------|-------------------| -| `TS_PRIORITY_LOW` | 0 | 1 | -| `TS_PRIORITY_NORMAL` | 5 | 6 | -| `TS_PRIORITY_HIGH` | 10 | 11 | +| 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 @@ -186,10 +192,10 @@ provided for convenience. In the DVX Shell, the main task runs at ### Defaults -| Name | Value | Description | -|-------------------------|-------|------------------------| +| Name | Value | Description | +|------|-------|-------------| | `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack per task | -| `TS_NAME_MAX` | 32 | Max task name length | +| `TS_NAME_MAX` | 32 | Max task name length | ## Types @@ -280,29 +286,29 @@ versions. Six callee-saved values are saved and restored per switch: -| Register | Offset | Purpose | -|----------|--------|------------------------------------------| -| EBX | 0 | Callee-saved general purpose | -| ESI | 4 | Callee-saved general purpose | -| EDI | 8 | Callee-saved general purpose | -| EBP | 12 | Frame pointer | -| ESP | 16 | Stack pointer | -| EIP | 20 | Resume address (captured as local label) | +| 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) | +| 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 @@ -326,32 +332,10 @@ and then `tsExit()`. each task's needs. -## Demo - -`demo.c` exercises five phases: - -1. **Priority scheduling** -- creates tasks at low, normal, and high - priority. All tasks run, but the high-priority task gets significantly - more turns. -2. **Pause** -- pauses one task mid-run and shows it stops being - scheduled. -3. **Resume** -- resumes the paused task and shows it picks up where it - left off. -4. **Priority boost** -- raises the low-priority task above all others - and shows it immediately gets more turns. -5. **Slot reuse** -- creates three waves of short-lived tasks that - terminate and shows subsequent waves reuse the same task IDs. - -Build and run: - -``` -make demo -tsdemo -``` - - ## Third-Party Dependencies - **stb_ds.h** (Sean Barrett) -- dynamic array and hashmap library. Located in `thirdparty/stb_ds.h`. Used for the task control block - array. Public domain / MIT licensed. + array. Included in the DXE build and exported via `-E _stbds_` so + other modules can use stb_ds without bundling their own copy. + Public domain / MIT licensed. diff --git a/widgets/README.md b/widgets/README.md new file mode 100644 index 0000000..6973b70 --- /dev/null +++ b/widgets/README.md @@ -0,0 +1,95 @@ +# DVX Widget Modules + +Individual widget type implementations for the DVX GUI, each built +as a separate `.wgt` DXE module. The loader scans the `WIDGETS/` +directory recursively at startup and loads every `.wgt` file it +finds, calling `wgtRegister()` on each to fill the widget class +table. + +Drop a new `.wgt` file in the directory and it is automatically +available -- no loader or core library changes needed. + + +## How Widget DXEs Work + +Each `.wgt` module contains: + +1. The widget implementation (paint, layout, mouse/key handlers) +2. A `static const WidgetClassT` vtable definition +3. A `wgtRegister()` function that writes the vtable pointer into + `widgetClassTable[]` at the widget's enum slot +4. The public API functions (e.g. `wgtTreeView()`, `wgtTreeViewGetSelected()`) + +At load time, the module resolves its dependencies against: +- The loader's `dlregsym` table (platform functions, libc) +- `libtasks.lib` exports (stb_ds dynamic arrays) +- `libdvx.lib` exports (widget infrastructure: `widgetAlloc`, + `widgetClassTable`, `widgetScrollbarThumb`, shared state, etc.) +- Other widget modules loaded earlier (via `RTLD_GLOBAL`) + + +## Widget Catalog + +| Module | Widget Types | Description | +|--------|-------------|-------------| +| `box.wgt` | VBox, HBox, Frame | Containers: vertical/horizontal box layout, titled frame | +| `button.wgt` | Button | Push button with text and keyboard accelerator | +| `canvas.wgt` | Canvas | Freeform drawing surface with mouse callbacks | +| `checkbox.wgt` | Checkbox | Toggle checkbox with label | +| `combobox.wgt` | ComboBox | Editable text input with dropdown list | +| `dropdown.wgt` | Dropdown | Non-editable dropdown selector | +| `image.wgt` | Image | Static image display (BMP/PNG/JPEG/GIF) | +| `imgbtn.wgt` | ImageButton | Button with icon image | +| `label.wgt` | Label | Static text display with optional accelerator | +| `listbox.wgt` | ListBox | Scrollable string list with single/multi select | +| `listview.wgt` | ListView | Multi-column list with headers, sorting, column resize | +| `progress.wgt` | ProgressBar | Horizontal or vertical progress indicator | +| `radio.wgt` | RadioGroup, Radio | Mutually exclusive option buttons | +| `scrlpane.wgt` | ScrollPane | Scrollable viewport for oversized content | +| `separatr.wgt` | Separator | Horizontal or vertical divider line | +| `slider.wgt` | Slider | Horizontal value slider with thumb | +| `spacer.wgt` | Spacer | Invisible layout spacer | +| `spinner.wgt` | Spinner | Numeric input with up/down buttons | +| `splitter.wgt` | Splitter | Resizable two-pane divider | +| `statbar.wgt` | StatusBar | Horizontal bar at window bottom | +| `tabctrl.wgt` | TabControl, TabPage | Tabbed page container | +| `terminal.wgt` | AnsiTerm | VT100 terminal emulator with scrollback | +| `textinpt.wgt` | TextInput, TextArea | Single-line and multi-line text editing | +| `timer.wgt` | Timer | Periodic callback (no visual) | +| `toolbar.wgt` | Toolbar | Horizontal button/widget bar | +| `treeview.wgt` | TreeView, TreeItem | Hierarchical tree with expand/collapse | + +26 modules implementing 32 widget types (some modules register +multiple related types). + + +## Writing a New Widget + +1. Create `widgets/myWidget.c` +2. Include `"widgetInternal.h"` for infrastructure access +3. Implement paint, calcMinSize, and event handlers as needed +4. Define a `static const WidgetClassT` with your vtable +5. Add a `wgtRegister()` function: + ```c + void wgtRegister(void) { + widgetClassTable[MyWidgetTypeE] = &sClassMyWidget; + } + ``` +6. Add the new enum value to `WidgetTypeE` in `core/dvxWidget.h` + (or use `wgtRegisterClass()` for a fully dynamic type ID) +7. Add public API functions (e.g. `wgtMyWidget()`) that call + `widgetAlloc(parent, MyWidgetTypeE)` +8. Add a build rule in `widgets/Makefile` +9. Build -- the loader discovers and loads it automatically + + +## Building + +``` +make # compiles all widget .c files, creates .wgt modules in ../bin/widgets/ +make clean +``` + +Each `.wgt` is created with `dxe3gen -E _wgt -U`, which exports all +symbols starting with `wgt` (public API + `wgtRegister`) and allows +unresolved symbols (resolved at load time from other modules).