Docs.
This commit is contained in:
parent
67872c6b98
commit
56816eedd8
12 changed files with 1582 additions and 2872 deletions
487
README.md
487
README.md
|
|
@ -1,125 +1,140 @@
|
||||||
# DVX -- DOS Visual eXecutive
|
# DVX -- DOS Visual eXecutive
|
||||||
|
|
||||||
A windowed GUI compositor and desktop shell for DOS, built with
|
A windowed GUI compositor and widget toolkit targeting DJGPP/DPMI on
|
||||||
DJGPP/DPMI. DVX combines a Motif-style window manager, dirty-rectangle
|
486+ hardware. VESA VBE 2.0+ LFB only. Motif-inspired visual style
|
||||||
compositor, cooperative task switcher, and a modular DXE3 architecture
|
with 2px bevels, fixed bitmap fonts, and dirty-rect compositing.
|
||||||
into a multitasking desktop environment. The bootstrap loader
|
|
||||||
dynamically discovers and loads core libraries, widget plugins, and
|
|
||||||
applications at runtime.
|
|
||||||
|
|
||||||
Targets real and emulated 486+ hardware with VESA VBE 2.0+ linear
|
The system runs on 86Box (primary target) and real DOS hardware.
|
||||||
framebuffer. No bank switching -- LFB or fail.
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Motif/GEOS-style beveled window chrome with drag, resize, minimize,
|
|
||||||
maximize, and restore
|
|
||||||
- Dirty-rectangle compositor -- only changed regions are flushed to video
|
|
||||||
memory, critical for acceptable frame rates on 486/Pentium hardware
|
|
||||||
- 32 widget types across 26 DXE modules: buttons, text inputs, list
|
|
||||||
boxes, tree views, list views, tab controls, sliders, spinners,
|
|
||||||
progress bars, dropdowns, combo boxes, splitters, scroll panes, ANSI
|
|
||||||
terminal emulator, and more
|
|
||||||
- Flexbox-style automatic layout engine (VBox/HBox containers with
|
|
||||||
weighted space distribution)
|
|
||||||
- Dropdown menus with cascading submenus, checkbox and radio items,
|
|
||||||
keyboard accelerators, and context menus
|
|
||||||
- Modal dialogs: message box (OK/Cancel/Yes/No/Retry) and file
|
|
||||||
open/save with directory navigation and filter dropdown
|
|
||||||
- 20-color theme system with live preview and INI-based theme files
|
|
||||||
- Wallpaper support: stretch, tile, or center with bilinear scaling and
|
|
||||||
16bpp ordered dithering
|
|
||||||
- Live video mode switching without restart
|
|
||||||
- Mouse wheel support (CuteMouse Wheel API)
|
|
||||||
- Screenshots (full screen or per-window) saved as PNG
|
|
||||||
- Clipboard (copy/cut/paste within DVX)
|
|
||||||
- Timer widget for periodic callbacks
|
|
||||||
- Cooperative task switcher for apps that need their own main loop
|
|
||||||
- DXE3 dynamic application loading with crash recovery (SIGSEGV,
|
|
||||||
SIGFPE, SIGILL caught and isolated per-app)
|
|
||||||
- INI-based preferences system with typed read/write accessors
|
|
||||||
- Encrypted serial networking stack (DH key exchange, XTEA-CTR cipher)
|
|
||||||
- Platform abstraction layer -- DOS/DJGPP production target, Linux/SDL2
|
|
||||||
development target
|
|
||||||
- Modular DXE architecture with dependency resolution -- core libraries,
|
|
||||||
widget plugins, and applications are all separate loadable modules
|
|
||||||
|
|
||||||
|
|
||||||
## Target Hardware
|
|
||||||
|
|
||||||
- **CPU**: 486 baseline, Pentium-optimized paths where significant
|
|
||||||
- **Video**: VESA VBE 2.0+ with linear framebuffer
|
|
||||||
- **OS**: DOS with DPMI (CWSDPMI or equivalent)
|
|
||||||
- **Supported depths**: 8, 15, 16, 24, 32 bpp
|
|
||||||
- **Test platform**: 86Box with PCI video cards
|
|
||||||
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
DVX uses a modular DXE3 architecture. The bootstrap loader (`dvx.exe`)
|
DVX is built as a set of DXE3 dynamic modules loaded by a bootstrap
|
||||||
is a small executable that discovers, dependency-sorts, and loads all
|
executable. The loader resolves dependencies and loads modules in
|
||||||
modules at startup. Core libraries, widget plugins, and applications
|
topological order, then hands control to the shell.
|
||||||
are separate `.lib`, `.wgt`, and `.app` DXE shared libraries.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
dvx.exe (bootstrap loader)
|
dvx.exe (loader)
|
||||||
|
|
|
|
||||||
+-- platformRegisterDxeExports() -- platform + libc/libm symbols
|
+-- libs/libtasks.lib cooperative task switcher
|
||||||
|
+-- libs/libdvx.lib core GUI (draw, comp, wm, app, widget infra)
|
||||||
|
+-- libs/texthelp.lib shared text editing helpers
|
||||||
|
+-- libs/listhelp.lib shared list/dropdown helpers
|
||||||
|
+-- libs/dvxshell.lib shell (app lifecycle, desktop, task manager)
|
||||||
|
|
|
|
||||||
+-- libs/ (scanned, dependency-ordered)
|
+-- widgets/*.wgt 26 widget type plugins
|
||||||
| libtasks.lib -- cooperative task switching, stb_ds
|
|
||||||
| libdvx.lib -- draw, comp, wm, app, widget infrastructure
|
|
||||||
| dvxshell.lib -- shell, app lifecycle, crash recovery
|
|
||||||
|
|
|
|
||||||
+-- widgets/ (scanned, each calls wgtRegister)
|
+-- apps/*/*.app DXE applications
|
||||||
| box.wgt, button.wgt, label.wgt, ... (26 modules)
|
|
||||||
|
|
|
||||||
+-- shellMain() -- found via dlsym, enters main loop
|
|
||||||
|
|
|
||||||
+-- apps/ (loaded on demand by shell)
|
|
||||||
progman.app, notepad.app, clock.app, ...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Boot Sequence
|
|
||||||
|
|
||||||
1. `dvx.exe` starts, calls `platformRegisterDxeExports()` to register
|
## Directory Structure
|
||||||
platform and libc/libm symbols for DXE modules
|
|
||||||
2. Scans `libs/` recursively for `*.lib` files, reads `.dep` files,
|
|
||||||
topologically sorts, and loads in dependency order
|
|
||||||
3. Scans `widgets/` recursively for `*.wgt` files, loads each, and
|
|
||||||
calls `wgtRegister()` on any module that exports it
|
|
||||||
4. Finds `shellMain()` across loaded modules via `dlsym`
|
|
||||||
5. Calls `shellMain()`, which initializes the GUI, loads the desktop
|
|
||||||
app, and enters the main event loop
|
|
||||||
|
|
||||||
### Dependency Resolution
|
| Directory | Output | Description |
|
||||||
|
|--------------|-------------------------|--------------------------------------------|
|
||||||
|
| `loader/` | `bin/dvx.exe` | Bootstrap loader, platform layer, stb_ds |
|
||||||
|
| `core/` | `bin/libs/libdvx.lib` | Core GUI library (5 layers + widget infra) |
|
||||||
|
| `tasks/` | `bin/libs/libtasks.lib` | Cooperative task switching |
|
||||||
|
| `texthelp/` | `bin/libs/texthelp.lib` | Shared text editing helpers |
|
||||||
|
| `listhelp/` | `bin/libs/listhelp.lib` | Shared list/dropdown helpers |
|
||||||
|
| `shell/` | `bin/libs/dvxshell.lib` | DVX Shell (app lifecycle, task manager) |
|
||||||
|
| `widgets/` | `bin/widgets/*.wgt` | 26 individual widget DXE modules |
|
||||||
|
| `apps/` | `bin/apps/*/*.app` | Application DXE modules |
|
||||||
|
| `config/` | `bin/config/`, `bin/libs/`, `bin/widgets/` | INI config, themes, wallpapers, dep files |
|
||||||
|
| `rs232/` | `lib/librs232.a` | ISR-driven UART serial driver |
|
||||||
|
| `packet/` | `lib/libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
|
||||||
|
| `security/` | `lib/libsecurity.a` | DH key exchange, XTEA-CTR cipher, RNG |
|
||||||
|
| `seclink/` | `lib/libseclink.a` | Secure serial link (channels, encryption) |
|
||||||
|
| `proxy/` | `bin/secproxy` | Linux proxy: 86Box <-> telnet BBS |
|
||||||
|
| `termdemo/` | `bin/termdemo.exe` | Standalone encrypted terminal demo |
|
||||||
|
|
||||||
Each DXE module may have a `.dep` file (same base name, `.dep`
|
|
||||||
extension) listing the base names of modules that must be loaded before
|
|
||||||
it. The loader reads all `.dep` files, builds a dependency graph, and
|
|
||||||
loads modules in topological order. For example, `libdvx.dep` contains
|
|
||||||
`libtasks`, and `dvxshell.dep` lists `libtasks`, `libdvx`, and all
|
|
||||||
widget modules it requires.
|
|
||||||
|
|
||||||
### Widget DXE Modules
|
## Build
|
||||||
|
|
||||||
The 32 widget types in the `WidgetTypeE` enum are implemented across 26
|
Requires the DJGPP cross-compiler at `~/djgpp/djgpp`.
|
||||||
`.wgt` DXE modules. Some modules register multiple widget types:
|
|
||||||
|
|
||||||
| Module | Widget Types |
|
```
|
||||||
|--------|-------------|
|
make # build everything
|
||||||
| `box.wgt` | VBox, HBox, Frame |
|
make clean # remove all build artifacts
|
||||||
| `radio.wgt` | RadioGroup, Radio |
|
./mkcd.sh # build + create ISO for 86Box
|
||||||
| `textinpt.wgt` | TextInput, TextArea |
|
```
|
||||||
| `tabctrl.wgt` | TabControl, TabPage |
|
|
||||||
| `treeview.wgt` | TreeView, TreeItem |
|
|
||||||
|
|
||||||
Each widget DXE exports a standard `wgtRegister()` function that
|
The top-level Makefile builds in dependency order:
|
||||||
registers its widget class(es) with the core widget infrastructure.
|
|
||||||
Widget DXEs are discovered by directory scanning, not hardcoded.
|
|
||||||
|
|
||||||
### App Types
|
```
|
||||||
|
core -> tasks -> loader -> texthelp -> listhelp -> widgets -> shell -> apps
|
||||||
|
```
|
||||||
|
|
||||||
|
Build output goes to `bin/` (executables, DXE modules, config) and
|
||||||
|
`obj/` (intermediate object files).
|
||||||
|
|
||||||
|
|
||||||
|
## Runtime Directory Layout (bin/)
|
||||||
|
|
||||||
|
```
|
||||||
|
bin/
|
||||||
|
dvx.exe bootstrap loader (entry point)
|
||||||
|
libs/
|
||||||
|
libtasks.lib task switching library
|
||||||
|
libdvx.lib core GUI library
|
||||||
|
texthelp.lib text editing helpers
|
||||||
|
listhelp.lib list/dropdown helpers
|
||||||
|
dvxshell.lib DVX shell
|
||||||
|
*.dep dependency files for load ordering
|
||||||
|
widgets/
|
||||||
|
box.wgt VBox/HBox/Frame containers
|
||||||
|
button.wgt push button
|
||||||
|
... (26 widget modules total)
|
||||||
|
*.dep dependency files
|
||||||
|
apps/
|
||||||
|
progman/progman.app Program Manager (desktop)
|
||||||
|
notepad/notepad.app text editor
|
||||||
|
clock/clock.app clock display
|
||||||
|
dvxdemo/dvxdemo.app widget showcase
|
||||||
|
cpanel/cpanel.app control panel
|
||||||
|
imgview/imgview.app image viewer
|
||||||
|
config/
|
||||||
|
dvx.ini system configuration
|
||||||
|
themes/ color theme files (.thm)
|
||||||
|
wpaper/ wallpaper images
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Core Architecture (5 Layers)
|
||||||
|
|
||||||
|
1. **dvxVideo** -- VESA VBE init, LFB mapping, backbuffer, pixel format, color packing
|
||||||
|
2. **dvxDraw** -- Rectangle fills, bevels, text rendering, cursor/icon drawing
|
||||||
|
3. **dvxComp** -- Dirty rectangle tracking, merge, compositor, LFB flush
|
||||||
|
4. **dvxWm** -- Window stack, chrome, drag/resize, menus, scrollbars, hit testing
|
||||||
|
5. **dvxApp** -- Event loop, input polling, public API, color schemes, wallpaper
|
||||||
|
|
||||||
|
|
||||||
|
## Widget System
|
||||||
|
|
||||||
|
Widgets are isolated DXE modules. Core knows nothing about individual
|
||||||
|
widget types -- no compile-time enum, no union, no per-widget structs in
|
||||||
|
dvxWidget.h.
|
||||||
|
|
||||||
|
* **Dynamic type IDs**: `wgtRegisterClass()` assigns IDs at load time
|
||||||
|
* **void *data**: Each widget allocates its own private data struct
|
||||||
|
* **Per-widget API registry**: `wgtRegisterApi("name", &api)` replaces the monolithic API
|
||||||
|
* **Per-widget headers**: `widgets/widgetButton.h` etc. provide typed macros
|
||||||
|
* **Shared helpers**: texthelp.lib (text editing) and listhelp.lib (dropdown/list)
|
||||||
|
|
||||||
|
|
||||||
|
## DXE Module System
|
||||||
|
|
||||||
|
All modules are DXE3 dynamic libraries loaded by dvx.exe at startup.
|
||||||
|
The loader scans `libs/` for `.lib` files and `widgets/` for `.wgt`
|
||||||
|
files, reads `.dep` files for dependencies, and loads in topological
|
||||||
|
order. Widget modules that export `wgtRegister()` have it called after
|
||||||
|
loading.
|
||||||
|
|
||||||
|
Each `.dep` file lists base names of modules that must load first, one
|
||||||
|
per line. Comments start with `#`.
|
||||||
|
|
||||||
|
|
||||||
|
## App Types
|
||||||
|
|
||||||
**Callback-only** (`hasMainLoop = false`): `appMain` creates windows,
|
**Callback-only** (`hasMainLoop = false`): `appMain` creates windows,
|
||||||
registers callbacks, and returns. The app lives through event callbacks
|
registers callbacks, and returns. The app lives through event callbacks
|
||||||
|
|
@ -130,7 +145,8 @@ the app. `appMain` runs its own loop calling `tsYield()` to share CPU.
|
||||||
Used for apps that need continuous processing (clocks, terminal
|
Used for apps that need continuous processing (clocks, terminal
|
||||||
emulators, games).
|
emulators, games).
|
||||||
|
|
||||||
### Crash Recovery
|
|
||||||
|
## Crash Recovery
|
||||||
|
|
||||||
The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL. If
|
The shell installs signal handlers for SIGSEGV, SIGFPE, and SIGILL. If
|
||||||
an app crashes, the handler `longjmp`s back to the shell's main loop,
|
an app crashes, the handler `longjmp`s back to the shell's main loop,
|
||||||
|
|
@ -138,221 +154,16 @@ the crashed app is force-killed, and the shell continues running.
|
||||||
Diagnostic information (registers, faulting EIP) is logged to `dvx.log`.
|
Diagnostic information (registers, faulting EIP) is logged to `dvx.log`.
|
||||||
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
### Source Tree
|
|
||||||
|
|
||||||
```
|
|
||||||
dvxgui/
|
|
||||||
core/ Core GUI infrastructure (-> libs/libdvx.lib)
|
|
||||||
platform/ Platform abstraction (dvxPlatform.h, dvxPlatformDos.c)
|
|
||||||
thirdparty/ stb_image.h, stb_image_write.h
|
|
||||||
tasks/ Cooperative task switcher (-> libs/libtasks.lib)
|
|
||||||
thirdparty/ stb_ds.h
|
|
||||||
shell/ Desktop shell (-> libs/dvxshell.lib)
|
|
||||||
widgets/ Widget modules (-> widgets/*.wgt, 26 files)
|
|
||||||
loader/ Bootstrap loader (-> dvx.exe)
|
|
||||||
apps/ Application modules (-> apps/*.app)
|
|
||||||
progman/ Program Manager -- app launcher grid
|
|
||||||
notepad/ Text editor with file I/O
|
|
||||||
clock/ Digital clock (multi-instance, main-loop)
|
|
||||||
dvxdemo/ Widget system showcase / demo app
|
|
||||||
cpanel/ Control Panel -- themes, wallpaper, video, mouse
|
|
||||||
imgview/ Image Viewer -- BMP/PNG/JPEG/GIF display
|
|
||||||
config/ Themes, wallpapers, dvx.ini, dependency files
|
|
||||||
rs232/ ISR-driven UART serial driver (librs232.a)
|
|
||||||
packet/ HDLC framing, CRC-16, Go-Back-N (libpacket.a)
|
|
||||||
security/ DH key exchange, XTEA-CTR cipher (libsecurity.a)
|
|
||||||
seclink/ Secure serial link wrapper (libseclink.a)
|
|
||||||
proxy/ Linux serial-to-telnet proxy (secproxy)
|
|
||||||
termdemo/ Encrypted ANSI terminal demo (termdemo.exe)
|
|
||||||
bin/ Build output
|
|
||||||
lib/ Build output (static libraries)
|
|
||||||
releases/ Release archives
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build Output (`bin/`)
|
|
||||||
|
|
||||||
```
|
|
||||||
bin/
|
|
||||||
dvx.exe Bootstrap loader
|
|
||||||
dvx.map Linker map
|
|
||||||
libs/
|
|
||||||
libtasks.lib Task switching DXE
|
|
||||||
libdvx.lib Core GUI DXE
|
|
||||||
libdvx.dep Dependencies: libtasks
|
|
||||||
dvxshell.lib Shell DXE
|
|
||||||
dvxshell.dep Dependencies: libtasks, libdvx, box, button, etc.
|
|
||||||
widgets/
|
|
||||||
box.wgt VBox/HBox/Frame
|
|
||||||
button.wgt Button
|
|
||||||
canvas.wgt Canvas
|
|
||||||
checkbox.wgt Checkbox
|
|
||||||
combobox.wgt ComboBox
|
|
||||||
dropdown.wgt Dropdown
|
|
||||||
image.wgt Image
|
|
||||||
imgbtn.wgt ImageButton
|
|
||||||
label.wgt Label
|
|
||||||
listbox.wgt ListBox
|
|
||||||
listview.wgt ListView
|
|
||||||
progress.wgt ProgressBar
|
|
||||||
radio.wgt RadioGroup/Radio
|
|
||||||
scrlpane.wgt ScrollPane
|
|
||||||
separatr.wgt Separator
|
|
||||||
slider.wgt Slider
|
|
||||||
spacer.wgt Spacer
|
|
||||||
spinner.wgt Spinner
|
|
||||||
splitter.wgt Splitter
|
|
||||||
statbar.wgt StatusBar
|
|
||||||
tabctrl.wgt TabControl/TabPage
|
|
||||||
terminal.wgt AnsiTerm
|
|
||||||
textinpt.wgt TextInput/TextArea
|
|
||||||
timer.wgt Timer
|
|
||||||
toolbar.wgt Toolbar
|
|
||||||
treeview.wgt TreeView/TreeItem
|
|
||||||
apps/
|
|
||||||
progman/progman.app
|
|
||||||
notepad/notepad.app
|
|
||||||
clock/clock.app
|
|
||||||
dvxdemo/dvxdemo.app
|
|
||||||
cpanel/cpanel.app
|
|
||||||
imgview/imgview.app
|
|
||||||
config/
|
|
||||||
dvx.ini
|
|
||||||
themes/
|
|
||||||
cde.thm
|
|
||||||
geos.thm
|
|
||||||
win31.thm
|
|
||||||
wpaper/
|
|
||||||
blueglow.jpg
|
|
||||||
swoop.jpg
|
|
||||||
triangle.jpg
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build everything (loader, core, tasks, shell, widgets, all apps)
|
|
||||||
make
|
|
||||||
|
|
||||||
# Build individual components
|
|
||||||
make -C loader # builds dvx.exe
|
|
||||||
make -C core # builds libs/libdvx.lib + widgets/*.wgt
|
|
||||||
make -C tasks # builds libs/libtasks.lib
|
|
||||||
make -C shell # builds libs/dvxshell.lib + config
|
|
||||||
make -C widgets # builds widgets/*.wgt
|
|
||||||
make -C apps # builds apps/*/*.app
|
|
||||||
|
|
||||||
# Clean all build artifacts
|
|
||||||
make clean
|
|
||||||
```
|
|
||||||
|
|
||||||
Set `DJGPP_PREFIX` in the component Makefiles if your toolchain is
|
|
||||||
installed somewhere other than `~/djgpp/djgpp`.
|
|
||||||
|
|
||||||
|
|
||||||
## Deployment
|
|
||||||
|
|
||||||
### CD-ROM ISO (86Box)
|
|
||||||
|
|
||||||
The primary deployment method is an ISO image mounted as a CD-ROM in
|
|
||||||
86Box:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./mkcd.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This builds everything, then creates an ISO 9660 image with 8.3
|
|
||||||
filenames at `~/.var/app/net._86box._86Box/data/86Box/dvx.iso`. Mount
|
|
||||||
it in 86Box as a CD-ROM drive and run `D:\DVX.EXE` (or whatever drive
|
|
||||||
letter is assigned).
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
DVX reads its configuration from `CONFIG\DVX.INI` on the target
|
|
||||||
filesystem. The INI file uses a standard `[section]` / `key = value`
|
|
||||||
format. Settings are applied at startup and can be changed live from the
|
|
||||||
Control Panel app.
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[video]
|
|
||||||
width = 640
|
|
||||||
height = 480
|
|
||||||
bpp = 16
|
|
||||||
|
|
||||||
[mouse]
|
|
||||||
wheel = normal
|
|
||||||
doubleclick = 500
|
|
||||||
acceleration = medium
|
|
||||||
|
|
||||||
[colors]
|
|
||||||
desktop = 0,128,128
|
|
||||||
windowFace = 192,192,192
|
|
||||||
windowHighlight = 255,255,255
|
|
||||||
windowShadow = 128,128,128
|
|
||||||
activeTitleBg = 48,48,48
|
|
||||||
activeTitleFg = 255,255,255
|
|
||||||
inactiveTitleBg = 160,160,160
|
|
||||||
inactiveTitleFg = 64,64,64
|
|
||||||
contentBg = 192,192,192
|
|
||||||
contentFg = 0,0,0
|
|
||||||
menuBg = 192,192,192
|
|
||||||
menuFg = 0,0,0
|
|
||||||
menuHighlightBg = 48,48,48
|
|
||||||
menuHighlightFg = 255,255,255
|
|
||||||
buttonFace = 192,192,192
|
|
||||||
scrollbarBg = 192,192,192
|
|
||||||
scrollbarFg = 128,128,128
|
|
||||||
scrollbarTrough = 160,160,160
|
|
||||||
cursorColor = 255,255,255
|
|
||||||
cursorOutline = 0,0,0
|
|
||||||
|
|
||||||
[desktop]
|
|
||||||
wallpaper = C:\DVX\WPAPER\SWOOP.JPG
|
|
||||||
mode = stretch
|
|
||||||
```
|
|
||||||
|
|
||||||
### Video Section
|
|
||||||
|
|
||||||
- `width`, `height` -- requested resolution (closest VESA mode is used)
|
|
||||||
- `bpp` -- preferred color depth (8, 15, 16, 24, or 32)
|
|
||||||
|
|
||||||
### Mouse Section
|
|
||||||
|
|
||||||
- `wheel` -- `normal` or `reversed`
|
|
||||||
- `doubleclick` -- double-click speed in milliseconds (200--900)
|
|
||||||
- `acceleration` -- `off`, `low`, `medium`, or `high`
|
|
||||||
|
|
||||||
### Colors Section
|
|
||||||
|
|
||||||
All 20 system colors as `R,G,B` triplets (0--255). Key names match the
|
|
||||||
`ColorIdE` enum: `desktop`, `windowFace`, `windowHighlight`, `windowShadow`,
|
|
||||||
`activeTitleBg`, `activeTitleFg`, `inactiveTitleBg`, `inactiveTitleFg`,
|
|
||||||
`contentBg`, `contentFg`, `menuBg`, `menuFg`, `menuHighlightBg`,
|
|
||||||
`menuHighlightFg`, `buttonFace`, `scrollbarBg`, `scrollbarFg`,
|
|
||||||
`scrollbarTrough`, `cursorColor`, `cursorOutline`. Missing keys fall back to
|
|
||||||
compiled-in defaults (GEOS Ensemble palette). Colors can also be loaded from
|
|
||||||
standalone `.thm` theme files via the Control Panel.
|
|
||||||
|
|
||||||
### Desktop Section
|
|
||||||
|
|
||||||
- `wallpaper` -- path to wallpaper image (BMP, PNG, JPEG, GIF)
|
|
||||||
- `mode` -- `stretch`, `tile`, or `center`
|
|
||||||
|
|
||||||
|
|
||||||
## Bundled Applications
|
## Bundled Applications
|
||||||
|
|
||||||
| App | File | Type | Description |
|
| App | File | Type | Description |
|
||||||
|-----|------|------|-------------|
|
|-----------------|---------------|-----------|------------------------------------------------------------|
|
||||||
| Program Manager | `progman.app` | Callback | App launcher grid with icons; Help menu opens the shell's Task Manager (Ctrl+Esc) |
|
| Program Manager | `progman.app` | Callback | App launcher grid, system info dialog |
|
||||||
| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save dialogs, clipboard, and undo |
|
| Notepad | `notepad.app` | Callback | Text editor with File/Edit menus, open/save, clipboard |
|
||||||
| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable |
|
| Clock | `clock.app` | Main-loop | Digital clock display; multi-instance capable |
|
||||||
| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating all 32 widget types |
|
| DVX Demo | `dvxdemo.app` | Callback | Widget system showcase demonstrating 31 widget types |
|
||||||
| Control Panel | `cpanel.app` | Callback | System settings: color themes with live preview, wallpaper selection, video mode switching, mouse configuration |
|
| Control Panel | `cpanel.app` | Callback | Themes, wallpaper, video mode, mouse configuration |
|
||||||
| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, and GIF images with file dialog |
|
| Image Viewer | `imgview.app` | Callback | Displays BMP, PNG, JPEG, GIF images with aspect-ratio zoom |
|
||||||
|
|
||||||
|
|
||||||
## Serial / Networking Stack
|
## Serial / Networking Stack
|
||||||
|
|
@ -360,38 +171,32 @@ standalone `.thm` theme files via the Control Panel.
|
||||||
A layered encrypted serial communications stack for connecting DVX to
|
A layered encrypted serial communications stack for connecting DVX to
|
||||||
remote systems (BBS, etc.) through 86Box's emulated UART:
|
remote systems (BBS, etc.) through 86Box's emulated UART:
|
||||||
|
|
||||||
| Layer | Library | Description |
|
| Layer | Library | Description |
|
||||||
|-------|---------|-------------|
|
|----------|------------------|---------------------------------------------------|
|
||||||
| rs232 | `librs232.a` | ISR-driven UART driver with FIFO support, automatic UART type detection (8250 through 16750), configurable baud rate |
|
| rs232 | `librs232.a` | ISR-driven UART driver, ring buffers, flow control |
|
||||||
| packet | `libpacket.a` | HDLC framing with byte stuffing, CRC-16 integrity checks, Go-Back-N sliding window for reliable delivery, 255-byte max payload |
|
| packet | `libpacket.a` | HDLC framing, CRC-16, Go-Back-N ARQ |
|
||||||
| security | `libsecurity.a` | 1024-bit Diffie-Hellman key exchange (RFC 2409 Group 2), XTEA-CTR stream cipher, XTEA-CTR DRBG random number generator |
|
| security | `libsecurity.a` | 1024-bit DH, XTEA-CTR cipher, DRBG RNG |
|
||||||
| seclink | `libseclink.a` | Convenience wrapper: multiplexed channels (0--127), per-packet encryption flag, bulk send helper |
|
| seclink | `libseclink.a` | Channel multiplexing, per-packet encryption |
|
||||||
| proxy | `secproxy` | Linux-side bridge: 86Box emulated serial port <-> secLink <-> telnet BBS; socket shim replaces rs232 API |
|
| proxy | `secproxy` | Linux bridge: 86Box serial <-> telnet BBS |
|
||||||
|
|
||||||
|
|
||||||
## Third-Party Dependencies
|
## Third-Party Dependencies
|
||||||
|
|
||||||
All third-party code is vendored as single-header libraries with no
|
All third-party code is vendored as single-header libraries:
|
||||||
external dependencies:
|
|
||||||
|
|
||||||
| Library | Location | Purpose |
|
| Library | Location | Purpose |
|
||||||
|---------|----------|---------|
|
|-------------------|--------------------|---------------------------------|
|
||||||
| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) |
|
| stb_image.h | `core/thirdparty/` | Image loading (BMP, PNG, JPEG, GIF) |
|
||||||
| stb_image_write.h | `core/thirdparty/` | Image writing (PNG export for screenshots) |
|
| stb_image_write.h | `core/thirdparty/` | PNG export for screenshots |
|
||||||
| stb_ds.h | `tasks/thirdparty/` | Dynamic array and hash map (used by task manager) |
|
| stb_ds.h | `core/thirdparty/` | Dynamic arrays and hash maps |
|
||||||
|
|
||||||
|
stb_ds implementation is compiled into dvx.exe (the loader) and
|
||||||
|
exported via `dlregsym` to all DXE modules.
|
||||||
|
|
||||||
|
|
||||||
## Component Documentation
|
## Target Hardware
|
||||||
|
|
||||||
Each component directory has its own README with detailed API reference:
|
* CPU: 486 baseline, Pentium-optimized paths where significant
|
||||||
|
* Video: VESA VBE 2.0+ with LFB (no bank switching)
|
||||||
- [`core/README.md`](core/README.md) -- Core GUI library architecture and API
|
* Platform: 86Box emulator (trusted reference), real DOS hardware
|
||||||
- [`tasks/README.md`](tasks/README.md) -- Task switcher API
|
* Resolutions: 640x480, 800x600, 1024x768 at 8/15/16/32 bpp
|
||||||
- [`shell/README.md`](shell/README.md) -- Shell internals and DXE app contract
|
|
||||||
- [`apps/README.md`](apps/README.md) -- Writing DXE applications
|
|
||||||
- [`rs232/README.md`](rs232/README.md) -- Serial port driver
|
|
||||||
- [`packet/README.md`](packet/README.md) -- Packet transport protocol
|
|
||||||
- [`security/README.md`](security/README.md) -- Cryptographic primitives
|
|
||||||
- [`seclink/README.md`](seclink/README.md) -- Secure serial link
|
|
||||||
- [`proxy/README.md`](proxy/README.md) -- Linux SecLink proxy
|
|
||||||
- [`termdemo/README.md`](termdemo/README.md) -- Encrypted terminal demo
|
|
||||||
|
|
|
||||||
573
apps/README.md
573
apps/README.md
|
|
@ -1,443 +1,178 @@
|
||||||
# DVX Shell Applications
|
# DVX Shell Applications
|
||||||
|
|
||||||
DXE3 application modules for the DVX Shell. Each app is a `.app` file (DXE3
|
DXE3 application modules for the DVX Shell. Each app is a `.app` file
|
||||||
shared object format) placed in a subdirectory under `apps/`. The Program
|
(DXE3 shared object) in a subdirectory under `apps/`. The Program
|
||||||
Manager scans this directory recursively at startup and displays all discovered
|
Manager scans this directory at startup and displays all discovered
|
||||||
apps in a launcher grid.
|
apps as launchable icons.
|
||||||
|
|
||||||
## App Contract
|
|
||||||
|
|
||||||
Every DXE app must export two symbols and may optionally export a third:
|
## DXE App Contract
|
||||||
|
|
||||||
```c
|
Every app exports two symbols:
|
||||||
// Required: app metadata
|
|
||||||
AppDescriptorT appDescriptor = {
|
|
||||||
.name = "My App",
|
|
||||||
.hasMainLoop = false,
|
|
||||||
.multiInstance = false,
|
|
||||||
.stackSize = SHELL_STACK_DEFAULT,
|
|
||||||
.priority = TS_PRIORITY_NORMAL
|
|
||||||
};
|
|
||||||
|
|
||||||
// Required: entry point -- called once by the shell after dlopen
|
* `appDescriptor` (`AppDescriptorT`) -- name, hasMainLoop, multiInstance, stackSize, priority
|
||||||
int32_t appMain(DxeAppContextT *ctx);
|
* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point
|
||||||
|
|
||||||
// Optional: graceful shutdown hook -- called before force-kill
|
Optional: `appShutdown` (`void appShutdown(void)`) -- called during
|
||||||
void appShutdown(void);
|
graceful shutdown.
|
||||||
```
|
|
||||||
|
|
||||||
`appMain` receives a `DxeAppContextT` with:
|
### Callback-Only Apps (hasMainLoop = false)
|
||||||
|
|
||||||
| Field | Type | Description |
|
`appMain()` creates windows, registers callbacks, and returns 0. The
|
||||||
|-------|------|-------------|
|
shell drives everything through event callbacks. No dedicated task or
|
||||||
| `shellCtx` | `AppContextT *` | The shell's GUI context -- pass to all `dvx*`/`wgt*` calls |
|
stack is allocated. Lifecycle ends when the last window closes.
|
||||||
| `appId` | `int32_t` | This app's unique ID (1-based slot index; 0 = shell) |
|
|
||||||
| `appDir` | `char[260]` | Directory containing the `.app` file (for relative resource paths) |
|
|
||||||
| `configDir` | `char[260]` | Writable config directory (`CONFIG/<apppath>/`) |
|
|
||||||
|
|
||||||
Return 0 from `appMain` on success, non-zero on failure (shell will unload).
|
### Main-Loop Apps (hasMainLoop = true)
|
||||||
|
|
||||||
### AppDescriptorT Fields
|
A cooperative task is created for the app. `appMain()` runs its own
|
||||||
|
loop calling `tsYield()` to share CPU. Lifecycle ends when `appMain()`
|
||||||
|
returns.
|
||||||
|
|
||||||
| Field | Type | Description |
|
|
||||||
|-------|------|-------------|
|
|
||||||
| `name` | `char[64]` | Display name shown in Task Manager and title bars |
|
|
||||||
| `hasMainLoop` | `bool` | `false` = callback-only (runs in task 0); `true` = gets own cooperative task |
|
|
||||||
| `multiInstance` | `bool` | `true` = allow multiple instances via temp file copy |
|
|
||||||
| `stackSize` | `int32_t` | Task stack in bytes; `SHELL_STACK_DEFAULT` (0) for the default |
|
|
||||||
| `priority` | `int32_t` | `TS_PRIORITY_LOW`, `TS_PRIORITY_NORMAL`, or `TS_PRIORITY_HIGH` |
|
|
||||||
|
|
||||||
## Building
|
## Applications
|
||||||
|
|
||||||
|
### Program Manager (progman)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `apps/progman/progman.app` |
|
||||||
|
| Type | Callback-only |
|
||||||
|
| Multi-instance | No |
|
||||||
|
|
||||||
|
The desktop app. Scans `apps/` recursively for `.app` files and
|
||||||
|
displays them in a grid. Double-click or Enter launches an app.
|
||||||
|
Includes a Help menu with system information dialog and About box.
|
||||||
|
|
||||||
|
Registers with `shellRegisterDesktopUpdate()` to refresh when apps
|
||||||
|
are loaded, crash, or terminate.
|
||||||
|
|
||||||
|
Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetLabel.h`,
|
||||||
|
`widgetStatusBar.h`, `widgetTextInput.h`.
|
||||||
|
|
||||||
|
### Notepad (notepad)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `apps/notepad/notepad.app` |
|
||||||
|
| Type | Callback-only |
|
||||||
|
| Multi-instance | Yes |
|
||||||
|
|
||||||
|
Text editor with File menu (New, Open, Save, Save As) and Edit menu
|
||||||
|
(Cut, Copy, Paste, Select All). Uses the TextArea widget for all
|
||||||
|
editing. 32KB text buffer limit. Tracks dirty state for save prompts.
|
||||||
|
Keyboard accelerators for all menu commands.
|
||||||
|
|
||||||
|
Widget headers used: `widgetTextInput.h`.
|
||||||
|
|
||||||
|
### Clock (clock)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `apps/clock/clock.app` |
|
||||||
|
| Type | Main-loop |
|
||||||
|
| Multi-instance | Yes |
|
||||||
|
|
||||||
|
Digital clock display showing time and date. Demonstrates the main-loop
|
||||||
|
app pattern -- polls the system clock every second and invalidates the
|
||||||
|
window to trigger a repaint. Uses raw `onPaint` callbacks (no widgets)
|
||||||
|
to draw centered text.
|
||||||
|
|
||||||
|
Widget headers used: none (raw paint callbacks).
|
||||||
|
|
||||||
|
### DVX Demo (dvxdemo)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `apps/dvxdemo/dvxdemo.app` |
|
||||||
|
| Type | Callback-only |
|
||||||
|
| Multi-instance | No |
|
||||||
|
|
||||||
|
Comprehensive widget system showcase. Opens multiple windows
|
||||||
|
demonstrating 31 of the 32 widget types (all except Timer):
|
||||||
|
|
||||||
|
* Main window: raw paint callbacks (gradients, patterns, text)
|
||||||
|
* Widget demo: form widgets (TextInput, Checkbox, Radio, ListBox)
|
||||||
|
* Controls window: TabControl with tabs for advanced widgets
|
||||||
|
(Dropdown, ProgressBar, Slider, Spinner, TreeView, ListView,
|
||||||
|
ScrollPane, Toolbar, Canvas, Splitter, Image)
|
||||||
|
* Terminal window: AnsiTerm widget
|
||||||
|
|
||||||
|
Widget headers used: 25 of the 26 widget headers (all except widgetTimer.h).
|
||||||
|
|
||||||
|
Resources: `logo.bmp`, `new.bmp`, `open.bmp`, `sample.bmp`, `save.bmp`.
|
||||||
|
|
||||||
|
### Control Panel (cpanel)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `apps/cpanel/cpanel.app` |
|
||||||
|
| Type | Callback-only |
|
||||||
|
| Multi-instance | No |
|
||||||
|
|
||||||
|
System configuration with four tabs:
|
||||||
|
|
||||||
|
* **Mouse** -- scroll direction, double-click speed, acceleration
|
||||||
|
* **Colors** -- all 20 system colors with live preview, theme load/save
|
||||||
|
* **Desktop** -- wallpaper image selection and display mode
|
||||||
|
* **Video** -- resolution and color depth switching
|
||||||
|
|
||||||
|
Changes preview live. OK saves to `DVX.INI`, Cancel reverts to the
|
||||||
|
state captured when the control panel was opened.
|
||||||
|
|
||||||
|
Widget headers used: `widgetBox.h`, `widgetButton.h`, `widgetCanvas.h`,
|
||||||
|
`widgetDropdown.h`, `widgetLabel.h`, `widgetListBox.h`, `widgetSlider.h`,
|
||||||
|
`widgetSpacer.h`, `widgetTabControl.h`.
|
||||||
|
|
||||||
|
### Image Viewer (imgview)
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| File | `apps/imgview/imgview.app` |
|
||||||
|
| Type | Callback-only |
|
||||||
|
| Multi-instance | Yes |
|
||||||
|
|
||||||
|
Displays BMP, PNG, JPEG, and GIF images. The image is scaled to fit
|
||||||
|
the window while preserving aspect ratio. Resize the window to zoom.
|
||||||
|
Open files via the File menu or by launching with Run in the Task
|
||||||
|
Manager.
|
||||||
|
|
||||||
|
Widget headers used: none (raw paint callbacks with `dvxLoadImage()`).
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
```
|
```
|
||||||
make # builds all .app files into ../bin/apps/<name>/
|
make # builds all 6 app DXE modules
|
||||||
make clean # removes objects and binaries
|
make clean # removes objects and app files
|
||||||
```
|
```
|
||||||
|
|
||||||
CFLAGS: `-O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../tasks -I../tasks/thirdparty -I../shell`
|
Each app compiles to a single `.o`, then is packaged via `dxe3gen`
|
||||||
|
into a `.app` DXE exporting `appDescriptor` and `appMain` (plus
|
||||||
|
`appShutdown` for clock).
|
||||||
|
|
||||||
Each app is compiled to an object file with the DJGPP cross-compiler, then
|
Output goes to `bin/apps/<name>/<name>.app`.
|
||||||
packaged into a `.app` via `dxe3gen`:
|
|
||||||
|
|
||||||
```makefile
|
|
||||||
$(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp
|
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
|
||||||
```
|
|
||||||
|
|
||||||
- `-E _appDescriptor` and `-E _appMain` export the required symbols (COFF
|
## Files
|
||||||
underscore prefix).
|
|
||||||
- `-E _appShutdown` is added for apps that export a shutdown hook (e.g.,
|
|
||||||
Clock).
|
|
||||||
- `-U` marks all other symbols as unresolved imports to be resolved at
|
|
||||||
`dlopen` time from the loader and all `RTLD_GLOBAL` modules (libdvx,
|
|
||||||
libtasks, widgets, and the shell's wrapper overrides).
|
|
||||||
|
|
||||||
Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`).
|
|
||||||
|
|
||||||
## Symbol Resolution
|
|
||||||
|
|
||||||
Apps do not link against static libraries. All symbols are resolved at load
|
|
||||||
time via the DXE3 dynamic linker:
|
|
||||||
|
|
||||||
1. The **loader** registers libc/libm/runtime symbols via `dlregsym()`.
|
|
||||||
2. **DXE modules** loaded with `RTLD_GLOBAL` (libdvx, libtasks, widgets)
|
|
||||||
export their public APIs to the global symbol namespace.
|
|
||||||
3. The **shell** registers 3 wrapper overrides (`dvxCreateWindow`,
|
|
||||||
`dvxCreateWindowCentered`, `dvxDestroyWindow`) via `dlregsym()` for
|
|
||||||
resource tracking.
|
|
||||||
|
|
||||||
When an app DXE is loaded, all its unresolved symbols are matched against
|
|
||||||
this combined symbol table.
|
|
||||||
|
|
||||||
## Directory Structure
|
|
||||||
|
|
||||||
```
|
```
|
||||||
apps/
|
apps/
|
||||||
Makefile -- build rules for all apps
|
Makefile top-level build for all apps
|
||||||
README.md -- this file
|
progman/
|
||||||
progman/
|
progman.c Program Manager
|
||||||
progman.c -- Program Manager (desktop shell)
|
notepad/
|
||||||
notepad/
|
notepad.c text editor
|
||||||
notepad.c -- text editor
|
clock/
|
||||||
clock/
|
clock.c digital clock
|
||||||
clock.c -- digital clock
|
dvxdemo/
|
||||||
cpanel/
|
dvxdemo.c widget demo
|
||||||
cpanel.c -- Control Panel (system settings)
|
logo.bmp DVX logo bitmap
|
||||||
imgview/
|
new.bmp toolbar icon
|
||||||
imgview.c -- image viewer
|
open.bmp toolbar icon
|
||||||
dvxdemo/
|
sample.bmp sample image
|
||||||
dvxdemo.c -- widget showcase
|
save.bmp toolbar icon
|
||||||
*.bmp -- toolbar icons and sample images
|
cpanel/
|
||||||
|
cpanel.c control panel
|
||||||
|
imgview/
|
||||||
|
imgview.c image viewer
|
||||||
```
|
```
|
||||||
|
|
||||||
Each app lives in its own subdirectory. The subdirectory name becomes the
|
|
||||||
output path under `bin/apps/` (e.g., `bin/apps/progman/progman.app`).
|
|
||||||
|
|
||||||
## Bundled Applications
|
|
||||||
|
|
||||||
### Program Manager (`progman/`)
|
|
||||||
|
|
||||||
The desktop shell and default app launcher. Loaded automatically by the shell
|
|
||||||
at startup as the "desktop app" -- closing it prompts to exit the entire DVX
|
|
||||||
Shell.
|
|
||||||
|
|
||||||
- **App launcher grid**: scans `apps/` recursively for `.app` files (skipping
|
|
||||||
itself), displays them as a grid of buttons. Click or double-click to
|
|
||||||
launch.
|
|
||||||
- **Menu bar**: File (Run..., Exit Shell), Options (Minimize on Run), Window
|
|
||||||
(Cascade, Tile, Tile H, Tile V), Help (About, System Information, Task
|
|
||||||
Manager). The Task Manager menu item opens the shell-level Task Manager
|
|
||||||
via `shellTaskMgrOpen()`.
|
|
||||||
- **Minimize on Run**: optional preference -- when enabled, Program Manager
|
|
||||||
minimizes itself after launching an app, getting out of the way.
|
|
||||||
- **Status bar**: shows the count of running applications, updated in
|
|
||||||
real-time via `shellRegisterDesktopUpdate()`.
|
|
||||||
- **System Information**: opens a read-only text area showing CPU, memory,
|
|
||||||
drive, and video details gathered by the platform layer.
|
|
||||||
- **Preferences**: saved to `CONFIG/PROGMAN/progman.ini` via the standard
|
|
||||||
prefs system. Currently stores the "Minimize on Run" setting.
|
|
||||||
|
|
||||||
Type: callback-only. Single instance.
|
|
||||||
|
|
||||||
### Notepad (`notepad/`)
|
|
||||||
|
|
||||||
A basic text editor with file I/O and dirty-change tracking.
|
|
||||||
|
|
||||||
- **TextArea widget**: handles all editing -- keyboard input, cursor movement,
|
|
||||||
selection, scrolling, word wrap, copy/paste, undo (Ctrl+Z). Notepad only
|
|
||||||
wires up menus and file I/O around it.
|
|
||||||
- **File menu**: New, Open, Save, Save As, Exit.
|
|
||||||
- **Edit menu**: Cut, Copy, Paste, Select All (Ctrl+X/C/V/A).
|
|
||||||
- **CR/LF handling**: files are opened in binary mode to avoid DJGPP's
|
|
||||||
translation. `platformStripLineEndings()` normalizes on load;
|
|
||||||
`platformLineEnding()` writes platform-native line endings on save.
|
|
||||||
- **Dirty tracking**: uses djb2-xor hash of the text content. Cheap detection
|
|
||||||
without storing a full shadow buffer. Prompts "Save changes?" on close/new/
|
|
||||||
open if dirty.
|
|
||||||
- **32 KB text buffer**: keeps memory bounded on DOS. Larger files are
|
|
||||||
silently truncated on load.
|
|
||||||
- **Multi-instance**: each instance gets its own DXE code+data via temp file
|
|
||||||
copy. Window positions are offset +20px so instances cascade naturally.
|
|
||||||
|
|
||||||
Type: callback-only. Multi-instance.
|
|
||||||
|
|
||||||
### Clock (`clock/`)
|
|
||||||
|
|
||||||
A digital clock displaying 12-hour time and date, centered in a small
|
|
||||||
non-resizable window.
|
|
||||||
|
|
||||||
- **Main-loop app**: polls `time()` each iteration, repaints when the second
|
|
||||||
changes, then calls `tsYield()`. CPU usage is near zero because the check
|
|
||||||
is cheap and yields immediately when nothing changes.
|
|
||||||
- **Raw paint callback**: renders directly into the window's content buffer
|
|
||||||
using `rectFill` and `drawText` -- no widget tree. Demonstrates the
|
|
||||||
lower-level alternative to the widget system for custom rendering.
|
|
||||||
- **Shutdown hook**: exports `appShutdown()` so the shell can signal a clean
|
|
||||||
exit when force-killing via Task Manager or during shell shutdown.
|
|
||||||
- **Low priority**: uses `TS_PRIORITY_LOW` since clock updates are cosmetic
|
|
||||||
and should never preempt interactive apps.
|
|
||||||
|
|
||||||
Type: main-loop. Multi-instance.
|
|
||||||
|
|
||||||
### Control Panel (`cpanel/`)
|
|
||||||
|
|
||||||
System configuration with four tabs, all changes previewing live. OK saves to
|
|
||||||
`CONFIG/DVX.INI`; Cancel reverts to the state captured when the panel opened.
|
|
||||||
|
|
||||||
**Mouse tab:**
|
|
||||||
- Scroll wheel direction (Normal / Reversed) via dropdown.
|
|
||||||
- Double-click speed (200-900 ms) via slider with numeric label and test
|
|
||||||
button.
|
|
||||||
- Mouse acceleration (Off / Low / Medium / High) via dropdown.
|
|
||||||
- All changes apply immediately via `dvxSetMouseConfig()`.
|
|
||||||
|
|
||||||
**Colors tab:**
|
|
||||||
- List of all 20 system colors (`ColorCountE` entries from `dvxColorName`).
|
|
||||||
- RGB sliders (0-255) for the selected color, with numeric labels and a
|
|
||||||
canvas swatch preview.
|
|
||||||
- Changes apply live via `dvxSetColor()` -- the entire desktop updates in
|
|
||||||
real time.
|
|
||||||
- **Themes**: dropdown of `.thm` files from `CONFIG/THEMES/`, with Apply,
|
|
||||||
Load..., Save As..., and Reset buttons. Themes are loaded/saved via
|
|
||||||
`dvxLoadTheme()`/`dvxSaveTheme()`.
|
|
||||||
- Reset restores the compiled-in default color scheme.
|
|
||||||
|
|
||||||
**Desktop tab:**
|
|
||||||
- Wallpaper list: scans `CONFIG/WPAPER/` for BMP/JPG/PNG files.
|
|
||||||
- Apply, Browse..., and Clear buttons.
|
|
||||||
- Mode dropdown: Stretch, Tile, Center. Changes apply live via
|
|
||||||
`dvxSetWallpaperMode()`.
|
|
||||||
|
|
||||||
**Video tab:**
|
|
||||||
- List of all enumerated VESA modes with human-readable depth names (e.g.,
|
|
||||||
"800x600 65 thousand colors").
|
|
||||||
- Apply Mode button or double-click to switch.
|
|
||||||
- **10-second confirmation dialog**: after switching, a modal "Keep this
|
|
||||||
mode?" dialog counts down. If the user clicks Yes, the mode is kept.
|
|
||||||
Clicking No, closing the dialog, or letting the timer expire reverts to
|
|
||||||
the previous mode. Prevents being stuck in an unsupported mode.
|
|
||||||
|
|
||||||
Type: callback-only. Single instance.
|
|
||||||
|
|
||||||
### Image Viewer (`imgview/`)
|
|
||||||
|
|
||||||
Displays BMP, PNG, JPG, and GIF images loaded via stb_image.
|
|
||||||
|
|
||||||
- **Bilinear scaling**: images are scaled to fit the window while preserving
|
|
||||||
aspect ratio. The scaler converts from RGB to the native pixel format
|
|
||||||
(8/16/32 bpp) during the scale pass.
|
|
||||||
- **Deferred resize**: during a window drag-resize, the old scaled image is
|
|
||||||
shown. The expensive bilinear rescale only runs after the drag ends
|
|
||||||
(`sAc->stack.resizeWindow < 0`), avoiding per-frame scaling lag.
|
|
||||||
- **Responsive scaling**: for large images, `dvxUpdate()` is called every 32
|
|
||||||
scanlines during the scale loop to keep the UI responsive.
|
|
||||||
- **File menu**: Open (Ctrl+O), Close. Keyboard accelerator table registered.
|
|
||||||
- **Multi-instance**: multiple viewers can be open simultaneously, each with
|
|
||||||
its own image.
|
|
||||||
- **Raw paint callback**: renders directly into the content buffer with a dark
|
|
||||||
gray background and centered blit of the scaled image.
|
|
||||||
|
|
||||||
Type: callback-only. Multi-instance.
|
|
||||||
|
|
||||||
### DVX Demo (`dvxdemo/`)
|
|
||||||
|
|
||||||
A comprehensive widget showcase and integration test. Opens several windows
|
|
||||||
demonstrating the full DVX widget system:
|
|
||||||
|
|
||||||
- **Main window**: three raw-paint windows -- text rendering with full menu
|
|
||||||
bar/accelerators/context menu, vertical gradient, and checkerboard pattern
|
|
||||||
with scrollbars.
|
|
||||||
- **Widget Demo window**: form pattern with labeled inputs (text, password,
|
|
||||||
masked phone number), checkboxes, radio groups, single and multi-select
|
|
||||||
list boxes with context menus and drag reorder.
|
|
||||||
- **Advanced Widgets window**: nine tab pages covering every widget type --
|
|
||||||
dropdown, combo box, progress bar (horizontal and vertical), slider,
|
|
||||||
spinner, tree view (with drag reorder), multi-column list view (with
|
|
||||||
multi-select and drag reorder), scroll pane, toolbar (with image buttons
|
|
||||||
and text fallback), image from file, text area, canvas (with mouse
|
|
||||||
drawing), splitter (nested horizontal+vertical for explorer-style layout),
|
|
||||||
and a disabled-state comparison of all widget types.
|
|
||||||
- **ANSI Terminal window**: terminal emulator widget with sample output
|
|
||||||
demonstrating bold, reverse, blink, all 16 colors, background colors,
|
|
||||||
CP437 box-drawing characters, and 500-line scrollback.
|
|
||||||
|
|
||||||
Type: callback-only. Single instance.
|
|
||||||
|
|
||||||
## App Preferences
|
|
||||||
|
|
||||||
Apps that need persistent settings use the shell's config directory system:
|
|
||||||
|
|
||||||
```c
|
|
||||||
// In appMain:
|
|
||||||
shellEnsureConfigDir(ctx); // create CONFIG/<apppath>/ if needed
|
|
||||||
|
|
||||||
char path[260];
|
|
||||||
shellConfigPath(ctx, "settings.ini", path, sizeof(path));
|
|
||||||
prefsLoad(path);
|
|
||||||
|
|
||||||
// Read/write:
|
|
||||||
int32_t val = prefsGetInt("section", "key", defaultVal);
|
|
||||||
prefsSetInt("section", "key", newVal);
|
|
||||||
prefsSave();
|
|
||||||
```
|
|
||||||
|
|
||||||
The preferences system handles INI file format with `[section]` headers and
|
|
||||||
`key=value` pairs. Missing files or keys silently return defaults.
|
|
||||||
|
|
||||||
## Event Model
|
|
||||||
|
|
||||||
DVX apps receive events through two mechanisms:
|
|
||||||
|
|
||||||
**Widget callbacks** (high-level):
|
|
||||||
- `onClick`, `onDblClick`, `onChange` on individual widgets.
|
|
||||||
- The widget system handles focus, tab order, mouse hit testing, keyboard
|
|
||||||
dispatch, and repainting automatically.
|
|
||||||
- Used by most apps for standard UI (buttons, inputs, lists, sliders, etc.).
|
|
||||||
|
|
||||||
**Window callbacks** (low-level):
|
|
||||||
- `onPaint(win, dirtyRect)` -- render directly into the window's content
|
|
||||||
buffer. Used by Clock, Image Viewer, and the DVX Demo paint windows.
|
|
||||||
- `onClose(win)` -- window close requested (close gadget, Alt+F4).
|
|
||||||
- `onResize(win, contentW, contentH)` -- window was resized.
|
|
||||||
- `onMenu(win, menuId)` -- menu item selected or keyboard accelerator fired.
|
|
||||||
|
|
||||||
Both mechanisms can be mixed in the same app. For example, DVX Demo uses
|
|
||||||
widgets in some windows and raw paint callbacks in others.
|
|
||||||
|
|
||||||
## Writing a New App
|
|
||||||
|
|
||||||
### Minimal callback-only app
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "dvxApp.h"
|
|
||||||
#include "dvxWidget.h"
|
|
||||||
#include "shellApp.h"
|
|
||||||
|
|
||||||
AppDescriptorT appDescriptor = {
|
|
||||||
.name = "My App",
|
|
||||||
.hasMainLoop = false,
|
|
||||||
.stackSize = SHELL_STACK_DEFAULT,
|
|
||||||
.priority = TS_PRIORITY_NORMAL
|
|
||||||
};
|
|
||||||
|
|
||||||
static DxeAppContextT *sCtx = NULL;
|
|
||||||
static WindowT *sWin = NULL;
|
|
||||||
|
|
||||||
static void onClose(WindowT *win) {
|
|
||||||
dvxDestroyWindow(sCtx->shellCtx, win);
|
|
||||||
sWin = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t appMain(DxeAppContextT *ctx) {
|
|
||||||
sCtx = ctx;
|
|
||||||
AppContextT *ac = ctx->shellCtx;
|
|
||||||
|
|
||||||
sWin = dvxCreateWindow(ac, "My App", 100, 100, 300, 200, true);
|
|
||||||
if (!sWin) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sWin->onClose = onClose;
|
|
||||||
|
|
||||||
WidgetT *root = wgtInitWindow(ac, sWin);
|
|
||||||
wgtLabel(root, "Hello, DVX!");
|
|
||||||
|
|
||||||
wgtInvalidate(root);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Minimal main-loop app
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include "dvxApp.h"
|
|
||||||
#include "dvxWidget.h"
|
|
||||||
#include "dvxWm.h"
|
|
||||||
#include "shellApp.h"
|
|
||||||
#include "taskswitch.h"
|
|
||||||
|
|
||||||
AppDescriptorT appDescriptor = {
|
|
||||||
.name = "My Task App",
|
|
||||||
.hasMainLoop = true,
|
|
||||||
.stackSize = SHELL_STACK_DEFAULT,
|
|
||||||
.priority = TS_PRIORITY_NORMAL
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool sQuit = false;
|
|
||||||
|
|
||||||
static void onClose(WindowT *win) {
|
|
||||||
(void)win;
|
|
||||||
sQuit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void appShutdown(void) {
|
|
||||||
sQuit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t appMain(DxeAppContextT *ctx) {
|
|
||||||
AppContextT *ac = ctx->shellCtx;
|
|
||||||
|
|
||||||
WindowT *win = dvxCreateWindow(ac, "My Task App", 100, 100, 200, 100, false);
|
|
||||||
if (!win) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
win->onClose = onClose;
|
|
||||||
|
|
||||||
while (!sQuit) {
|
|
||||||
// Do work, update window content
|
|
||||||
tsYield();
|
|
||||||
}
|
|
||||||
|
|
||||||
dvxDestroyWindow(ac, win);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding to the build
|
|
||||||
|
|
||||||
Add your app directory and source to `apps/Makefile`:
|
|
||||||
|
|
||||||
```makefile
|
|
||||||
APPS = ... myapp
|
|
||||||
|
|
||||||
myapp: $(BINDIR)/myapp/myapp.app
|
|
||||||
|
|
||||||
$(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp
|
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
|
||||||
|
|
||||||
$(OBJDIR)/myapp.o: myapp/myapp.c | $(OBJDIR)
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(BINDIR)/myapp:
|
|
||||||
mkdir -p $(BINDIR)/myapp
|
|
||||||
```
|
|
||||||
|
|
||||||
Add `-E _appShutdown` to the `dxe3gen` line if the app exports a shutdown
|
|
||||||
hook.
|
|
||||||
|
|
||||||
## App Guidelines
|
|
||||||
|
|
||||||
- Include `shellApp.h` for `AppDescriptorT`, `DxeAppContextT`, and
|
|
||||||
`SHELL_STACK_DEFAULT`.
|
|
||||||
- Use `ctx->shellCtx` (the `AppContextT *`) for all DVX API calls.
|
|
||||||
- Callback-only apps must destroy their own windows in `onClose` via
|
|
||||||
`dvxDestroyWindow()`. The shell detects the last window closing and reaps
|
|
||||||
the app automatically.
|
|
||||||
- Main-loop apps must call `tsYield()` regularly. A task that never yields
|
|
||||||
blocks the entire system (cooperative multitasking -- no preemption).
|
|
||||||
- Export `appShutdown()` for main-loop apps so the shell can signal a clean
|
|
||||||
exit on force-kill or shell shutdown.
|
|
||||||
- Use file-scoped `static` variables for app state. Each DXE has its own
|
|
||||||
data segment, so there is no collision between apps even with identical
|
|
||||||
variable names.
|
|
||||||
- Set `multiInstance = true` only if the app can safely run multiple copies
|
|
||||||
simultaneously. Each instance gets independent globals and statics.
|
|
||||||
- Avoid `static inline` functions in shared headers. Code inlined into the
|
|
||||||
DXE binary cannot be updated without recompiling the app. Use macros for
|
|
||||||
trivial expressions or regular functions exported through the DXE module
|
|
||||||
system.
|
|
||||||
- Use `ctx->appDir` for loading app-relative resources (icons, data files).
|
|
||||||
The working directory is shared by all apps and belongs to the shell.
|
|
||||||
- Use `shellEnsureConfigDir()` + `shellConfigPath()` for persistent settings.
|
|
||||||
Never write to the app's own directory -- use `CONFIG/<apppath>/` instead.
|
|
||||||
|
|
|
||||||
194
config/README.md
194
config/README.md
|
|
@ -1,93 +1,153 @@
|
||||||
# DVX Configuration Files
|
# DVX Configuration Files
|
||||||
|
|
||||||
Runtime configuration, theme files, wallpaper images, and module
|
Runtime configuration, theme files, wallpaper images, and module
|
||||||
dependency files. These are copied into `bin/config/` (INI, themes,
|
dependency files. These are copied into `bin/config/` (INI, themes,
|
||||||
wallpapers) or `bin/libs/` (dep files) during the build.
|
wallpapers) or `bin/libs/` and `bin/widgets/` (dep files) during the
|
||||||
|
build. Text files are converted to DOS line endings (CR+LF) via sed.
|
||||||
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
| File | Destination | Purpose |
|
| Source File | Build Output | Description |
|
||||||
|------|-------------|---------|
|
|-------------|-------------|-------------|
|
||||||
| `dvx.ini` | `bin/config/dvx.ini` | Main configuration (video, mouse, colors, desktop) |
|
| `dvx.ini` | `bin/config/dvx.ini` | System configuration |
|
||||||
| `themes/*.thm` | `bin/config/themes/` | Color theme files (INI format with `[colors]` section) |
|
| `themes/cde.thm` | `bin/config/themes/cde.thm` | CDE color theme |
|
||||||
| `wpaper/*.jpg` | `bin/config/wpaper/` | Bundled wallpaper images |
|
| `themes/geos.thm` | `bin/config/themes/geos.thm` | GEOS Ensemble theme |
|
||||||
| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx.lib dependency list |
|
| `themes/win31.thm` | `bin/config/themes/win31.thm` | Windows 3.1 theme |
|
||||||
| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell.lib dependency list |
|
| `wpaper/blueglow.jpg` | `bin/config/wpaper/blueglow.jpg` | Wallpaper image |
|
||||||
|
| `wpaper/swoop.jpg` | `bin/config/wpaper/swoop.jpg` | Wallpaper image |
|
||||||
|
| `wpaper/triangle.jpg` | `bin/config/wpaper/triangle.jpg` | Wallpaper image |
|
||||||
|
| `libdvx.dep` | `bin/libs/libdvx.dep` | libdvx dependency file |
|
||||||
|
| `texthelp.dep` | `bin/libs/texthelp.dep` | texthelp dependency file |
|
||||||
|
| `listhelp.dep` | `bin/libs/listhelp.dep` | listhelp dependency file |
|
||||||
|
| `dvxshell.dep` | `bin/libs/dvxshell.dep` | dvxshell dependency file |
|
||||||
|
| `textinpt.dep` | `bin/widgets/textinpt.dep` | TextInput widget dep file |
|
||||||
|
| `combobox.dep` | `bin/widgets/combobox.dep` | ComboBox widget dep file |
|
||||||
|
| `spinner.dep` | `bin/widgets/spinner.dep` | Spinner widget dep file |
|
||||||
|
| `terminal.dep` | `bin/widgets/terminal.dep` | AnsiTerm widget dep file |
|
||||||
|
| `dropdown.dep` | `bin/widgets/dropdown.dep` | Dropdown widget dep file |
|
||||||
|
| `listbox.dep` | `bin/widgets/listbox.dep` | ListBox widget dep file |
|
||||||
|
| `listview.dep` | `bin/widgets/listview.dep` | ListView widget dep file |
|
||||||
|
| `treeview.dep` | `bin/widgets/treeview.dep` | TreeView widget dep file |
|
||||||
|
|
||||||
|
|
||||||
## DVX.INI Format
|
## dvx.ini Format
|
||||||
|
|
||||||
Standard `[section]` / `key = value` INI format. Converted to DOS
|
Standard INI format with `[section]` headers and `key = value` pairs.
|
||||||
line endings (`\r\n`) during the build.
|
Comments start with `;`. The shell loads this at startup via
|
||||||
|
`prefsLoad("CONFIG/DVX.INI")`.
|
||||||
|
|
||||||
```ini
|
### [video] Section
|
||||||
[video]
|
|
||||||
width = 640
|
|
||||||
height = 480
|
|
||||||
bpp = 16
|
|
||||||
|
|
||||||
[mouse]
|
| Key | Default | Description |
|
||||||
wheel = normal # normal | reversed
|
|-----|---------|-------------|
|
||||||
doubleclick = 500 # milliseconds (200-900)
|
| `width` | 640 | Requested horizontal resolution |
|
||||||
acceleration = medium # off | low | medium | high
|
| `height` | 480 | Requested vertical resolution |
|
||||||
|
| `bpp` | 16 | Preferred color depth (8, 15, 16, 24, 32) |
|
||||||
|
|
||||||
[colors]
|
The system picks the closest available VESA mode.
|
||||||
desktop = 0,128,128
|
|
||||||
windowFace = 192,192,192
|
|
||||||
# ... 20 color keys total (R,G,B triplets 0-255)
|
|
||||||
|
|
||||||
[desktop]
|
### [mouse] Section
|
||||||
wallpaper = CONFIG\WPAPER\SWOOP.JPG
|
|
||||||
mode = stretch # stretch | tile | center
|
|
||||||
```
|
|
||||||
|
|
||||||
|
| Key | Default | Values | Description |
|
||||||
|
|-----|---------|--------|-------------|
|
||||||
|
| `wheel` | normal | normal, reversed | Scroll wheel direction |
|
||||||
|
| `doubleclick` | 500 | 200-900 | Double-click speed (ms) |
|
||||||
|
| `acceleration` | medium | off, low, medium, high | Mouse acceleration |
|
||||||
|
|
||||||
## Dependency Files (.dep)
|
### [shell] Section
|
||||||
|
|
||||||
Plain text, one dependency per line. Each line is the base name
|
| Key | Default | Description |
|
||||||
(without extension) of a module that must be loaded before this one.
|
|-----|---------|-------------|
|
||||||
Lines starting with `#` are comments. Empty lines are ignored.
|
| `desktop` | apps/progman/progman.app | Path to the desktop app loaded at startup |
|
||||||
|
|
||||||
Example -- `dvxshell.dep`:
|
### [colors] Section
|
||||||
```
|
|
||||||
# Core libraries
|
|
||||||
libtasks
|
|
||||||
libdvx
|
|
||||||
|
|
||||||
# Widget modules used by the shell
|
All 20 system colors as `R,G,B` triplets (0-255). Missing keys use
|
||||||
box
|
compiled-in defaults.
|
||||||
button
|
|
||||||
checkbox
|
|
||||||
dropdown
|
|
||||||
label
|
|
||||||
listbox
|
|
||||||
listview
|
|
||||||
radio
|
|
||||||
separator
|
|
||||||
spacer
|
|
||||||
statbar
|
|
||||||
textinpt
|
|
||||||
```
|
|
||||||
|
|
||||||
Dep files are read by the loader during startup to determine the
|
| Key | Description |
|
||||||
correct load order via topological sort.
|
|-----|-------------|
|
||||||
|
| `desktop` | Desktop background |
|
||||||
|
| `windowFace` | Window frame and widget face |
|
||||||
|
| `windowHighlight` | Bevel highlight (top/left) |
|
||||||
|
| `windowShadow` | Bevel shadow (bottom/right) |
|
||||||
|
| `activeTitleBg` | Focused window title bar background |
|
||||||
|
| `activeTitleFg` | Focused window title bar text |
|
||||||
|
| `inactiveTitleBg` | Unfocused window title bar background |
|
||||||
|
| `inactiveTitleFg` | Unfocused window title bar text |
|
||||||
|
| `contentBg` | Content area background |
|
||||||
|
| `contentFg` | Content area text |
|
||||||
|
| `menuBg` | Menu background |
|
||||||
|
| `menuFg` | Menu text |
|
||||||
|
| `menuHighlightBg` | Menu selection background |
|
||||||
|
| `menuHighlightFg` | Menu selection text |
|
||||||
|
| `buttonFace` | Button face color |
|
||||||
|
| `scrollbarBg` | Scrollbar background |
|
||||||
|
| `scrollbarFg` | Scrollbar foreground |
|
||||||
|
| `scrollbarTrough` | Scrollbar track color |
|
||||||
|
| `cursorColor` | Mouse cursor foreground |
|
||||||
|
| `cursorOutline` | Mouse cursor outline |
|
||||||
|
|
||||||
|
### [desktop] Section
|
||||||
|
|
||||||
|
| Key | Default | Values | Description |
|
||||||
|
|-----|---------|--------|-------------|
|
||||||
|
| `wallpaper` | (none) | file path | Path to wallpaper image |
|
||||||
|
| `mode` | stretch | stretch, tile, center | Wallpaper display mode |
|
||||||
|
|
||||||
|
|
||||||
## Theme Files (.thm)
|
## Theme Files (.thm)
|
||||||
|
|
||||||
INI format with a `[colors]` section containing the same 20 color
|
Theme files use the same INI format as dvx.ini but contain only a
|
||||||
keys as `dvx.ini`. Loaded via the Control Panel app or
|
`[colors]` section with the 20 color keys. The Control Panel can
|
||||||
`dvxLoadTheme()`.
|
load and save themes via `dvxLoadTheme()` / `dvxSaveTheme()`.
|
||||||
|
|
||||||
```ini
|
Bundled themes:
|
||||||
[colors]
|
|
||||||
desktop = 0,128,128
|
|
||||||
windowFace = 192,192,192
|
|
||||||
windowHighlight = 255,255,255
|
|
||||||
windowShadow = 128,128,128
|
|
||||||
; ... remaining colors
|
|
||||||
```
|
|
||||||
|
|
||||||
Three themes are bundled: `geos.thm` (GEOS Ensemble), `win31.thm`
|
| File | Description |
|
||||||
(Windows 3.1), `cde.thm` (CDE/Motif).
|
|------|-------------|
|
||||||
|
| `cde.thm` | CDE (Common Desktop Environment) -- warm tan/blue palette |
|
||||||
|
| `geos.thm` | GEOS Ensemble -- cyan/grey palette |
|
||||||
|
| `win31.thm` | Windows 3.1 -- grey/navy palette |
|
||||||
|
|
||||||
|
|
||||||
|
## Dependency Files (.dep)
|
||||||
|
|
||||||
|
Plain text files listing module base names that must be loaded before
|
||||||
|
this module. One name per line. Empty lines and `#` comments are
|
||||||
|
ignored. Names are case-insensitive.
|
||||||
|
|
||||||
|
### Library Dependencies
|
||||||
|
|
||||||
|
| Dep File | Module | Dependencies |
|
||||||
|
|----------|--------|--------------|
|
||||||
|
| `libdvx.dep` | libdvx.lib | libtasks |
|
||||||
|
| `texthelp.dep` | texthelp.lib | libtasks, libdvx |
|
||||||
|
| `listhelp.dep` | listhelp.lib | libtasks, libdvx |
|
||||||
|
| `dvxshell.dep` | dvxshell.lib | libtasks, libdvx, texthelp, listhelp |
|
||||||
|
|
||||||
|
### Widget Dependencies
|
||||||
|
|
||||||
|
| Dep File | Widget | Dependencies |
|
||||||
|
|----------|--------|--------------|
|
||||||
|
| `textinpt.dep` | textinpt.wgt | texthelp |
|
||||||
|
| `combobox.dep` | combobox.wgt | texthelp, listhelp |
|
||||||
|
| `spinner.dep` | spinner.wgt | texthelp |
|
||||||
|
| `terminal.dep` | terminal.wgt | texthelp |
|
||||||
|
| `dropdown.dep` | dropdown.wgt | listhelp |
|
||||||
|
| `listbox.dep` | listbox.wgt | listhelp |
|
||||||
|
| `listview.dep` | listview.wgt | listhelp |
|
||||||
|
| `treeview.dep` | treeview.wgt | listhelp |
|
||||||
|
|
||||||
|
|
||||||
|
## Wallpaper Images
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `wpaper/blueglow.jpg` | Blue gradient glow |
|
||||||
|
| `wpaper/swoop.jpg` | Curved swoosh pattern |
|
||||||
|
| `wpaper/triangle.jpg` | Geometric triangle pattern |
|
||||||
|
|
||||||
|
Wallpapers support BMP, PNG, JPEG, and GIF formats. They are
|
||||||
|
pre-rendered to screen dimensions in native pixel format at load time.
|
||||||
|
|
|
||||||
1455
core/README.md
1455
core/README.md
File diff suppressed because it is too large
Load diff
100
listhelp/README.md
Normal file
100
listhelp/README.md
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# listhelp -- Shared List/Dropdown Helper Library
|
||||||
|
|
||||||
|
Shared infrastructure for list and dropdown widgets, built as
|
||||||
|
`listhelp.lib` (DXE3 module). Provides dropdown arrow drawing, item
|
||||||
|
measurement, keyboard navigation, popup rectangle calculation, and
|
||||||
|
popup list painting.
|
||||||
|
|
||||||
|
Used by: Dropdown, ComboBox, ListBox, ListView, TreeView.
|
||||||
|
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Dropdown Arrow Glyph
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops,
|
||||||
|
int32_t centerX, int32_t centerY, uint32_t color);
|
||||||
|
```
|
||||||
|
|
||||||
|
Draws the small downward-pointing triangle glyph used on dropdown
|
||||||
|
buttons.
|
||||||
|
|
||||||
|
### Item Measurement
|
||||||
|
|
||||||
|
```c
|
||||||
|
int32_t widgetMaxItemLen(const char **items, int32_t count);
|
||||||
|
```
|
||||||
|
|
||||||
|
Scans an array of item strings and returns the length of the longest
|
||||||
|
one. Used to size dropdown popups and list columns to fit their
|
||||||
|
content.
|
||||||
|
|
||||||
|
### Keyboard Navigation
|
||||||
|
|
||||||
|
```c
|
||||||
|
int32_t widgetNavigateIndex(int32_t key, int32_t current,
|
||||||
|
int32_t count, int32_t pageSize);
|
||||||
|
```
|
||||||
|
|
||||||
|
Maps arrow key presses to index changes for list navigation. Handles
|
||||||
|
Up, Down, Home, End, PageUp, and PageDown. Returns the new selected
|
||||||
|
index, clamped to valid range.
|
||||||
|
|
||||||
|
### Popup Rectangle Calculation
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font,
|
||||||
|
int32_t contentH, int32_t *popX, int32_t *popY,
|
||||||
|
int32_t *popW, int32_t *popH);
|
||||||
|
```
|
||||||
|
|
||||||
|
Computes the screen rectangle for a dropdown popup overlay. Positions
|
||||||
|
the popup below the widget (or above if there is not enough room
|
||||||
|
below). Limits height to `DROPDOWN_MAX_VISIBLE` items.
|
||||||
|
|
||||||
|
### Popup List Painting
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops,
|
||||||
|
const BitmapFontT *font, const ColorSchemeT *colors,
|
||||||
|
int32_t popX, int32_t popY, int32_t popW, int32_t popH,
|
||||||
|
const char **items, int32_t itemCount,
|
||||||
|
int32_t hoverIdx, int32_t scrollPos);
|
||||||
|
```
|
||||||
|
|
||||||
|
Renders the popup overlay list with items, selection highlight, scroll
|
||||||
|
position, and beveled border. Used by Dropdown and ComboBox for their
|
||||||
|
popup overlays.
|
||||||
|
|
||||||
|
### Constants
|
||||||
|
|
||||||
|
| Name | Value | Description |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| `DROPDOWN_BTN_WIDTH` | 16 | Width of dropdown arrow button area |
|
||||||
|
| `DROPDOWN_MAX_VISIBLE` | 8 | Maximum visible items in popup list |
|
||||||
|
|
||||||
|
|
||||||
|
## Exported Symbols
|
||||||
|
|
||||||
|
Matches prefixes: `_widgetDraw*`, `_widgetDropdown*`, `_widgetMax*`,
|
||||||
|
`_widgetNavigate*`, `_widgetPaint*`.
|
||||||
|
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `listHelp.h` | Public API header |
|
||||||
|
| `listHelp.c` | Complete implementation |
|
||||||
|
| `Makefile` | Builds `bin/libs/listhelp.lib` + dep file |
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
make # builds bin/libs/listhelp.lib + listhelp.dep
|
||||||
|
make clean # removes objects, library, and dep file
|
||||||
|
```
|
||||||
|
|
||||||
|
Depends on: `libtasks.lib`, `libdvx.lib` (via listhelp.dep).
|
||||||
151
loader/README.md
151
loader/README.md
|
|
@ -1,73 +1,110 @@
|
||||||
# DVX Loader
|
# DVX Loader
|
||||||
|
|
||||||
Bootstrap loader for the DVX desktop environment. Builds as
|
Bootstrap loader for the DVX desktop environment. Builds as `dvx.exe`
|
||||||
`dvx.exe` -- the only native executable in the system. Everything
|
-- the only native executable in the system. Everything else is a
|
||||||
else is a dynamically loaded DXE module.
|
dynamically loaded DXE3 module.
|
||||||
|
|
||||||
|
|
||||||
## What It Does
|
## What It Does
|
||||||
|
|
||||||
1. Changes to the executable's directory so relative paths resolve
|
1. Changes working directory to the directory containing `dvx.exe`
|
||||||
2. Calls `platformRegisterDxeExports()` to register platform functions
|
2. Truncates `dvx.log` and initializes logging
|
||||||
and C runtime symbols (libc, libm, libgcc, DJGPP internals) with
|
3. Calls `platformInit()` to suppress Ctrl+C and install signal handlers
|
||||||
the DXE3 loader via `dlregsym()`
|
4. Calls `platformRegisterDxeExports()` to register platform and C
|
||||||
3. Recursively scans `LIBS/` for `*.lib` modules and `WIDGETS/` for
|
runtime symbols (libc, libm, libgcc) for DXE module resolution
|
||||||
`*.wgt` modules
|
5. Scans and loads all modules in two phases (see below)
|
||||||
4. Reads `.dep` files to build a dependency graph
|
6. Finds `shellMain()` via `dlsym` across loaded modules
|
||||||
5. Topologically sorts all modules and loads them in order with
|
7. Calls `shellMain()` -- the shell takes over from here
|
||||||
`RTLD_GLOBAL` (each module's exports become available to
|
8. On return, closes all module handles in reverse load order
|
||||||
subsequent modules)
|
|
||||||
6. Calls `wgtRegister()` on any module that exports it (widget
|
|
||||||
registration)
|
## Two-Phase Module Loading
|
||||||
7. Finds `shellMain()` across all loaded modules and calls it
|
|
||||||
8. On return, closes all modules in reverse order
|
### Phase 1: Libraries (libs/*.lib)
|
||||||
|
|
||||||
|
Scans the `LIBS/` directory for `.lib` files. Each module may have a
|
||||||
|
`.dep` file (same base name, `.dep` extension) listing base names of
|
||||||
|
modules that must load first. The loader resolves the dependency graph
|
||||||
|
and loads in topological order.
|
||||||
|
|
||||||
|
Load order (via dep files):
|
||||||
|
```
|
||||||
|
libtasks.lib (no deps)
|
||||||
|
libdvx.lib (deps: libtasks)
|
||||||
|
texthelp.lib (deps: libtasks, libdvx)
|
||||||
|
listhelp.lib (deps: libtasks, libdvx)
|
||||||
|
dvxshell.lib (deps: libtasks, libdvx, texthelp, listhelp)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Widgets (widgets/*.wgt)
|
||||||
|
|
||||||
|
Scans the `WIDGETS/` directory for `.wgt` files. Widget modules may
|
||||||
|
also have `.dep` files (e.g., `textinpt.dep` lists `texthelp`).
|
||||||
|
Loaded in topological order, same as libs.
|
||||||
|
|
||||||
|
After loading each module, the loader checks for a `wgtRegister`
|
||||||
|
export. If present, it is called immediately so the widget registers
|
||||||
|
its class(es) and API with the core.
|
||||||
|
|
||||||
|
|
||||||
|
## Dependency File Format
|
||||||
|
|
||||||
|
Plain text, one dependency base name per line. Empty lines and lines
|
||||||
|
starting with `#` are ignored. Names are case-insensitive.
|
||||||
|
|
||||||
|
Example (`combobox.dep`):
|
||||||
|
```
|
||||||
|
texthelp
|
||||||
|
listhelp
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Hosted Components
|
||||||
|
|
||||||
|
### dvxLog()
|
||||||
|
|
||||||
|
The global logging function is defined in `loaderMain.c` and exported
|
||||||
|
to all DXE modules. It appends a line to `dvx.log`, opening and
|
||||||
|
closing the file per write so it is never held open.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void dvxLog(const char *fmt, ...);
|
||||||
|
```
|
||||||
|
|
||||||
|
### stb_ds
|
||||||
|
|
||||||
|
The `STB_DS_IMPLEMENTATION` is compiled into the loader. stb_ds
|
||||||
|
functions are exported to all DXE modules via the platform export
|
||||||
|
table.
|
||||||
|
|
||||||
|
### Platform Layer
|
||||||
|
|
||||||
|
`dvxPlatformDos.c` is compiled into the loader (not into libdvx.lib).
|
||||||
|
All platform functions are exported to DXE modules. This includes:
|
||||||
|
|
||||||
|
* Video: VESA VBE init, LFB mapping, mode enumeration
|
||||||
|
* Input: INT 33h mouse, INT 16h keyboard, CuteMouse wheel API
|
||||||
|
* Spans: `rep stosl`/`rep movsd` asm inner loops (8/16/32 bpp)
|
||||||
|
* DXE: symbol registration, symbol overrides
|
||||||
|
* Crash: signal handler installation, register dump logging
|
||||||
|
* System: memory info, directory creation, path utilities
|
||||||
|
|
||||||
|
|
||||||
## Files
|
## Files
|
||||||
|
|
||||||
| File | Purpose |
|
| File | Description |
|
||||||
|------|---------|
|
|------|-------------|
|
||||||
| `loaderMain.c` | Entry point, directory scanning, dependency resolution, module loading |
|
| `loaderMain.c` | Entry point, module scanner, dependency resolver, logger |
|
||||||
| `Makefile` | Cross-compilation rules; links `dvxPlatformDos.c` directly |
|
| `Makefile` | Builds `bin/dvx.exe` |
|
||||||
|
|
||||||
The DXE export table lives in `core/platform/dvxPlatformDos.c`, not
|
|
||||||
in the loader. The loader is platform-agnostic -- all DJGPP/DXE3
|
|
||||||
knowledge is in the platform layer.
|
|
||||||
|
|
||||||
|
|
||||||
## Dependency Resolution
|
## Build
|
||||||
|
|
||||||
Each module may have a `.dep` file alongside it (same base name,
|
|
||||||
`.dep` extension). The file lists base names of modules that must
|
|
||||||
be loaded first, one per line. Lines starting with `#` are comments.
|
|
||||||
|
|
||||||
Example -- `dvxshell.dep`:
|
|
||||||
```
|
|
||||||
libtasks
|
|
||||||
libdvx
|
|
||||||
box
|
|
||||||
button
|
|
||||||
label
|
|
||||||
listview
|
|
||||||
```
|
|
||||||
|
|
||||||
A dependency is satisfied when either:
|
|
||||||
- A module with that base name has been loaded, or
|
|
||||||
- No module with that base name exists in the scan (external, assumed OK)
|
|
||||||
|
|
||||||
The loader iterates until all modules are loaded or no further
|
|
||||||
progress can be made (circular dependency). Unloadable modules are
|
|
||||||
reported to stderr.
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```
|
```
|
||||||
make # builds ../bin/dvx.exe
|
make # builds bin/dvx.exe
|
||||||
make clean
|
make clean # removes objects and binary
|
||||||
```
|
```
|
||||||
|
|
||||||
The Makefile compiles `loaderMain.c` and links it with
|
The loader links `loaderMain.c` + `dvxPlatformDos.c` into a native
|
||||||
`dvxPlatformDos.o` (compiled from `../core/platform/dvxPlatformDos.c`)
|
DJGPP executable. The CWSDPMI stub is prepended via exe2coff +
|
||||||
and `-lm`. The result goes through `exe2coff` + `CWSDSTUB` to
|
CWSDSTUB.EXE for standalone execution.
|
||||||
produce a DOS executable.
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ $(WPAPERDIR)/%.jpg: ../config/wpaper/%.jpg | $(WPAPERDIR)
|
||||||
# Dependencies
|
# Dependencies
|
||||||
$(OBJDIR)/shellMain.o: shellMain.c shellApp.h
|
$(OBJDIR)/shellMain.o: shellMain.c shellApp.h
|
||||||
$(OBJDIR)/shellApp.o: shellApp.c shellApp.h
|
$(OBJDIR)/shellApp.o: shellApp.c shellApp.h
|
||||||
$(OBJDIR)/shellExport.o: shellExport.c shellApp.h
|
|
||||||
$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h
|
$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h
|
||||||
$(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h
|
$(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h
|
||||||
|
|
||||||
|
|
|
||||||
447
shell/README.md
447
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
|
The DVX Shell is a DXE3 module loaded by the DVX loader at startup.
|
||||||
startup. It initializes the GUI subsystem, loads DXE3 application modules at
|
It initializes the GUI subsystem, loads DXE3 application modules on
|
||||||
runtime, runs the cooperative main loop, and provides crash recovery so a
|
demand, runs the cooperative main loop, and provides crash recovery so
|
||||||
faulting app does not take down the entire system.
|
a faulting app does not bring down the entire system.
|
||||||
|
|
||||||
The shell is not a standalone executable. The loader maps it into memory via
|
|
||||||
`dlopen`, resolves its entry point (`shellMain`), and calls it. All symbols
|
|
||||||
the shell needs -- libdvx, libtasks, widgets, libc -- are resolved at load
|
|
||||||
time from DXE modules already loaded by the loader and the loader's own
|
|
||||||
`dlregsym` table.
|
|
||||||
|
|
||||||
## Files
|
## Entry Point
|
||||||
|
|
||||||
| File | Purpose |
|
The loader finds and calls `shellMain()` after all libs and widgets
|
||||||
|------|---------|
|
are loaded. `shellMain()`:
|
||||||
| `shellMain.c` | Entry point (`shellMain`), main loop, crash recovery, logging |
|
|
||||||
| `shellApp.c` | App loading via `dlopen`/`dlsym`, lifecycle, reaping |
|
|
||||||
| `shellApp.h` | `ShellAppT`, `AppDescriptorT`, `AppStateE`, `DxeAppContextT`, API |
|
|
||||||
| `shellExport.c` | 3 DXE wrapper overrides for window resource tracking |
|
|
||||||
| `shellInfo.c` | System info gathering |
|
|
||||||
| `shellInfo.h` | `shellInfoInit()`, `shellGetSystemInfo()` |
|
|
||||||
| `shellTaskMgr.c` | Task Manager window |
|
|
||||||
| `shellTaskMgr.h` | `shellTaskMgrOpen()`, `shellTaskMgrRefresh()` |
|
|
||||||
| `Makefile` | Cross-compile rules, produces `dvxshell.lib` and `dvxshell.dep` |
|
|
||||||
|
|
||||||
## Building
|
1. Loads preferences from `CONFIG/DVX.INI`
|
||||||
|
2. Initializes the GUI via `dvxInit()` with configured video mode
|
||||||
|
3. Applies saved mouse, color, and wallpaper settings from INI
|
||||||
|
4. Initializes the cooperative task system (`tsInit()`)
|
||||||
|
5. Sets shell task (task 0) to `TS_PRIORITY_HIGH`
|
||||||
|
6. Gathers system information via the platform layer
|
||||||
|
7. Initializes the app slot table
|
||||||
|
8. Registers idle callback, Ctrl+Esc handler, and title change handler
|
||||||
|
9. Loads the desktop app (default: `apps/progman/progman.app`)
|
||||||
|
10. Installs the crash handler (after init, so init crashes are fatal)
|
||||||
|
11. Enters the main loop
|
||||||
|
|
||||||
```
|
|
||||||
make # builds ../bin/libs/dvxshell.lib and ../bin/libs/dvxshell.dep
|
|
||||||
make clean # removes objects, binary, and deployed config files
|
|
||||||
```
|
|
||||||
|
|
||||||
CFLAGS: `-O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty`
|
|
||||||
|
|
||||||
The shell is compiled to object files and packaged into a DXE via `dxe3gen`.
|
|
||||||
It exports `_shell` so the loader can resolve `shellMain`. All other symbols
|
|
||||||
are unresolved imports (`-U`) satisfied at load time.
|
|
||||||
|
|
||||||
Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`).
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
`dvxshell.dep` lists the modules the loader must load before the shell:
|
|
||||||
|
|
||||||
```
|
|
||||||
libtasks
|
|
||||||
libdvx
|
|
||||||
box
|
|
||||||
button
|
|
||||||
checkbox
|
|
||||||
dropdown
|
|
||||||
label
|
|
||||||
listbox
|
|
||||||
listview
|
|
||||||
radio
|
|
||||||
separator
|
|
||||||
spacer
|
|
||||||
statbar
|
|
||||||
textinpt
|
|
||||||
```
|
|
||||||
|
|
||||||
The loader reads this file, loads each module via `dlopen` with `RTLD_GLOBAL`,
|
|
||||||
then loads the shell itself. This is how all symbols (dvx*, wgt*, ts*, etc.)
|
|
||||||
become available without the shell statically linking anything.
|
|
||||||
|
|
||||||
## Startup Sequence
|
|
||||||
|
|
||||||
`shellMain()` in `shellMain.c` is called by the loader after all dependencies
|
|
||||||
are loaded. It performs initialization in this order:
|
|
||||||
|
|
||||||
1. **Truncate log** -- open `dvx.log` for write to clear it, then close.
|
|
||||||
All subsequent writes use append-per-write (the file is never held open).
|
|
||||||
2. **Load preferences** -- `prefsLoad("CONFIG/DVX.INI")`. Missing file or
|
|
||||||
keys silently fall back to compiled-in defaults.
|
|
||||||
3. **dvxInit** -- initialize VESA video (LFB), backbuffer, compositor, window
|
|
||||||
manager, font, cursor, input subsystems. Reads video width/height/bpp from
|
|
||||||
preferences (default 640x480x16).
|
|
||||||
4. **Mouse config** -- read wheel direction, double-click speed, acceleration
|
|
||||||
from `[mouse]` section and call `dvxSetMouseConfig()`.
|
|
||||||
5. **Color scheme** -- read `[colors]` section (20 RGB triplets), apply via
|
|
||||||
`dvxApplyColorScheme()`.
|
|
||||||
6. **Wallpaper** -- read `[desktop]` section for wallpaper path and mode
|
|
||||||
(stretch/tile/center), load via `dvxSetWallpaper()`.
|
|
||||||
7. **Video mode log** -- enumerate all available VESA modes to `dvx.log`.
|
|
||||||
8. **Task system** -- `tsInit()`, set shell task (task 0) to
|
|
||||||
`TS_PRIORITY_HIGH` so the UI stays responsive under load.
|
|
||||||
9. **System info** -- `shellInfoInit()` gathers CPU, memory, drive info via
|
|
||||||
the platform layer and logs it.
|
|
||||||
10. **DXE exports** -- `shellExportInit()` calls `dlregsym()` to register the
|
|
||||||
3 wrapper overrides. Must happen before any `dlopen()` of app DXEs.
|
|
||||||
11. **App slot table** -- `shellAppInit()` zeroes the 32-slot fixed array.
|
|
||||||
12. **Idle/hotkey callbacks** -- wire up `idleYield`, `ctrlEscHandler`,
|
|
||||||
`titleChangeHandler` on the `AppContextT`.
|
|
||||||
13. **Desktop app** -- `shellLoadApp(ctx, "apps/progman/progman.app")`. If
|
|
||||||
this fails, the shell exits.
|
|
||||||
14. **Crash handlers** -- `installCrashHandler()` registers signal handlers
|
|
||||||
for SIGSEGV, SIGFPE, SIGILL. Installed last so initialization crashes
|
|
||||||
get the default DJGPP abort-with-register-dump instead of our recovery
|
|
||||||
path.
|
|
||||||
|
|
||||||
## Main Loop
|
## Main Loop
|
||||||
|
|
||||||
The main loop runs until `dvxQuit()` sets `ctx->running = false`:
|
Each iteration of the main loop does four things:
|
||||||
|
|
||||||
```
|
1. `dvxUpdate()` -- process input events, dispatch callbacks, composite
|
||||||
while (ctx->running) {
|
dirty rects, flush to LFB
|
||||||
dvxUpdate(ctx); // (1)
|
2. `tsYield()` -- give CPU time to app tasks (if any are active)
|
||||||
tsYield(); // (2)
|
3. `shellReapApps()` -- clean up any apps that terminated this frame
|
||||||
shellReapApps(ctx); // (3)
|
4. `shellDesktopUpdate()` -- notify desktop app if apps were reaped
|
||||||
shellDesktopUpdate(); // (4)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
1. **dvxUpdate** -- processes mouse/keyboard input, dispatches widget and
|
An idle callback (`idleYield`) is also registered so that `dvxUpdate()`
|
||||||
window callbacks, composites dirty rects, flushes to the LFB. This is the
|
yields to app tasks during quiet frames.
|
||||||
shell's primary job. When idle (no events, no dirty rects), dvxUpdate
|
|
||||||
calls the registered `idleCallback` which yields to app tasks.
|
|
||||||
2. **tsYield** -- explicit yield to give app tasks CPU time even during busy
|
|
||||||
frames with many repaints. Without this, a flurry of mouse-move events
|
|
||||||
could starve app tasks because dvxUpdate would keep finding work.
|
|
||||||
3. **shellReapApps** -- scans the 32-slot app table for apps in
|
|
||||||
`AppStateTerminatingE`. Calls the shutdown hook (if present), destroys
|
|
||||||
all windows owned by the app, kills the task, closes the DXE handle,
|
|
||||||
and cleans up temp files. Returns true if anything was reaped.
|
|
||||||
4. **shellDesktopUpdate** -- if apps were reaped, iterates the registered
|
|
||||||
desktop update callback list so listeners (Program Manager, Task Manager)
|
|
||||||
can refresh their UI.
|
|
||||||
|
|
||||||
## Crash Recovery
|
|
||||||
|
|
||||||
The shell provides Windows 3.1-style fault tolerance using `setjmp`/`longjmp`:
|
|
||||||
|
|
||||||
1. `installCrashHandler()` registers `crashHandler` for SIGSEGV, SIGFPE,
|
|
||||||
SIGILL.
|
|
||||||
2. `setjmp(sCrashJmp)` in `shellMain()` establishes the recovery point.
|
|
||||||
3. If a signal fires (in any task), `crashHandler` logs the crash details
|
|
||||||
(signal, app name, full register dump from `__djgpp_exception_state_ptr`),
|
|
||||||
re-installs the handler (DJGPP uses SysV semantics -- handler resets to
|
|
||||||
`SIG_DFL` after each delivery), then `longjmp(sCrashJmp, 1)`.
|
|
||||||
4. `longjmp` restores the main task's stack frame to the `setjmp` point.
|
|
||||||
This is safe because cooperative switching means the main task's stack
|
|
||||||
is always intact -- it was cleanly suspended at a yield point.
|
|
||||||
5. `tsRecoverToMain()` fixes the task scheduler's `currentIdx` so it points
|
|
||||||
back to task 0 instead of the crashed task.
|
|
||||||
6. The crashed app is force-killed via `shellForceKillApp()`, and a message
|
|
||||||
box is displayed: "'AppName' has caused a fault and will be terminated."
|
|
||||||
7. The desktop update callbacks are notified so the UI refreshes.
|
|
||||||
|
|
||||||
The register dump logged includes EIP, CS, all GPRs (EAX-EDI), segment
|
|
||||||
registers, and EFLAGS -- invaluable for post-mortem debugging.
|
|
||||||
|
|
||||||
## App Lifecycle
|
## App Lifecycle
|
||||||
|
|
||||||
### DXE3 Loading
|
### DXE App Contract
|
||||||
|
|
||||||
DXE3 is DJGPP's dynamic linking mechanism. Each `.app` file is a DXE3 shared
|
Every DXE app exports two symbols:
|
||||||
object. Symbols are resolved from the loaded RTLD_GLOBAL modules (libdvx,
|
|
||||||
libtasks, widgets) and the shell's 3 wrapper overrides registered via
|
|
||||||
`dlregsym()`. The load sequence in `shellLoadApp()`:
|
|
||||||
|
|
||||||
1. Allocate a slot from the 32-entry fixed array (slot 0 is the shell).
|
* `appDescriptor` (`AppDescriptorT`) -- metadata:
|
||||||
2. Check if this DXE path is already loaded (`findLoadedPath`).
|
- `name` -- display name (max 64 chars)
|
||||||
- If the existing app's descriptor says `multiInstance = true`, copy
|
- `hasMainLoop` -- true for main-loop apps, false for callback-only
|
||||||
the `.app` to a unique temp file (using `TEMP`/`TMP` env var) so
|
- `multiInstance` -- true to allow multiple instances via temp copy
|
||||||
`dlopen` gets an independent code+data image.
|
- `stackSize` -- `SHELL_STACK_DEFAULT` (32KB) or explicit byte count
|
||||||
- If `multiInstance = false`, block with an error message.
|
- `priority` -- `TS_PRIORITY_NORMAL` or custom
|
||||||
3. `dlopen(loadPath, RTLD_GLOBAL)` -- load the DXE.
|
|
||||||
4. `dlsym(handle, "_appDescriptor")` -- resolve the metadata struct.
|
|
||||||
5. `dlsym(handle, "_appMain")` -- resolve the entry point.
|
|
||||||
6. `dlsym(handle, "_appShutdown")` -- resolve the optional shutdown hook.
|
|
||||||
7. Fill in the `ShellAppT` slot (name, path, handle, state, etc.).
|
|
||||||
8. Derive `appDir` (directory containing the `.app` file) and `configDir`
|
|
||||||
(`CONFIG/<apppath>/` -- mirrors the app's directory structure under
|
|
||||||
`CONFIG/`).
|
|
||||||
9. Set `sCurrentAppId` to the slot index.
|
|
||||||
10. Launch:
|
|
||||||
- **Callback-only** (`hasMainLoop = false`): call `entryFn()` directly
|
|
||||||
in task 0. The app creates windows, registers callbacks, and returns.
|
|
||||||
- **Main-loop** (`hasMainLoop = true`): call `tsCreate()` to make a
|
|
||||||
cooperative task with the descriptor's stack size and priority. The
|
|
||||||
task wrapper sets `sCurrentAppId`, calls `entryFn()`, and marks the
|
|
||||||
app `AppStateTerminatingE` when it returns.
|
|
||||||
11. Reset `sCurrentAppId` to 0. Set state to `AppStateRunningE`. Notify
|
|
||||||
desktop update callbacks.
|
|
||||||
|
|
||||||
### AppDescriptorT
|
* `appMain` (`int appMain(DxeAppContextT *)`) -- entry point
|
||||||
|
|
||||||
Every DXE app exports a global `AppDescriptorT`:
|
Optional export: `appShutdown` (`void appShutdown(void)`) -- called
|
||||||
|
during graceful shutdown.
|
||||||
|
|
||||||
| Field | Type | Description |
|
### Callback-Only Apps (hasMainLoop = false)
|
||||||
|-------|------|-------------|
|
|
||||||
| `name` | `char[64]` | Display name (Task Manager, title bars) |
|
|
||||||
| `hasMainLoop` | `bool` | `true` = dedicated cooperative task; `false` = callback-only |
|
|
||||||
| `multiInstance` | `bool` | `true` = allow multiple simultaneous instances via temp file copy |
|
|
||||||
| `stackSize` | `int32_t` | Task stack in bytes (`SHELL_STACK_DEFAULT` = use default) |
|
|
||||||
| `priority` | `int32_t` | `TS_PRIORITY_LOW` / `TS_PRIORITY_NORMAL` / `TS_PRIORITY_HIGH` |
|
|
||||||
|
|
||||||
### Callback vs Main-Loop Apps
|
`appMain()` is called directly in the shell's task 0. It creates
|
||||||
|
windows, registers callbacks, and returns immediately. The app lives
|
||||||
|
entirely through GUI callbacks. Lifecycle ends when the last window
|
||||||
|
closes.
|
||||||
|
|
||||||
**Callback-only** (`hasMainLoop = false`):
|
### Main-Loop Apps (hasMainLoop = true)
|
||||||
- `appMain()` runs in the shell's task 0, creates windows, registers event
|
|
||||||
callbacks, and returns immediately.
|
|
||||||
- All subsequent work happens through callbacks dispatched by `dvxUpdate()`.
|
|
||||||
- Lifecycle ends when the last window is closed (detected by the
|
|
||||||
`shellWrapDestroyWindow` wrapper).
|
|
||||||
- No task stack allocated -- simpler and cheaper.
|
|
||||||
- Examples: Program Manager, Notepad, Control Panel, DVX Demo, Image Viewer.
|
|
||||||
|
|
||||||
**Main-loop** (`hasMainLoop = true`):
|
A dedicated cooperative task is created. `appMain()` runs in that task
|
||||||
- A cooperative task is created via `tsCreate()`.
|
and can do its own polling/processing loop, calling `tsYield()` to
|
||||||
- `appMain()` runs in that task with its own loop calling `tsYield()`.
|
share CPU. Lifecycle ends when `appMain()` returns or the task is
|
||||||
- Needed when the app has ongoing work that cannot be expressed purely as
|
killed.
|
||||||
event callbacks (polling, computation, animation).
|
|
||||||
- Lifecycle ends when `appMain()` returns.
|
|
||||||
- Example: Clock (polls `time()` each second).
|
|
||||||
|
|
||||||
### Multi-Instance Support
|
### App States
|
||||||
|
|
||||||
DXE3's `dlopen` is reference-counted per path -- loading the same `.app`
|
|
||||||
twice returns the same handle, sharing all globals and statics. For apps
|
|
||||||
that set `multiInstance = true`, the shell copies the `.app` to a temp file
|
|
||||||
(e.g., `C:\TEMP\_dvx02.app`) before `dlopen`, giving each instance its own
|
|
||||||
code and data segment. Temp files are cleaned up on app termination.
|
|
||||||
|
|
||||||
### App State Machine
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Free -> Loaded -> Running -> Terminating -> Free
|
Free -> Loaded -> Running -> Terminating -> Free
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Free**: slot available.
|
| State | Description |
|
||||||
- **Loaded**: DXE loaded, entry point not yet called (transient).
|
|-------|-------------|
|
||||||
- **Running**: entry point called, app is active.
|
| `AppStateFreeE` | Slot available for reuse |
|
||||||
- **Terminating**: app's task returned or last window closed; awaiting reap.
|
| `AppStateLoadedE` | DXE loaded, not yet started (transient) |
|
||||||
|
| `AppStateRunningE` | Entry point called, active |
|
||||||
|
| `AppStateTerminatingE` | Shutdown in progress, awaiting reap |
|
||||||
|
|
||||||
## Resource Tracking
|
### App Slots
|
||||||
|
|
||||||
`sCurrentAppId` is a global set before calling any app code (entry, shutdown,
|
App slots are managed as a stb_ds dynamic array (no fixed max). Each
|
||||||
callbacks). The shell's `dvxCreateWindow` wrapper stamps every new window with
|
slot tracks: app ID, name, path, DXE handle, state, task ID, entry/
|
||||||
`win->appId = sCurrentAppId`. This is how the shell knows which windows belong
|
shutdown function pointers, and the `DxeAppContextT` passed to the app.
|
||||||
to which app, enabling:
|
|
||||||
|
|
||||||
- Per-app window cleanup on crash or termination (walk the window stack,
|
The `DxeAppContextT` gives each app:
|
||||||
destroy all windows with matching `appId`).
|
- `shellCtx` -- pointer to the shell's `AppContextT`
|
||||||
- The last-window-closes-app rule for callback-only apps: when
|
- `appId` -- this app's unique ID
|
||||||
`shellWrapDestroyWindow` detects that a callback-only app has no remaining
|
- `appDir` -- directory containing the `.app` file (for resources)
|
||||||
windows, it marks the app `AppStateTerminatingE`.
|
- `configDir` -- writable config directory (`CONFIG/<apppath>/`)
|
||||||
|
|
||||||
`sCurrentAppId` is a simple global (not thread-local) because cooperative
|
### App ID Tracking
|
||||||
multitasking means only one task runs at a time.
|
|
||||||
|
|
||||||
## DXE Export Table
|
`ctx->currentAppId` on AppContextT tracks which app is currently
|
||||||
|
executing. The shell sets this before calling app code.
|
||||||
|
`dvxCreateWindow()` stamps `win->appId` directly so the shell can
|
||||||
|
associate windows with apps for cleanup.
|
||||||
|
|
||||||
`shellExport.c` registers 3 wrapper functions via `dlregsym()` that override
|
|
||||||
the real implementations for subsequently loaded app DXEs:
|
|
||||||
|
|
||||||
1. `dvxCreateWindow` -- stamps `win->appId` for resource ownership tracking.
|
## Crash Recovery
|
||||||
2. `dvxCreateWindowCentered` -- stamps `win->appId` for resource ownership
|
|
||||||
tracking.
|
|
||||||
3. `dvxDestroyWindow` -- checks if the destroyed window was a callback-only
|
|
||||||
app's last window and marks it for reaping.
|
|
||||||
|
|
||||||
The key mechanic: `dlregsym` takes precedence over `RTLD_GLOBAL` exports.
|
The platform layer installs signal handlers for SIGSEGV, SIGFPE, and
|
||||||
Since libdvx (which has the real functions) was loaded before
|
SIGILL via `platformInstallCrashHandler()`. If a crash occurs:
|
||||||
`shellExportInit()` registers these wrappers, libdvx keeps the real
|
|
||||||
implementations. But any app DXE loaded afterward gets the wrappers, which
|
1. Platform handler logs signal name and register dump (DJGPP)
|
||||||
add resource tracking transparently.
|
2. Handler `longjmp`s to the `setjmp` point in `shellMain()`
|
||||||
|
3. `tsRecoverToMain()` fixes the scheduler's bookkeeping
|
||||||
|
4. Shell logs app-specific info (name, path, task ID)
|
||||||
|
5. Crashed app is force-killed (`shellForceKillApp()`)
|
||||||
|
6. Error dialog is shown to the user
|
||||||
|
7. Desktop is notified to refresh
|
||||||
|
8. Main loop continues normally
|
||||||
|
|
||||||
|
This gives Windows 3.1-style fault tolerance -- one bad app does not
|
||||||
|
take down the whole system.
|
||||||
|
|
||||||
All other symbol exports -- dvx*, wgt*, ts*, platform*, libc, libm -- come
|
|
||||||
from the DXE modules loaded with `RTLD_GLOBAL` by the loader. They no longer
|
|
||||||
need to be listed in the shell's export table.
|
|
||||||
|
|
||||||
## Task Manager
|
## Task Manager
|
||||||
|
|
||||||
`shellTaskMgr.c` implements a shell-level Task Manager accessible via
|
The built-in Task Manager is always available via Ctrl+Esc. It shows
|
||||||
Ctrl+Esc regardless of which app is focused or whether the desktop app
|
all running apps with their names and provides an "End Task" button
|
||||||
is running. It is owned by the shell (appId = 0), not by any DXE app.
|
for force-killing hung apps. The Task Manager is a shell-level
|
||||||
|
component, not tied to any app -- it persists even if the desktop app
|
||||||
|
is terminated.
|
||||||
|
|
||||||
Features:
|
|
||||||
- **ListView** with 5 columns: Name, Title, File, Type (Task/Callback), Status.
|
|
||||||
- **Switch To** -- find the app's topmost window, restore if minimized, raise
|
|
||||||
and focus it.
|
|
||||||
- **End Task** -- force-kill the selected app via `shellForceKillApp()`.
|
|
||||||
- **Run...** -- open a file dialog filtered to `*.app`, load the selected file.
|
|
||||||
- **Status bar** -- shows running app count and memory usage (total/free MB).
|
|
||||||
- Registers with `shellRegisterDesktopUpdate` so the list auto-refreshes when
|
|
||||||
apps load, terminate, or crash.
|
|
||||||
|
|
||||||
## Desktop Update Callbacks
|
## Desktop Update Notifications
|
||||||
|
|
||||||
`shellRegisterDesktopUpdate(fn)` adds a function pointer to a dynamic array
|
Apps (especially the desktop app) register callbacks via
|
||||||
(managed via stb_ds `arrput`/`arrdel`). `shellDesktopUpdate()` iterates the
|
`shellRegisterDesktopUpdate()` to be notified when app state changes
|
||||||
array and calls each registered function.
|
(load, reap, crash, title change). Multiple callbacks are supported.
|
||||||
|
|
||||||
This is the mechanism by which the shell notifies interested parties (Program
|
|
||||||
Manager status bar, Task Manager list) when app state changes -- without
|
|
||||||
polling. Multiple listeners are supported. Listeners should call
|
|
||||||
`shellUnregisterDesktopUpdate()` before they are destroyed.
|
|
||||||
|
|
||||||
## App Config Storage
|
## Files
|
||||||
|
|
||||||
Each app gets a dedicated writable config directory under `CONFIG/`. The path
|
| File | Description |
|
||||||
mirrors the app's location under `APPS/`:
|
|------|-------------|
|
||||||
|
| `shellMain.c` | Entry point, main loop, crash recovery, idle callback |
|
||||||
|
| `shellApp.h` | App lifecycle types: `AppDescriptorT`, `DxeAppContextT`, `ShellAppT`, `AppStateE` |
|
||||||
|
| `shellApp.c` | App loading, reaping, task creation, DXE management |
|
||||||
|
| `shellInfo.h` | System information wrapper |
|
||||||
|
| `shellInfo.c` | Gathers and caches hardware info via platform layer |
|
||||||
|
| `shellTaskMgr.h` | Task Manager API |
|
||||||
|
| `shellTaskMgr.c` | Task Manager window (list of running apps, End Task) |
|
||||||
|
| `Makefile` | Builds `bin/libs/dvxshell.lib` + config/themes/wallpapers |
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
```
|
```
|
||||||
App path: APPS/PROGMAN/progman.app
|
make # builds dvxshell.lib + dvxshell.dep + config files
|
||||||
Config dir: CONFIG/PROGMAN/
|
make clean # removes objects, library, and config output
|
||||||
```
|
```
|
||||||
|
|
||||||
API:
|
Depends on: `libtasks.lib`, `libdvx.lib`, `texthelp.lib`, `listhelp.lib`
|
||||||
- `shellEnsureConfigDir(ctx)` -- create the directory tree via
|
(via dvxshell.dep).
|
||||||
`platformMkdirRecursive()`. Returns 0 on success.
|
|
||||||
- `shellConfigPath(ctx, "settings.ini", buf, sizeof(buf))` -- build a full
|
|
||||||
path to a file in the config directory.
|
|
||||||
|
|
||||||
Apps use the standard preferences system (`prefsLoad`/`prefsSave`) pointed at
|
|
||||||
their config directory for persistent settings.
|
|
||||||
|
|
||||||
## System Hotkeys
|
|
||||||
|
|
||||||
These are always active regardless of which app is focused:
|
|
||||||
|
|
||||||
| Hotkey | Action |
|
|
||||||
|--------|--------|
|
|
||||||
| Alt+Tab | Cycle windows forward (rotate top to bottom of stack) |
|
|
||||||
| Shift+Alt+Tab | Cycle windows backward (pull bottom to top) |
|
|
||||||
| Alt+F4 | Close the focused window (calls its onClose callback) |
|
|
||||||
| Ctrl+F12 | Full screen screenshot -- prompts for save path (PNG/BMP/JPG) |
|
|
||||||
| Ctrl+Shift+F12 | Focused window screenshot -- prompts for save path |
|
|
||||||
| Ctrl+Esc | Open/raise the Task Manager (shell-level, always available) |
|
|
||||||
| F10 | Activate/toggle the focused window's menu bar |
|
|
||||||
| Alt+Space | Open/close the system menu on the focused window |
|
|
||||||
|
|
||||||
## Screenshot System
|
|
||||||
|
|
||||||
Two screenshot functions are available, both accessible from the system menu
|
|
||||||
(Alt+Space) or via hotkeys:
|
|
||||||
|
|
||||||
- `interactiveScreenshot(ctx)` -- captures the full screen (composited
|
|
||||||
backbuffer), then opens a Save As file dialog filtered to PNG/BMP/JPG/TGA.
|
|
||||||
Called by Ctrl+F12 or the "Screenshot..." system menu item.
|
|
||||||
- `interactiveWindowScreenshot(ctx, win)` -- captures just the focused
|
|
||||||
window's content buffer. Called by Ctrl+Shift+F12 or the "Window Shot..."
|
|
||||||
system menu item.
|
|
||||||
|
|
||||||
The system menu also includes standard window operations: Restore, Move, Size,
|
|
||||||
Minimize, Maximize, and Close.
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
`shellLog(fmt, ...)` appends a printf-formatted line to `dvx.log`. The file
|
|
||||||
is opened, written, and closed on each call (append-per-write) so:
|
|
||||||
- The file is never held open, allowing Notepad to read it while the shell
|
|
||||||
runs.
|
|
||||||
- Writes are flushed immediately, important for crash diagnostics.
|
|
||||||
- The file is truncated once at startup.
|
|
||||||
|
|
||||||
Log output includes: startup sequence, preferences applied, video modes
|
|
||||||
enumerated, system information, app load/reap events, crash details with
|
|
||||||
full register dumps.
|
|
||||||
|
|
||||||
## Shell API Summary
|
|
||||||
|
|
||||||
| Function | Description |
|
|
||||||
|----------|-------------|
|
|
||||||
| `shellMain(argc, argv)` | Entry point called by the DVX loader |
|
|
||||||
| `shellAppInit()` | Zero the 32-slot app table |
|
|
||||||
| `shellLoadApp(ctx, path)` | Load and start a DXE app. Returns app ID (>= 1) or -1 |
|
|
||||||
| `shellReapApps(ctx)` | Clean up terminated apps (call each frame). Returns true if any reaped |
|
|
||||||
| `shellReapApp(ctx, app)` | Gracefully shut down one app (calls shutdown hook) |
|
|
||||||
| `shellForceKillApp(ctx, app)` | Forcibly kill an app -- skips shutdown hook |
|
|
||||||
| `shellTerminateAllApps(ctx)` | Kill all running apps (shell shutdown) |
|
|
||||||
| `shellGetApp(appId)` | Get app slot by ID (NULL if invalid/free) |
|
|
||||||
| `shellRunningAppCount()` | Count running apps (excluding the shell) |
|
|
||||||
| `shellLog(fmt, ...)` | Append to dvx.log |
|
|
||||||
| `shellEnsureConfigDir(ctx)` | Create an app's config directory tree |
|
|
||||||
| `shellConfigPath(ctx, name, buf, size)` | Build path to file in app's config dir |
|
|
||||||
| `shellExportInit()` | Register 3 DXE wrapper overrides via dlregsym() |
|
|
||||||
| `shellRegisterDesktopUpdate(fn)` | Register callback for app state changes |
|
|
||||||
| `shellUnregisterDesktopUpdate(fn)` | Remove a previously registered callback |
|
|
||||||
| `shellDesktopUpdate()` | Notify all registered listeners |
|
|
||||||
| `shellTaskMgrOpen(ctx)` | Open or raise the Task Manager window |
|
|
||||||
| `shellTaskMgrRefresh()` | Refresh the Task Manager list and status |
|
|
||||||
| `shellInfoInit(ctx)` | Gather and log system hardware information |
|
|
||||||
| `shellGetSystemInfo()` | Return cached system info text |
|
|
||||||
|
|
|
||||||
401
tasks/README.md
401
tasks/README.md
|
|
@ -1,341 +1,118 @@
|
||||||
# taskswitch -- Cooperative Task Switching Library
|
# taskswitch -- Cooperative Task Switching Library
|
||||||
|
|
||||||
Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS
|
Cooperative (non-preemptive) multitasking library for DJGPP/DPMI (DOS
|
||||||
protected mode). Part of the DVX GUI project.
|
protected mode). Built as `libtasks.lib` -- a DXE3 module loaded by
|
||||||
|
the DVX loader.
|
||||||
Tasks voluntarily yield the CPU by calling `tsYield()`. A credit-based
|
|
||||||
weighted round-robin scheduler gives higher-priority tasks proportionally
|
|
||||||
more CPU time while guaranteeing that low-priority tasks are never
|
|
||||||
starved. A priority-10 task gets 11 turns per scheduling round; a
|
|
||||||
priority-0 task gets 1 -- but it always runs eventually.
|
|
||||||
|
|
||||||
The task array is backed by stb_ds and grows dynamically. Terminated
|
|
||||||
task slots are recycled, so there is no fixed upper limit on the number
|
|
||||||
of tasks created over the lifetime of the application.
|
|
||||||
|
|
||||||
|
|
||||||
## Why Cooperative?
|
## Why Cooperative
|
||||||
|
|
||||||
DOS is single-threaded. DPMI provides no timer-based preemption. The
|
DOS is single-threaded with no kernel scheduler. DPMI provides no
|
||||||
DVX GUI event model is inherently single-threaded: one compositor, one
|
thread or timer-based preemption. The DVX GUI event model is
|
||||||
input queue, one window stack. Preemptive switching would require
|
inherently single-threaded (one compositor, one input queue, one
|
||||||
locking around every GUI call for no benefit. Cooperative switching
|
window stack). Cooperative switching lets each task yield at safe
|
||||||
lets each task yield at safe points, avoiding synchronization entirely.
|
points, avoiding the need for synchronization primitives entirely.
|
||||||
|
|
||||||
|
|
||||||
## Files
|
## Scheduling Algorithm
|
||||||
|
|
||||||
| File | Purpose |
|
Credit-based weighted fair-share, round-robin within a credit epoch.
|
||||||
|------|---------|
|
|
||||||
| `taskswitch.h` | Public API -- types, constants, function prototypes |
|
Each task receives `(priority + 1)` credits per scheduling round.
|
||||||
| `taskswitch.c` | Implementation (scheduler, context switch, slots, includes stb_ds implementation) |
|
Tasks run round-robin, consuming one credit per turn. When all
|
||||||
| `thirdparty/stb_ds.h` | stb dynamic array/hashmap library (third-party) |
|
credits are spent, every ready task is refilled.
|
||||||
| `Makefile` | DJGPP cross-compilation build rules |
|
|
||||||
|
| Priority | Constant | Credits/Round | Relative Share |
|
||||||
|
|----------|----------|---------------|----------------|
|
||||||
|
| 0 | `TS_PRIORITY_LOW` | 1 | ~4% |
|
||||||
|
| 5 | `TS_PRIORITY_NORMAL` | 6 | ~22% |
|
||||||
|
| 10 | `TS_PRIORITY_HIGH` | 11 | ~41% |
|
||||||
|
|
||||||
|
Higher-priority tasks run proportionally more often but never starve
|
||||||
|
lower ones. A priority-10 task gets 11 turns per round while a
|
||||||
|
priority-0 task gets 1.
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Task States
|
||||||
|
|
||||||
Cross-compile from Linux:
|
| State | Description |
|
||||||
|
|-------|-------------|
|
||||||
```
|
| `TaskStateReady` | Eligible for scheduling |
|
||||||
make # builds ../bin/libs/libtasks.lib
|
| `TaskStateRunning` | Currently executing (cosmetic marker) |
|
||||||
make clean # removes objects and library
|
| `TaskStatePaused` | Skipped until resumed |
|
||||||
```
|
| `TaskStateTerminated` | Slot available for reuse |
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| `../bin/libs/libtasks.lib` | DXE module |
|
|
||||||
| `../obj/tasks/` | Object files |
|
|
||||||
|
|
||||||
The library is compiled to an object file and packaged into a DXE via
|
|
||||||
`dxe3gen`. It exports all `ts*` symbols (`-E _ts`) and stb_ds internals
|
|
||||||
(`-E _stbds_`) so that other DXE modules loaded with `RTLD_GLOBAL` can
|
|
||||||
use both the task API and stb_ds data structures. All other symbols are
|
|
||||||
unresolved imports (`-U`) resolved at load time from the loader's
|
|
||||||
`dlregsym` table.
|
|
||||||
|
|
||||||
Requires the DJGPP cross-compiler toolchain and the DXE3 tools (`dxe3gen`).
|
|
||||||
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <stdio.h>
|
|
||||||
#include "taskswitch.h"
|
|
||||||
|
|
||||||
void myTask(void *arg) {
|
|
||||||
const char *name = (const char *)arg;
|
|
||||||
for (int i = 0; i < 3; i++) {
|
|
||||||
printf("[%s] working...\n", name);
|
|
||||||
tsYield();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
tsInit();
|
|
||||||
tsCreate("alpha", myTask, "alpha", 0, TS_PRIORITY_NORMAL);
|
|
||||||
tsCreate("beta", myTask, "beta", 0, TS_PRIORITY_HIGH);
|
|
||||||
|
|
||||||
while (tsActiveCount() > 1) {
|
|
||||||
tsYield();
|
|
||||||
}
|
|
||||||
|
|
||||||
tsShutdown();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Lifecycle
|
|
||||||
|
|
||||||
1. `tsInit()` -- Initialize the task system. The calling context
|
|
||||||
(typically `main`) becomes task 0 with `TS_PRIORITY_NORMAL`. No
|
|
||||||
separate stack is allocated for task 0 -- it uses the process stack.
|
|
||||||
|
|
||||||
2. `tsCreate(...)` -- Create tasks. Each gets a name, entry function,
|
|
||||||
argument pointer, stack size (0 for the default), and a priority.
|
|
||||||
Returns the task ID (>= 0) or a negative error code. Terminated
|
|
||||||
task slots are reused automatically.
|
|
||||||
|
|
||||||
3. `tsYield()` -- Call from any task (including main) to hand the CPU
|
|
||||||
to the next eligible task. This is the sole mechanism for task
|
|
||||||
switching.
|
|
||||||
|
|
||||||
4. `tsShutdown()` -- Free all task stacks and the task array.
|
|
||||||
|
|
||||||
Tasks terminate by returning from their entry function or by calling
|
|
||||||
`tsExit()`. The main task (id 0) must never call `tsExit()`. When a
|
|
||||||
task terminates, its stack is freed immediately and its slot becomes
|
|
||||||
available for reuse by the next `tsCreate()` call.
|
|
||||||
|
|
||||||
|
|
||||||
## API Reference
|
## API Reference
|
||||||
|
|
||||||
### Initialization and Teardown
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
| Function | Signature | Description |
|
| `tsInit()` | Initialize task system; calling context becomes task 0 (main) |
|
||||||
|----------|-----------|-------------|
|
| `tsShutdown()` | Shut down and free all resources |
|
||||||
| `tsInit` | `int32_t tsInit(void)` | Initialize the library. Returns `TS_OK` or a negative error code. |
|
| `tsCreate(name, entry, arg, stackSize, priority)` | Create a new task; returns task ID |
|
||||||
| `tsShutdown` | `void tsShutdown(void)` | Free all resources. Safe to call even if `tsInit` was never called. |
|
| `tsYield()` | Yield CPU to next eligible task (credit-based round-robin) |
|
||||||
|
| `tsPause(taskId)` | Pause a task (implicit yield if self) |
|
||||||
### Task Creation and Termination
|
| `tsResume(taskId)` | Resume a paused task (refills credits) |
|
||||||
|
| `tsSetPriority(taskId, priority)` | Set priority (refills credits immediately) |
|
||||||
| Function | Signature | Description |
|
| `tsGetPriority(taskId)` | Get task priority |
|
||||||
|----------|-----------|-------------|
|
| `tsGetState(taskId)` | Get task state |
|
||||||
| `tsCreate` | `int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t ss, int32_t pri)` | Create a ready task. Returns the task ID (>= 0) or a negative error code. Pass 0 for `ss` to use `TS_DEFAULT_STACK_SIZE`. Reuses terminated slots. |
|
| `tsCurrentId()` | Get currently running task's ID |
|
||||||
| `tsExit` | `void tsExit(void)` | Terminate the calling task. Must not be called from the main task. Never returns. |
|
| `tsGetName(taskId)` | Get task name |
|
||||||
| `tsKill` | `int32_t tsKill(uint32_t taskId)` | Forcibly terminate another task. Cannot kill main (id 0) or self (use `tsExit` instead). |
|
| `tsExit()` | Terminate calling task (never returns) |
|
||||||
|
| `tsKill(taskId)` | Forcibly terminate another task |
|
||||||
### Scheduling
|
| `tsRecoverToMain()` | Force scheduler back to task 0 after crash longjmp |
|
||||||
|
| `tsActiveCount()` | Count of non-terminated tasks |
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `tsYield` | `void tsYield(void)` | Voluntarily relinquish the CPU to the next eligible ready task. |
|
|
||||||
|
|
||||||
### Pausing and Resuming
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `tsPause` | `int32_t tsPause(uint32_t id)` | Pause a task. Main task (id 0) cannot be paused. Self-pause triggers an implicit yield. |
|
|
||||||
| `tsResume` | `int32_t tsResume(uint32_t id)` | Resume a paused task. Credits are refilled so it is not penalized for having been paused. |
|
|
||||||
|
|
||||||
### Priority
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `tsSetPriority` | `int32_t tsSetPriority(uint32_t id, int32_t pri)` | Change a task's priority. Credits are reset so the change takes effect immediately. |
|
|
||||||
| `tsGetPriority` | `int32_t tsGetPriority(uint32_t id)` | Return the task's priority, or `TS_ERR_PARAM` on an invalid ID. |
|
|
||||||
|
|
||||||
### Crash Recovery
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `tsRecoverToMain` | `void tsRecoverToMain(void)` | Reset scheduler state to task 0 after a `longjmp` from a signal handler. Call before `tsKill` on the crashed task. The crashed task's slot is NOT freed -- call `tsKill` afterward. |
|
|
||||||
|
|
||||||
### Query
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `tsGetState` | `TaskStateE tsGetState(uint32_t id)` | Return the task's state enum value. |
|
|
||||||
| `tsCurrentId` | `uint32_t tsCurrentId(void)` | Return the ID of the currently running task. |
|
|
||||||
| `tsGetName` | `const char *tsGetName(uint32_t id)` | Return the task's name string, or `NULL` on invalid ID. |
|
|
||||||
| `tsActiveCount` | `uint32_t tsActiveCount(void)` | Return the number of non-terminated tasks. |
|
|
||||||
|
|
||||||
|
|
||||||
## Constants
|
|
||||||
|
|
||||||
### Error Codes
|
### Error Codes
|
||||||
|
|
||||||
| Name | Value | Meaning |
|
| Constant | Value | Description |
|
||||||
|------|-------|---------|
|
|----------|-------|-------------|
|
||||||
| `TS_OK` | 0 | Success |
|
| `TS_OK` | 0 | Success |
|
||||||
| `TS_ERR_INIT` | -1 | Library not initialized |
|
| `TS_ERR_INIT` | -1 | Initialization failure |
|
||||||
| `TS_ERR_PARAM` | -2 | Invalid parameter |
|
| `TS_ERR_PARAM` | -2 | Invalid parameter |
|
||||||
| `TS_ERR_FULL` | -3 | Task table full (unused, kept for compatibility) |
|
| `TS_ERR_FULL` | -3 | No available task slots |
|
||||||
| `TS_ERR_NOMEM` | -4 | Memory allocation failed |
|
| `TS_ERR_NOMEM` | -4 | Stack allocation failed |
|
||||||
| `TS_ERR_STATE` | -5 | Invalid state transition |
|
| `TS_ERR_STATE` | -5 | Invalid state transition |
|
||||||
|
|
||||||
### Priority Presets
|
### Constants
|
||||||
|
|
||||||
| Name | Value | Credits per Round |
|
| Constant | Value | Description |
|
||||||
|------|-------|-------------------|
|
|----------|-------|-------------|
|
||||||
| `TS_PRIORITY_LOW` | 0 | 1 |
|
| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack size (32KB) |
|
||||||
| `TS_PRIORITY_NORMAL` | 5 | 6 |
|
| `TS_NAME_MAX` | 32 | Maximum task name length |
|
||||||
| `TS_PRIORITY_HIGH` | 10 | 11 |
|
|
||||||
|
|
||||||
Any non-negative `int32_t` may be used as a priority. The presets are
|
|
||||||
provided for convenience. In the DVX Shell, the main task runs at
|
|
||||||
`TS_PRIORITY_HIGH` to keep the UI responsive; app tasks default to
|
|
||||||
`TS_PRIORITY_NORMAL`.
|
|
||||||
|
|
||||||
### Defaults
|
|
||||||
|
|
||||||
| Name | Value | Description |
|
|
||||||
|------|-------|-------------|
|
|
||||||
| `TS_DEFAULT_STACK_SIZE` | 32768 | Default stack per task |
|
|
||||||
| `TS_NAME_MAX` | 32 | Max task name length |
|
|
||||||
|
|
||||||
|
|
||||||
## Types
|
## Task 0 (Main Task)
|
||||||
|
|
||||||
### TaskStateE
|
Task 0 is special:
|
||||||
|
* Cannot be killed or paused
|
||||||
|
* Uses the process stack directly (no separate allocation)
|
||||||
|
* `tsRecoverToMain()` always returns control here after a crash
|
||||||
|
* The DVX shell runs as task 0 with `TS_PRIORITY_HIGH`
|
||||||
|
|
||||||
```c
|
|
||||||
typedef enum {
|
## Crash Recovery
|
||||||
TaskStateReady = 0, // Eligible for scheduling
|
|
||||||
TaskStateRunning = 1, // Currently executing
|
`tsRecoverToMain()` is called after `longjmp` from a signal handler
|
||||||
TaskStatePaused = 2, // Suspended until tsResume()
|
that fired in a non-main task. It fixes the scheduler's `currentIdx`
|
||||||
TaskStateTerminated = 3 // Finished; slot will be recycled
|
to point back to task 0. The crashed task is NOT cleaned up by this
|
||||||
} TaskStateE;
|
call -- `tsKill()` must be called separately afterward.
|
||||||
|
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `taskswitch.h` | Public API header |
|
||||||
|
| `taskswitch.c` | Complete implementation (context switch, scheduler, stack management) |
|
||||||
|
| `Makefile` | Builds `bin/libs/libtasks.lib` |
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
make # builds bin/libs/libtasks.lib
|
||||||
|
make clean # removes objects and library
|
||||||
```
|
```
|
||||||
|
|
||||||
Only Ready tasks participate in scheduling. Running is cosmetic (marks
|
No dependencies. Exports all symbols matching `_ts*`.
|
||||||
the currently executing task). Paused tasks are skipped until explicitly
|
|
||||||
resumed. Terminated slots are recycled by `tsCreate`.
|
|
||||||
|
|
||||||
### TaskEntryT
|
|
||||||
|
|
||||||
```c
|
|
||||||
typedef void (*TaskEntryT)(void *arg);
|
|
||||||
```
|
|
||||||
|
|
||||||
The signature every task entry function must follow. The `arg` parameter
|
|
||||||
is the pointer passed to `tsCreate`.
|
|
||||||
|
|
||||||
|
|
||||||
## Scheduler Details
|
|
||||||
|
|
||||||
The scheduler is a credit-based weighted round-robin, a variant of the
|
|
||||||
Linux 2.4 goodness() scheduler.
|
|
||||||
|
|
||||||
1. Every ready task holds a credit counter initialized to `priority + 1`.
|
|
||||||
2. When `tsYield()` is called, the scheduler scans tasks starting one
|
|
||||||
past the current task (wrapping around) looking for a ready task with
|
|
||||||
credits > 0. When found, that task's credits are decremented and it
|
|
||||||
becomes the running task.
|
|
||||||
3. When no ready task has credits remaining, every ready task is
|
|
||||||
refilled to `priority + 1` (one "epoch") and the scan repeats.
|
|
||||||
|
|
||||||
This means a priority-10 task receives 11 turns for every 1 turn a
|
|
||||||
priority-0 task receives, but the low-priority task still runs -- it
|
|
||||||
is never starved.
|
|
||||||
|
|
||||||
Credits are also refilled when:
|
|
||||||
|
|
||||||
- A task is created (`tsCreate`) -- starts with `priority + 1`.
|
|
||||||
- A task is resumed (`tsResume`) -- refilled so it runs promptly.
|
|
||||||
- A task's priority changes (`tsSetPriority`) -- reset to `new + 1`.
|
|
||||||
|
|
||||||
|
|
||||||
## Task Slot Management
|
|
||||||
|
|
||||||
The task array is a stb_ds dynamic array that grows automatically.
|
|
||||||
Each slot has an `allocated` flag:
|
|
||||||
|
|
||||||
- `tsCreate()` scans for the first unallocated slot (starting at index
|
|
||||||
1, since slot 0 is always the main task). If no free slot exists, the
|
|
||||||
array is extended with `arrput()`.
|
|
||||||
- `tsExit()` and `tsKill()` free the terminated task's stack immediately
|
|
||||||
and mark the slot as unallocated, making it available for the next
|
|
||||||
`tsCreate()` call.
|
|
||||||
- Task IDs are stable array indices. Slots are never removed or
|
|
||||||
reordered, so a task ID remains valid for queries until the slot is
|
|
||||||
recycled.
|
|
||||||
|
|
||||||
This supports long-running applications (like the DVX Shell) that
|
|
||||||
create and destroy many tasks over their lifetime without unbounded
|
|
||||||
memory growth.
|
|
||||||
|
|
||||||
|
|
||||||
## Context Switch Internals
|
|
||||||
|
|
||||||
Context switching uses inline assembly with both i386 and x86_64 code
|
|
||||||
paths. The `contextSwitch` function is marked `noinline` to preserve
|
|
||||||
callee-saved register assumptions.
|
|
||||||
|
|
||||||
Why inline asm instead of setjmp/longjmp: setjmp/longjmp only save
|
|
||||||
callee-saved registers and do not give control over the stack pointer
|
|
||||||
in a portable way. New tasks need a fresh stack with the instruction
|
|
||||||
pointer set to a trampoline -- setjmp cannot bootstrap that. The asm
|
|
||||||
approach also avoids ABI differences in jmp_buf layout across DJGPP
|
|
||||||
versions.
|
|
||||||
|
|
||||||
### i386 (DJGPP target)
|
|
||||||
|
|
||||||
Six callee-saved values are saved and restored per switch:
|
|
||||||
|
|
||||||
| Register | Offset | Purpose |
|
|
||||||
|----------|--------|---------|
|
|
||||||
| EBX | 0 | Callee-saved general purpose |
|
|
||||||
| ESI | 4 | Callee-saved general purpose |
|
|
||||||
| EDI | 8 | Callee-saved general purpose |
|
|
||||||
| EBP | 12 | Frame pointer |
|
|
||||||
| ESP | 16 | Stack pointer |
|
|
||||||
| EIP | 20 | Resume address (captured as local label) |
|
|
||||||
|
|
||||||
### x86_64 (for native Linux testing)
|
|
||||||
|
|
||||||
Eight callee-saved values are saved and restored per switch:
|
|
||||||
|
|
||||||
| Register | Offset | Purpose |
|
|
||||||
|----------|--------|---------|
|
|
||||||
| RBX | 0 | Callee-saved general purpose |
|
|
||||||
| R12 | 8 | Callee-saved general purpose |
|
|
||||||
| R13 | 16 | Callee-saved general purpose |
|
|
||||||
| R14 | 24 | Callee-saved general purpose |
|
|
||||||
| R15 | 32 | Callee-saved general purpose |
|
|
||||||
| RBP | 40 | Frame pointer |
|
|
||||||
| RSP | 48 | Stack pointer |
|
|
||||||
| RIP | 56 | Resume address (RIP-relative lea) |
|
|
||||||
|
|
||||||
Segment registers are not saved because DJGPP runs in a flat
|
|
||||||
protected-mode environment where CS, DS, ES, and SS share the same
|
|
||||||
base.
|
|
||||||
|
|
||||||
New tasks have their initial stack pointer set to a 16-byte-aligned
|
|
||||||
region at the top of a malloc'd stack, with the instruction pointer
|
|
||||||
set to an internal trampoline that calls the user's entry function
|
|
||||||
and then `tsExit()`.
|
|
||||||
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- **Cooperative only** -- tasks must call `tsYield()` (or
|
|
||||||
`tsPause`/`tsExit`) to allow other tasks to run. A task that never
|
|
||||||
yields blocks everything.
|
|
||||||
- **Not interrupt-safe** -- no locking or volatile module state. Do not
|
|
||||||
call library functions from interrupt handlers.
|
|
||||||
- **Single-threaded** -- designed for one CPU under DOS protected mode.
|
|
||||||
- **Stack overflow is not detected** -- size the stack appropriately for
|
|
||||||
each task's needs.
|
|
||||||
|
|
||||||
|
|
||||||
## Third-Party Dependencies
|
|
||||||
|
|
||||||
- **stb_ds.h** (Sean Barrett) -- dynamic array and hashmap library.
|
|
||||||
Located in `thirdparty/stb_ds.h`. Used for the task control block
|
|
||||||
array. Included in the DXE build and exported via `-E _stbds_` so
|
|
||||||
other modules can use stb_ds without bundling their own copy.
|
|
||||||
Public domain / MIT licensed.
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@ the DVX widget system outside the shell framework.
|
||||||
Connects to a remote BBS through the SecLink proxy, providing a full
|
Connects to a remote BBS through the SecLink proxy, providing a full
|
||||||
ANSI terminal in a DVX-style window with encrypted transport.
|
ANSI terminal in a DVX-style window with encrypted transport.
|
||||||
|
|
||||||
|
NOTE: This program predates the DXE module refactoring and links
|
||||||
|
against static libraries from `lib/`. It may need Makefile updates
|
||||||
|
to build against the current source tree layout.
|
||||||
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
|
|
@ -77,19 +81,6 @@ The handshake completes in text mode before the GUI starts, so the
|
||||||
DOS console shows progress messages during connection setup.
|
DOS console shows progress messages during connection setup.
|
||||||
|
|
||||||
|
|
||||||
## Main Loop
|
|
||||||
|
|
||||||
Each iteration:
|
|
||||||
|
|
||||||
1. `secLinkPoll()` -- read serial data, decrypt, deliver to ring buffer.
|
|
||||||
2. `dvxUpdate()` -- process mouse, keyboard, paint, and window events.
|
|
||||||
During paint, the terminal widget calls `commRead` to drain the ring
|
|
||||||
buffer and render new data.
|
|
||||||
|
|
||||||
An idle callback also calls `secLinkPoll()` so incoming data is
|
|
||||||
processed even when the user is not interacting with the terminal.
|
|
||||||
|
|
||||||
|
|
||||||
## Data Flow
|
## Data Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -100,25 +91,10 @@ Keyboard -> widgetAnsiTermOnKey() -> commWrite()
|
||||||
-> secLinkSend() -> serial -> proxy -> BBS
|
-> secLinkSend() -> serial -> proxy -> BBS
|
||||||
```
|
```
|
||||||
|
|
||||||
A 4KB ring buffer (`RECV_BUF_SIZE`) bridges the SecLink receive
|
A 4KB ring buffer bridges the SecLink receive callback (which fires
|
||||||
callback (which fires asynchronously during `secLinkPoll()`) and the
|
asynchronously during `secLinkPoll()`) and the terminal widget's comm
|
||||||
terminal widget's comm read interface (which is polled synchronously
|
read interface (which is polled synchronously during the widget paint
|
||||||
during the widget paint cycle). This decoupling is necessary because
|
cycle).
|
||||||
the callback can fire at any time during polling, but the terminal
|
|
||||||
widget expects to read data synchronously.
|
|
||||||
|
|
||||||
|
|
||||||
## GUI
|
|
||||||
|
|
||||||
- **Window**: resizable, titled "SecLink Terminal"
|
|
||||||
- **Menu bar**: File -> Quit
|
|
||||||
- **Terminal**: 80x25 ANSI terminal widget with 1000-line scrollback
|
|
||||||
- **Status bar**: shows COM port, baud rate, and encryption status
|
|
||||||
|
|
||||||
The ANSI terminal widget supports standard escape sequences including
|
|
||||||
cursor control, SGR colors (16-color CGA palette), erase, scroll,
|
|
||||||
insert/delete lines, and DEC private modes (cursor visibility, line
|
|
||||||
wrap).
|
|
||||||
|
|
||||||
|
|
||||||
## Test Setup
|
## Test Setup
|
||||||
|
|
@ -138,27 +114,10 @@ wrap).
|
||||||
termdemo
|
termdemo
|
||||||
```
|
```
|
||||||
|
|
||||||
4. The handshake completes, the GUI appears, and BBS output is
|
|
||||||
displayed in the terminal window.
|
|
||||||
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
```
|
|
||||||
make # builds ../bin/termdemo.exe (and all dependency libs)
|
|
||||||
make clean # removes objects and binary
|
|
||||||
```
|
|
||||||
|
|
||||||
The Makefile automatically builds all dependency libraries before
|
|
||||||
linking. Objects are placed in `../obj/termdemo/`, the binary in
|
|
||||||
`../bin/`.
|
|
||||||
|
|
||||||
Target: DJGPP cross-compiler, 486+ CPU, VESA VBE 2.0+ video.
|
|
||||||
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
All libraries are built into `../lib/`:
|
All libraries are built into `lib/`:
|
||||||
|
|
||||||
| Library | Purpose |
|
| Library | Purpose |
|
||||||
|------------------|--------------------------------------|
|
|------------------|--------------------------------------|
|
||||||
|
|
@ -176,3 +135,11 @@ termdemo/
|
||||||
termdemo.c terminal emulator program
|
termdemo.c terminal emulator program
|
||||||
Makefile DJGPP cross-compilation build
|
Makefile DJGPP cross-compilation build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
make # builds bin/termdemo.exe (and dependency libs)
|
||||||
|
make clean # removes objects and binary
|
||||||
|
```
|
||||||
|
|
|
||||||
132
texthelp/README.md
Normal file
132
texthelp/README.md
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
# texthelp -- Shared Text Editing Helper Library
|
||||||
|
|
||||||
|
Shared infrastructure for text editing widgets, built as
|
||||||
|
`texthelp.lib` (DXE3 module). Provides clipboard operations, cursor
|
||||||
|
blink management, word boundary detection, cross-widget selection
|
||||||
|
clearing, and a complete single-line text editing engine.
|
||||||
|
|
||||||
|
Used by: TextInput, TextArea, Spinner, ComboBox, AnsiTerm.
|
||||||
|
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Cursor Blink
|
||||||
|
|
||||||
|
```c
|
||||||
|
void wgtUpdateCursorBlink(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
Toggles the global cursor blink state (`sCursorBlinkOn`) based on
|
||||||
|
`clock()` timing. Called from the event dispatch layer during paint.
|
||||||
|
Widgets that show a text cursor check `sCursorBlinkOn` to decide
|
||||||
|
whether to draw or hide the cursor.
|
||||||
|
|
||||||
|
### Selection Management
|
||||||
|
|
||||||
|
```c
|
||||||
|
void clearOtherSelections(WidgetT *except);
|
||||||
|
```
|
||||||
|
|
||||||
|
Clears text/item selections in all widgets except the specified one.
|
||||||
|
Called when a widget gains focus or starts a new selection, ensuring
|
||||||
|
only one widget has an active selection at a time.
|
||||||
|
|
||||||
|
### Word / Character Helpers
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool isWordChar(char c);
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns true if `c` is a word character (alphanumeric or underscore).
|
||||||
|
Used for double-click word selection and Ctrl+arrow word navigation.
|
||||||
|
|
||||||
|
```c
|
||||||
|
int32_t wordStart(const char *buf, int32_t pos);
|
||||||
|
```
|
||||||
|
|
||||||
|
Scans backward from `pos` to find the start of the current word.
|
||||||
|
|
||||||
|
```c
|
||||||
|
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||||
|
```
|
||||||
|
|
||||||
|
Scans forward from `pos` to find the end of the current word.
|
||||||
|
|
||||||
|
### Single-Line Text Editing Engine
|
||||||
|
|
||||||
|
Four functions implement the complete editing behavior shared across
|
||||||
|
TextInput, Spinner, ComboBox, and other single-line text widgets:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod,
|
||||||
|
char *buf, int32_t bufSize, int32_t *pLen,
|
||||||
|
int32_t *pCursor, int32_t *pScrollOff,
|
||||||
|
int32_t *pSelStart, int32_t *pSelEnd,
|
||||||
|
char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||||
|
```
|
||||||
|
|
||||||
|
Handles all keyboard input: character insertion, deletion (Backspace,
|
||||||
|
Delete), cursor movement (arrows, Home, End), word navigation
|
||||||
|
(Ctrl+arrows), selection (Shift+arrows, Ctrl+Shift+arrows, Ctrl+A),
|
||||||
|
clipboard (Ctrl+C/X/V), and undo (Ctrl+Z).
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy,
|
||||||
|
int32_t textLeftX, const BitmapFontT *font,
|
||||||
|
const char *buf, int32_t len, int32_t scrollOff,
|
||||||
|
int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd,
|
||||||
|
bool wordSelect, bool dragSelect);
|
||||||
|
```
|
||||||
|
|
||||||
|
Handles mouse click positioning: converts pixel coordinates to cursor
|
||||||
|
position, handles word selection (double-click), and drag-select.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge,
|
||||||
|
int32_t maxChars, const BitmapFontT *font, int32_t len,
|
||||||
|
int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
|
||||||
|
```
|
||||||
|
|
||||||
|
Updates cursor and selection during mouse drag operations.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops,
|
||||||
|
const BitmapFontT *font, const ColorSchemeT *colors,
|
||||||
|
int32_t textX, int32_t textY, const char *buf,
|
||||||
|
int32_t visLen, int32_t scrollOff, int32_t cursorPos,
|
||||||
|
int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg,
|
||||||
|
bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
|
||||||
|
```
|
||||||
|
|
||||||
|
Renders a single line of text with selection highlighting and cursor.
|
||||||
|
|
||||||
|
### Constants
|
||||||
|
|
||||||
|
| Name | Value | Description |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| `TEXT_INPUT_PAD` | 3 | Padding inside text input border |
|
||||||
|
|
||||||
|
|
||||||
|
## Exported Symbols
|
||||||
|
|
||||||
|
Matches prefixes: `_clipboard*`, `_clearOther*`, `_isWordChar*`,
|
||||||
|
`_multiClick*`, `_widgetText*`, `_wordStart*`, `_wordEnd*`.
|
||||||
|
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `textHelp.h` | Public API header |
|
||||||
|
| `textHelp.c` | Complete implementation |
|
||||||
|
| `Makefile` | Builds `bin/libs/texthelp.lib` + dep file |
|
||||||
|
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
make # builds bin/libs/texthelp.lib + texthelp.dep
|
||||||
|
make clean # removes objects, library, and dep file
|
||||||
|
```
|
||||||
|
|
||||||
|
Depends on: `libtasks.lib`, `libdvx.lib` (via texthelp.dep).
|
||||||
|
|
@ -1,95 +1,391 @@
|
||||||
# DVX Widget Modules
|
# DVX Widget Modules
|
||||||
|
|
||||||
Individual widget type implementations for the DVX GUI, each built
|
Individual widget type implementations, each built as a separate `.wgt`
|
||||||
as a separate `.wgt` DXE module. The loader scans the `WIDGETS/`
|
DXE module. The loader scans the `WIDGETS/` directory at startup and
|
||||||
directory recursively at startup and loads every `.wgt` file it
|
loads every `.wgt` file it finds. Each module exports `wgtRegister()`,
|
||||||
finds, calling `wgtRegister()` on each to fill the widget class
|
which registers its widget class(es) and API struct with the core.
|
||||||
table.
|
|
||||||
|
|
||||||
Drop a new `.wgt` file in the directory and it is automatically
|
Core knows nothing about individual widgets. All per-widget state lives
|
||||||
available -- no loader or core library changes needed.
|
in `w->data` (allocated by the widget DXE). All per-widget behavior is
|
||||||
|
dispatched through the WidgetClassT vtable. Each widget provides a
|
||||||
|
public header with its API struct, typed accessor, and convenience
|
||||||
|
macros.
|
||||||
|
|
||||||
|
|
||||||
## How Widget DXEs Work
|
## Widget Summary
|
||||||
|
|
||||||
Each `.wgt` module contains:
|
26 source files produce 26 `.wgt` modules containing 32 widget types:
|
||||||
|
|
||||||
1. The widget implementation (paint, layout, mouse/key handlers)
|
| Source File | .wgt File | Types | Public Header | Dependencies |
|
||||||
2. A `static const WidgetClassT` vtable definition
|
|-------------|-----------|-------|---------------|--------------|
|
||||||
3. A `wgtRegister()` function that writes the vtable pointer into
|
| `widgetBox.c` | `box.wgt` | VBox, HBox, Frame | `widgetBox.h` | -- |
|
||||||
`widgetClassTable[]` at the widget's enum slot
|
| `widgetButton.c` | `button.wgt` | Button | `widgetButton.h` | -- |
|
||||||
4. The public API functions (e.g. `wgtTreeView()`, `wgtTreeViewGetSelected()`)
|
| `widgetLabel.c` | `label.wgt` | Label | `widgetLabel.h` | -- |
|
||||||
|
| `widgetTextInput.c` | `textinpt.wgt` | TextInput, TextArea | `widgetTextInput.h` | texthelp |
|
||||||
At load time, the module resolves its dependencies against:
|
| `widgetCheckbox.c` | `checkbox.wgt` | Checkbox | `widgetCheckbox.h` | -- |
|
||||||
- The loader's `dlregsym` table (platform functions, libc)
|
| `widgetRadio.c` | `radio.wgt` | RadioGroup, Radio | `widgetRadio.h` | -- |
|
||||||
- `libtasks.lib` exports (stb_ds dynamic arrays)
|
| `widgetDropdown.c` | `dropdown.wgt` | Dropdown | `widgetDropdown.h` | listhelp |
|
||||||
- `libdvx.lib` exports (widget infrastructure: `widgetAlloc`,
|
| `widgetComboBox.c` | `combobox.wgt` | ComboBox | `widgetComboBox.h` | texthelp, listhelp |
|
||||||
`widgetClassTable`, `widgetScrollbarThumb`, shared state, etc.)
|
| `widgetListBox.c` | `listbox.wgt` | ListBox | `widgetListBox.h` | listhelp |
|
||||||
- Other widget modules loaded earlier (via `RTLD_GLOBAL`)
|
| `widgetListView.c` | `listview.wgt` | ListView | `widgetListView.h` | listhelp |
|
||||||
|
| `widgetTreeView.c` | `treeview.wgt` | TreeView, TreeItem | `widgetTreeView.h` | listhelp |
|
||||||
|
| `widgetSpinner.c` | `spinner.wgt` | Spinner | `widgetSpinner.h` | texthelp |
|
||||||
|
| `widgetSlider.c` | `slider.wgt` | Slider | `widgetSlider.h` | -- |
|
||||||
|
| `widgetProgressBar.c` | `progress.wgt` | ProgressBar | `widgetProgressBar.h` | -- |
|
||||||
|
| `widgetImage.c` | `image.wgt` | Image | `widgetImage.h` | -- |
|
||||||
|
| `widgetImageButton.c` | `imgbtn.wgt` | ImageButton | `widgetImageButton.h` | -- |
|
||||||
|
| `widgetCanvas.c` | `canvas.wgt` | Canvas | `widgetCanvas.h` | -- |
|
||||||
|
| `widgetSeparator.c` | `separatr.wgt` | Separator | `widgetSeparator.h` | -- |
|
||||||
|
| `widgetSpacer.c` | `spacer.wgt` | Spacer | `widgetSpacer.h` | -- |
|
||||||
|
| `widgetTabControl.c` | `tabctrl.wgt` | TabControl, TabPage | `widgetTabControl.h` | -- |
|
||||||
|
| `widgetScrollPane.c` | `scrlpane.wgt` | ScrollPane | `widgetScrollPane.h` | -- |
|
||||||
|
| `widgetSplitter.c` | `splitter.wgt` | Splitter | `widgetSplitter.h` | -- |
|
||||||
|
| `widgetToolbar.c` | `toolbar.wgt` | Toolbar | `widgetToolbar.h` | -- |
|
||||||
|
| `widgetStatusBar.c` | `statbar.wgt` | StatusBar | `widgetStatusBar.h` | -- |
|
||||||
|
| `widgetTimer.c` | `timer.wgt` | Timer | `widgetTimer.h` | -- |
|
||||||
|
| `widgetAnsiTerm.c` | `terminal.wgt` | AnsiTerm | `widgetAnsiTerm.h` | texthelp |
|
||||||
|
|
||||||
|
|
||||||
## Widget Catalog
|
## Dependency Files (.dep)
|
||||||
|
|
||||||
| Module | Widget Types | Description |
|
Widgets that use shared helper libraries need a `.dep` file so the
|
||||||
|--------|-------------|-------------|
|
loader loads the helper first. Dep files are in `config/` and copied
|
||||||
| `box.wgt` | VBox, HBox, Frame | Containers: vertical/horizontal box layout, titled frame |
|
to `bin/widgets/` during build:
|
||||||
| `button.wgt` | Button | Push button with text and keyboard accelerator |
|
|
||||||
| `canvas.wgt` | Canvas | Freeform drawing surface with mouse callbacks |
|
|
||||||
| `checkbox.wgt` | Checkbox | Toggle checkbox with label |
|
|
||||||
| `combobox.wgt` | ComboBox | Editable text input with dropdown list |
|
|
||||||
| `dropdown.wgt` | Dropdown | Non-editable dropdown selector |
|
|
||||||
| `image.wgt` | Image | Static image display (BMP/PNG/JPEG/GIF) |
|
|
||||||
| `imgbtn.wgt` | ImageButton | Button with icon image |
|
|
||||||
| `label.wgt` | Label | Static text display with optional accelerator |
|
|
||||||
| `listbox.wgt` | ListBox | Scrollable string list with single/multi select |
|
|
||||||
| `listview.wgt` | ListView | Multi-column list with headers, sorting, column resize |
|
|
||||||
| `progress.wgt` | ProgressBar | Horizontal or vertical progress indicator |
|
|
||||||
| `radio.wgt` | RadioGroup, Radio | Mutually exclusive option buttons |
|
|
||||||
| `scrlpane.wgt` | ScrollPane | Scrollable viewport for oversized content |
|
|
||||||
| `separatr.wgt` | Separator | Horizontal or vertical divider line |
|
|
||||||
| `slider.wgt` | Slider | Horizontal value slider with thumb |
|
|
||||||
| `spacer.wgt` | Spacer | Invisible layout spacer |
|
|
||||||
| `spinner.wgt` | Spinner | Numeric input with up/down buttons |
|
|
||||||
| `splitter.wgt` | Splitter | Resizable two-pane divider |
|
|
||||||
| `statbar.wgt` | StatusBar | Horizontal bar at window bottom |
|
|
||||||
| `tabctrl.wgt` | TabControl, TabPage | Tabbed page container |
|
|
||||||
| `terminal.wgt` | AnsiTerm | VT100 terminal emulator with scrollback |
|
|
||||||
| `textinpt.wgt` | TextInput, TextArea | Single-line and multi-line text editing |
|
|
||||||
| `timer.wgt` | Timer | Periodic callback (no visual) |
|
|
||||||
| `toolbar.wgt` | Toolbar | Horizontal button/widget bar |
|
|
||||||
| `treeview.wgt` | TreeView, TreeItem | Hierarchical tree with expand/collapse |
|
|
||||||
|
|
||||||
26 modules implementing 32 widget types (some modules register
|
| Widget | Dep File | Dependencies |
|
||||||
multiple related types).
|
|--------|----------|--------------|
|
||||||
|
| textinpt | `textinpt.dep` | texthelp |
|
||||||
|
| combobox | `combobox.dep` | texthelp, listhelp |
|
||||||
|
| spinner | `spinner.dep` | texthelp |
|
||||||
|
| terminal | `terminal.dep` | texthelp |
|
||||||
|
| dropdown | `dropdown.dep` | listhelp |
|
||||||
|
| listbox | `listbox.dep` | listhelp |
|
||||||
|
| listview | `listview.dep` | listhelp |
|
||||||
|
| treeview | `treeview.dep` | listhelp |
|
||||||
|
|
||||||
|
|
||||||
## Writing a New Widget
|
## Per-Widget API Details
|
||||||
|
|
||||||
1. Create `widgets/myWidget.c`
|
### Box (VBox, HBox, Frame)
|
||||||
2. Include `"dvxWidgetPlugin.h"` for infrastructure access
|
|
||||||
3. Implement paint, calcMinSize, and event handlers as needed
|
Container widgets that arrange children vertically or horizontally.
|
||||||
4. Define a `static const WidgetClassT` with your vtable
|
Frame adds a titled border around its children.
|
||||||
5. Add a `wgtRegister()` function:
|
|
||||||
```c
|
| API Function | Macro | Description |
|
||||||
void wgtRegister(void) {
|
|--------------|-------|-------------|
|
||||||
widgetClassTable[MyWidgetTypeE] = &sClassMyWidget;
|
| `vBox(parent)` | `wgtVBox(parent)` | Create vertical container |
|
||||||
}
|
| `hBox(parent)` | `wgtHBox(parent)` | Create horizontal container |
|
||||||
```
|
| `frame(parent, title)` | `wgtFrame(parent, title)` | Create titled frame container |
|
||||||
6. Add the new enum value to `WidgetTypeE` in `core/dvxWidget.h`
|
|
||||||
(or use `wgtRegisterClass()` for a fully dynamic type ID)
|
### Button
|
||||||
7. Add public API functions (e.g. `wgtMyWidget()`) that call
|
|
||||||
`widgetAlloc(parent, MyWidgetTypeE)`
|
Push button with text label and accelerator key support.
|
||||||
8. Add a build rule in `widgets/Makefile`
|
|
||||||
9. Build -- the loader discovers and loads it automatically
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, text)` | `wgtButton(parent, text)` | Create a button |
|
||||||
|
|
||||||
|
### Label
|
||||||
|
|
||||||
|
Static text label with optional alignment.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, text)` | `wgtLabel(parent, text)` | Create a label |
|
||||||
|
| `setAlign(w, align)` | `wgtLabelSetAlign(w, align)` | Set text alignment |
|
||||||
|
|
||||||
|
### TextInput / TextArea
|
||||||
|
|
||||||
|
Single-line text input, password input, masked input, and multiline
|
||||||
|
text area. Uses texthelp.lib for editing engine, cursor blink, word
|
||||||
|
boundaries, clipboard, and selection.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, maxLen)` | `wgtTextInput(parent, maxLen)` | Single-line text input |
|
||||||
|
| `password(parent, maxLen)` | `wgtPasswordInput(parent, maxLen)` | Password input (masked display) |
|
||||||
|
| `masked(parent, mask)` | `wgtMaskedInput(parent, mask)` | Masked input (e.g. phone number) |
|
||||||
|
| `textArea(parent, maxLen)` | `wgtTextArea(parent, maxLen)` | Multiline text editor |
|
||||||
|
|
||||||
|
### Checkbox
|
||||||
|
|
||||||
|
Toggle checkbox with text label.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, text)` | `wgtCheckbox(parent, text)` | Create a checkbox |
|
||||||
|
| `isChecked(w)` | `wgtCheckboxIsChecked(w)` | Get checked state |
|
||||||
|
| `setChecked(w, checked)` | `wgtCheckboxSetChecked(w, checked)` | Set checked state |
|
||||||
|
|
||||||
|
### Radio (RadioGroup, Radio)
|
||||||
|
|
||||||
|
Mutually exclusive radio buttons within a group container.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `group(parent)` | `wgtRadioGroup(parent)` | Create radio group container |
|
||||||
|
| `create(parent, text)` | `wgtRadio(parent, text)` | Create radio button in group |
|
||||||
|
| `groupSetSelected(w, idx)` | `wgtRadioGroupSetSelected(w, idx)` | Select radio by index |
|
||||||
|
| `getIndex(w)` | `wgtRadioGetIndex(w)` | Get selected index |
|
||||||
|
|
||||||
|
### Dropdown
|
||||||
|
|
||||||
|
Drop-down selection list with popup overlay.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtDropdown(parent)` | Create a dropdown |
|
||||||
|
| `setItems(w, items, count)` | `wgtDropdownSetItems(w, items, count)` | Set item list |
|
||||||
|
| `getSelected(w)` | `wgtDropdownGetSelected(w)` | Get selected index |
|
||||||
|
| `setSelected(w, idx)` | `wgtDropdownSetSelected(w, idx)` | Set selected index |
|
||||||
|
|
||||||
|
### ComboBox
|
||||||
|
|
||||||
|
Editable text input with dropdown suggestion list. Combines TextInput
|
||||||
|
and Dropdown behavior.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, maxLen)` | `wgtComboBox(parent, maxLen)` | Create a combo box |
|
||||||
|
| `setItems(w, items, count)` | `wgtComboBoxSetItems(w, items, count)` | Set suggestion list |
|
||||||
|
| `getSelected(w)` | `wgtComboBoxGetSelected(w)` | Get selected index (-1 if typed) |
|
||||||
|
| `setSelected(w, idx)` | `wgtComboBoxSetSelected(w, idx)` | Select item by index |
|
||||||
|
|
||||||
|
### ListBox
|
||||||
|
|
||||||
|
Scrollable list of text items with single or multi-select and optional
|
||||||
|
drag-reorder.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtListBox(parent)` | Create a list box |
|
||||||
|
| `setItems(w, items, count)` | `wgtListBoxSetItems(w, items, count)` | Set item list |
|
||||||
|
| `getSelected(w)` | `wgtListBoxGetSelected(w)` | Get selected index |
|
||||||
|
| `setSelected(w, idx)` | `wgtListBoxSetSelected(w, idx)` | Set selected index |
|
||||||
|
| `setMultiSelect(w, multi)` | `wgtListBoxSetMultiSelect(w, multi)` | Enable multi-select |
|
||||||
|
| `isItemSelected(w, idx)` | `wgtListBoxIsItemSelected(w, idx)` | Check if item is selected |
|
||||||
|
| `setItemSelected(w, idx, sel)` | `wgtListBoxSetItemSelected(w, idx, sel)` | Select/deselect item |
|
||||||
|
| `selectAll(w)` | `wgtListBoxSelectAll(w)` | Select all items |
|
||||||
|
| `clearSelection(w)` | `wgtListBoxClearSelection(w)` | Deselect all items |
|
||||||
|
| `setReorderable(w, r)` | `wgtListBoxSetReorderable(w, r)` | Enable drag reorder |
|
||||||
|
|
||||||
|
### ListView
|
||||||
|
|
||||||
|
Multi-column list with sortable headers, resizable columns, single or
|
||||||
|
multi-select, and optional drag-reorder.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtListView(parent)` | Create a list view |
|
||||||
|
| `setColumns(w, cols, count)` | `wgtListViewSetColumns(w, cols, count)` | Define columns |
|
||||||
|
| `setData(w, cellData, rowCount)` | `wgtListViewSetData(w, cellData, rowCount)` | Set row data |
|
||||||
|
| `getSelected(w)` | `wgtListViewGetSelected(w)` | Get selected row |
|
||||||
|
| `setSelected(w, idx)` | `wgtListViewSetSelected(w, idx)` | Set selected row |
|
||||||
|
| `setSort(w, col, dir)` | `wgtListViewSetSort(w, col, dir)` | Set sort column/direction |
|
||||||
|
| `setHeaderClickCallback(w, cb)` | `wgtListViewSetHeaderClickCallback(w, cb)` | Set header click handler |
|
||||||
|
| `setMultiSelect(w, multi)` | `wgtListViewSetMultiSelect(w, multi)` | Enable multi-select |
|
||||||
|
| `isItemSelected(w, idx)` | `wgtListViewIsItemSelected(w, idx)` | Check if row is selected |
|
||||||
|
| `setItemSelected(w, idx, sel)` | `wgtListViewSetItemSelected(w, idx, sel)` | Select/deselect row |
|
||||||
|
| `selectAll(w)` | `wgtListViewSelectAll(w)` | Select all rows |
|
||||||
|
| `clearSelection(w)` | `wgtListViewClearSelection(w)` | Deselect all rows |
|
||||||
|
| `setReorderable(w, r)` | `wgtListViewSetReorderable(w, r)` | Enable drag reorder |
|
||||||
|
|
||||||
|
Column definition uses `ListViewColT` with title, tagged width, and alignment
|
||||||
|
(`ListViewAlignLeftE`, `ListViewAlignCenterE`, `ListViewAlignRightE`).
|
||||||
|
Sort direction: `ListViewSortNoneE`, `ListViewSortAscE`, `ListViewSortDescE`.
|
||||||
|
|
||||||
|
### TreeView (TreeView, TreeItem)
|
||||||
|
|
||||||
|
Hierarchical tree with expand/collapse, single or multi-select, and
|
||||||
|
optional drag-reorder.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtTreeView(parent)` | Create a tree view |
|
||||||
|
| `getSelected(w)` | `wgtTreeViewGetSelected(w)` | Get selected item widget |
|
||||||
|
| `setSelected(w, item)` | `wgtTreeViewSetSelected(w, item)` | Set selected item |
|
||||||
|
| `setMultiSelect(w, multi)` | `wgtTreeViewSetMultiSelect(w, multi)` | Enable multi-select |
|
||||||
|
| `setReorderable(w, r)` | `wgtTreeViewSetReorderable(w, r)` | Enable drag reorder |
|
||||||
|
| `item(parent, text)` | `wgtTreeItem(parent, text)` | Create tree item |
|
||||||
|
| `itemSetExpanded(w, exp)` | `wgtTreeItemSetExpanded(w, exp)` | Expand/collapse item |
|
||||||
|
| `itemIsExpanded(w)` | `wgtTreeItemIsExpanded(w)` | Check if expanded |
|
||||||
|
| `itemIsSelected(w)` | `wgtTreeItemIsSelected(w)` | Check if selected |
|
||||||
|
| `itemSetSelected(w, sel)` | `wgtTreeItemSetSelected(w, sel)` | Select/deselect item |
|
||||||
|
|
||||||
|
### Spinner
|
||||||
|
|
||||||
|
Numeric input with up/down buttons.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, min, max, step)` | `wgtSpinner(parent, min, max, step)` | Create a spinner |
|
||||||
|
| `setValue(w, value)` | `wgtSpinnerSetValue(w, value)` | Set current value |
|
||||||
|
| `getValue(w)` | `wgtSpinnerGetValue(w)` | Get current value |
|
||||||
|
| `setRange(w, min, max)` | `wgtSpinnerSetRange(w, min, max)` | Set value range |
|
||||||
|
| `setStep(w, step)` | `wgtSpinnerSetStep(w, step)` | Set increment step |
|
||||||
|
|
||||||
|
### Slider
|
||||||
|
|
||||||
|
Horizontal track with draggable thumb for value selection.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, min, max)` | `wgtSlider(parent, min, max)` | Create a slider |
|
||||||
|
| `setValue(w, value)` | `wgtSliderSetValue(w, value)` | Set current value |
|
||||||
|
| `getValue(w)` | `wgtSliderGetValue(w)` | Get current value |
|
||||||
|
|
||||||
|
### ProgressBar
|
||||||
|
|
||||||
|
Horizontal or vertical progress indicator (0-100).
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtProgressBar(parent)` | Create horizontal progress bar |
|
||||||
|
| `createV(parent)` | `wgtProgressBarV(parent)` | Create vertical progress bar |
|
||||||
|
| `setValue(w, value)` | `wgtProgressBarSetValue(w, value)` | Set value (0-100) |
|
||||||
|
| `getValue(w)` | `wgtProgressBarGetValue(w)` | Get current value |
|
||||||
|
|
||||||
|
### Image
|
||||||
|
|
||||||
|
Displays a pixel buffer or image file in native display format.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, data, w, h, pitch)` | `wgtImage(parent, data, w, h, pitch)` | Create from pixel data |
|
||||||
|
| `fromFile(parent, path)` | `wgtImageFromFile(parent, path)` | Create from image file |
|
||||||
|
| `setData(w, data, imgW, imgH, pitch)` | `wgtImageSetData(w, data, imgW, imgH, pitch)` | Replace image data |
|
||||||
|
|
||||||
|
### ImageButton
|
||||||
|
|
||||||
|
Clickable button displaying an image instead of text.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, data, w, h, pitch)` | `wgtImageButton(parent, data, w, h, pitch)` | Create from pixel data |
|
||||||
|
| `fromFile(parent, path)` | `wgtImageButtonFromFile(parent, path)` | Create from image file |
|
||||||
|
| `setData(w, data, imgW, imgH, pitch)` | `wgtImageButtonSetData(w, data, imgW, imgH, pitch)` | Replace image data |
|
||||||
|
|
||||||
|
### Canvas
|
||||||
|
|
||||||
|
Drawable surface with pen tools, shapes, pixel access, and file I/O.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, w, h)` | `wgtCanvas(parent, w, h)` | Create a canvas |
|
||||||
|
| `clear(w, color)` | `wgtCanvasClear(w, color)` | Fill with solid color |
|
||||||
|
| `setPenColor(w, color)` | `wgtCanvasSetPenColor(w, color)` | Set drawing color |
|
||||||
|
| `setPenSize(w, size)` | `wgtCanvasSetPenSize(w, size)` | Set pen width |
|
||||||
|
| `setMouseCallback(w, cb)` | `wgtCanvasSetMouseCallback(w, cb)` | Set mouse event handler |
|
||||||
|
| `save(w, path)` | `wgtCanvasSave(w, path)` | Save canvas to file |
|
||||||
|
| `load(w, path)` | `wgtCanvasLoad(w, path)` | Load image into canvas |
|
||||||
|
| `drawLine(w, x0, y0, x1, y1)` | `wgtCanvasDrawLine(w, ...)` | Draw line |
|
||||||
|
| `drawRect(w, x, y, w, h)` | `wgtCanvasDrawRect(w, ...)` | Draw rectangle outline |
|
||||||
|
| `fillRect(w, x, y, w, h)` | `wgtCanvasFillRect(w, ...)` | Fill rectangle |
|
||||||
|
| `fillCircle(w, cx, cy, r)` | `wgtCanvasFillCircle(w, ...)` | Fill circle |
|
||||||
|
| `setPixel(w, x, y, color)` | `wgtCanvasSetPixel(w, x, y, color)` | Set individual pixel |
|
||||||
|
| `getPixel(w, x, y)` | `wgtCanvasGetPixel(w, x, y)` | Read pixel color |
|
||||||
|
|
||||||
|
### Separator (HSeparator, VSeparator)
|
||||||
|
|
||||||
|
Thin decorative line (horizontal or vertical).
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `hSeparator(parent)` | `wgtHSeparator(parent)` | Create horizontal separator |
|
||||||
|
| `vSeparator(parent)` | `wgtVSeparator(parent)` | Create vertical separator |
|
||||||
|
|
||||||
|
### Spacer
|
||||||
|
|
||||||
|
Invisible flexible space for layout padding.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtSpacer(parent)` | Create a spacer |
|
||||||
|
|
||||||
|
### TabControl (TabControl, TabPage)
|
||||||
|
|
||||||
|
Tabbed container with clickable tab headers.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtTabControl(parent)` | Create tab control |
|
||||||
|
| `page(parent, title)` | `wgtTabPage(parent, title)` | Add a tab page |
|
||||||
|
| `setActive(w, idx)` | `wgtTabControlSetActive(w, idx)` | Switch to tab by index |
|
||||||
|
| `getActive(w)` | `wgtTabControlGetActive(w)` | Get active tab index |
|
||||||
|
|
||||||
|
### ScrollPane
|
||||||
|
|
||||||
|
Scrollable container with internal scrollbars. Child content can
|
||||||
|
exceed the visible area; the ScrollPane handles clipping and scroll.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtScrollPane(parent)` | Create a scroll pane |
|
||||||
|
|
||||||
|
### Splitter
|
||||||
|
|
||||||
|
Resizable split between two child regions with a draggable divider.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, vertical)` | `wgtSplitter(parent, vertical)` | Create splitter |
|
||||||
|
| `setPos(w, pos)` | `wgtSplitterSetPos(w, pos)` | Set divider position (pixels) |
|
||||||
|
| `getPos(w)` | `wgtSplitterGetPos(w)` | Get divider position |
|
||||||
|
|
||||||
|
### Toolbar
|
||||||
|
|
||||||
|
Horizontal container for toolbar buttons (typically ImageButtons).
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtToolbar(parent)` | Create a toolbar |
|
||||||
|
|
||||||
|
### StatusBar
|
||||||
|
|
||||||
|
Single-line text display at the bottom of a window.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent)` | `wgtStatusBar(parent)` | Create a status bar |
|
||||||
|
|
||||||
|
Set text via `wgtSetText(statusBar, "text")`.
|
||||||
|
|
||||||
|
### Timer
|
||||||
|
|
||||||
|
Non-visual widget that fires callbacks at a specified interval.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, intervalMs, repeat)` | `wgtTimer(parent, intervalMs, repeat)` | Create a timer |
|
||||||
|
| `start(w)` | `wgtTimerStart(w)` | Start the timer |
|
||||||
|
| `stop(w)` | `wgtTimerStop(w)` | Stop the timer |
|
||||||
|
| `setInterval(w, intervalMs)` | `wgtTimerSetInterval(w, intervalMs)` | Change interval |
|
||||||
|
| `isRunning(w)` | `wgtTimerIsRunning(w)` | Check if running |
|
||||||
|
| `updateTimers()` | `wgtUpdateTimers()` | Process all pending timers |
|
||||||
|
|
||||||
|
### AnsiTerm
|
||||||
|
|
||||||
|
ANSI terminal emulator widget with scrollback buffer. Supports standard
|
||||||
|
escape sequences (cursor control, SGR colors, erase, scroll, DEC
|
||||||
|
private modes). Optionally connects to a comm channel for serial I/O.
|
||||||
|
|
||||||
|
| API Function | Macro | Description |
|
||||||
|
|--------------|-------|-------------|
|
||||||
|
| `create(parent, cols, rows)` | `wgtAnsiTerm(parent, cols, rows)` | Create terminal |
|
||||||
|
| `write(w, data, len)` | `wgtAnsiTermWrite(w, data, len)` | Write data to terminal |
|
||||||
|
| `clear(w)` | `wgtAnsiTermClear(w)` | Clear terminal screen |
|
||||||
|
| `setComm(w, ctx, readFn, writeFn)` | `wgtAnsiTermSetComm(w, ctx, readFn, writeFn)` | Set comm channel |
|
||||||
|
| `setScrollback(w, maxLines)` | `wgtAnsiTermSetScrollback(w, maxLines)` | Set scrollback depth |
|
||||||
|
| `poll(w)` | `wgtAnsiTermPoll(w)` | Poll comm channel |
|
||||||
|
| `repaint(w, outY, outH)` | `wgtAnsiTermRepaint(w, outY, outH)` | Fast incremental repaint |
|
||||||
|
|
||||||
|
|
||||||
## Building
|
## Build
|
||||||
|
|
||||||
```
|
```
|
||||||
make # compiles all widget .c files, creates .wgt modules in ../bin/widgets/
|
make # builds all 26 .wgt modules + dep files
|
||||||
make clean
|
make clean # removes objects, .wgt files, and dep files
|
||||||
```
|
```
|
||||||
|
|
||||||
Each `.wgt` is created with `dxe3gen -E _wgt -U`, which exports all
|
Each widget is compiled to a single `.o` file, then packaged via
|
||||||
symbols starting with `wgt` (public API + `wgtRegister`) and allows
|
`dxe3gen` into a `.wgt` DXE module exporting only `wgtRegister`.
|
||||||
unresolved symbols (resolved at load time from other modules).
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue