426 lines
15 KiB
Markdown
426 lines
15 KiB
Markdown
# DVX Shell Applications
|
|
|
|
DXE3 application modules for the DVX Shell. Each app is a `.app` file (DXE3
|
|
shared object format) placed in a subdirectory under `apps/`. The Program
|
|
Manager scans this directory recursively at startup and displays all discovered
|
|
apps in a launcher grid.
|
|
|
|
## App Contract
|
|
|
|
Every DXE app must export two symbols and may optionally export a third:
|
|
|
|
```c
|
|
// 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
|
|
int32_t appMain(DxeAppContextT *ctx);
|
|
|
|
// Optional: graceful shutdown hook -- called before force-kill
|
|
void appShutdown(void);
|
|
```
|
|
|
|
`appMain` receives a `DxeAppContextT` with:
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `shellCtx` | `AppContextT *` | The shell's GUI context -- pass to all `dvx*`/`wgt*` calls |
|
|
| `appId` | `int32_t` | This app's unique ID (1-based slot index; 0 = shell) |
|
|
| `appDir` | `char[260]` | Directory containing the `.app` file (for relative resource paths) |
|
|
| `configDir` | `char[260]` | Writable config directory (`CONFIG/<apppath>/`) |
|
|
|
|
Return 0 from `appMain` on success, non-zero on failure (shell will unload).
|
|
|
|
### AppDescriptorT Fields
|
|
|
|
| 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
|
|
|
|
```
|
|
make # builds all .app files into ../bin/apps/<name>/
|
|
make clean # removes objects and binaries
|
|
```
|
|
|
|
Each app is compiled to an object file with the DJGPP cross-compiler, then
|
|
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
|
|
underscore prefix).
|
|
- `-E _appShutdown` is added for apps that export a shutdown hook (e.g.,
|
|
Clock).
|
|
- `-U` marks all other symbols as unresolved imports to be resolved from the
|
|
shell's export table at dlopen time.
|
|
|
|
Requires `lib/libdvx.a`, `lib/libtasks.a`, and the DXE3 tools from the DJGPP
|
|
toolchain.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
apps/
|
|
Makefile -- build rules for all apps
|
|
README.md -- this file
|
|
progman/
|
|
progman.c -- Program Manager (desktop shell)
|
|
notepad/
|
|
notepad.c -- text editor
|
|
clock/
|
|
clock.c -- digital clock
|
|
cpanel/
|
|
cpanel.c -- Control Panel (system settings)
|
|
imgview/
|
|
imgview.c -- image viewer
|
|
dvxdemo/
|
|
dvxdemo.c -- widget showcase
|
|
*.bmp -- toolbar icons and sample images
|
|
```
|
|
|
|
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 shell's DXE
|
|
table.
|
|
- 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.
|