DVX_GUI/apps
2026-03-20 21:15:37 -05:00
..
clock Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! 2026-03-20 19:10:53 -05:00
cpanel Updated docs. 2026-03-20 20:00:05 -05:00
dvxdemo Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! 2026-03-20 19:10:53 -05:00
imgview Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! 2026-03-20 19:10:53 -05:00
notepad Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! 2026-03-20 19:10:53 -05:00
progman Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! 2026-03-20 19:10:53 -05:00
Makefile Unicode removed from source. Lots of bugs fixed. Close to Alpha 2! 2026-03-20 19:10:53 -05:00
README.md More docs. 2026-03-20 21:15:37 -05:00

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 _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:

// 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

#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.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.