15 KiB
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:
// 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:
$(BINDIR)/myapp/myapp.app: $(OBJDIR)/myapp.o | $(BINDIR)/myapp
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
-E _appDescriptorand-E _appMainexport the required symbols (COFF underscore prefix).-E _appShutdownis added for apps that export a shutdown hook (e.g., Clock).-Umarks 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.appfiles (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).
- 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.inivia 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 callstsYield(). 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
rectFillanddrawText-- 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_LOWsince 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 (
ColorCountEentries fromdvxColorName). - 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
.thmfiles fromCONFIG/THEMES/, with Apply, Load..., Save As..., and Reset buttons. Themes are loaded/saved viadvxLoadTheme()/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:
// 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,onChangeon 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
#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
#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:
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.hforAppDescriptorT,DxeAppContextT, andSHELL_STACK_DEFAULT. - Use
ctx->shellCtx(theAppContextT *) for all DVX API calls. - Callback-only apps must destroy their own windows in
onCloseviadvxDestroyWindow(). 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
staticvariables for app state. Each DXE has its own data segment, so there is no collision between apps even with identical variable names. - Set
multiInstance = trueonly if the app can safely run multiple copies simultaneously. Each instance gets independent globals and statics. - Avoid
static inlinefunctions 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->appDirfor 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 -- useCONFIG/<apppath>/instead.