# 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//`) | 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// 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// 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//` instead.