Unicode removed from source. Lots of bugs fixed. Close to Alpha 2!

This commit is contained in:
Scott Duensing 2026-03-20 19:10:53 -05:00
parent 70459616cc
commit 5a1332d024
103 changed files with 1682 additions and 1096 deletions

View file

@ -1,4 +1,4 @@
# DVX GUI Top-level Makefile
# DVX GUI -- Top-level Makefile
#
# Builds the full DVX stack: library, task switcher, shell, and apps.

View file

@ -1,4 +1,4 @@
# DVX DOS Visual eXecutive
# DVX -- DOS Visual eXecutive
A Windows 3.x-style desktop shell for DOS, built with DJGPP/DPMI. Combines a
windowed GUI compositor, cooperative task switcher, and DXE3 dynamic loading to
@ -103,20 +103,20 @@ mcopy -o -i <floppy.img> bin/dvx.exe ::DVX.EXE
mcopy -s -o -i <floppy.img> bin/apps ::APPS
```
The `apps/` directory structure must be preserved on the target Program Manager
The `apps/` directory structure must be preserved on the target -- Program Manager
recursively scans `apps/` for `.app` files at startup.
## Documentation
Each component directory has its own README with detailed API reference:
- [`dvx/README.md`](dvx/README.md) GUI library architecture and API
- [`tasks/README.md`](tasks/README.md) Task switcher API
- [`dvxshell/README.md`](dvxshell/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
- [`dvx/README.md`](dvx/README.md) -- GUI library architecture and API
- [`tasks/README.md`](tasks/README.md) -- Task switcher API
- [`dvxshell/README.md`](dvxshell/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

View file

@ -1,4 +1,4 @@
# DVX Shell Applications Makefile builds DXE3 modules
# DVX Shell Applications Makefile -- builds DXE3 modules
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib

View file

@ -1,4 +1,4 @@
// clock.c Clock DXE application (main-loop with tsYield)
// clock.c -- Clock DXE application (main-loop with tsYield)
//
// This is a main-loop DXE app (hasMainLoop = true), in contrast to callback-
// only apps like notepad or progman. The difference is fundamental:
@ -8,14 +8,14 @@
// to cooperatively yield to the shell and other tasks.
//
// A main-loop app is needed when the app has ongoing work that can't be
// expressed purely as event callbacks here, polling the system clock
// expressed purely as event callbacks -- here, polling the system clock
// every second and repainting. The shell allocates a dedicated task stack
// and schedules this task alongside the main event loop.
//
// The onPaint/onClose callbacks still execute in task 0 during dvxUpdate(),
// not in this task. The main loop only handles the clock-tick polling; the
// actual rendering and window management happen through the normal callback
// path. tsYield() is what makes this cooperative without it, this task
// path. tsYield() is what makes this cooperative -- without it, this task
// would starve the shell and all other apps.
#include "dvxApp.h"
@ -87,7 +87,7 @@ static void onClose(WindowT *win) {
// onPaint demonstrates the raw paint callback approach (no widget tree).
// Instead of using wgtInitWindow + widgets, this app renders directly into
// the window's content buffer. This is the lower-level alternative to the
// widget system useful for custom rendering like this centered clock display.
// widget system -- useful for custom rendering like this centered clock display.
//
// We create a temporary DisplayT struct pointing at the window's content buffer
// so the drawing primitives (rectFill, drawText) can operate on the window
@ -197,13 +197,13 @@ int32_t appMain(DxeAppContextT *ctx) {
sWin->onClose = onClose;
sWin->onPaint = onPaint;
// Initial paint dvxInvalidateWindow calls onPaint automatically
// Initial paint -- dvxInvalidateWindow calls onPaint automatically
dvxInvalidateWindow(ac, sWin);
// Main loop: check if the second has changed, repaint if so, then yield.
// tsYield() transfers control back to the shell's task scheduler.
// On a 486, time() resolution is 1 second, so we yield many times per
// second between actual updates this keeps CPU usage near zero.
// second between actual updates -- this keeps CPU usage near zero.
// dvxInvalidateWindow marks the window dirty and calls onPaint to
// update the content buffer before the compositor flushes it.
while (!sState.quit) {

View file

@ -1,10 +1,10 @@
// ctrlpanel.c DVX Control Panel
// ctrlpanel.c -- DVX Control Panel
//
// System configuration app with four tabs:
// Mouse scroll direction, double-click speed, acceleration
// Colors all 18 system colors, theme load/save
// Desktop wallpaper image (stretch mode)
// Video resolution and color depth
// Mouse -- scroll direction, double-click speed, acceleration
// Colors -- all 18 system colors, theme load/save
// Desktop -- wallpaper image (stretch mode)
// Video -- resolution and color depth
//
// Changes preview live. OK saves to DVX.INI, Cancel reverts to the
// state captured when the control panel was opened.
@ -71,8 +71,8 @@ static int32_t sSavedDblClick;
static int32_t sSavedAccel;
static int32_t sSavedVideoW;
static int32_t sSavedVideoH;
static int32_t sSavedVideoBpp;
static bool sSavedHadWallpaper;
static int32_t sSavedVideoBpp;
static WallpaperModeE sSavedWpMode;
// Mouse tab widgets
static WidgetT *sWheelDrop = NULL;
@ -94,6 +94,7 @@ static WidgetT *sColorSwatch = NULL;
// Desktop tab widgets
static WidgetT *sWallpaperLbl = NULL;
static WidgetT *sWpaperList = NULL;
static WidgetT *sWpModeDrop = NULL;
static char sWallpaperPath[260];
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
static const char **sWpaperLabels = NULL; // stb_ds dynamic array
@ -125,6 +126,7 @@ static void onApplyTheme(WidgetT *w);
static void onBrowseTheme(WidgetT *w);
static void onResetColors(WidgetT *w);
static void onApplyWallpaper(WidgetT *w);
static void onWallpaperMode(WidgetT *w);
static void onCancel(WidgetT *w);
static void onChooseWallpaper(WidgetT *w);
static void onClearWallpaper(WidgetT *w);
@ -167,7 +169,7 @@ static void buildColorsTab(WidgetT *page) {
static const char *colorNames[ColorCountE];
for (int32_t i = 0; i < ColorCountE; i++) {
colorNames[i] = dvxColorName((ColorIdE)i);
colorNames[i] = dvxColorLabel((ColorIdE)i);
}
sColorList = wgtListBox(leftVbox);
@ -203,28 +205,37 @@ static void buildColorsTab(WidgetT *page) {
resetBtn->onClick = onResetColors;
resetBtn->prefW = wgtPixels(60);
// Right side: RGB sliders
// Right side: RGB sliders in a non-weighted inner box so they stay
// at natural size. The outer box absorbs extra vertical space.
WidgetT *rightVbox = wgtVBox(hbox);
rightVbox->weight = 50;
rightVbox->spacing = wgtPixels(4);
wgtLabel(rightVbox, "Red:");
sRedSldr = wgtSlider(rightVbox, 0, 255);
WidgetT *sliderBox = wgtVBox(rightVbox);
sliderBox->spacing = wgtPixels(4);
wgtLabel(sliderBox, "Red:");
sRedSldr = wgtSlider(sliderBox, 0, 255);
sRedSldr->onChange = onColorSlider;
sRedLbl = wgtLabel(rightVbox, "0");
sRedLbl = wgtLabel(sliderBox, "0");
wgtLabelSetAlign(sRedLbl, AlignEndE);
wgtLabel(rightVbox, "Green:");
sGreenSldr = wgtSlider(rightVbox, 0, 255);
wgtLabel(sliderBox, "Green:");
sGreenSldr = wgtSlider(sliderBox, 0, 255);
sGreenSldr->onChange = onColorSlider;
sGreenLbl = wgtLabel(rightVbox, "0");
sGreenLbl = wgtLabel(sliderBox, "0");
wgtLabelSetAlign(sGreenLbl, AlignEndE);
wgtLabel(rightVbox, "Blue:");
sBlueSldr = wgtSlider(rightVbox, 0, 255);
wgtLabel(sliderBox, "Blue:");
sBlueSldr = wgtSlider(sliderBox, 0, 255);
sBlueSldr->onChange = onColorSlider;
sBlueLbl = wgtLabel(rightVbox, "0");
sBlueLbl = wgtLabel(sliderBox, "0");
wgtLabelSetAlign(sBlueLbl, AlignEndE);
wgtLabel(rightVbox, "Preview:");
sColorSwatch = wgtCanvas(rightVbox, 64, 24);
wgtLabel(sliderBox, "Preview:");
sColorSwatch = wgtCanvas(sliderBox, 64, 24);
// Absorb remaining vertical space below the sliders
wgtSpacer(rightVbox)->weight = 100;
updateColorSliders();
}
@ -259,6 +270,15 @@ static void buildDesktopTab(WidgetT *page) {
WidgetT *clearBtn = wgtButton(btnRow, "Clear");
clearBtn->onClick = onClearWallpaper;
clearBtn->prefW = wgtPixels(90);
wgtSpacer(btnRow)->weight = 100;
wgtLabel(btnRow, "Mode:");
static const char *modeItems[] = {"Stretch", "Tile", "Center"};
sWpModeDrop = wgtDropdown(btnRow);
sWpModeDrop->onChange = onWallpaperMode;
wgtDropdownSetItems(sWpModeDrop, modeItems, 3);
wgtDropdownSetSelected(sWpModeDrop, (int32_t)sAc->wallpaperMode);
}
@ -420,7 +440,7 @@ static const char *mapAccelValue(int32_t idx) {
// ============================================================
// Callbacks Mouse tab
// Callbacks -- Mouse tab
// ============================================================
static void onWheelChange(WidgetT *w) {
@ -472,7 +492,7 @@ static void onAccelChange(WidgetT *w) {
// ============================================================
// Callbacks Colors tab
// Callbacks -- Colors tab
// ============================================================
static void onColorSelect(WidgetT *w) {
@ -565,7 +585,7 @@ static void onResetColors(WidgetT *w) {
// ============================================================
// Callbacks Desktop tab
// Callbacks -- Desktop tab
// ============================================================
static void onApplyWallpaper(WidgetT *w) {
@ -615,8 +635,17 @@ static void onClearWallpaper(WidgetT *w) {
}
static void onWallpaperMode(WidgetT *w) {
int32_t sel = wgtDropdownGetSelected(w);
if (sel >= 0 && sel <= 2) {
dvxSetWallpaperMode(sAc, (WallpaperModeE)sel);
}
}
// ============================================================
// Callbacks — Video tab
// Callbacks -- Video tab
// ============================================================
static int32_t sVideoConfirmResult = -1;
@ -743,7 +772,7 @@ static void onVideoApply(WidgetT *w) {
// ============================================================
// Callbacks OK / Cancel / Close
// Callbacks -- OK / Cancel / Close
// ============================================================
static void onOk(WidgetT *w) {
@ -774,6 +803,16 @@ static void onOk(WidgetT *w) {
prefsRemove("desktop", "wallpaper");
}
const char *modeStr = "stretch";
if (sAc->wallpaperMode == WallpaperTileE) {
modeStr = "tile";
} else if (sAc->wallpaperMode == WallpaperCenterE) {
modeStr = "center";
}
prefsSetString("desktop", "mode", modeStr);
// Save video settings
prefsSetInt("video", "width", sAc->display.width);
prefsSetInt("video", "height", sAc->display.height);
@ -804,6 +843,8 @@ static void onClose(WindowT *win) {
// saveSnapshot / restoreSnapshot
// ============================================================
static char sSavedWallpaperPath[260];
static void saveSnapshot(void) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
sSavedWheelDir = sAc->wheelDirection;
@ -812,16 +853,11 @@ static void saveSnapshot(void) {
sSavedVideoW = sAc->display.width;
sSavedVideoH = sAc->display.height;
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
sSavedHadWallpaper = (sAc->wallpaperBuf != NULL);
const char *wp = prefsGetString("desktop", "wallpaper", NULL);
if (wp) {
strncpy(sWallpaperPath, wp, sizeof(sWallpaperPath) - 1);
sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
} else {
sWallpaperPath[0] = '\0';
}
// Save the active wallpaper path and mode from the context
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
sSavedWpMode = sAc->wallpaperMode;
}
@ -842,9 +878,11 @@ static void restoreSnapshot(void) {
dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp);
}
// Restore wallpaper
if (sSavedHadWallpaper && sWallpaperPath[0]) {
dvxSetWallpaper(sAc, sWallpaperPath);
// Restore wallpaper mode and image
sAc->wallpaperMode = sSavedWpMode;
if (sSavedWallpaperPath[0]) {
dvxSetWallpaper(sAc, sSavedWallpaperPath);
} else {
dvxSetWallpaper(sAc, NULL);
}

View file

@ -1,4 +1,4 @@
// dvxdemo.c DVX GUI demonstration app (DXE version)
// dvxdemo.c -- DVX GUI demonstration app (DXE version)
//
// Callback-only DXE app (hasMainLoop = false) that opens several windows
// showcasing the DVX widget system, paint callbacks, menus, accelerators, etc.
@ -12,7 +12,7 @@
// - setupTerminalWindow: ANSI terminal emulator widget
//
// Each window is independent; closing one doesn't affect the others.
// The app has no persistent state it's purely a showcase.
// The app has no persistent state -- it's purely a showcase.
#include "dvxApp.h"
#include "dvxDialog.h"
@ -234,7 +234,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
// Walk up the widget tree to find the root, then use wgtFind() to locate
// the named status label. This pattern avoids static coupling between the
// button and the status label they're connected only by the name string.
// button and the status label -- they're connected only by the name string.
static void onOkClick(WidgetT *w) {
WidgetT *root = w;
@ -314,7 +314,7 @@ static void onPaintPattern(WindowT *win, RectT *dirtyArea) {
// Renders text manually using the bitmap font glyph data, bypassing the
// normal drawText helper. This demonstrates direct glyph rendering and
// also serves as a regression test for the font subsystem if the glyph
// also serves as a regression test for the font subsystem -- if the glyph
// data format ever changes, this paint callback would visibly break.
static void onPaintText(WindowT *win, RectT *dirtyArea) {
(void)dirtyArea;
@ -424,7 +424,7 @@ static void onToolbarClick(WidgetT *w) {
// ============================================================
// setupControlsWindow advanced widgets with tabs
// setupControlsWindow -- advanced widgets with tabs
// ============================================================
// Item arrays are static because dropdown/combobox widgets store pointers,
@ -790,7 +790,7 @@ static void setupControlsWindow(void) {
// ============================================================
// setupMainWindow info window + paint demos
// setupMainWindow -- info window + paint demos
// ============================================================
// Creates three windows that demonstrate raw onPaint rendering (no widgets):
@ -931,7 +931,7 @@ static void setupMainWindow(void) {
// ============================================================
// setupTerminalWindow ANSI terminal widget demo
// setupTerminalWindow -- ANSI terminal widget demo
// ============================================================
// Creates an ANSI terminal widget with sample output demonstrating all
@ -1014,13 +1014,13 @@ static void setupTerminalWindow(void) {
// ============================================================
// setupWidgetDemo form with accelerators
// setupWidgetDemo -- form with accelerators
// ============================================================
// Demonstrates the standard form pattern: labeled inputs in frames, checkbox
// and radio groups, list boxes (single and multi-select with context menus),
// and a button row. The '&' in label text marks the Alt+key mnemonic that
// moves focus to the associated widget this is the accelerator mechanism.
// moves focus to the associated widget -- this is the accelerator mechanism.
static void setupWidgetDemo(void) {
WindowT *win = dvxCreateWindow(sAc, "Widget Demo", 80, 200, 280, 360, true);

View file

@ -1,4 +1,4 @@
// imgview.c DVX Image Viewer
// imgview.c -- DVX Image Viewer
//
// Displays BMP, PNG, JPG, and GIF images. The image is scaled to fit
// the window while preserving aspect ratio. Resize the window to zoom.
@ -61,19 +61,23 @@ static int32_t sImgW = 0;
static int32_t sImgH = 0;
// Scaled image in native pixel format (for direct blit)
static uint8_t *sScaled = NULL;
static int32_t sScaledW = 0;
static int32_t sScaledH = 0;
static uint8_t *sScaled = NULL;
static int32_t sScaledW = 0;
static int32_t sScaledH = 0;
static int32_t sScaledPitch = 0;
static int32_t sLastFitW = 0; // window size the image was last scaled for
static int32_t sLastFitH = 0;
// ============================================================
// buildScaled scale source image to fit window
// buildScaled -- scale source image to fit window
// ============================================================
static void buildScaled(int32_t fitW, int32_t fitH) {
free(sScaled);
sScaled = NULL;
sScaled = NULL;
sLastFitW = fitW;
sLastFitH = fitH;
if (!sImgRgb || fitW < 1 || fitH < 1) {
return;
@ -113,7 +117,6 @@ static void buildScaled(int32_t fitW, int32_t fitH) {
int32_t srcStride = sImgW * 3;
for (int32_t y = 0; y < dstH; y++) {
// Yield every 32 rows so the UI stays responsive
if ((y & 31) == 0 && y > 0) {
dvxUpdate(sAc);
}
@ -236,6 +239,15 @@ static void onMenu(WindowT *win, int32_t menuId) {
static void onPaint(WindowT *win, RectT *dirty) {
(void)dirty;
// If the resize drag has ended and the window size changed since
// the last scale, rebuild now. During drag, just show the old
// scaled image (or dark background) to avoid expensive per-frame scaling.
if (sImgRgb && sAc->stack.resizeWindow < 0) {
if (sLastFitW != win->contentW || sLastFitH != win->contentH) {
buildScaled(win->contentW, win->contentH);
}
}
DisplayT cd = sAc->display;
cd.backBuf = win->contentBuf;
cd.width = win->contentW;
@ -250,22 +262,42 @@ static void onPaint(WindowT *win, RectT *dirty) {
uint32_t bg = packColor(&sAc->display, 32, 32, 32);
rectFill(&cd, &sAc->blitOps, 0, 0, win->contentW, win->contentH, bg);
// Blit scaled image centered
// Blit scaled image centered, clipped to content bounds
if (sScaled) {
int32_t offX = (win->contentW - sScaledW) / 2;
int32_t offY = (win->contentH - sScaledH) / 2;
int32_t bpp = sAc->display.format.bytesPerPixel;
for (int32_t y = 0; y < sScaledH; y++) {
int32_t dstY = offY + y;
// Compute visible region (clip source and dest)
int32_t srcX = 0;
int32_t srcY = 0;
int32_t blitW = sScaledW;
int32_t blitH = sScaledH;
if (dstY < 0 || dstY >= win->contentH) {
continue;
}
if (offX < 0) {
srcX = -offX;
blitW += offX;
offX = 0;
}
uint8_t *src = sScaled + y * sScaledPitch;
uint8_t *dst = win->contentBuf + dstY * win->contentPitch + offX * bpp;
memcpy(dst, src, sScaledW * bpp);
if (offY < 0) {
srcY = -offY;
blitH += offY;
offY = 0;
}
if (offX + blitW > win->contentW) {
blitW = win->contentW - offX;
}
if (offY + blitH > win->contentH) {
blitH = win->contentH - offY;
}
for (int32_t y = 0; y < blitH; y++) {
uint8_t *src = sScaled + (srcY + y) * sScaledPitch + srcX * bpp;
uint8_t *dst = win->contentBuf + (offY + y) * win->contentPitch + offX * bpp;
memcpy(dst, src, blitW * bpp);
}
}
}
@ -277,7 +309,10 @@ static void onPaint(WindowT *win, RectT *dirty) {
static void onResize(WindowT *win, int32_t contentW, int32_t contentH) {
(void)win;
buildScaled(contentW, contentH);
(void)contentW;
(void)contentH;
// Don't rescale here -- onPaint handles it after the drag ends.
// This avoids expensive bilinear scaling on every frame of a drag.
}

View file

@ -1,4 +1,4 @@
// notepad.c Simple text editor DXE application (callback-only)
// notepad.c -- Simple text editor DXE application (callback-only)
//
// A callback-only DXE app (hasMainLoop = false) that provides basic text
// editing with file I/O. Demonstrates the standard DXE app pattern:
@ -87,7 +87,7 @@ AppDescriptorT appDescriptor = {
// Dirty tracking
// ============================================================
// djb2-xor hash for dirty detection. Not cryptographic just a fast way
// djb2-xor hash for dirty detection. Not cryptographic -- just a fast way
// to detect changes without storing a full copy of the last-saved text.
// False negatives are theoretically possible but vanishingly unlikely for
// text edits. This avoids the memory cost of keeping a shadow buffer.
@ -181,7 +181,7 @@ static void doOpen(void) {
}
// Read entire file into a temporary buffer. Files larger than the
// TextArea's buffer are silently truncated this matches the behavior
// TextArea's buffer are silently truncated -- this matches the behavior
// of Windows Notepad on large files.
fseek(f, 0, SEEK_END);
long size = ftell(f);
@ -368,7 +368,7 @@ int32_t appMain(DxeAppContextT *ctx) {
// The widget tree is minimal: just a TextArea filling the entire content
// area (weight=100). The TextArea widget provides editing, scrolling,
// selection, copy/paste, and undo all driven by keyboard events that
// selection, copy/paste, and undo -- all driven by keyboard events that
// the shell dispatches to the focused widget.
WidgetT *root = wgtInitWindow(ac, sWin);

View file

@ -1,4 +1,4 @@
// progman.c Program Manager application for DVX Shell
// progman.c -- Program Manager application for DVX Shell
//
// Displays a grid of available apps from the apps/ directory.
// Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc).
@ -20,8 +20,10 @@
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "platform/dvxPlatform.h"
#include "shellApp.h"
#include "shellTaskMgr.h"
#include "shellInfo.h"
@ -144,7 +146,7 @@ static void buildPmWindow(void) {
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
MenuT *optMenu = wmAddMenu(menuBar, "&Options");
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, false);
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun);
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
@ -293,6 +295,9 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
case CMD_MIN_ON_RUN:
sMinOnRun = !sMinOnRun;
shellEnsureConfigDir(sCtx);
prefsSetBool("options", "minimizeOnRun", sMinOnRun);
prefsSave();
break;
case CMD_ABOUT:
@ -346,7 +351,7 @@ static void scanAppsDirRecurse(const char *dirPath) {
char fullPath[MAX_PATH_LEN];
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name);
// Check if this is a directory recurse into it
// Check if this is a directory -- recurse into it
struct stat st;
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
@ -431,7 +436,7 @@ static void showSystemInfo(void) {
ta->weight = 100;
wgtSetText(ta, info);
// Don't disable wgtSetEnabled(false) blocks all input including scrollbar
// Don't disable -- wgtSetEnabled(false) blocks all input including scrollbar
wgtSetReadOnly(ta, true);
}
@ -467,6 +472,12 @@ int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
sAc = ctx->shellCtx;
// Load saved preferences
char prefsPath[260];
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
prefsLoad(prefsPath);
sMinOnRun = prefsGetBool("options", "minimizeOnRun", false);
scanAppsDir();
buildPmWindow();

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,14 @@
// dvx_app.h Layer 5: Application API for DVX GUI
// dvx_app.h -- Layer 5: Application API for DVX GUI
//
// The topmost layer and the public-facing API for applications. Aggregates
// all lower layers into a single AppContextT and provides a clean interface
// for window creation, event dispatch, and utilities. Applications interact
// exclusively through dvx*() functions and window callbacks they never
// exclusively through dvx*() functions and window callbacks -- they never
// need to call the lower WM, compositor, or draw layers directly.
//
// The event loop (dvxRun/dvxUpdate) follows a cooperative model: poll mouse
// and keyboard, dispatch events to the focused window, run the compositor
// for dirty regions, then yield. There's no preemptive scheduling the
// for dirty regions, then yield. There's no preemptive scheduling -- the
// application must return from callbacks promptly.
#ifndef DVX_APP_H
#define DVX_APP_H
@ -26,7 +26,7 @@
// ============================================================
//
// Single monolithic context that owns all GUI state. Allocated on the
// caller's stack (or statically) no internal heap allocation for the
// caller's stack (or statically) -- no internal heap allocation for the
// context itself. This "god struct" approach keeps the API simple (one
// pointer to pass everywhere) and avoids the overhead of a handle-based
// system with opaque lookups. The tradeoff is a large struct, but on DOS
@ -82,7 +82,7 @@ typedef struct AppContextT {
void *ctrlEscCtx;
void (*onTitleChange)(void *ctx); // called when any window title changes
void *titleChangeCtx;
// Tooltip state tooltip appears after the mouse hovers over a widget
// Tooltip state -- tooltip appears after the mouse hovers over a widget
// with a tooltip string for a brief delay. Pre-computing W/H avoids
// re-measuring on every paint frame.
clock_t tooltipHoverStart; // when mouse stopped moving
@ -104,12 +104,13 @@ typedef struct AppContextT {
// Available video modes (enumerated once at init)
VideoModeInfoT *videoModes; // stb_ds dynamic array
int32_t videoModeCount;
// Wallpaper — pre-scaled to screen dimensions in native pixel format.
// Wallpaper -- pre-rendered to screen dimensions in native pixel format.
// NULL means no wallpaper (solid desktop color). wallpaperPath is
// kept so the image can be reloaded after a video mode change.
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
int32_t wallpaperPitch; // bytes per row
char wallpaperPath[260]; // source image path (empty = none)
// kept so the image can be reloaded after a video mode or mode change.
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
int32_t wallpaperPitch; // bytes per row
char wallpaperPath[260]; // source image path (empty = none)
WallpaperModeE wallpaperMode; // stretch, tile, or center
} AppContextT;
// Initialize the entire GUI stack: video mode, input devices, font,
@ -154,14 +155,21 @@ bool dvxSaveTheme(const AppContextT *ctx, const char *filename);
// Return the INI key name for a color ID (e.g. "desktop", "windowFace").
const char *dvxColorName(ColorIdE id);
// Return a human-readable display label (e.g. "Desktop", "Cursor Color").
const char *dvxColorLabel(ColorIdE id);
// ============================================================
// Wallpaper
// ============================================================
// Load and apply a wallpaper image (stretched to screen). Pass NULL
// to clear the wallpaper and revert to the solid desktop color.
// Load and apply a wallpaper image. Uses the current wallpaperMode
// (stretch/tile/center). Pass NULL to clear the wallpaper.
bool dvxSetWallpaper(AppContextT *ctx, const char *path);
// Change the wallpaper display mode and re-render the buffer.
// Has no effect if no wallpaper is loaded.
void dvxSetWallpaperMode(AppContextT *ctx, WallpaperModeE mode);
// Return the list of available video modes (enumerated at init).
// count receives the number of entries.
const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx, int32_t *count);
@ -255,7 +263,7 @@ void dvxFreeAccelTable(AccelTableT *table);
// for menu item IDs so the same handler works for both menus and hotkeys.
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId);
// Window arrangement functions operate on all visible, non-minimized
// Window arrangement functions -- operate on all visible, non-minimized
// windows. These mirror the classic Windows 3.x "Window" menu commands.
// Cascade: offset each window diagonally by the title bar height.
@ -285,7 +293,7 @@ void dvxFreeImage(uint8_t *data);
int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data, int32_t w, int32_t h, int32_t pitch, const char *path);
// Copy text to the process-wide clipboard buffer. The clipboard is a
// simple static buffer (not inter-process) adequate for copy/paste
// simple static buffer (not inter-process) -- adequate for copy/paste
// within the DVX environment on single-tasking DOS.
void dvxClipboardCopy(const char *text, int32_t len);

View file

@ -1,9 +1,9 @@
// dvx_comp.c Layer 3: Dirty rectangle compositor for DVX GUI (optimized)
// dvx_comp.c -- Layer 3: Dirty rectangle compositor for DVX GUI (optimized)
//
// This layer implements dirty rectangle tracking and merging. The compositor
// avoids full-screen redraws, which would be prohibitively expensive on the
// target 486/Pentium hardware over ISA bus VESA LFB. A full 640x480x16bpp
// framebuffer is ~600KB at ISA's ~8MB/s theoretical peak, a blind full
// framebuffer is ~600KB -- at ISA's ~8MB/s theoretical peak, a blind full
// flush costs ~75ms (>1 frame at 60Hz). By tracking which rectangles have
// actually changed and flushing only those regions from the system RAM
// backbuffer to the LFB, the bandwidth consumed per frame scales with the
@ -11,7 +11,7 @@
//
// The compositing loop lives in dvxApp.c (compositeAndFlush). For each dirty
// rect, it repaints the desktop, then walks the window stack bottom-to-top
// painting chrome, content, scrollbars, popup menus, and the cursor all
// painting chrome, content, scrollbars, popup menus, and the cursor -- all
// clipped to the dirty rect. Only then is the dirty rect flushed to the LFB.
// This means each pixel in a dirty region is written to system RAM potentially
// multiple times (painter's algorithm), but the expensive LFB write happens
@ -26,7 +26,7 @@
// overlap. A small gap tolerance absorbs jitter from mouse movement and
// closely-spaced UI invalidations (e.g. title bar + content during a drag)
// without bloating merged rects excessively. The value 4 was chosen to match
// the chrome border width adjacent chrome/content invalidations merge
// the chrome border width -- adjacent chrome/content invalidations merge
// naturally.
#define DIRTY_MERGE_GAP 4
@ -43,7 +43,7 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
// ============================================================
//
// Appends a dirty rect to the list. Uses a fixed-size array (MAX_DIRTY_RECTS
// = 128) rather than a dynamic allocation this is called on every UI
// = 128) rather than a dynamic allocation -- this is called on every UI
// mutation (drag, repaint, focus change) so allocation overhead must be zero.
//
// When the list fills up, an eager merge pass tries to consolidate rects.
@ -54,7 +54,7 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
// GUI mutations tend to cluster spatially.
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
// Branch hint: degenerate rects are rare callers usually validate first
// Branch hint: degenerate rects are rare -- callers usually validate first
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
@ -64,7 +64,7 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
dirtyListMerge(dl);
if (dl->count >= MAX_DIRTY_RECTS) {
// Still full collapse the entire list plus the new rect into one
// Still full -- collapse the entire list plus the new rect into one
// bounding box. This is a last resort; it means the next flush will
// repaint a potentially large region, but at least we won't lose
// dirty information or crash.
@ -123,7 +123,7 @@ void dirtyListInit(DirtyListT *dl) {
// Algorithm: O(N^2) pairwise sweep with bounded restarts. For each rect i,
// scan all rects j>i and merge any that overlap or are within DIRTY_MERGE_GAP
// pixels. When a merge happens, rect i grows and may now overlap rects that
// it previously missed, so the inner scan restarts but restarts are capped
// it previously missed, so the inner scan restarts -- but restarts are capped
// at 3 per slot to prevent O(N^3) cascading in pathological layouts (e.g.
// a diagonal scatter of tiny rects). The cap of 3 was chosen empirically:
// typical GUI operations produce clustered invalidations that converge in
@ -172,7 +172,7 @@ void dirtyListMerge(DirtyListT *dl) {
// on 486+ to move aligned 32-bit words, maximizing bus utilization.
//
// Crucially, we flush per dirty rect AFTER all painting for that rect is
// complete. This avoids visible tearing the LFB is never in a half-painted
// complete. This avoids visible tearing -- the LFB is never in a half-painted
// state for any given region.
void flushRect(DisplayT *d, const RectT *r) {
@ -187,7 +187,7 @@ void flushRect(DisplayT *d, const RectT *r) {
// Used heavily in the compositing loop to test whether a window overlaps
// a dirty rect before painting it. The branch hint marks the non-overlapping
// case as unlikely because the compositing loop already does a coarse AABB
// check before calling this when we get here, intersection is expected.
// check before calling this -- when we get here, intersection is expected.
// The min/max formulation avoids branches in the hot path.
bool rectIntersect(const RectT *a, const RectT *b, RectT *result) {
@ -246,7 +246,7 @@ static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_
//
// Axis-aligned bounding box of two rects. Supports in-place operation
// (result == a) for the merge loop. Note that this may grow the rect
// substantially if the two inputs are far apart this is the inherent
// substantially if the two inputs are far apart -- this is the inherent
// cost of bounding-box merging vs. maintaining a true region (list of
// non-overlapping rects). Bounding-box was chosen because the merge
// list is bounded to 128 entries and the extra repaint cost of a few

View file

@ -1,8 +1,8 @@
// dvx_comp.h Layer 3: Dirty rectangle compositor for DVX GUI
// dvx_comp.h -- Layer 3: Dirty rectangle compositor for DVX GUI
//
// The compositor tracks which screen regions have changed and ensures
// only those regions are redrawn and flushed to video memory. This is
// the key to acceptable frame rates on 486/Pentium hardware a full
// the key to acceptable frame rates on 486/Pentium hardware -- a full
// 640x480x32bpp frame is 1.2 MB, but a typical dirty region during
// normal interaction (e.g. typing in a text field) might be a few KB.
//
@ -40,7 +40,7 @@ void dirtyListMerge(DirtyListT *dl);
void dirtyListClear(DirtyListT *dl);
// Copy a rectangle from the system RAM backbuffer to the LFB (video memory).
// This is the final step the only place where the real framebuffer is
// This is the final step -- the only place where the real framebuffer is
// written. Uses platform-specific fast copy (rep movsd on DOS) for each
// scanline of the rect.
void flushRect(DisplayT *d, const RectT *r);

View file

@ -1,6 +1,6 @@
// dvxCursor.h Embedded mouse cursor bitmaps for DVX GUI
// dvxCursor.h -- Embedded mouse cursor bitmaps for DVX GUI
//
// All cursor shapes are compiled in as static const data no external
// All cursor shapes are compiled in as static const data -- no external
// cursor files to load. This is intentional: the cursors are needed
// before any file I/O infrastructure is ready, and embedding them avoids
// a runtime dependency on a file path.
@ -37,7 +37,7 @@
// XOR data: 0 = black, 1 = white (where AND = 0)
//
// The cursor is a left-pointing arrow with black outline and white fill.
// Hot spot is at (0, 0) top-left corner of the arrow tip.
// Hot spot is at (0, 0) -- top-left corner of the arrow tip.
static const uint16_t cursorArrowAnd[16] = {
0x3FFF, // 0011111111111111 row 0
@ -91,42 +91,57 @@ static const uint16_t cursorArrowXor[16] = {
// .X..XX..X.
// ..XX..XX..
// Left-right resize cursor -- exact 90 deg rotation of the vertical cursor.
// Left arrow tip at col 2, right arrow tip at col 13, 1px stem at row 7.
// Bit 15 = leftmost pixel (col 0), bit 0 = rightmost (col 15).
//
// XOR (1=white): Outline (AND=0 border):
// row 4: ....X......X.... row 3: ...XXX....XXX...
// row 5: ...XX......XX... row 4: ..XXXX....XXXX..
// row 6: ..XXX......XXX.. row 5: .XXXXX....XXXXX.
// row 7: .XXXXXXXXXXXX.. row 6: XXXXXXXXXXXXXX.. (1px border around all)
// row 8: ..XXX......XXX.. row 7: XXXXXXXXXXXXXXXX
// row 9: ...XX......XX... row 8: XXXXXXXXXXXXXX..
// row 10: ....X......X.... row 9: .XXXXX....XXXXX.
// row 10: ..XXXX....XXXX..
// row 11: ...XXX....XXX...
static const uint16_t cursorResizeHAnd[16] = {
0xFFFF, // row 0 (transparent)
0xFFFF, // row 1
0xFFFF, // row 2
0xF7EF, // 1111011111101111 row 3 arrows start
0xE3C7, // 1110001111000111 row 4
0xC1A3, // 1100000110100011 row 5
0x8081, // 1000000010000001 row 6
0x0000, // 0000000000000000 row 7 center row
0x8081, // 1000000010000001 row 8
0xC1A3, // 1100000110100011 row 9
0xE3C7, // 1110001111000111 row 10
0xF7EF, // 1111011111101111 row 11
0xFFFF, // row 12
0xFFFF, // row 13
0xFFFF, // row 14
0xFFFF // row 15
0xFFFF, // 1111111111111111 row 0
0xFFFF, // 1111111111111111 row 1
0xFFFF, // 1111111111111111 row 2
0xF18F, // 1111000110001111 row 3 outline top
0xE187, // 1110000110000111 row 4
0xC183, // 1100000110000011 row 5
0x8001, // 1000000000000001 row 6
0x8001, // 1000000000000001 row 7 center
0x8001, // 1000000000000001 row 8
0xC183, // 1100000110000011 row 9
0xE187, // 1110000110000111 row 10
0xF18F, // 1111000110001111 row 11 outline bottom
0xFFFF, // 1111111111111111 row 12
0xFFFF, // 1111111111111111 row 13
0xFFFF, // 1111111111111111 row 14
0xFFFF // 1111111111111111 row 15
};
static const uint16_t cursorResizeHXor[16] = {
0x0000, // row 0
0x0000, // row 1
0x0000, // row 2
0x0000, // row 3
0x0810, // 0000100000010000 row 4
0x1C38, // 0001110000111000 row 5
0x3E7C, // 0011111001111100 row 6
0x7FFE, // 0111111111111110 row 7
0x3E7C, // 0011111001111100 row 8
0x1C38, // 0001110000111000 row 9
0x0810, // 0000100000010000 row 10
0x0000, // row 11
0x0000, // row 12
0x0000, // row 13
0x0000, // row 14
0x0000 // row 15
0x0000, // 0000000000000000 row 0
0x0000, // 0000000000000000 row 1
0x0000, // 0000000000000000 row 2
0x0000, // 0000000000000000 row 3
0x0420, // 0000010000100000 row 4 arrow tips
0x0C30, // 0000110000110000 row 5
0x1C38, // 0001110000111000 row 6
0x3FFC, // 0011111111111100 row 7 bases + stem
0x1C38, // 0001110000111000 row 8
0x0C30, // 0000110000110000 row 9
0x0420, // 0000010000100000 row 10 arrow tips
0x0000, // 0000000000000000 row 11
0x0000, // 0000000000000000 row 12
0x0000, // 0000000000000000 row 13
0x0000, // 0000000000000000 row 14
0x0000 // 0000000000000000 row 15
};
// ============================================================
@ -302,7 +317,7 @@ static const CursorT dvxCursors[CURSOR_COUNT] = {
{ 16, 16, 7, 7, cursorResizeDiagNESWAnd, cursorResizeDiagNESWXor }, // CURSOR_RESIZE_DIAG_NESW
};
// Legacy alias kept for backward compatibility with code that predates
// Legacy alias -- kept for backward compatibility with code that predates
// the multi-cursor support.
static const CursorT dvxCursor = { 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor };

View file

@ -1,10 +1,10 @@
// dvxDialog.c Modal dialogs for DVX GUI
// dvxDialog.c -- Modal dialogs for DVX GUI
//
// Provides two standard dialog types: message boxes and file dialogs.
// Both use the nested-event-loop modal pattern: the dialog creates a
// window, sets it as the AppContext's modalWindow, then runs dvxUpdate
// in a tight loop until the user dismisses the dialog. This blocks the
// caller's code flow, which is the simplest possible modal API the
// caller's code flow, which is the simplest possible modal API -- the
// caller gets the result as a return value, no callbacks needed.
//
// The nested loop approach works because dvxUpdate is re-entrant: it
@ -15,7 +15,7 @@
//
// State for each dialog type is stored in a single static struct (sMsgBox,
// sFd) rather than on the heap. This means only one dialog of each type
// can be active at a time, but that's fine you never need two file
// can be active at a time, but that's fine -- you never need two file
// dialogs simultaneously. The static approach avoids malloc/free and
// keeps the state accessible to callback functions without threading
// context pointers through every widget callback.
@ -110,7 +110,7 @@ static MsgBoxStateT sMsgBox;
// ============================================================
// drawIconGlyph draw a simple icon shape
// drawIconGlyph -- draw a simple icon shape
// ============================================================
//
// Procedurally draws message box icons using only integer math:
@ -122,7 +122,7 @@ static MsgBoxStateT sMsgBox;
// Circles use the integer distance-squared test: for each pixel, compute
// dx*dx + dy*dy and check if it falls between INNER_R2 and OUTER_R2
// to draw a 2-pixel-wide ring. This is a brute-force O(n^2) approach
// but n is only 24 pixels, so it's 576 iterations trivial even on a 486.
// but n is only 24 pixels, so it's 576 iterations -- trivial even on a 486.
//
// The inner symbols (i, !, X, ?) are drawn with hardcoded rectFill calls
// at specific pixel offsets. This is less elegant than using the font
@ -222,7 +222,7 @@ static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y
//
// Button labels use '&' to mark accelerator keys (e.g., "&OK" makes
// Alt+O activate the button). Button IDs are stored in widget->userData
// via intptr_t cast a common pattern when you need to associate a
// via intptr_t cast -- a common pattern when you need to associate a
// small integer with a widget without allocating a separate struct.
int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags) {
@ -381,12 +381,12 @@ int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message,
// Save previous modal so stacked modals work correctly. This happens
// when a message box opens from within a file dialog (e.g., overwrite
// confirmation) the file dialog's modal is pushed and restored
// confirmation) -- the file dialog's modal is pushed and restored
// when the message box closes.
WindowT *prevModal = ctx->modalWindow;
ctx->modalWindow = win;
// Nested event loop blocks here until user clicks a button or closes.
// Nested event loop -- blocks here until user clicks a button or closes.
// dvxUpdate handles all input/rendering; sMsgBox.done is set by the
// button onClick callback or the window close callback.
while (!sMsgBox.done && ctx->running) {
@ -422,7 +422,7 @@ static void onMsgBoxClose(WindowT *win) {
// ============================================================
// onMsgBoxPaint custom paint: background + text/icon + widgets
// onMsgBoxPaint -- custom paint: background + text/icon + widgets
// ============================================================
//
// The message box uses a custom onPaint callback rather than pure widgets
@ -454,7 +454,7 @@ static void onMsgBoxPaint(WindowT *win, RectT *dirtyArea) {
cd.clipW = win->contentW;
cd.clipH = win->contentH;
// Fill background with window face color (not content bg dialog style)
// Fill background with window face color (not content bg -- dialog style)
rectFill(&cd, &ctx->blitOps, 0, 0, win->contentW, win->contentH, ctx->colors.windowFace);
// Draw word-wrapped message text
@ -488,14 +488,14 @@ static void onMsgBoxPaint(WindowT *win, RectT *dirtyArea) {
// ============================================================
// wordWrapDraw draw word-wrapped text
// wordWrapDraw -- draw word-wrapped text
// ============================================================
//
// Simple greedy word-wrap: fill each line with as many characters as fit
// within maxW, breaking at the last space if the line overflows. If a
// single word is longer than maxW, it gets its own line (may be clipped).
// Explicit newlines are honored. This is a fixed-width font, so "max
// chars per line" is just maxW / charWidth no per-character width
// chars per line" is just maxW / charWidth -- no per-character width
// accumulation needed.
static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t maxW, uint32_t fg, uint32_t bg) {
@ -550,12 +550,12 @@ static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
// ============================================================
// wordWrapHeight compute height of word-wrapped text
// wordWrapHeight -- compute height of word-wrapped text
// ============================================================
//
// Duplicates the word-wrap logic from wordWrapDraw but only counts lines
// instead of drawing. This is needed to compute the dialog height before
// creating the window. The duplication is intentional combining them
// creating the window. The duplication is intentional -- combining them
// into a single function with a "just measure" flag would add branching
// to the draw path and make both harder to read.
@ -650,7 +650,7 @@ static FileDialogStateT sFd;
// ============================================================
// fdFilterMatch check if filename matches a glob pattern
// fdFilterMatch -- check if filename matches a glob pattern
// ============================================================
//
// Supports only the most common DOS file filter patterns: "*.*", "*",
@ -732,7 +732,7 @@ static bool fdFilterMatch(const char *name, const char *pattern) {
// ============================================================
// fdFreeEntries free allocated entry name strings
// fdFreeEntries -- free allocated entry name strings
// ============================================================
static void fdFreeEntries(void) {
@ -746,7 +746,7 @@ static void fdFreeEntries(void) {
// ============================================================
// fdEntryCompare sort: dirs first, then alphabetical
// fdEntryCompare -- sort: dirs first, then alphabetical
// ============================================================
//
// Sort comparator for the indirect sort in fdLoadDir. Uses an index
@ -769,7 +769,7 @@ static int fdEntryCompare(const void *a, const void *b) {
// ============================================================
// fdLoadDir read directory contents into state
// fdLoadDir -- read directory contents into state
// ============================================================
//
// Reads the current directory, applies the active file filter, sorts
@ -872,7 +872,7 @@ static void fdLoadDir(void) {
// ============================================================
// fdNavigate change to a new directory
// fdNavigate -- change to a new directory
// ============================================================
//
// Handles both absolute and relative paths. Relative paths are resolved
@ -918,7 +918,7 @@ static void fdNavigate(const char *path) {
// ============================================================
// fdValidateFilename check filename is valid for target OS
// fdValidateFilename -- check filename is valid for target OS
// ============================================================
static bool fdValidateFilename(const char *name) {
@ -934,7 +934,7 @@ static bool fdValidateFilename(const char *name) {
// ============================================================
// fdAcceptFile confirm and accept the selected filename
// fdAcceptFile -- confirm and accept the selected filename
// ============================================================
//
// Validates the filename (platform-specific rules), then checks for
@ -979,7 +979,7 @@ static bool fdAcceptFile(const char *name) {
// ============================================================
// fdOnListClick file list selection changed
// fdOnListClick -- file list selection changed
// ============================================================
static void fdOnListClick(WidgetT *w) {
@ -996,7 +996,7 @@ static void fdOnListClick(WidgetT *w) {
// ============================================================
// fdOnListDblClick file list double-click
// fdOnListDblClick -- file list double-click
// ============================================================
//
// Double-click on a directory navigates into it. Double-click on a file
@ -1031,7 +1031,7 @@ static void fdOnListDblClick(WidgetT *w) {
fdNavigate(dirName);
wgtSetText(sFd.nameInput, "");
} else {
// Double-click on file accept it (with confirmation if needed)
// Double-click on file -- accept it (with confirmation if needed)
wgtSetText(sFd.nameInput, sFd.entryNames[sel]);
fdAcceptFile(sFd.entryNames[sel]);
}
@ -1039,7 +1039,7 @@ static void fdOnListDblClick(WidgetT *w) {
// ============================================================
// fdOnOk OK button clicked
// fdOnOk -- OK button clicked
// ============================================================
//
// OK has three behaviors depending on context:
@ -1055,7 +1055,7 @@ static void fdOnOk(WidgetT *w) {
// If the filename input is empty and a directory is selected in the
// list, navigate into it. But if the user has typed a filename,
// always accept it don't let the listbox selection override.
// always accept it -- don't let the listbox selection override.
if (!name || name[0] == '\0') {
int32_t sel = wgtListBoxGetSelected(sFd.fileList);
@ -1105,7 +1105,7 @@ static void fdOnOk(WidgetT *w) {
// ============================================================
// fdOnCancel Cancel button clicked
// fdOnCancel -- Cancel button clicked
// ============================================================
static void fdOnCancel(WidgetT *w) {
@ -1116,7 +1116,7 @@ static void fdOnCancel(WidgetT *w) {
// ============================================================
// fdOnClose window close button
// fdOnClose -- window close button
// ============================================================
static void fdOnClose(WindowT *win) {
@ -1127,7 +1127,7 @@ static void fdOnClose(WindowT *win) {
// ============================================================
// fdOnFilterChange filter dropdown changed
// fdOnFilterChange -- filter dropdown changed
// ============================================================
static void fdOnFilterChange(WidgetT *w) {
@ -1137,7 +1137,7 @@ static void fdOnFilterChange(WidgetT *w) {
// ============================================================
// fdOnPathSubmit enter pressed in path input
// fdOnPathSubmit -- enter pressed in path input
// ============================================================
static void fdOnPathSubmit(WidgetT *w) {
@ -1223,7 +1223,7 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
wgtLabel(filterRow, "F&ilter:");
sFd.filterDd = wgtDropdown(filterRow);
// Build filter label array (static lives for dialog lifetime)
// Build filter label array (static -- lives for dialog lifetime)
static const char *filterLabels[16];
int32_t fc = filterCount < 16 ? filterCount : 16;

View file

@ -1,4 +1,4 @@
// dvxDialog.h Modal dialogs for DVX GUI
// dvxDialog.h -- Modal dialogs for DVX GUI
//
// Provides pre-built modal dialog boxes (message box, file dialog) that
// block the caller and run their own event loop via dvxUpdate() until the

View file

@ -1,4 +1,4 @@
// dvx_draw.c Layer 2: Drawing primitives for DVX GUI (optimized)
// dvx_draw.c -- Layer 2: Drawing primitives for DVX GUI (optimized)
//
// This is the second layer of the DVX compositor stack, sitting on top
// of dvxVideo (layer 1) and below dvxComp (layer 3). It provides all
@ -39,7 +39,7 @@
// 3) For the most critical glyph paths (unclipped 32bpp and 16bpp),
// the pixel loops are fully unrolled into 8 direct array stores
// with literal bit masks. This eliminates the sGlyphBit[] table
// lookup, the loop counter, and the loop branch saving ~3 cycles
// lookup, the loop counter, and the loop branch -- saving ~3 cycles
// per pixel on a 486. The clipped path falls back to the table.
//
// Clip rectangle handling: All draw functions clip against
@ -94,7 +94,7 @@ char accelParse(const char *text) {
text++;
if (*text == '&') {
// Escaped && literal &, not an accelerator
// Escaped && -- literal &, not an accelerator
text++;
continue;
}
@ -136,9 +136,9 @@ char accelParse(const char *text) {
// clip region, w or h will be <= 0 and callers bail out.
//
// Marked static inline because this is called on every rectFill,
// rectCopy, and indirectly on every glyph it must compile to
// rectCopy, and indirectly on every glyph -- it must compile to
// straight-line clamp instructions with zero call overhead.
// __builtin_expect(, 0) marks clipping as unlikely; in the
// __builtin_expect(..., 0) marks clipping as unlikely; in the
// common case windows are fully within the clip rect and all
// four branches fall through untaken. On Pentium this keeps the
// branch predictor happy (static not-taken prediction for forward
@ -183,7 +183,7 @@ static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *
// The implementation has special-cased fast paths for bw==2 and bw==1
// that emit exact spans via rectFill rather than looping. This
// matters because drawBevel is called for every window frame, button,
// menu, and scrollbar element on every repaint the loop overhead
// menu, and scrollbar element on every repaint -- the loop overhead
// and extra rectFill calls in the general case add up. Each rectFill
// call already handles clipping internally, so the bevels clip
// correctly even when a window is partially off-screen.
@ -248,7 +248,7 @@ void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w
// (bit 7 = leftmost pixel). This is the standard VGA/PC BIOS font
// format. We use 8-pixel-wide glyphs exclusively because 8 bits fit
// in one byte per scanline, making the inner loop a single byte load
// plus 8 bit tests no multi-byte glyph row assembly needed.
// plus 8 bit tests -- no multi-byte glyph row assembly needed.
//
// The function has six specialized code paths (3 bpp x 2 modes),
// chosen with if/else chains rather than function pointers. On 486
@ -319,7 +319,7 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
if (opaque) {
// Opaque mode: every pixel in the cell gets written (fg or bg).
// The unclipped 32bpp and 16bpp paths use branchless ternary
// stores the compiler emits cmov or conditional-set sequences
// stores -- the compiler emits cmov or conditional-set sequences
// that avoid branch misprediction penalties. Each row is 8
// direct array stores with no loop, no table lookup.
if (unclipped && bpp == 4) {
@ -357,7 +357,7 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
// Clipped path or 8bpp: use spanFill for bg (leveraging
// rep stosl), then iterate visible columns with sGlyphBit[]
// table for fg. 8bpp always takes this path because 8-bit
// stores can't be branchlessly ternary'd as efficiently
// stores can't be branchlessly ternary'd as efficiently --
// the compiler can't cmov into a byte store.
for (int32_t row = rowStart; row < rowEnd; row++) {
int32_t py = y + row;
@ -658,7 +658,7 @@ void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_
// expressed as a span fill (which writes uniform color).
//
// The parity calculations on the bottom and right edges ensure the
// dot pattern is visually continuous around corners the starting
// dot pattern is visually continuous around corners -- the starting
// pixel of each edge is offset so dots don't double up or gap at
// the corner where two edges meet.
//
@ -723,7 +723,7 @@ void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32
// drawHLine
// ============================================================
//
// Thin convenience wrapper a horizontal line is just a 1px-tall rect.
// Thin convenience wrapper -- a horizontal line is just a 1px-tall rect.
// Delegates to rectFill which handles clipping and uses spanFill (rep
// stosl) for the actual write.
@ -745,8 +745,8 @@ void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w
// spanFill and spanCopy are called per-scanline (not per-pixel),
// so the indirect call overhead (~5 cycles on Pentium for the
// mispredicted first call, then predicted afterward) is amortized
// over an entire row of pixels. The alternative a switch inside
// rectFill's inner loop would branch every scanline for no gain.
// over an entire row of pixels. The alternative -- a switch inside
// rectFill's inner loop -- would branch every scanline for no gain.
//
// The platform implementations (dvxPlatformDos.c) use inline asm:
// spanFill8/16/32 -> rep stosl (fills 4 bytes per clock)
@ -799,7 +799,7 @@ void drawInit(BlitOpsT *ops, const DisplayT *d) {
// The colMask optimization pre-computes which bits in each row fall
// within the visible (clipped) columns. For fully transparent rows
// (all visible bits have andMask=1), the entire row is skipped with
// a single bitwise AND + compare no per-pixel iteration needed.
// a single bitwise AND + compare -- no per-pixel iteration needed.
void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const uint16_t *andMask, const uint16_t *xorData, uint32_t fgColor, uint32_t bgColor) {
int32_t bpp = ops->bytesPerPixel;
@ -893,7 +893,7 @@ void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, in
// can have a different fg/bg pair, so the bg can't be filled in a
// single bulk pass. Instead each cell is rendered individually,
// always in opaque mode (every pixel gets a write). The bpp branch
// is still hoisted outside the per-pixel loop the outer loop
// is still hoisted outside the per-pixel loop -- the outer loop
// selects the bpp path once, then iterates cells within it.
//
// blinkVisible controls the blink phase: when false, fg is replaced
@ -1095,7 +1095,7 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
text++;
if (*text == '&') {
// Escaped && draw literal &
// Escaped && -- draw literal &
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, '&', fg, bg, opaque);
}
@ -1106,7 +1106,7 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
}
if (*text) {
// Accelerator character draw it then underline
// Accelerator character -- draw it then underline
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
drawHLine(d, ops, x, y + font->charHeight - 1, cw, fg);
@ -1135,7 +1135,7 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
// ============================================================
//
// Draws a vertical line pixel-by-pixel. Unlike drawHLine (which
// delegates to rectFill spanFill for a single-row span), a
// delegates to rectFill -> spanFill for a single-row span), a
// vertical line can't use spanFill because each pixel is on a
// different scanline. Instead we advance by d->pitch per pixel
// and write directly, branching on bpp once at the top.
@ -1224,7 +1224,7 @@ static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
// This function does NOT handle overlapping source and destination
// regions (no memmove). That's fine because the source is always a
// per-window content buffer and the destination is the shared
// backbuffer they never overlap.
// backbuffer -- they never overlap.
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h) {
int32_t bpp = ops->bytesPerPixel;
@ -1268,7 +1268,7 @@ void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, cons
// The workhorse fill primitive. Clips to the display clip rect,
// then fills one scanline at a time via the spanFill function
// pointer (which routes to rep stosl on DOS). This is the most
// frequently called function in the draw layer it backs rectFill
// frequently called function in the draw layer -- it backs rectFill
// directly, plus drawHLine, drawBevel interior fills, and the bg
// fill in opaque text rendering.
//
@ -1299,7 +1299,7 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
// ============================================================
//
// Returns the pixel width of a null-terminated string. Because all
// fonts are fixed-width, this is just strlen * charWidth but we
// fonts are fixed-width, this is just strlen * charWidth -- but we
// iterate manually rather than calling strlen to avoid a second pass
// over the string. This is used heavily for layout calculations
// (centering text in buttons, sizing menu popups, etc.).
@ -1334,14 +1334,14 @@ int32_t textWidthAccel(const BitmapFontT *font, const char *text) {
text++;
if (*text == '&') {
// Escaped && counts as one character
// Escaped && -- counts as one character
w += font->charWidth;
text++;
continue;
}
if (*text) {
// Accelerator character counts as one character, & is skipped
// Accelerator character -- counts as one character, & is skipped
w += font->charWidth;
text++;
continue;

View file

@ -1,9 +1,9 @@
// dvx_draw.h Layer 2: Drawing primitives for DVX GUI
// dvx_draw.h -- Layer 2: Drawing primitives for DVX GUI
//
// Provides all 2D drawing operations: rectangle fills, bitmap blits, text
// rendering, bevels, lines, and cursor/icon rendering. Every function in
// this layer draws into the display's backbuffer and clips to the display's
// current clip rectangle no direct LFB writes happen here.
// current clip rectangle -- no direct LFB writes happen here.
//
// This layer is deliberately stateless beyond the clip rect on DisplayT.
// All drawing context (colors, fonts, bevel styles) is passed explicitly
@ -34,7 +34,7 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w,
void rectCopy(DisplayT *d, const BlitOpsT *ops, int32_t dstX, int32_t dstY, const uint8_t *srcBuf, int32_t srcPitch, int32_t srcX, int32_t srcY, int32_t w, int32_t h);
// Draw a beveled frame. The bevel is drawn as overlapping horizontal and
// vertical spans top/left in highlight color, bottom/right in shadow.
// vertical spans -- top/left in highlight color, bottom/right in shadow.
// The face color fills the interior if non-zero.
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style);

View file

@ -1,4 +1,4 @@
// dvx_font.h Embedded VGA bitmap font data (CP437) for DVX GUI
// dvx_font.h -- Embedded VGA bitmap font data (CP437) for DVX GUI
//
// Contains the raw glyph bitmaps for two standard VGA ROM fonts (8x14 and
// 8x16) covering the full IBM Code Page 437 character set (256 glyphs).
@ -15,7 +15,7 @@
// The glyph format is 1 bit per pixel, 8 pixels wide, with MSB = leftmost
// pixel. Each glyph occupies charHeight consecutive bytes (14 or 16).
// The drawing code in dvxDraw.c processes one byte per scanline, testing
// each bit to decide foreground vs. background the 8-pixel width means
// each bit to decide foreground vs. background -- the 8-pixel width means
// no bit-shifting across byte boundaries is ever needed.
//
// CP437 includes the full ASCII printable range (32-126) plus box-drawing
@ -29,7 +29,7 @@
#include "dvxTypes.h"
// ============================================================
// 8x14 VGA ROM font standard IBM CP437 character set
// 8x14 VGA ROM font -- standard IBM CP437 character set
// 256 glyphs, 14 bytes per glyph, MSB = leftmost pixel
// ============================================================
@ -551,7 +551,7 @@ static const uint8_t font8x14[256 * 14] = {
};
// ============================================================
// 8x16 VGA ROM font standard IBM CP437 character set
// 8x16 VGA ROM font -- standard IBM CP437 character set
// 256 glyphs, 16 bytes per glyph, MSB = leftmost pixel
// ============================================================

View file

@ -1,4 +1,4 @@
// dvxIcon.c stb_image implementation for DVX GUI
// dvxIcon.c -- stb_image implementation for DVX GUI
//
// This file exists solely to instantiate the stb_image implementation.
// stb_image is a single-header library: you #define STB_IMAGE_IMPLEMENTATION
@ -9,7 +9,7 @@
// 3. Doesn't slow down incremental rebuilds (only recompiles if stb changes)
//
// stb_image was chosen over libpng/libjpeg because:
// - Single header, no external dependencies critical for DJGPP cross-compile
// - Single header, no external dependencies -- critical for DJGPP cross-compile
// - Supports BMP, PNG, JPEG, GIF with one include
// - Public domain license, no linking restrictions
// - Small code footprint suitable for DOS targets

View file

@ -1,8 +1,8 @@
// dvxImageWrite.c stb_image_write implementation for DVX GUI
// dvxImageWrite.c -- stb_image_write implementation for DVX GUI
//
// Companion to dvxIcon.c: instantiates stb_image_write for PNG output
// (used by dvxScreenshot and dvxWindowScreenshot). Same rationale as
// dvxIcon.c for using stb zero external dependencies, single header,
// dvxIcon.c for using stb -- zero external dependencies, single header,
// public domain. Kept in a separate translation unit from the read side
// so projects that don't need screenshot support can omit this file and
// save the code size.

View file

@ -1,10 +1,10 @@
// dvx_palette.h 8-bit mode palette definition for DVX GUI
// dvx_palette.h -- 8-bit mode palette definition for DVX GUI
//
// Defines the 256-color palette used in 8-bit (VGA Mode 13h / VESA 8bpp)
// video modes. The palette layout follows the same strategy as the X11
// default colormap and Netscape's web-safe colors:
//
// 0-215: 6x6x6 color cube 6 levels per channel (0, 51, 102, 153,
// 0-215: 6x6x6 color cube -- 6 levels per channel (0, 51, 102, 153,
// 204, 255), giving 216 uniformly distributed colors. The index
// formula (r*36 + g*6 + b) enables O(1) color lookup without
// searching.

View file

@ -1,4 +1,4 @@
// dvxPrefs.c INI-based preferences system (read/write)
// dvxPrefs.c -- INI-based preferences system (read/write)
//
// Custom INI parser and writer. Stores entries as a dynamic array of
// section/key/value triples using stb_ds. Preserves insertion order
@ -210,14 +210,16 @@ const char *prefsGetString(const char *section, const char *key, const char *def
bool prefsLoad(const char *filename) {
prefsFree();
// Always store the path so prefsSave can create the file
// even if it doesn't exist yet.
sFilePath = dupStr(filename);
FILE *fp = fopen(filename, "rb");
if (!fp) {
return false;
}
sFilePath = dupStr(filename);
char line[512];
char *currentSection = dupStr("");
@ -236,7 +238,7 @@ bool prefsLoad(const char *filename) {
p++;
}
// Blank line store as comment to preserve formatting
// Blank line -- store as comment to preserve formatting
if (*p == '\0') {
PrefsEntryT e = {0};
e.section = dupStr(currentSection);

View file

@ -1,4 +1,4 @@
// dvxPrefs.h INI-based preferences system (read/write)
// dvxPrefs.h -- INI-based preferences system (read/write)
//
// Loads a configuration file at startup and provides typed accessors
// with caller-supplied defaults. Values can be modified at runtime

View file

@ -1,4 +1,4 @@
// dvx_types.h Shared type definitions for DVX GUI
// dvx_types.h -- Shared type definitions for DVX GUI
//
// Central type definitions shared across all five layers of the DVX GUI
// stack (video, draw, comp, wm, app). Every header includes this file,
@ -18,7 +18,7 @@
// Describes the pixel encoding for the active VESA video mode.
// Populated once at startup from the VBE mode info block and then
// treated as read-only. Storing shift/mask/bits separately avoids
// repeated bit-scanning when packing colors packColor() uses these
// repeated bit-scanning when packing colors -- packColor() uses these
// fields directly for shift-and-mask arithmetic rather than computing
// them on the fly, which matters when called per-glyph during text
// rendering on a 486.
@ -49,13 +49,13 @@ typedef struct {
// The double-buffer strategy (backBuf in system RAM, lfb is the real
// framebuffer) exists because writes to video memory over the PCI bus
// are dramatically slower than writes to main RAM on 486/Pentium hardware
// often 10-50x slower for random-access patterns. All drawing goes to
// -- often 10-50x slower for random-access patterns. All drawing goes to
// backBuf, and only dirty rectangles are flushed to lfb via fast aligned
// copies (rep movsd). This is the single most important performance
// decision in the entire compositor.
//
// The clip rectangle is mutable state set before each draw call so that
// the drawing layer doesn't need per-window context it just clips to
// the drawing layer doesn't need per-window context -- it just clips to
// whatever rectangle is currently set on the display. This avoids an
// extra parameter on every draw function and mirrors how classic windowing
// systems (X11, GDI) handle clipping.
@ -123,7 +123,7 @@ typedef struct {
//
// Bevels are the defining visual element of the Motif/DESQview/X aesthetic.
// Swapping highlight and shadow colors flips between raised and sunken
// appearance the BEVEL_RAISED and BEVEL_SUNKEN macros encode this
// appearance -- the BEVEL_RAISED and BEVEL_SUNKEN macros encode this
// convention so callers don't get the colors backwards.
//
// A width of 2 pixels is the standard Motif bevel size. Using 1 creates
@ -142,6 +142,7 @@ typedef struct {
#define BEVEL_RAISED(cs, bw) (BevelStyleT){ (cs)->windowHighlight, (cs)->windowShadow, (cs)->windowFace, (bw) }
#define BEVEL_SUNKEN(cs, face, bw) (BevelStyleT){ (cs)->windowShadow, (cs)->windowHighlight, (face), (bw) }
#define BEVEL_TROUGH(cs) (BevelStyleT){ (cs)->windowShadow, (cs)->windowHighlight, (cs)->scrollbarTrough, 1 }
#define BEVEL_SB_BUTTON(cs) (BevelStyleT){ (cs)->windowHighlight, (cs)->windowShadow, (cs)->scrollbarBg, 1 }
// ============================================================
// Bitmap font
@ -154,7 +155,7 @@ typedef struct {
//
// Two font sizes are provided (8x14 and 8x16), matching the standard
// VGA ROM fonts and CP437 encoding. Proportional fonts would require
// glyph-width tables, kerning, and per-character positioning none of
// glyph-width tables, kerning, and per-character positioning -- none of
// which is worth the complexity for a DOS-era window manager targeting
// 640x480 screens. The 8-pixel width also aligns nicely with byte
// boundaries, enabling per-scanline glyph rendering without bit shifting.
@ -182,7 +183,7 @@ typedef struct {
// The color set mirrors classic Motif/Windows 3.x conventions:
// windowHighlight/windowShadow form bevel pairs, activeTitleBg gives
// the focused window's title bar its distinctive color, and so on.
// Keeping all colors in one struct makes theme support trivial just
// Keeping all colors in one struct makes theme support trivial -- just
// swap the struct.
typedef struct {
@ -204,6 +205,8 @@ typedef struct {
uint32_t scrollbarBg;
uint32_t scrollbarFg;
uint32_t scrollbarTrough;
uint32_t cursorFg;
uint32_t cursorBg;
} ColorSchemeT;
// Color IDs for addressing individual colors in ColorSchemeT.
@ -227,9 +230,17 @@ typedef enum {
ColorScrollbarBgE,
ColorScrollbarFgE,
ColorScrollbarTroughE,
ColorCursorFgE,
ColorCursorBgE,
ColorCountE
} ColorIdE;
typedef enum {
WallpaperStretchE,
WallpaperTileE,
WallpaperCenterE
} WallpaperModeE;
// Video mode entry (enumerated at init, available to apps)
typedef struct {
int32_t w;
@ -261,7 +272,7 @@ typedef struct {
//
// These define the pixel geometry of the window frame (border, title bar,
// menu bar). They're compile-time constants because the DV/X look demands
// a fixed, uniform chrome thickness across all windows there's no
// a fixed, uniform chrome thickness across all windows -- there's no
// per-window customization of frame geometry.
//
// CHROME_TOTAL_TOP/SIDE/BOTTOM are the total chrome insets from the
@ -430,7 +441,7 @@ typedef struct {
//
// WindowT is the central object of the window manager. Each window owns
// its own content backbuffer, which persists across frames so that
// applications don't need to repaint on every expose event only when
// applications don't need to repaint on every expose event -- only when
// their actual content changes. This is crucial for performance on slow
// hardware: dragging a window across others doesn't force every obscured
// window to repaint.
@ -533,7 +544,7 @@ typedef struct WindowT {
// Accelerator table (NULL if none, caller owns allocation)
AccelTableT *accelTable;
// Callbacks the application's interface to the window manager.
// Callbacks -- the application's interface to the window manager.
// Using function pointers rather than a message queue keeps latency
// low (no queuing/dispatching overhead) and matches the callback-driven
// model that DOS programs expect. The widget system installs its own
@ -562,7 +573,7 @@ typedef struct WindowT {
// which matters because WindowT is large.
//
// Drag/resize/scroll state lives here rather than on individual windows
// because only one interaction can be active at a time system-wide
// because only one interaction can be active at a time system-wide --
// you can't drag two windows simultaneously with a single mouse.
typedef struct {
@ -596,7 +607,7 @@ typedef struct {
// four pixel states: transparent (AND=1, XOR=0), inverted (AND=1, XOR=1),
// black (AND=0, XOR=0), and white (AND=0, XOR=1). The cursor is drawn
// in software on top of the composited frame because VESA VBE doesn't
// provide a hardware sprite the cursor is painted into the backbuffer
// provide a hardware sprite -- the cursor is painted into the backbuffer
// and the affected region is flushed to the LFB each frame.
//
// 16 pixels wide using uint16_t rows, one word per scanline. This keeps

View file

@ -1,4 +1,4 @@
// dvx_video.c Layer 1: Video backend for DVX GUI
// dvx_video.c -- Layer 1: Video backend for DVX GUI
//
// Platform-independent video utilities. The actual VESA/VBE code
// now lives in dvxPlatformDos.c (or the platform file for whatever
@ -18,7 +18,7 @@
// of dirty rects per frame. Requiring LFB eliminates per-scanline
// bank math, allows rep movsd bulk copies, and simplifies every span
// operation in the draw layer. The tradeoff is dropping cards that
// only expose banked modes acceptable since the target is 486+ with
// only expose banked modes -- acceptable since the target is 486+ with
// a VESA 2.0 BIOS (virtually universal by 1994-95).
//
// System-RAM backbuffer with dirty-rect flushing: Drawing directly
@ -63,7 +63,7 @@
// For direct-color modes (15/16/32-bit), we right-shift each channel
// to discard the low bits that don't fit in the mode's color field,
// then left-shift into position. This approach truncates rather than
// rounds, which is a deliberate simplicity tradeoff dithering or
// rounds, which is a deliberate simplicity tradeoff -- dithering or
// rounding would add per-pixel branches on a 486 for minimal visual
// benefit in a desktop GUI.
@ -136,8 +136,8 @@ void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
// ============================================================
//
// Thin wrapper that delegates to the platform layer. All the heavy
// lifting VBE enumeration, mode scoring, LFB DPMI mapping,
// backbuffer allocation lives in dvxPlatformDos.c so this file
// lifting -- VBE enumeration, mode scoring, LFB DPMI mapping,
// backbuffer allocation -- lives in dvxPlatformDos.c so this file
// stays portable. The platform layer fills in every field of
// DisplayT (dimensions, pitch, pixel format, lfb pointer,
// backBuf pointer, palette, initial clip rect).

View file

@ -1,4 +1,4 @@
// dvx_video.h Layer 1: VESA VBE video backend for DVX GUI
// dvx_video.h -- Layer 1: VESA VBE video backend for DVX GUI
//
// The lowest layer in the DVX stack. Responsible for VESA VBE mode
// negotiation, linear framebuffer (LFB) mapping via DPMI, system RAM
@ -22,7 +22,7 @@
// Probes VBE for a mode matching the requested resolution and depth,
// enables it, maps the LFB into the DPMI linear address space, and
// allocates a system RAM backbuffer of the same size. preferredBpp is
// a hint if the exact depth isn't available, the closest match is used.
// a hint -- if the exact depth isn't available, the closest match is used.
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Restores VGA text mode (INT 10h AH=0, mode 3), unmaps the LFB, and

View file

@ -1,4 +1,4 @@
// dvxWidget.h Widget system for DVX GUI
// dvxWidget.h -- Widget system for DVX GUI
//
// A retained-mode widget toolkit layered on top of the DVX window manager.
// Widgets form a tree (parent-child via firstChild/lastChild/nextSibling
@ -150,7 +150,7 @@ typedef enum {
// ============================================================
typedef enum {
FrameInE, // beveled inward (sunken) default
FrameInE, // beveled inward (sunken) -- default
FrameOutE, // beveled outward (raised)
FrameFlatE // solid color line
} FrameStyleE;
@ -176,7 +176,7 @@ typedef enum {
// state. The pointer indirection adds one dereference but saves
// significant memory across a typical widget tree.
// AnsiTermDataT full VT100/ANSI terminal emulator state.
// AnsiTermDataT -- full VT100/ANSI terminal emulator state.
// Implements a subset of DEC VT100 escape sequences sufficient for BBS
// and DOS ANSI art rendering: cursor movement, color attributes (16-color
// with bold-as-bright), scrolling regions, and blink. The parser is a
@ -201,7 +201,7 @@ typedef struct {
// Scrolling region (0-based, inclusive)
int32_t scrollTop; // top row of scroll region
int32_t scrollBot; // bottom row of scroll region
// Scrollback circular buffer so old lines age out naturally without
// Scrollback -- circular buffer so old lines age out naturally without
// memmove. Each line is cols*2 bytes (same ch/attr format as cells).
// scrollPos tracks the view offset: when equal to scrollbackCount,
// the user sees the live screen; when less, they're viewing history.
@ -216,7 +216,7 @@ typedef struct {
// Cursor blink
bool cursorOn; // current cursor blink phase
clock_t cursorTime; // timestamp of last cursor toggle
// Dirty tracking a 32-bit bitmask where each bit corresponds to one
// Dirty tracking -- a 32-bit bitmask where each bit corresponds to one
// terminal row. Only dirty rows are repainted, which is critical because
// the ANSI terminal can receive data every frame (at 9600+ baud) and
// re-rendering all 25 rows of 80 columns each frame would dominate the
@ -235,7 +235,7 @@ typedef struct {
int32_t selEndLine;
int32_t selEndCol;
bool selecting;
// Communications interface abstracted so the terminal can connect to
// Communications interface -- abstracted so the terminal can connect to
// different backends (serial port, secLink channel, local pipe) without
// knowing the transport details. When all are NULL, the terminal is in
// offline/disconnected mode (useful for viewing .ANS files).
@ -244,7 +244,7 @@ typedef struct {
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
} AnsiTermDataT;
// ListViewDataT multi-column list with sortable headers and optional
// ListViewDataT -- multi-column list with sortable headers and optional
// multi-select. cellData is a flat array of strings indexed as
// cellData[row * colCount + col]. The sortIndex is an indirection array
// that maps displayed row numbers to data row numbers, allowing sort
@ -282,7 +282,7 @@ typedef struct WidgetT {
WidgetTypeE type;
// wclass points to the vtable for this widget type. Looked up once at
// creation from widgetClassTable[type]. This avoids a switch on type
// in every paint/layout/event dispatch the cost is one pointer per
// in every paint/layout/event dispatch -- the cost is one pointer per
// widget, which is negligible.
const struct WidgetClassT *wclass;
char name[MAX_WIDGET_NAME];
@ -304,7 +304,7 @@ typedef struct WidgetT {
int32_t w;
int32_t h;
// Computed minimum size set bottom-up by calcMinSize during layout.
// Computed minimum size -- set bottom-up by calcMinSize during layout.
// These represent the smallest possible size for this widget (including
// its children if it's a container). The layout engine uses these as
// the starting point for space allocation.
@ -356,7 +356,7 @@ typedef struct WidgetT {
void (*onFocus)(struct WidgetT *w);
void (*onBlur)(struct WidgetT *w);
// Type-specific data tagged union keyed by the `type` field.
// Type-specific data -- tagged union keyed by the `type` field.
// Only the member corresponding to `type` is valid. This is the C
// equivalent of a discriminated union / variant type. Using a union
// instead of separate structs per widget type keeps all widget data
@ -365,6 +365,7 @@ typedef struct WidgetT {
union {
struct {
const char *text;
WidgetAlignE textAlign; // AlignStartE=left, AlignCenterE, AlignEndE=right
} label;
struct {
@ -389,7 +390,7 @@ typedef struct WidgetT {
// Text input has its own edit buffer (not a pointer to external
// storage) so the widget fully owns its text lifecycle. The undo
// buffer holds a single-level snapshot taken before each edit
// operation Ctrl+Z restores to the snapshot. This is simpler
// operation -- Ctrl+Z restores to the snapshot. This is simpler
// than a full undo stack but sufficient for single-line fields.
struct {
char *buf;
@ -605,7 +606,7 @@ typedef struct WidgetT {
// that fills the window's content area, and installs callback handlers
// (onPaint, onMouse, onKey, onResize) that dispatch events to the widget
// tree. After this call, the window is fully managed by the widget system
// the application builds its UI by adding child widgets to the returned
// -- the application builds its UI by adding child widgets to the returned
// root container. The window's userData is set to the AppContextT pointer.
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
@ -628,6 +629,7 @@ WidgetT *wgtFrame(WidgetT *parent, const char *title);
// ============================================================
WidgetT *wgtLabel(WidgetT *parent, const char *text);
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align);
WidgetT *wgtButton(WidgetT *parent, const char *text);
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
@ -651,7 +653,7 @@ WidgetT *wgtRadio(WidgetT *parent, const char *text);
// ============================================================
//
// Spacer is an invisible flexible widget (weight=100) that absorbs
// extra space useful for pushing subsequent siblings to the end of
// extra space -- useful for pushing subsequent siblings to the end of
// a container (like CSS flex: 1 auto). Separators are thin beveled
// lines for visual grouping.
@ -895,7 +897,7 @@ int32_t wgtAnsiTermPoll(WidgetT *w);
// full widget paint pipeline (which would repaint the entire widget), this
// renders only the dirty rows (tracked via the dirtyRows bitmask) directly
// into the window's content buffer. This is essential for responsive terminal
// output incoming serial data can dirty a few rows per frame, and
// output -- incoming serial data can dirty a few rows per frame, and
// repainting only those rows keeps the cost proportional to the actual
// change rather than the full 80x25 grid. Returns the number of rows
// repainted; outY/outH report the affected region for dirty-rect tracking.
@ -972,7 +974,7 @@ void wgtListBoxSetReorderable(WidgetT *w, bool reorderable);
// ============================================================
// Set tooltip text for a widget (NULL to remove).
// Caller owns the string it must outlive the widget.
// Caller owns the string -- it must outlive the widget.
void wgtSetTooltip(WidgetT *w, const char *text);
// ============================================================

View file

@ -1,4 +1,4 @@
// dvx_wm.c Layer 4: Window manager for DVX GUI
// dvx_wm.c -- Layer 4: Window manager for DVX GUI
//
// This layer manages the window stack (z-order), window chrome rendering
// (title bars, borders, bevels, gadgets, menu bars, scrollbars), and user
@ -21,7 +21,7 @@
//
// - Each window has its own content backbuffer (contentBuf). The WM blits
// this to the display backbuffer during compositing. This means window
// content survives being occluded apps don't get expose events and don't
// content survives being occluded -- apps don't get expose events and don't
// need to repaint when uncovered, which hugely simplifies app code and
// avoids the latency of synchronous repaint-on-expose.
//
@ -66,7 +66,7 @@
// Extracted into a struct so that both drawTitleBar() and wmHitTest() compute
// identical geometry from the same code path (computeTitleGeom). Without this,
// a discrepancy between draw and hit-test coordinates would cause clicks to
// land on the wrong gadget a subtle bug that's hard to reproduce visually.
// land on the wrong gadget -- a subtle bug that's hard to reproduce visually.
typedef struct {
int32_t titleX;
@ -110,7 +110,7 @@ static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
// padding (CHROME_TITLE_PAD) on both sides and MENU_BAR_GAP between labels.
// Positions are cached and only recomputed when positionsDirty is set (after
// adding/removing a menu), avoiding redundant textWidthAccel calls on every
// paint font metric calculation is expensive relative to a simple flag check.
// paint -- font metric calculation is expensive relative to a simple flag check.
// barX values are relative to the window, not the screen; the draw path adds
// the window's screen position.
static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
@ -198,7 +198,7 @@ static void computeTitleGeom(const WindowT *win, TitleGeomT *g) {
// Row 0: highlight (bright edge catches the "light" from top-left)
// Row 1: highlight (doubled for visual weight at low resolutions)
// Row 2: face (flat middle band)
// Row 3: shadow (inner crease makes the border feel like two beveled
// Row 3: shadow (inner crease -- makes the border feel like two beveled
// ridges rather than one flat edge)
//
// Bottom/right are mirror-reversed (shadow, shadow, face, highlight). This
@ -253,7 +253,7 @@ static void drawBorderFrame(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
// Draws a small raised beveled square (1px bevel) used as the base for
// close, minimize, and maximize buttons. The icon (bar, box, dot) is drawn
// on top by the caller. Using 1px bevels on gadgets (vs 4px on the frame)
// keeps them visually subordinate to the window border a Motif convention
// keeps them visually subordinate to the window border -- a Motif convention
// that helps users distinguish clickable controls from structural chrome.
static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size) {
@ -278,7 +278,7 @@ static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT
// that long label text doesn't bleed into the window border. The clip is
// saved/restored rather than set to the full dirty rect because this
// function is called from wmDrawChrome which has already set the clip to
// the dirty rect we need to intersect with both. The separator line at
// the dirty rect -- we need to intersect with both. The separator line at
// the bottom visually separates the menu bar from the content area.
static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win) {
@ -331,7 +331,7 @@ static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fon
// ============================================================
//
// GEOS Ensemble Motif-style resize indicators: perpendicular grooves that
// cut across the window border near each corner. Two breaks per corner
// cut across the window border near each corner. Two breaks per corner --
// one on each edge meeting at that corner. Each groove is a 2px sunken
// notch (shadow line + highlight line) cutting across the full 4px border
// width, perpendicular to the edge direction.
@ -479,11 +479,11 @@ static void drawScaledRect(DisplayT *d, int32_t dstX, int32_t dstY, int32_t dstW
// over the trough ends, and finally the thumb bevel in the middle. Arrow
// buttons and thumb use 1px raised bevels for a clickable appearance;
// the trough uses a 1px sunken bevel (swapped highlight/shadow) to appear
// recessed standard Motif scrollbar convention.
// recessed -- standard Motif scrollbar convention.
//
// winX/winY are the window's screen position; sb->x/y are relative to the
// window origin. This split lets scrollbar positions survive window drags
// without recalculation only winX/winY change.
// without recalculation -- only winX/winY change.
static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, const ScrollbarT *sb, int32_t winX, int32_t winY) {
int32_t x = winX + sb->x;
@ -492,7 +492,7 @@ static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
BevelStyleT btnBevel;
btnBevel.highlight = colors->windowHighlight;
btnBevel.shadow = colors->windowShadow;
btnBevel.face = colors->buttonFace;
btnBevel.face = colors->scrollbarBg;
btnBevel.width = 1;
if (sb->orient == ScrollbarVerticalE) {
@ -563,13 +563,13 @@ static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
// row is 2 pixels wider (1 pixel on each side), producing a symmetric
// isoceles triangle. SB_ARROW_ROWS (4) rows gives a 7-pixel-wide base,
// which fits well inside SCROLLBAR_WIDTH (16px) buttons. The glyph is
// centered on the button using integer division no sub-pixel alignment
// centered on the button using integer division -- no sub-pixel alignment
// needed at these sizes.
static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size, int32_t dir) {
int32_t cx = x + size / 2;
int32_t cy = y + size / 2;
uint32_t fg = colors->contentFg;
uint32_t fg = colors->scrollbarFg;
// Draw a small triangle
for (int32_t i = 0; i < SB_ARROW_ROWS; i++) {
@ -602,7 +602,7 @@ static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSche
// and maximize gadgets (right), and centered title text.
//
// The title bar background uses active/inactive colors to provide a strong
// visual cue for which window has keyboard focus the same convention used
// visual cue for which window has keyboard focus -- the same convention used
// by Windows 3.x, Motif, and CDE. Only the title bar changes color on
// focus change; the rest of the chrome stays the same. This is why
// wmSetFocus dirties only the title bar area, not the entire window.
@ -667,7 +667,7 @@ static void drawTitleBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
// Title text is centered in the available space between gadgets. If the
// title is too long, it's truncated by character count (not pixel width)
// since the font is fixed-width. No ellipsis is added at these sizes,
// since the font is fixed-width. No ellipsis is added -- at these sizes,
// ellipsis would consume 3 characters that could show useful text instead.
int32_t availW = g.textRightEdge - g.textLeftEdge;
@ -1003,7 +1003,7 @@ void wmAddMenuSeparator(MenuT *menu) {
// Creates a cascading submenu. Unlike top-level menus (inline in MenuBarT),
// submenus are heap-allocated because the nesting depth is unpredictable and
// the submenu is owned by its parent item. The item's id is set to -1 to
// distinguish it from leaf items during event dispatch clicking a submenu
// distinguish it from leaf items during event dispatch -- clicking a submenu
// item opens the child rather than firing a command.
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) {
@ -1070,13 +1070,13 @@ ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
// the frame.
//
// Each window gets a unique monotonic ID (static nextId) used for lookup
// by the event loop and DXE app system. IDs are never reused with 2^31
// by the event loop and DXE app system. IDs are never reused -- with 2^31
// IDs available and windows being created/destroyed interactively, this
// will never wrap in practice.
//
// The content buffer is initialized to 0xFF (white) so newly created
// windows have a clean background before the app's first onPaint fires.
// maxW/maxH default to -1 meaning "use screen dimensions" apps can
// maxW/maxH default to -1 meaning "use screen dimensions" -- apps can
// override this to constrain maximized size (e.g. for dialog-like windows).
//
// New windows are added at stack->count (the top), so they appear in front
@ -1213,7 +1213,7 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) {
//
// Initiates a window drag by recording the mouse offset from the window
// origin. This offset is maintained throughout the drag so that the window
// tracks the mouse cursor without jumping the window position under the
// tracks the mouse cursor without jumping -- the window position under the
// cursor stays consistent from mousedown to mouseup.
void wmDragBegin(WindowStackT *stack, int32_t idx, int32_t mouseX, int32_t mouseY) {
@ -1244,7 +1244,7 @@ void wmDragEnd(WindowStackT *stack) {
//
// Unlike some WMs that use an "outline drag" (drawing a wireframe while
// dragging and moving the real window on mouse-up), we do full content
// dragging. This is feasible because the content buffer is persistent
// dragging. This is feasible because the content buffer is persistent --
// we don't need to ask the app to repaint during the drag, just blit from
// its buffer at the new position.
@ -1261,6 +1261,10 @@ void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mou
win->x = mouseX - stack->dragOffX;
win->y = mouseY - stack->dragOffY;
if (win->maximized) {
win->maximized = false;
}
// Clamp: keep title bar reachable
if (win->y < 0) {
win->y = 0;
@ -1293,7 +1297,7 @@ void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mou
//
// The clip rect is set to the dirty rect so all draw operations are
// automatically clipped. This is the mechanism by which partial chrome
// repaints work if only the title bar is dirty, the border fill runs
// repaints work -- if only the title bar is dirty, the border fill runs
// but its output is clipped away for scanlines outside the dirty rect.
// The clip rect is saved/restored because the caller (compositeAndFlush)
// manages its own clip state across multiple windows.
@ -1359,7 +1363,7 @@ void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, con
// The blit uses direct memcpy rather than going through the rectCopy
// drawing primitive, because we've already computed the exact intersection
// and don't need the general-purpose clip logic. On 486/Pentium, memcpy
// compiles to rep movsd which saturates the memory bus no further
// compiles to rep movsd which saturates the memory bus -- no further
// optimization is possible at this level.
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo) {
@ -1406,7 +1410,7 @@ void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *
// 2. A live thumbnail of the window's content buffer (also scaled)
// 3. A grey fill if neither is available
//
// The live thumbnail approach (option 2) is a DESQview/X homage DV/X
// The live thumbnail approach (option 2) is a DESQview/X homage -- DV/X
// showed miniature window contents in its icon/task view. The thumbnail
// is rendered from the existing content buffer, so no extra rendering
// pass is needed. contentDirty tracks whether the content has changed
@ -1459,7 +1463,7 @@ void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp);
} else {
// No content draw grey fill
// No content -- draw grey fill
rectFill(d, ops, contentX, contentY, ICON_SIZE, ICON_SIZE, colors->windowFace);
}
}
@ -1557,7 +1561,7 @@ int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hi
return i;
}
// Title bar (drag area between gadgets)
// Title bar (drag area -- between gadgets)
if (my >= g.titleY && my < g.titleY + CHROME_TITLE_HEIGHT &&
mx >= g.titleX && mx < g.titleX + g.titleW) {
*hitPart = HIT_TITLE;
@ -1653,7 +1657,7 @@ void wmInit(WindowStackT *stack) {
// The content buffer must be reallocated because the content area changes
// size. After reallocation, onResize notifies the app of the new dimensions,
// then onPaint requests a full repaint into the new buffer. This is
// synchronous the maximize completes in one frame, avoiding flicker.
// synchronous -- the maximize completes in one frame, avoiding flicker.
//
// Both old and new positions are dirtied: old to expose what was behind the
// window at its previous size, new to paint the window at its maximized size.
@ -1706,7 +1710,7 @@ void wmMaximize(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT
//
// Minimizes a window: marks it minimized so it's skipped during compositing
// (except for icon drawing) and moves focus to the next available window.
// The window's geometry and content buffer are preserved no reallocation
// The window's geometry and content buffer are preserved -- no reallocation
// is needed since the window retains its size and will be restored to the
// same position.
//
@ -1788,7 +1792,7 @@ int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t
// + menu bar (if present) + scrollbar space (if present).
//
// This function is called on every resize move event, so it must be cheap.
// No allocations, no string operations just arithmetic on cached values.
// No allocations, no string operations -- just arithmetic on cached values.
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH) {
int32_t gadgetS = CHROME_TITLE_HEIGHT - GADGET_INSET * 2;
@ -1932,7 +1936,7 @@ void wmRaiseWindow(WindowStackT *stack, DirtyListT *dl, int32_t idx) {
//
// Frees and reallocates the content buffer to match the current contentW/H.
// Called after any geometry change (resize, maximize, restore). The old
// buffer contents are discarded the caller is expected to trigger
// buffer contents are discarded -- the caller is expected to trigger
// onResize + onPaint to refill it. This is simpler (and on 486, faster)
// than copying and scaling the old content.
//
@ -1993,7 +1997,7 @@ void wmResizeBegin(WindowStackT *stack, int32_t idx, int32_t edge, int32_t mouse
// The grab area extends 2 pixels beyond the visual border (CHROME_BORDER_WIDTH
// + 2 = 6px) to make edges easier to grab, especially the 4px-wide border
// which would be frustratingly small on its own. This is a common usability
// trick the visual border is narrower than the hit zone.
// trick -- the visual border is narrower than the hit zone.
int32_t wmResizeEdgeHit(const WindowT *win, int32_t mx, int32_t my) {
int32_t edge = RESIZE_NONE;
@ -2047,12 +2051,12 @@ void wmResizeEnd(WindowStackT *stack) {
// only on axes where the resize was actually applied. If clamped (window at
// min/max size), dragOff is NOT updated on that axis, so the accumulated
// delta tracks how far the mouse moved past the border. When the user
// reverses direction, the border immediately follows it "sticks" to
// reverses direction, the border immediately follows -- it "sticks" to
// the mouse pointer instead of creating a dead zone.
//
// If the user resizes while maximized, the maximized flag is cleared.
// This prevents wmRestore from snapping back to the pre-maximize geometry,
// which would be confusing the user's manual resize represents their
// which would be confusing -- the user's manual resize represents their
// new intent.
void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t *mouseX, int32_t *mouseY) {
@ -2665,7 +2669,7 @@ void wmSetTitle(WindowT *win, DirtyListT *dl, const char *title) {
// scrollbar's presence. This creates the standard L-shaped layout where the
// scrollbars meet at the bottom-right corner with a small dead zone between
// them (the corner square where both scrollbars would overlap is simply not
// covered it shows the window face color from drawBorderFrame).
// covered -- it shows the window face color from drawBorderFrame).
void wmUpdateContentRect(WindowT *win) {
int32_t topChrome = CHROME_TOTAL_TOP;

View file

@ -1,4 +1,4 @@
// dvx_wm.h Layer 4: Window manager for DVX GUI
// dvx_wm.h -- Layer 4: Window manager for DVX GUI
//
// Manages the window lifecycle, Z-order stack, chrome drawing, hit testing,
// and interactive operations (drag, resize, scroll). This layer bridges the
@ -8,7 +8,7 @@
// Design philosophy: the WM owns window geometry and chrome, but the
// window's content is owned by the application (via callbacks or the widget
// system). This separation means the WM can move, resize, and repaint
// window frames without involving the application at all only content
// window frames without involving the application at all -- only content
// changes trigger application callbacks.
//
// All WM operations that change visible screen state accept a DirtyListT*
@ -24,7 +24,7 @@ void wmInit(WindowStackT *stack);
// Allocate a new WindowT, initialize its geometry and content buffer, and
// push it to the top of the Z-order stack. The returned window has default
// callbacks (all NULL) the caller should set onPaint/onKey/etc. before
// callbacks (all NULL) -- the caller should set onPaint/onKey/etc. before
// the next event loop iteration.
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);
@ -50,7 +50,7 @@ void wmUpdateContentRect(WindowT *win);
// Reallocate the per-window content backbuffer to match the current
// contentW/H. Returns 0 on success, -1 if allocation fails. The old
// buffer contents are lost the caller should trigger a full repaint
// buffer contents are lost -- the caller should trigger a full repaint
// via onPaint after a successful realloc.
int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);
@ -72,7 +72,7 @@ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked);
// Add a radio-style item. Radio groups are defined implicitly by
// consecutive radio items in the same menu selecting one unchecks
// consecutive radio items in the same menu -- selecting one unchecks
// the others in the group.
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked);
@ -101,7 +101,7 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win, const RectT *clipTo);
// Blit the window's content backbuffer into the display backbuffer,
// clipped to the dirty rect. This is a pure copy (no drawing) the
// clipped to the dirty rect. This is a pure copy (no drawing) -- the
// content was already rendered by the application into contentBuf.
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo);

View file

@ -1,13 +1,13 @@
// dvxPlatform.h Platform abstraction layer for DVX GUI
// dvxPlatform.h -- Platform abstraction layer for DVX GUI
//
// All OS-specific and CPU-specific code is isolated behind this
// interface. To port DVX to a new platform, implement a new
// dvxPlatformXxx.c against this header.
//
// Currently two implementations exist:
// dvxPlatformDos.c DJGPP/DPMI: real VESA VBE, INT 33h mouse,
// dvxPlatformDos.c -- DJGPP/DPMI: real VESA VBE, INT 33h mouse,
// INT 16h keyboard, rep movsd/stosl asm spans
// dvxPlatformLinux.c SDL2: software rendering to an SDL window,
// dvxPlatformLinux.c -- SDL2: software rendering to an SDL window,
// used for development and testing on Linux
//
// The abstraction covers five areas: video mode setup, framebuffer
@ -45,7 +45,7 @@ typedef struct {
// for clean shutdown on Ctrl+C/Ctrl+Break. On Linux this initializes SDL.
void platformInit(void);
// Cooperative yield give up the CPU timeslice when the event loop has
// Cooperative yield -- give up the CPU timeslice when the event loop has
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
// multitaskers (Windows 3.x, OS/2, DESQview). On Linux this calls
// SDL_Delay(1) to avoid busy-spinning at 100% CPU.
@ -82,7 +82,7 @@ void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t cou
// Copy a rectangle from the system RAM backbuffer (d->backBuf) to the
// display surface (d->lfb). On DOS this copies to real video memory via
// the LFB mapping the critical path where PCI bus write speed matters.
// the LFB mapping -- the critical path where PCI bus write speed matters.
// On Linux this copies to the SDL surface, then SDL_UpdateRect is called.
// Each scanline is copied as a contiguous block; rep movsd on DOS gives
// near-optimal bus utilization for aligned 32-bit writes.
@ -92,7 +92,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r);
// Optimised memory operations (span fill / copy)
// ============================================================
//
// These are the innermost loops of the renderer called once per
// These are the innermost loops of the renderer -- called once per
// scanline of every rectangle fill, blit, and text draw. On DOS they
// use inline assembly: rep stosl for fills (one instruction fills an
// entire scanline) and rep movsd for copies. On Linux they use memset/
@ -112,7 +112,7 @@ void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
// ============================================================
// Input Mouse
// Input -- Mouse
// ============================================================
// Initialize the mouse driver and constrain movement to the screen bounds.
@ -122,14 +122,14 @@ void platformMouseInit(int32_t screenW, int32_t screenH);
// Poll the current mouse state. Buttons is a bitmask: bit 0 = left,
// bit 1 = right, bit 2 = middle. Polling (rather than event-driven
// callbacks) is the natural model for a cooperative event loop the
// callbacks) is the natural model for a cooperative event loop -- the
// main loop polls once per frame and compares with the previous state
// to detect press/release edges.
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);
// Detect and activate mouse wheel support. Returns true if the mouse
// driver supports the CuteMouse Wheel API (INT 33h AX=0011h). This
// call also activates wheel reporting after it returns true, function
// call also activates wheel reporting -- after it returns true, function
// 03h will return wheel delta in BH. Must be called after platformMouseInit.
bool platformMouseWheelInit(void);
@ -150,7 +150,7 @@ void platformMouseSetAccel(int32_t threshold);
void platformMouseWarp(int32_t x, int32_t y);
// ============================================================
// Input Keyboard
// Input -- Keyboard
// ============================================================
// Return the current modifier key state in BIOS shift-state format:
@ -167,7 +167,7 @@ int32_t platformKeyboardGetModifiers(void);
bool platformKeyboardRead(PlatformKeyEventT *evt);
// Translate an Alt+key scancode to its corresponding ASCII character.
// When Alt is held, DOS doesn't provide the ASCII value only the
// When Alt is held, DOS doesn't provide the ASCII value -- only the
// scancode. This function contains a lookup table mapping scancodes
// to their unshifted letter/digit. Returns 0 for scancodes that don't
// map to a printable character (e.g. Alt+F1).
@ -204,6 +204,11 @@ const char *platformValidateFilename(const char *name);
// and free physical memory in kilobytes. Returns false if unavailable.
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
// Create a directory and all parent directories (like mkdir -p).
// Returns 0 on success, -1 on failure. Existing directories are not
// an error.
int32_t platformMkdirRecursive(const char *path);
// Change the working directory, including drive letter on DOS. Standard
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
// first when the path contains a drive prefix (e.g. "A:\DVX").

View file

@ -1,4 +1,4 @@
// dvxPlatformDos.c DOS/DJGPP platform implementation for DVX GUI
// dvxPlatformDos.c -- DOS/DJGPP platform implementation for DVX GUI
//
// All BIOS calls, DPMI functions, port I/O, inline assembly, and
// DOS-specific file handling are isolated in this single file.
@ -21,7 +21,7 @@
// IRQ1 to feed the BIOS buffer. The BIOS approach is simpler and more
// portable across emulators (DOSBox, 86Box, PCem all handle it correctly).
//
// Why INT 33h for mouse: same rationale the mouse driver handles
// Why INT 33h for mouse: same rationale -- the mouse driver handles
// PS/2 and serial mice transparently, and every DOS emulator provides
// a compatible driver. Polling via function 03h avoids the complexity
// of installing a real-mode callback for mouse events.
@ -31,14 +31,16 @@
#include <ctype.h>
#include <dir.h>
#include <errno.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
// DJGPP-specific headers this is the ONLY file that includes these
// DJGPP-specific headers -- this is the ONLY file that includes these
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
@ -132,7 +134,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pref
memset(&bestDisplay, 0, sizeof(bestDisplay));
// Get VBE controller info the transfer buffer (__tb) is the DJGPP-
// Get VBE controller info -- the transfer buffer (__tb) is the DJGPP-
// provided region in conventional memory that real-mode BIOS calls
// can read/write. We split it into seg:off for the INT 10h call.
uint32_t infoSeg = __tb >> 4;
@ -172,7 +174,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pref
// VBE 2.0+ is required for LFB (Linear Frame Buffer) support.
// VBE 1.x only supports bank switching, which we explicitly don't
// implement the complexity isn't worth it for 486+ targets.
// implement -- the complexity isn't worth it for 486+ targets.
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
if (vbeVersion < 0x0200) {
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
@ -323,7 +325,7 @@ void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *
// pixels can't use dword-aligned rep stosl fills without masking.
//
// The physical LFB address is temporarily stored in d->lfb as a raw
// integer cast it will be properly mapped via DPMI in mapLfb() later.
// integer cast -- it will be properly mapped via DPMI in mapLfb() later.
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
__dpmi_regs r;
@ -343,7 +345,7 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
// VBE mode attribute word at offset 0:
// bit 7 = LFB available, bit 4 = graphics mode (not text)
// Both are required we never bank-switch and never want text modes.
// Both are required -- we never bank-switch and never want text modes.
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
if (!(attr & 0x0080)) {
@ -442,14 +444,14 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
// access.
//
// The mapping process has three steps:
// 1. __dpmi_physical_address_mapping() asks the DPMI host to
// 1. __dpmi_physical_address_mapping() -- asks the DPMI host to
// create a linear address mapping for the physical framebuffer.
// This is necessary because DPMI runs in protected mode with
// paging; physical addresses aren't directly accessible.
// 2. __dpmi_lock_linear_region() pins the mapped pages so they
// 2. __dpmi_lock_linear_region() -- pins the mapped pages so they
// can't be swapped out. The LFB is memory-mapped I/O to the
// video card; paging it would be catastrophic.
// 3. __djgpp_nearptr_enable() disables DJGPP's default segment
// 3. __djgpp_nearptr_enable() -- disables DJGPP's default segment
// limit checking so we can use plain C pointers to access the
// LFB address. Without this, all LFB access would require far
// pointer calls (_farpokeb etc.), which are much slower because
@ -457,7 +459,7 @@ static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requ
//
// Why near pointers: the performance difference is dramatic.
// platformFlushRect() copies thousands of dwords per frame using
// rep movsl this only works with near pointers. Far pointer access
// rep movsl -- this only works with near pointers. Far pointer access
// would add ~10 cycles per byte and make 60fps impossible on a 486.
//
// The final pointer calculation adds __djgpp_conventional_base, which
@ -529,7 +531,7 @@ void platformChdir(const char *path) {
// ============================================================
//
// Copies a dirty rectangle from the system RAM backbuffer to the LFB.
// This is the critical path for display updates the compositor calls
// This is the critical path for display updates -- the compositor calls
// it once per dirty rect per frame.
//
// Two code paths:
@ -580,7 +582,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r) {
*dst++ = *src++;
}
} else {
// Partial scanlines copy row by row with rep movsd
// Partial scanlines -- copy row by row with rep movsd
int32_t dwords = rowBytes >> 2;
int32_t remainder = rowBytes & 3;
for (int32_t i = 0; i < h; i++) {
@ -607,7 +609,7 @@ void platformFlushRect(const DisplayT *d, const RectT *r) {
// ============================================================
// System information static buffer and helpers
// System information -- static buffer and helpers
// ============================================================
static char sSysInfoBuf[PLATFORM_SYSINFO_MAX];
@ -637,7 +639,7 @@ static void sysInfoAppend(const char *fmt, ...) {
// ============================================================
// estimateClockMhz RDTSC calibration via BIOS timer
// estimateClockMhz -- RDTSC calibration via BIOS timer
// ============================================================
//
// Measures TSC ticks over 3 BIOS timer ticks (~165 ms). The BIOS timer
@ -688,7 +690,7 @@ static uint32_t estimateClockMhz(void) {
// ============================================================
// hasCpuid check if CPUID instruction is available
// hasCpuid -- check if CPUID instruction is available
// ============================================================
//
// The CPUID instruction exists if bit 21 (ID flag) of EFLAGS can be
@ -1079,15 +1081,67 @@ bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
}
// ============================================================
// platformMkdirRecursive
// ============================================================
//
// Creates a directory and all parent directories that don't exist.
// Works by walking the path from left to right, creating each
// component. mkdir() on an existing directory returns EEXIST which
// is silently ignored.
int32_t platformMkdirRecursive(const char *path) {
char buf[260];
strncpy(buf, path, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0';
for (char *p = buf; *p; p++) {
// Skip drive letter (e.g. "C:\")
if (p == buf + 1 && *p == ':') {
continue;
}
if (*p == '/' || *p == '\\') {
*p = '\0';
if (buf[0] != '\0') {
mkdir(buf, 0755);
}
*p = '/';
}
}
// Create the final directory
if (mkdir(buf, 0755) != 0 && errno != EEXIST) {
return -1;
}
return 0;
}
// ============================================================
// platformInit
// ============================================================
void platformInit(void) {
// Disable Ctrl+C/Break so the user can't accidentally kill the
// GUI while in graphics mode (which would leave the display in
// an unusable state without restoring text mode first).
// Disable Ctrl+C/Break at every level so the user can't
// accidentally kill the GUI while in graphics mode.
// 1. DJGPP-level: disable Ctrl+C detection in the runtime
__djgpp_set_ctrl_c(0);
// 2. C-level: ignore SIGINT if it somehow fires
signal(SIGINT, SIG_IGN);
// 3. DOS-level: disable break checking during INT 21h calls
// (function 33h, subfunction 01h, DL=0 = break off)
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x3301;
r.x.dx = 0x0000;
__dpmi_int(0x21, &r);
}
@ -1125,7 +1179,7 @@ int32_t platformKeyboardGetModifiers(void) {
// functions have been standard since AT-class machines (1984).
//
// The two-step peek-then-read is necessary because function 10h
// (read key) blocks until a key is available there's no non-blocking
// (read key) blocks until a key is available -- there's no non-blocking
// read in the BIOS API. Function 11h (check key) peeks without
// consuming, letting us poll without blocking the event loop.
@ -1200,7 +1254,7 @@ int32_t platformStripLineEndings(char *buf, int32_t len) {
// because the default range may be 640x200 (CGA text mode).
// Without this, mouse coordinates would be wrong or clipped.
//
// The hardware cursor is never shown DVX composites its own
// The hardware cursor is never shown -- DVX composites its own
// software cursor on top of the backbuffer. We only use INT 33h
// for position/button state via polling (function 03h).
@ -1390,7 +1444,7 @@ char *platformPathDirEnd(const char *path) {
// bus speed.
//
// rep movsl moves 4 bytes per iteration with hardware loop decrement,
// which is faster than a C for-loop the CPU string move pipeline
// which is faster than a C for-loop -- the CPU string move pipeline
// optimizes sequential memory access patterns.
void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
@ -1461,7 +1515,7 @@ void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
// ============================================================
//
// 32-bit pixels are inherently dword-aligned, so no alignment
// preamble is needed straight to rep movsl.
// preamble is needed -- straight to rep movsl.
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
__asm__ __volatile__ (
@ -1487,7 +1541,7 @@ void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) {
uint8_t c = (uint8_t)color;
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
// Align to 4 bytes skip if already aligned
// Align to 4 bytes -- skip if already aligned
if (__builtin_expect((uintptr_t)dst & 3, 0)) {
while (((uintptr_t)dst & 3) && count > 0) {
*dst++ = c;
@ -1555,7 +1609,7 @@ void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) {
// platformSpanFill32
// ============================================================
//
// 32-bit fill is the simplest case each pixel is already a dword,
// 32-bit fill is the simplest case -- each pixel is already a dword,
// so rep stosl writes exactly one pixel per iteration with no
// alignment or packing concerns.
@ -1570,7 +1624,7 @@ void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
// ============================================================
// platformValidateFilename DOS 8.3 filename validation
// platformValidateFilename -- DOS 8.3 filename validation
// ============================================================
//
// Validates that a filename conforms to DOS 8.3 conventions:
@ -1580,7 +1634,7 @@ void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) {
//
// The reserved name check compares the base name only (before the
// dot), case-insensitive, because DOS treats "CON.TXT" the same
// as the CON device the extension is ignored for device names.
// as the CON device -- the extension is ignored for device names.
//
// Returns NULL on success, or a human-readable error string on failure.
// On non-DOS platforms, this function would be replaced with one that
@ -1664,9 +1718,9 @@ const char *platformValidateFilename(const char *name) {
// ============================================================
//
// Complete video initialization sequence:
// 1. findBestMode() enumerate VESA modes and pick the best match
// 2. setVesaMode() actually switch to the chosen mode with LFB
// 3. mapLfb() DPMI-map the physical framebuffer into linear memory
// 1. findBestMode() -- enumerate VESA modes and pick the best match
// 2. setVesaMode() -- actually switch to the chosen mode with LFB
// 3. mapLfb() -- DPMI-map the physical framebuffer into linear memory
// 4. Allocate system RAM backbuffer (same size as LFB)
// 5. Set up 8-bit palette if needed
// 6. Initialize clip rect to full display
@ -1755,7 +1809,7 @@ int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, i
//
// Direct port I/O is used instead of VBE function 09h (set palette)
// because the VGA DAC ports are faster (no BIOS call overhead) and
// universally compatible even VBE 3.0 cards still have the standard
// universally compatible -- even VBE 3.0 cards still have the standard
// VGA DAC at ports 0x3C8/0x3C9.
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {

View file

@ -1,4 +1,4 @@
// widgetAnsiTerm.c ANSI BBS terminal emulator widget
// widgetAnsiTerm.c -- ANSI BBS terminal emulator widget
//
// Implements a VT100/ANSI-compatible terminal emulator widget designed for
// connecting to BBS systems over the serial link. The terminal uses a
@ -7,15 +7,15 @@
// chosen because:
// 1. It maps directly to the BBS/ANSI art paradigm (CP437 character set,
// 16-color CGA palette, blink attribute)
// 2. Cell-based storage is extremely compact 2 bytes per cell means an
// 2. Cell-based storage is extremely compact -- 2 bytes per cell means an
// 80x25 screen is only 4000 bytes, fitting in L1 cache on a 486
// 3. Dirty-row tracking via a 32-bit bitmask allows sub-millisecond
// incremental repaints without scanning the entire buffer
//
// The ANSI parser is a 3-state machine (NORMAL → ESC → CSI) that handles
// The ANSI parser is a 3-state machine (NORMAL -> ESC -> CSI) that handles
// the subset of sequences commonly used by DOS BBS software: cursor movement,
// screen/line erase, scrolling regions, SGR colors, and a few DEC private modes.
// Full VT100 conformance is explicitly NOT a goal only sequences actually
// Full VT100 conformance is explicitly NOT a goal -- only sequences actually
// emitted by real BBS systems are implemented.
//
// Scrollback is implemented as a circular buffer of row snapshots. Only
@ -28,7 +28,7 @@
// - Fast repaint (wgtAnsiTermRepaint): bypasses the widget pipeline
// entirely, rendering dirty rows directly into the window's content
// buffer. This is critical for serial communication where ACK turnaround
// time matters the fewer milliseconds between receiving data and
// time matters -- the fewer milliseconds between receiving data and
// displaying it, the higher the effective throughput.
//
// Communication is abstracted through read/write function pointers, allowing
@ -135,7 +135,7 @@ static void ansiTermSelectionRange(const WidgetT *w, int32_t *startLine, int32_t
// ============================================================
//
// Copy a screen row into the scrollback circular buffer.
// The circular buffer avoids any need for memmove when the buffer fills
// The circular buffer avoids any need for memmove when the buffer fills --
// the head index simply wraps around, overwriting the oldest entry. This
// is O(1) per row regardless of scrollback size, which matters when BBS
// software rapidly dumps text (e.g. file listings, ANSI art).
@ -225,7 +225,7 @@ static void ansiTermCopySelection(WidgetT *w) {
// Build text from selected cells (strip trailing spaces per line).
// Trailing spaces are stripped because BBS text mode fills the entire
// row with spaces without stripping, pasting would include many
// row with spaces -- without stripping, pasting would include many
// unwanted trailing blanks. Fixed 4KB buffer is sufficient for typical
// terminal selections (80 cols * 50 rows = 4000 chars max).
char buf[4096];
@ -269,7 +269,7 @@ static void ansiTermCopySelection(WidgetT *w) {
// ============================================================
//
// Mark rows dirty that are touched by a cell range [startCell, startCell+count).
// Uses a 32-bit bitmask — one bit per row — which limits tracking to the first
// Uses a 32-bit bitmask -- one bit per row -- which limits tracking to the first
// 32 rows. This is fine because standard terminal sizes are 24-25 rows, and
// bitmask operations are single-cycle on the target CPU. The bitmask approach
// is much cheaper than maintaining a dirty rect list for per-row tracking.
@ -345,7 +345,7 @@ static void ansiTermDeleteLines(WidgetT *w, int32_t count) {
// Central CSI dispatcher. After the parser accumulates parameters in the CSI
// state, the final byte triggers dispatch here. Only sequences commonly used
// by BBS software are implemented exotic VT220+ sequences are silently ignored.
// by BBS software are implemented -- exotic VT220+ sequences are silently ignored.
// DEC private modes (ESC[?...) are handled separately since they use a different
// parameter namespace than standard ECMA-48 sequences.
static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
@ -474,7 +474,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
if (w->as.ansiTerm->commWrite) {
// Respond as VT100 with advanced video option (AVO).
// Many BBS door games query DA to detect terminal capabilities.
// Claiming VT100+AVO is the safest response it tells the remote
// Claiming VT100+AVO is the safest response -- it tells the remote
// side we support ANSI color without implying VT220+ features.
const uint8_t reply[] = "\033[?1;2c";
w->as.ansiTerm->commWrite(w->as.ansiTerm->commCtx, reply, 7);
@ -632,7 +632,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
if (w->as.ansiTerm->commWrite) {
if (mode == 6) {
// CPR cursor position report: ESC[row;colR (1-based).
// CPR -- cursor position report: ESC[row;colR (1-based).
// BBS software uses this for screen-size detection and
// to synchronize cursor positioning in door games.
char reply[16];
@ -673,7 +673,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
w->as.ansiTerm->scrollTop = top;
w->as.ansiTerm->scrollBot = bot;
} else {
// Invalid or reset restore full screen
// Invalid or reset -- restore full screen
w->as.ansiTerm->scrollTop = 0;
w->as.ansiTerm->scrollBot = rows - 1;
}
@ -706,7 +706,7 @@ static void ansiTermDispatchCsi(WidgetT *w, uint8_t cmd) {
// Erase Display (ED): mode 0 = cursor to end, 1 = start to cursor, 2 = all.
// Mode 2 pushes all visible lines to scrollback before clearing, preserving
// content that was on screen this is what users expect when a BBS sends
// content that was on screen -- this is what users expect when a BBS sends
// a clear-screen sequence (they can scroll back to see previous content).
// The wasAtBottom check ensures auto-scroll tracking: if the user was
// already viewing the latest content, they stay at the bottom after new
@ -723,7 +723,7 @@ static void ansiTermEraseDisplay(WidgetT *w, int32_t mode) {
// Erase from start to cursor
ansiTermFillCells(w, 0, cur + 1);
} else if (mode == 2) {
// Erase entire screen push all lines to scrollback first
// Erase entire screen -- push all lines to scrollback first
bool wasAtBottom = (w->as.ansiTerm->scrollPos == w->as.ansiTerm->scrollbackCount);
for (int32_t r = 0; r < rows; r++) {
@ -768,7 +768,7 @@ static void ansiTermEraseLine(WidgetT *w, int32_t mode) {
//
// Fill a range of cells with space + current attribute.
// Uses the current attribute (not default) so that erasing respects the
// currently active background color this is correct per ECMA-48 and
// currently active background color -- this is correct per ECMA-48 and
// matches what BBS software expects (e.g., colored backgrounds that
// persist after a line erase).
@ -801,10 +801,10 @@ static void ansiTermFillCells(WidgetT *w, int32_t start, int32_t count) {
//
// Get a pointer to the cell data for a given line index in the
// combined scrollback+screen view.
// lineIndex < scrollbackCount scrollback line
// lineIndex >= scrollbackCount screen line
// lineIndex < scrollbackCount -> scrollback line
// lineIndex >= scrollbackCount -> screen line
//
// The unified line index simplifies paint and selection code they don't
// The unified line index simplifies paint and selection code -- they don't
// need to know whether a given line is in scrollback or on screen. The
// circular buffer index computation uses modular arithmetic to map from
// logical scrollback line number to physical buffer position.
@ -909,15 +909,15 @@ static void ansiTermNewline(WidgetT *w) {
// ============================================================
//
// Feed one byte through the ANSI parser state machine.
// This is the hot path for data reception called once per byte received
// This is the hot path for data reception -- called once per byte received
// from the serial link. The state machine is kept as simple as possible
// (no tables, no indirect calls) because this runs on every incoming byte
// and branch prediction on a 486/Pentium benefits from straightforward
// if/switch chains.
//
// In PARSE_NORMAL, C0 control characters (CR, LF, BS, TAB, FF, BEL, ESC)
// are handled first. All other bytes including CP437 graphic characters
// in the 0x01-0x1F range that aren't C0 controls, plus 0x80-0xFF are
// are handled first. All other bytes -- including CP437 graphic characters
// in the 0x01-0x1F range that aren't C0 controls, plus 0x80-0xFF -- are
// treated as printable and placed at the cursor via ansiTermPutChar.
static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
@ -942,12 +942,12 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
w->as.ansiTerm->cursorCol = w->as.ansiTerm->cols - 1;
}
} else if (ch == '\f') {
// Form feed clear screen and home cursor
// Form feed -- clear screen and home cursor
ansiTermEraseDisplay(w, 2);
w->as.ansiTerm->cursorRow = 0;
w->as.ansiTerm->cursorCol = 0;
} else if (ch == '\a') {
// Bell ignored
// Bell -- ignored
} else {
// CP437 graphic characters (smileys, card suits, etc.)
// and all printable characters
@ -962,15 +962,15 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
w->as.ansiTerm->csiPrivate = false;
memset(w->as.ansiTerm->params, 0, sizeof(w->as.ansiTerm->params));
} else if (ch == 'D') {
// IND scroll up one line
// IND -- scroll up one line
ansiTermScrollUp(w);
w->as.ansiTerm->parseState = PARSE_NORMAL;
} else if (ch == 'M') {
// RI scroll down one line
// RI -- scroll down one line
ansiTermScrollDown(w);
w->as.ansiTerm->parseState = PARSE_NORMAL;
} else if (ch == 'c') {
// RIS terminal reset
// RIS -- terminal reset
w->as.ansiTerm->cursorRow = 0;
w->as.ansiTerm->cursorCol = 0;
w->as.ansiTerm->curAttr = ANSI_DEFAULT_ATTR;
@ -983,7 +983,7 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
ansiTermEraseDisplay(w, 2);
w->as.ansiTerm->parseState = PARSE_NORMAL;
} else {
// Unknown escape return to normal
// Unknown escape -- return to normal
w->as.ansiTerm->parseState = PARSE_NORMAL;
}
break;
@ -1011,11 +1011,11 @@ static void ansiTermProcessByte(WidgetT *w, uint8_t ch) {
w->as.ansiTerm->paramCount++;
}
} else if (ch >= 0x40 && ch <= 0x7E) {
// Final byte dispatch the CSI sequence
// Final byte -- dispatch the CSI sequence
ansiTermDispatchCsi(w, ch);
w->as.ansiTerm->parseState = PARSE_NORMAL;
} else {
// Unexpected byte abort sequence
// Unexpected byte -- abort sequence
w->as.ansiTerm->parseState = PARSE_NORMAL;
}
break;
@ -1061,7 +1061,7 @@ static void ansiTermProcessSgr(WidgetT *w) {
w->as.ansiTerm->bold = true;
fg |= 8;
} else if (code == 5) {
// Blink sets bit 7 of attr byte via bg bit 3
// Blink -- sets bit 7 of attr byte via bg bit 3
bg |= 8;
} else if (code == 25) {
// Blink off
@ -1072,7 +1072,7 @@ static void ansiTermProcessSgr(WidgetT *w) {
fg = bg;
bg = tmp;
} else if (code == 8) {
// Invisible foreground same as background
// Invisible -- foreground same as background
fg = bg & 0x07;
} else if (code == 22) {
// Normal intensity
@ -1267,7 +1267,7 @@ static void ansiTermSelectionRange(const WidgetT *w, int32_t *startLine, int32_t
// ============================================================
// Create a new ANSI terminal widget. The cell buffer is allocated as a
// flat array of (char, attr) pairs rows * cols * 2 bytes. The separate
// flat array of (char, attr) pairs -- rows * cols * 2 bytes. The separate
// heap allocation for AnsiTermDataT (via calloc) keeps the WidgetT union
// small; terminal state is substantial (~100+ bytes of fields plus the
// cell and scrollback buffers) so it's pointed to rather than inlined.
@ -1434,19 +1434,19 @@ void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines) {
// Poll the terminal for incoming data and update timers. This should be called
// from the application's main loop at a reasonable frequency. It handles three
// things:
// 1. Text blink timer (500ms) toggles visibility of cells with the blink
// 1. Text blink timer (500ms) -- toggles visibility of cells with the blink
// attribute. Only rows containing blink cells are dirtied, avoiding
// unnecessary repaints of static content.
// 2. Cursor blink timer (250ms) faster than text blink to feel responsive.
// 2. Cursor blink timer (250ms) -- faster than text blink to feel responsive.
// Only the cursor's row is dirtied.
// 3. Comm read pulls up to 256 bytes from the transport and feeds them
// 3. Comm read -- pulls up to 256 bytes from the transport and feeds them
// through the ANSI parser. The 256-byte chunk size balances between
// responsiveness (smaller = more frequent repaints) and throughput
// (larger = fewer function call overhead per byte).
int32_t wgtAnsiTermPoll(WidgetT *w) {
VALIDATE_WIDGET(w, WidgetAnsiTermE, 0);
// Text blink timer toggle visibility and dirty rows with blinking cells
// Text blink timer -- toggle visibility and dirty rows with blinking cells
clock_t now = clock();
clock_t blinkInterval = (clock_t)BLINK_MS * CLOCKS_PER_SEC / 1000;
clock_t curInterval = (clock_t)CURSOR_MS * CLOCKS_PER_SEC / 1000;
@ -1509,15 +1509,15 @@ int32_t wgtAnsiTermPoll(WidgetT *w) {
// for the serial link.
// Fast repaint: renders dirty rows directly into the window's content buffer,
// completely bypassing the normal widget paint pipeline (widgetOnPaint
// widgetPaintOne full tree walk). Returns the number of rows repainted
// completely bypassing the normal widget paint pipeline (widgetOnPaint ->
// widgetPaintOne -> full tree walk). Returns the number of rows repainted
// and optionally reports the vertical extent via outY/outH so the caller
// can issue a minimal compositor dirty rect.
//
// This exists because the normal paint path clears the entire content area
// and repaints all widgets, which is far too expensive to do on every
// incoming serial byte. With fast repaint, the path from data reception
// to LFB flush is: poll → write → dirtyRows → repaint → compositor dirty,
// to LFB flush is: poll -> write -> dirtyRows -> repaint -> compositor dirty,
// keeping the round-trip under 1ms on a Pentium.
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
if (!w || w->type != WidgetAnsiTermE || !w->window) {
@ -1626,7 +1626,7 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
// ============================================================
// Attach communication callbacks to the terminal. The read/write function
// pointers are transport-agnostic the terminal doesn't care whether
// pointers are transport-agnostic -- the terminal doesn't care whether
// data comes from a raw UART, the secLink encrypted channel, or a
// socket-based proxy. The ctx pointer is passed through opaquely.
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t)) {
@ -1645,7 +1645,7 @@ void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t
// Write raw bytes into the terminal for parsing and display. Any active
// selection is cleared first since the screen content is changing and the
// selection coordinates would become stale. Each byte is fed through the
// ANSI parser individually the parser maintains state between calls so
// ANSI parser individually -- the parser maintains state between calls so
// multi-byte sequences split across writes are handled correctly.
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len) {
if (!w || w->type != WidgetAnsiTermE || !data || len <= 0) {
@ -1677,7 +1677,7 @@ void widgetAnsiTermDestroy(WidgetT *w) {
// ============================================================
// Min size = exact pixel dimensions needed for the grid plus border and
// scrollbar. The terminal is not designed to be resizable the grid
// scrollbar. The terminal is not designed to be resizable -- the grid
// dimensions (cols x rows) are fixed at creation time, matching the BBS
// convention of 80x25 or similar fixed screen sizes.
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font) {
@ -1763,7 +1763,7 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// No selection fall through to send ^C to terminal
// No selection -- fall through to send ^C to terminal
}
// Ctrl+V: paste from clipboard to terminal
@ -1806,39 +1806,39 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
buf[0] = 0x08;
len = 1;
} else if (key == (0x48 | 0x100)) {
// Up arrow ESC[A
// Up arrow -> ESC[A
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'A';
len = 3;
} else if (key == (0x50 | 0x100)) {
// Down arrow ESC[B
// Down arrow -> ESC[B
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'B';
len = 3;
} else if (key == (0x4D | 0x100)) {
// Right arrow ESC[C
// Right arrow -> ESC[C
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'C';
len = 3;
} else if (key == (0x4B | 0x100)) {
// Left arrow ESC[D
// Left arrow -> ESC[D
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'D';
len = 3;
} else if (key == (0x47 | 0x100)) {
// Home ESC[H
// Home -> ESC[H
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'H';
len = 3;
} else if (key == (0x4F | 0x100)) {
// End ESC[F
// End -> ESC[F
buf[0] = 0x1B; buf[1] = '['; buf[2] = 'F';
len = 3;
} else if (key == (0x49 | 0x100)) {
// PgUp ESC[5~
// PgUp -> ESC[5~
buf[0] = 0x1B; buf[1] = '['; buf[2] = '5'; buf[3] = '~';
len = 4;
} else if (key == (0x51 | 0x100)) {
// PgDn ESC[6~
// PgDn -> ESC[6~
buf[0] = 0x1B; buf[1] = '['; buf[2] = '6'; buf[3] = '~';
len = 4;
} else if (key == (0x53 | 0x100)) {
// Delete ESC[3~
// Delete -> ESC[3~
buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~';
len = 4;
} else if (key >= 1 && key < 32) {
@ -1866,7 +1866,7 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
// The scrollbar area uses direct hit testing on up/down arrow buttons and
// page-up/page-down regions, with proportional thumb positioning.
// Selection drag is handled externally by the widget event dispatcher via
// sDragTextSelect on mouse-down we set the anchor and enable drag mode.
// sDragTextSelect -- on mouse-down we set the anchor and enable drag mode.
void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
AppContextT *actx = (AppContextT *)root->userData;
const BitmapFontT *font = &actx->font;
@ -1880,7 +1880,7 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
int32_t sbH = rows * font->charHeight;
int32_t arrowH = ANSI_SB_W;
// Click in text area start selection
// Click in text area -- start selection
if (vx < sbX) {
int32_t baseX = hit->x + ANSI_BORDER;
int32_t baseY = hit->y + ANSI_BORDER;
@ -1964,7 +1964,7 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// Down arrow
hit->as.ansiTerm->scrollPos++;
} else if (vy >= sbY + arrowH && vy < sbY + sbH - arrowH) {
// Track area compute thumb position to determine page direction
// Track area -- compute thumb position to determine page direction
int32_t trackY = sbY + arrowH;
int32_t trackH = sbH - arrowH * 2;
@ -2014,12 +2014,12 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// rows, selection overlay, and scrollbar. This is called through the normal
// widget paint pipeline (e.g., on window expose or full invalidation).
// For incremental updates during data reception, wgtAnsiTermRepaint is used
// instead it's much faster since it only repaints dirty rows and skips
// instead -- it's much faster since it only repaints dirty rows and skips
// the border/scrollbar.
//
// The terminal renders its own scrollbar rather than using the shared
// widgetDrawScrollbarV because the scrollbar's total/visible/position
// semantics are different the terminal scrollbar represents scrollback
// semantics are different -- the terminal scrollbar represents scrollback
// lines (historical content above the screen), not a viewport over a
// virtual content area.
void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
@ -2082,7 +2082,7 @@ void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
int32_t arrowH = ANSI_SB_W;
if (sbCount == 0) {
// No scrollback fill scrollbar area with trough color
// No scrollback -- fill scrollbar area with trough color
rectFill(d, ops, sbX, sbY, sbW, sbH, colors->scrollbarTrough);
return;
}

View file

@ -1,21 +1,21 @@
// widgetBox.c VBox, HBox, and Frame container widgets
// widgetBox.c -- VBox, HBox, and Frame container widgets
//
// VBox and HBox are the primary layout containers. They have no visual
// representation of their own they exist purely to arrange children
// representation of their own -- they exist purely to arrange children
// vertically or horizontally. The actual layout algorithm lives in
// widgetLayout.c (widgetCalcMinSizeBox / widgetLayoutBox) which handles
// weight-based space distribution, spacing, padding, and alignment.
//
// VBox and HBox are distinguished by a flag (WCLASS_HORIZ_CONTAINER) in
// the class table rather than having separate code. This keeps the layout
// engine unified the same algorithm works in both orientations by
// engine unified -- the same algorithm works in both orientations by
// swapping which axis is "major" vs "minor".
//
// Frame is a labeled grouping box with a Motif-style beveled border.
// It acts as a VBox for layout purposes (children stack vertically inside
// the frame's padded interior). The title text sits centered vertically
// on the top border line, with a small background-filled gap to visually
// "break" the border behind the title this is the classic Win3.1/Motif
// "break" the border behind the title -- this is the classic Win3.1/Motif
// group box appearance.
#include "widgetInternal.h"
@ -27,14 +27,14 @@
// Paint the frame border and optional title. The border is offset down by
// half the font height so the title text can sit centered on the top edge.
// This creates the illusion of the title "interrupting" the border a
// This creates the illusion of the title "interrupting" the border -- a
// background-colored rectangle is drawn behind the title to erase the
// border pixels, then the title is drawn on top.
//
// Three border styles are supported:
// FrameFlatE single-pixel solid color outline
// FrameInE Motif "groove" (inset bevel: shadow-then-highlight)
// FrameOutE Motif "ridge" (outset bevel: highlight-then-shadow)
// FrameFlatE -- single-pixel solid color outline
// FrameInE -- Motif "groove" (inset bevel: shadow-then-highlight)
// FrameOutE -- Motif "ridge" (outset bevel: highlight-then-shadow)
// The groove/ridge are each two nested 1px bevels with swapped colors.
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
int32_t fb = widgetFrameBorderWidth(w);
@ -100,7 +100,7 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
// Create a Frame container. The title string supports accelerator keys
// (prefixed with '&') which are parsed by accelParse. The title pointer
// is stored directly (not copied) the caller must ensure it remains valid.
// is stored directly (not copied) -- the caller must ensure it remains valid.
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
WidgetT *w = widgetAlloc(parent, WidgetFrameE);

View file

@ -1,9 +1,9 @@
// widgetButton.c Button widget
// widgetButton.c -- Button widget
//
// Standard push button with text label, Motif-style 2px beveled border,
// and press animation. The button uses a two-phase press model:
// - Mouse press: sets pressed=true and stores the widget in sPressedButton.
// The event dispatcher tracks mouse movement if the mouse leaves the
// The event dispatcher tracks mouse movement -- if the mouse leaves the
// button bounds, pressed is cleared (visual feedback), and if it re-enters,
// pressed is re-set. The onClick callback fires only on mouse-up while
// still inside the button. This gives the user a chance to cancel by
@ -105,10 +105,10 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// ============================================================
// Paint: draws the beveled border, centered text, and optional focus rect.
// When pressed, the bevel colors swap (highlightshadow) creating the sunken
// When pressed, the bevel colors swap (highlight<->shadow) creating the sunken
// appearance, and the text shifts by BUTTON_PRESS_OFFSET pixels. Disabled
// buttons use the "embossed" text technique (highlight color at +1,+1, then
// shadow color at 0,0) to create a chiseled/etched look this is the
// shadow color at 0,0) to create a chiseled/etched look -- this is the
// standard Windows 3.1 disabled control appearance.
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;

View file

@ -1,9 +1,9 @@
// widgetCanvas.c Drawable canvas widget (freehand draw, PNG save/load)
// widgetCanvas.c -- Drawable canvas widget (freehand draw, PNG save/load)
//
// The canvas widget provides a pixel buffer in the display's native pixel
// format that applications can draw into directly. It stores pixels in
// display format (not always RGB) to avoid per-pixel conversion on every
// repaint the paint function just does a straight rectCopy blit from
// repaint -- the paint function just does a straight rectCopy blit from
// the canvas buffer to the display. This is critical on a 486 where
// per-pixel format conversion during repaint would be prohibitively slow.
//
@ -17,7 +17,7 @@
// Bresenham's algorithm placing dots along the path. This gives smooth
// freehand drawing with variable pen widths.
//
// Canvas coordinates are independent of widget position (0,0) is the
// Canvas coordinates are independent of widget position -- (0,0) is the
// top-left of the canvas content, not the widget. Mouse events translate
// widget-space coordinates to canvas-space by subtracting the border offset.
@ -41,7 +41,7 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
// Read/write a single pixel at the given address, respecting the display's
// bytes-per-pixel depth (1=8-bit palette, 2=16-bit hicolor, 4=32-bit truecolor).
// These are inline because they're called per-pixel in tight loops (circle fill,
// line draw) the function call overhead would dominate on a 486. The bpp
// line draw) -- the function call overhead would dominate on a 486. The bpp
// branch is predictable since it doesn't change within a single draw operation.
static inline uint32_t canvasGetPixel(const uint8_t *src, int32_t bpp) {
@ -96,7 +96,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
// center), compute the horizontal extent using the circle equation
// dx^2 + dy^2 <= r^2. The horizontal half-span is floor(sqrt(r^2 - dy^2)).
// This approach is faster than checking each pixel individually because
// the inner loop just fills a horizontal run no per-pixel distance check.
// the inner loop just fills a horizontal run -- no per-pixel distance check.
int32_t r2 = rad * rad;
for (int32_t dy = -rad; dy <= rad; dy++) {
@ -106,7 +106,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
continue;
}
// Compute horizontal half-span: dx² <= r² - dy²
// Compute horizontal half-span: dx^2 <= r^2 - dy^2
int32_t dy2 = dy * dy;
int32_t rem = r2 - dy2;
int32_t hspan = 0;
@ -158,7 +158,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
// Bresenham line from (x0,y0) to (x1,y1), placing dots along the path.
// Each point on the line gets a full pen dot (canvasDrawDot), which means
// lines with large pen sizes are smooth rather than aliased. Bresenham was
// chosen over DDA because it's pure integer arithmetic no floating point
// chosen over DDA because it's pure integer arithmetic -- no floating point
// needed, which matters on 486SX (no FPU).
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
@ -202,7 +202,7 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
// is allocated in the display's native pixel format by walking up the widget
// tree to find the AppContextT (which holds the display format info). This
// tree-walk pattern is necessary because the widget doesn't have direct access
// to the display only the root widget's userData points to the AppContextT.
// to the display -- only the root widget's userData points to the AppContextT.
// The buffer is initialized to white using spanFill for performance.
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
if (!parent || w <= 0 || h <= 0) {
@ -401,7 +401,7 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
continue;
}
// Compute horizontal half-span: dx² <= r² - dy²
// Compute horizontal half-span: dx^2 <= r^2 - dy^2
int32_t dy2 = dy * dy;
int32_t rem = r2 - dy2;
int32_t hspan = 0;
@ -526,7 +526,7 @@ uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y) {
// Load an image file into the canvas, replacing the current content.
// Delegates to dvxLoadImage for format decoding and pixel conversion.
// The old buffer is freed and replaced with the new one canvas
// The old buffer is freed and replaced with the new one -- canvas
// dimensions change to match the loaded image.
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
if (!w || w->type != WidgetCanvasE || !path) {
@ -648,7 +648,7 @@ void widgetCanvasDestroy(WidgetT *w) {
// The canvas requests exactly its pixel dimensions plus the sunken bevel
// border. The font parameter is unused since the canvas has no text content.
// The canvas is not designed to scale it reports its exact size as the
// The canvas is not designed to scale -- it reports its exact size as the
// minimum, and the layout engine should respect that.
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
@ -697,7 +697,7 @@ void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// Paint: draws a sunken bevel border then blits the canvas buffer. Because
// the canvas stores pixels in the display's native format, rectCopy is a
// straight memcpy per scanline no per-pixel conversion needed. This makes
// straight memcpy per scanline -- no per-pixel conversion needed. This makes
// repaint essentially free relative to the display bandwidth.
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;

View file

@ -1,8 +1,8 @@
// widgetCheckbox.c Checkbox widget
// widgetCheckbox.c -- Checkbox widget
//
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
// label to the right. The check mark is drawn as two diagonal lines forming an
// "X" pattern rather than a traditional checkmark glyph this is simpler to
// "X" pattern rather than a traditional checkmark glyph -- this is simpler to
// render with drawHLine primitives and matches the DV/X aesthetic.
//
// State management is simple: a boolean 'checked' flag toggles on each click

View file

@ -1,13 +1,13 @@
// widgetClass.c Widget class vtable definitions
// widgetClass.c -- Widget class vtable definitions
//
// This file implements a C vtable pattern for the widget type system.
// Each widget type has a static const WidgetClassT struct that defines
// its behavior paint, layout, mouse handling, keyboard handling, etc.
// its behavior -- paint, layout, mouse handling, keyboard handling, etc.
// A master table (widgetClassTable[]) maps WidgetTypeE enum values to
// their class definitions, enabling O(1) dispatch.
//
// Why a vtable approach instead of switch statements:
// 1. Adding a new widget type is purely additive define a new class
// 1. Adding a new widget type is purely additive -- define a new class
// struct and add one entry to the table. No existing switch
// statements need modification, reducing the risk of forgetting
// a case.
@ -20,13 +20,13 @@
// like hit testing and layout.
//
// Class flags:
// WCLASS_FOCUSABLE widget can receive keyboard focus (Tab order)
// WCLASS_BOX_CONTAINER uses the generic box layout engine (VBox/HBox)
// WCLASS_HORIZ_CONTAINER lays out children horizontally (HBox variant)
// WCLASS_PAINTS_CHILDREN widget handles its own child painting (TabControl,
// WCLASS_FOCUSABLE -- widget can receive keyboard focus (Tab order)
// WCLASS_BOX_CONTAINER -- uses the generic box layout engine (VBox/HBox)
// WCLASS_HORIZ_CONTAINER -- lays out children horizontally (HBox variant)
// WCLASS_PAINTS_CHILDREN -- widget handles its own child painting (TabControl,
// TreeView, ScrollPane, Splitter). The default paint
// walker skips children for these widgets.
// WCLASS_NO_HIT_RECURSE hit testing stops at this widget instead of
// WCLASS_NO_HIT_RECURSE -- hit testing stops at this widget instead of
// recursing into children. Used by widgets that
// manage their own internal click regions (TreeView,
// ScrollPane, ListView, Splitter).
@ -328,9 +328,9 @@ static const WidgetClassT sClassToolbar = {
};
// TreeView uses all three special flags:
// PAINTS_CHILDREN it renders tree items itself (with indentation,
// PAINTS_CHILDREN -- it renders tree items itself (with indentation,
// expand/collapse buttons, and selection highlighting)
// NO_HIT_RECURSE mouse clicks go to the TreeView widget, which
// NO_HIT_RECURSE -- mouse clicks go to the TreeView widget, which
// figures out which tree item was clicked based on scroll position
// and item Y coordinates, rather than letting the hit tester
// recurse into child TreeItem widgets
@ -347,7 +347,7 @@ static const WidgetClassT sClassTreeView = {
.setText = NULL
};
// TreeItem has no paint/mouse/key handlers it's a data-only node.
// TreeItem has no paint/mouse/key handlers -- it's a data-only node.
// The TreeView parent widget handles all rendering and interaction.
// TreeItem exists as a WidgetT so it can participate in the tree
// structure (parent/child/sibling links) for hierarchical data.
@ -485,14 +485,14 @@ static const WidgetClassT sClassSpinner = {
};
// ============================================================
// Class table indexed by WidgetTypeE
// Class table -- indexed by WidgetTypeE
// ============================================================
//
// This array is the central dispatch table for the widget system.
// Indexed by the WidgetTypeE enum, it provides O(1) lookup of any
// widget type's class definition. Every WidgetT stores a pointer
// to its class (w->wclass) set at allocation time, so per-widget
// dispatch doesn't even need to index this table it's a direct
// dispatch doesn't even need to index this table -- it's a direct
// pointer dereference through the vtable.
//
// Using C99 designated initializers ensures the array slots match

View file

@ -1,4 +1,4 @@
// widgetComboBox.c ComboBox widget (editable text + dropdown list)
// widgetComboBox.c -- ComboBox widget (editable text + dropdown list)
//
// Combines a single-line text input with a dropdown list. The text area
// supports full editing (cursor movement, selection, undo, clipboard) via
@ -12,7 +12,7 @@
//
// The popup list is painted as an overlay (widgetComboBoxPaintPopup) that
// renders on top of all other widgets. Popup visibility is coordinated
// through the sOpenPopup global only one popup can be open at a time.
// through the sOpenPopup global -- only one popup can be open at a time.
// The sClosedPopup mechanism prevents click-to-close from immediately
// reopening the popup when the close click lands on the dropdown button.
//
@ -83,7 +83,7 @@ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
// Cache max item string length so calcMinSize doesn't need to re-scan
// the entire item array on every layout pass. Items are stored as
// external pointers (not copied) the caller owns the string data.
// external pointers (not copied) -- the caller owns the string data.
int32_t maxLen = 0;
for (int32_t i = 0; i < count; i++) {
@ -282,12 +282,12 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
return;
}
// Button click toggle popup
// Button click -- toggle popup
w->as.comboBox.open = !w->as.comboBox.open;
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
sOpenPopup = w->as.comboBox.open ? w : NULL;
} else {
// Text area click focus for editing
// Text area click -- focus for editing
clearOtherSelections(w);
AppContextT *ctx = (AppContextT *)root->userData;
@ -300,7 +300,7 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// widgetComboBoxPaint
// ============================================================
// Paint: two regions side-by-side a sunken text area (left) and a raised
// Paint: two regions side-by-side -- a sunken text area (left) and a raised
// dropdown button (right). The text area renders the edit buffer with optional
// selection highlighting (up to 3 text runs: pre-selection, selection,
// post-selection). The dropdown button has a small triangular arrow glyph

View file

@ -1,4 +1,4 @@
// widgetCore.c Core widget infrastructure (alloc, tree ops, helpers)
// widgetCore.c -- Core widget infrastructure (alloc, tree ops, helpers)
//
// This file provides the foundation for the widget tree: allocation,
// parent-child linking, focus management, hit testing, and shared
@ -30,7 +30,7 @@
//
// Each pointer is set when an interaction begins (e.g. mouse-down on a
// slider) and cleared when it ends (mouse-up). The event dispatcher in
// widgetEvent.c checks these before normal hit testing active drags
// widgetEvent.c checks these before normal hit testing -- active drags
// take priority over everything else.
//
// All of these must be NULLed when the pointed-to widget is destroyed,
@ -90,7 +90,7 @@ void widgetAddChild(WidgetT *parent, WidgetT *child) {
// class vtable via widgetClassTable[], and optionally adds it as
// a child of the given parent.
//
// The memset to 0 is intentional it establishes sane defaults
// The memset to 0 is intentional -- it establishes sane defaults
// for all fields: NULL pointers, zero coordinates, no focus,
// no accel key, etc. Only visible and enabled default to true.
//
@ -282,7 +282,7 @@ void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t conten
// Special case for TabPage widgets: even if the tab page itself is
// not visible (inactive tab), its accelKey is still checked. This
// allows Alt+key to switch to a different tab. However, children
// of invisible tab pages are NOT searched their accelerators
// of invisible tab pages are NOT searched -- their accelerators
// should not be active when the tab is hidden.
WidgetT *widgetFindByAccel(WidgetT *root, char key) {
@ -323,12 +323,12 @@ WidgetT *widgetFindByAccel(WidgetT *root, char key) {
// Implements Tab-order navigation: finds the next focusable widget
// after 'after' in depth-first tree order. The two-pass approach
// (search from 'after' to end, then wrap to start) ensures circular
// tabbing Tab on the last focusable widget wraps to the first.
// tabbing -- Tab on the last focusable widget wraps to the first.
//
// The pastAfter flag tracks whether we've passed the 'after' widget
// during traversal. Once past it, the next focusable widget is the
// answer. This avoids collecting all focusable widgets into an array
// just to find the next one the common case returns quickly.
// just to find the next one -- the common case returns quickly.
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
if (!w->visible || !w->enabled) {
@ -364,7 +364,7 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
return found;
}
// Wrap around search from the beginning
// Wrap around -- search from the beginning
pastAfter = true;
return findNextFocusableImpl(root, NULL, &pastAfter);
}
@ -476,7 +476,7 @@ int32_t widgetFrameBorderWidth(const WidgetT *w) {
// on top of earlier ones). This is important for overlapping widgets,
// though in practice the layout engine rarely produces overlap.
//
// Widgets with WCLASS_NO_HIT_RECURSE stop the recursion the parent
// Widgets with WCLASS_NO_HIT_RECURSE stop the recursion -- the parent
// widget handles all mouse events for its children. This is used by
// TreeView, ScrollPane, ListView, and Splitter, which need to manage
// their own internal regions (scrollbars, column headers, tree
@ -496,7 +496,7 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
return w;
}
// Check children take the last match (topmost in Z-order)
// Check children -- take the last match (topmost in Z-order)
WidgetT *hit = NULL;
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
@ -551,7 +551,7 @@ bool widgetIsHorizContainer(WidgetTypeE type) {
// so each widget doesn't have to reimplement index clamping.
//
// Key values use the 0x100 flag to mark extended scan codes (arrow
// keys, Home, End, etc.) this is the DVX convention for passing
// keys, Home, End, etc.) -- this is the DVX convention for passing
// scan codes through the same int32_t channel as ASCII values.
//
// Returns -1 for unrecognized keys so callers can check whether the
@ -648,7 +648,7 @@ void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *f
// Used by both the WM-level scrollbars and widget-internal scrollbars
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
//
// The thumb size is proportional to visibleSize/totalSize a larger
// The thumb size is proportional to visibleSize/totalSize -- a larger
// visible area means a larger thumb, giving visual feedback about how
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
// becoming too small to grab with a mouse.

View file

@ -1,7 +1,7 @@
// widgetDropdown.c Dropdown (select) widget
// widgetDropdown.c -- Dropdown (select) widget
//
// A non-editable dropdown list (HTML <select> equivalent). Unlike ComboBox,
// the user cannot type a custom value they can only choose from the
// the user cannot type a custom value -- they can only choose from the
// predefined items. This simplifies the widget considerably: no text buffer,
// no undo, no cursor, no text editing key handling.
//
@ -135,7 +135,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (w->as.dropdown.open) {
// Popup is open navigate items
// Popup is open -- navigate items
if (key == (0x48 | 0x100)) {
if (w->as.dropdown.hoverIdx > 0) {
w->as.dropdown.hoverIdx--;
@ -254,7 +254,7 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
btnBevel.width = 2;
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
// Down arrow glyph in button a small filled triangle drawn as horizontal
// Down arrow glyph in button -- a small filled triangle drawn as horizontal
// lines of decreasing width (7, 5, 3, 1 pixels). This creates a 4-pixel
// tall downward-pointing triangle centered in the button.
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;

View file

@ -1,4 +1,4 @@
// widgetEvent.c Window event handlers for widget system
// widgetEvent.c -- Window event handlers for widget system
//
// This file routes window-level events (mouse, keyboard, paint, resize,
// scroll) into the widget tree. It serves as the bridge between the
@ -19,7 +19,7 @@
#include "widgetInternal.h"
// Widget whose popup was just closed by click-outside prevents
// Widget whose popup was just closed by click-outside -- prevents
// immediate re-open on the same click. Without this, clicking the
// dropdown button to close its popup would immediately hit-test the
// button again and re-open the popup in the same event.
@ -148,7 +148,7 @@ void widgetManageScrollbars(WindowT *win, AppContextT *ctx) {
// The cached pointer avoids an O(n) tree walk to find the focused
// widget on every keypress.
//
// There is no keyboard event bubbling if the focused widget doesn't
// There is no keyboard event bubbling -- if the focused widget doesn't
// handle a key, it's simply dropped. Accelerators (Alt+key) are
// handled at a higher level in the app event loop, not here.
@ -159,7 +159,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
return;
}
// Use cached focus pointer O(1) instead of O(n) tree walk
// Use cached focus pointer -- O(1) instead of O(n) tree walk
WidgetT *focus = sFocusedWidget;
if (!focus || !focus->focused || focus->window != win) {
@ -191,7 +191,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
// Only if no interaction is active does the event fall through to
// normal hit testing.
//
// Coordinates (x, y) are in content-buffer space the window manager
// Coordinates (x, y) are in content-buffer space -- the window manager
// has already subtracted the window chrome offset. Widget positions
// are also in content-buffer space (set during layout), so no
// coordinate transform is needed for hit testing.
@ -412,7 +412,15 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Handle scrollbar thumb drag (mouse move while pressed)
if (sDragScrollbar && (buttons & MOUSE_LEFT)) {
widgetScrollbarDragUpdate(sDragScrollbar, sDragScrollbarOrient, sDragScrollbarOff, x, y);
wgtInvalidatePaint(root);
// ScrollPane needs full relayout because it positions children
// at absolute coordinates incorporating the scroll offset.
if (sDragScrollbar->type == WidgetScrollPaneE) {
wgtInvalidate(sDragScrollbar);
} else {
wgtInvalidatePaint(root);
}
return;
}
@ -523,7 +531,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
return;
}
// Click outside popup close it and remember which widget it was
// Click outside popup -- close it and remember which widget it was
sClosedPopup = sOpenPopup;
if (sOpenPopup->type == WidgetDropdownE) {
@ -554,7 +562,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Clear focus from the previously focused widget. This is done via
// the cached sFocusedWidget pointer rather than walking the tree to
// find the focused widget an O(1) operation vs O(n).
// find the focused widget -- an O(1) operation vs O(n).
WidgetT *prevFocus = sFocusedWidget;
if (sFocusedWidget) {
@ -568,7 +576,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
hit->wclass->onMouse(hit, root, vx, vy);
}
// Universal click/double-click callbacks fire for ALL widget types
// Universal click/double-click callbacks -- fire for ALL widget types
// after the type-specific handler has run. Buttons and image buttons
// are excluded from onClick because they use press-release semantics
// (onClick fires on button-up, not button-down) and already handle it
@ -619,7 +627,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Scroll offset is applied by shifting the root widget's position
// to negative coordinates (-scrollX, -scrollY). This elegantly makes
// scrolling work without any special scroll handling in individual
// widgets their positions are simply offset, and the clip rect
// widgets -- their positions are simply offset, and the clip rect
// on the DisplayT limits drawing to the visible area.
//
// The conditional re-layout avoids redundant layout passes when only
@ -728,7 +736,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
(void)orient;
(void)value;
// Repaint with new scroll position dvxInvalidateWindow calls onPaint
// Repaint with new scroll position -- dvxInvalidateWindow calls onPaint
if (win->widgetRoot) {
AppContextT *ctx = wgtGetContext(win->widgetRoot);
@ -740,7 +748,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
// ============================================================
// widgetReorderDrop finalize drag-reorder on mouse release
// widgetReorderDrop -- finalize drag-reorder on mouse release
// ============================================================
//
// Completes a drag-reorder operation for ListBox, ListView, or TreeView.
@ -963,7 +971,7 @@ void widgetReorderDrop(WidgetT *w) {
// ============================================================
// widgetReorderUpdate update drop position during drag
// widgetReorderUpdate -- update drop position during drag
// ============================================================
//
// Tracks the mouse during a drag-reorder, updating the drop indicator

View file

@ -1,4 +1,4 @@
// widgetImage.c Image widget (displays bitmap, responds to clicks)
// widgetImage.c -- Image widget (displays bitmap, responds to clicks)
//
// Displays a bitmap image, optionally responding to clicks. The image data
// must be in the display's native pixel format (pre-converted). Two creation
@ -9,11 +9,11 @@
//
// The widget supports a simple press effect (1px offset on click) and fires
// onClick immediately on mouse-down. Unlike Button which has press/release
// tracking, Image fires instantly this is intentional for image-based
// tracking, Image fires instantly -- this is intentional for image-based
// click targets where visual press feedback is less important than
// responsiveness.
//
// No border or bevel is drawn the image fills its widget bounds with
// No border or bevel is drawn -- the image fills its widget bounds with
// centering if the widget is larger than the image.
#include "widgetInternal.h"
@ -114,7 +114,7 @@ void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
// invalidates to show the press, fires onClick, then immediately clears
// pressed. The press is purely visual there's no release tracking like
// pressed. The press is purely visual -- there's no release tracking like
// Button has, since Image clicks are meant for instant actions (e.g.,
// clicking a logo or icon area).
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {

View file

@ -1,4 +1,4 @@
// widgetImageButton.c Image button widget (button with image instead of text)
// widgetImageButton.c -- Image button widget (button with image instead of text)
//
// Combines a Button's beveled border and press behavior with an Image's
// bitmap rendering. The image is centered within the button bounds and
@ -9,11 +9,11 @@
// and the event dispatcher handles release/cancel. The onClick callback
// fires on release, not press.
//
// The widget takes ownership of the image data buffer if widget creation
// The widget takes ownership of the image data buffer -- if widget creation
// fails, the data is freed to prevent leaks.
//
// The 4px added to min size (widgetImageButtonCalcMinSize) accounts for
// the 2px bevel on each side no extra padding is added beyond that,
// the 2px bevel on each side -- no extra padding is added beyond that,
// keeping image buttons compact for toolbar use.
#include "widgetInternal.h"

View file

@ -1,4 +1,4 @@
// widgetInternal.h Shared internal header for widget implementation files
// widgetInternal.h -- Shared internal header for widget implementation files
//
// This header is included only by widget implementation .c files (in the
// widgets/ directory), never by application code. It exposes the vtable
@ -6,11 +6,11 @@
// widget implementations need but the public API should not expose.
//
// The widget system is split across multiple .c files by concern:
// widgetCore.c allocation, tree ops, focus management, shared helpers
// widgetLayout.c measure + layout algorithms for box containers
// widgetEvent.c mouse/keyboard dispatch, scrollbar management
// widgetOps.c paint dispatch, overlay rendering
// widget*.c per-type paint, event, and layout implementations
// widgetCore.c -- allocation, tree ops, focus management, shared helpers
// widgetLayout.c -- measure + layout algorithms for box containers
// widgetEvent.c -- mouse/keyboard dispatch, scrollbar management
// widgetOps.c -- paint dispatch, overlay rendering
// widget*.c -- per-type paint, event, and layout implementations
#ifndef WIDGET_INTERNAL_H
#define WIDGET_INTERNAL_H
@ -35,12 +35,12 @@
//
// Flags encode static properties that the framework needs without calling
// into the vtable:
// FOCUSABLE can receive keyboard focus (Tab navigation)
// BOX_CONTAINER uses the generic VBox/HBox layout algorithm
// HORIZ_CONTAINER lays out children horizontally (vs. default vertical)
// PAINTS_CHILDREN the widget's paint function handles child rendering
// FOCUSABLE -- can receive keyboard focus (Tab navigation)
// BOX_CONTAINER -- uses the generic VBox/HBox layout algorithm
// HORIZ_CONTAINER -- lays out children horizontally (vs. default vertical)
// PAINTS_CHILDREN -- the widget's paint function handles child rendering
// (e.g. TabControl only paints the active tab page)
// NO_HIT_RECURSE hit testing stops at this widget and doesn't recurse
// NO_HIT_RECURSE -- hit testing stops at this widget and doesn't recurse
// into children (e.g. ListBox handles its own items)
//
// NULL function pointers are valid and mean "no-op" for that operation.
@ -66,7 +66,7 @@ typedef struct WidgetClassT {
void (*setText)(WidgetT *w, const char *text);
} WidgetClassT;
// Global vtable array one entry per WidgetTypeE value. Defined in
// Global vtable array -- one entry per WidgetTypeE value. Defined in
// widgetCore.c. Must stay in sync with the WidgetTypeE enum order.
extern const WidgetClassT *widgetClassTable[];
@ -149,7 +149,7 @@ static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
// The illusion works by drawing the text twice: first offset +1,+1 in
// the highlight color (creating a light "shadow" behind the text), then
// at the original position in the shadow color. The result is text that
// appears chiseled into the surface the universal visual indicator for
// appears chiseled into the surface -- the universal visual indicator for
// "greyed out" in Motif and Windows 3.x era GUIs.
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
@ -170,7 +170,7 @@ static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const
// slider and resize a listview column simultaneously). Using globals
// rather than per-widget state avoids bloating every widget with fields
// that are only relevant during a single drag operation. The tradeoff
// is that these are truly global but since the GUI is single-threaded
// is that these are truly global -- but since the GUI is single-threaded
// and only one mouse can exist, this is safe.
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
@ -209,13 +209,13 @@ void widgetDestroyChildren(WidgetT *w);
// Allocate a new widget, set its type and vtable, and add it as a child.
WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type);
// Focus management Tab/Shift-Tab navigation walks the tree in creation
// Focus management -- Tab/Shift-Tab navigation walks the tree in creation
// order, skipping non-focusable and hidden widgets.
void widgetClearFocus(WidgetT *root);
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
// Alt+key accelerator lookup searches the tree for a widget whose
// Alt+key accelerator lookup -- searches the tree for a widget whose
// accelKey matches, used for keyboard navigation of labeled controls.
WidgetT *widgetFindByAccel(WidgetT *root, char key);
@ -226,7 +226,7 @@ bool widgetIsFocusable(WidgetTypeE type);
bool widgetIsBoxContainer(WidgetTypeE type);
bool widgetIsHorizContainer(WidgetTypeE type);
// Hit testing find the deepest widget containing the point (x,y).
// Hit testing -- find the deepest widget containing the point (x,y).
// Respects WCLASS_NO_HIT_RECURSE to stop at list/tree widgets.
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
@ -498,7 +498,7 @@ void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetTimerDestroy(WidgetT *w);
// Drag-reorder helpers generic drag-and-drop reordering shared by
// Drag-reorder helpers -- generic drag-and-drop reordering shared by
// ListBox, ListView, and TreeView. The update function tracks the mouse
// position and computes the drop target, the drop function commits the
// reorder by relinking the item in the data structure.

View file

@ -1,12 +1,12 @@
// widgetLabel.c Label widget
// widgetLabel.c -- Label widget
//
// Static text display the simplest widget. Not focusable, not interactive.
// Static text display -- the simplest widget. Not focusable, not interactive.
// Supports accelerator keys via '&' prefix: when the user presses Alt+key,
// focus moves to the next focusable widget after this label. This follows
// the Win3.1 convention where labels act as keyboard shortcuts for adjacent
// controls (e.g., a label "&Name:" before a text input).
//
// The text pointer is stored directly (not copied) the caller must ensure
// The text pointer is stored directly (not copied) -- the caller must ensure
// the string remains valid for the widget's lifetime, or use widgetLabelSetText
// to update it. This avoids unnecessary allocations for the common case of
// literal string labels.
@ -68,15 +68,34 @@ void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// ============================================================
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
int32_t textW = textWidthAccel(font, w->as.label.text);
int32_t textX = w->x;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
if (w->as.label.textAlign == AlignCenterE) {
textX = w->x + (w->w - textW) / 2;
} else if (w->as.label.textAlign == AlignEndE) {
textX = w->x + w->w - textW;
}
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, w->x, textY, w->as.label.text, colors);
drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.label.text, colors);
return;
}
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
drawTextAccel(d, ops, font, w->x, textY, w->as.label.text, fg, bg, false);
drawTextAccel(d, ops, font, textX, textY, w->as.label.text, fg, bg, false);
}
// ============================================================
// wgtLabelSetAlign
// ============================================================
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
if (w && w->type == WidgetLabelE) {
w->as.label.textAlign = align;
}
}

View file

@ -1,4 +1,4 @@
// widgetLayout.c Layout engine (measure + arrange)
// widgetLayout.c -- Layout engine (measure + arrange)
//
// Implements a two-pass layout algorithm inspired by CSS flexbox:
// Pass 1 (Measure): bottom-up calculation of each widget's minimum
@ -15,7 +15,7 @@
// be bottom-up before arrange can be top-down.
//
// Why flexbox-like rather than constraint-based:
// Flexbox maps naturally to the VBox/HBox container model it's simple
// Flexbox maps naturally to the VBox/HBox container model -- it's simple
// to understand, implement, and debug. Constraint-based layout (like
// Cassowary) would add significant complexity for little benefit in a
// DOS GUI where most layouts are linear stacks of widgets.
@ -406,7 +406,7 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
// The tradeoff is that pixel values are limited to 30 bits (~1 billion),
// which is far more than any supported display resolution.
//
// A value of 0 means "auto" (use intrinsic/calculated size) the
// A value of 0 means "auto" (use intrinsic/calculated size) -- the
// caller checks for 0 before calling this function.
int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth) {

View file

@ -1,4 +1,4 @@
// widgetListBox.c ListBox widget (single and multi-select)
// widgetListBox.c -- ListBox widget (single and multi-select)
//
// Scrollable list of text items with single-select or multi-select modes.
// Items are stored as external string pointers (not copied), with a vertical
@ -50,7 +50,7 @@ static void selectRange(WidgetT *w, int32_t from, int32_t to);
// ============================================================
// Allocate (or re-allocate) the selection bits array. Only allocated in
// multi-select mode in single-select, selectedIdx alone tracks the
// multi-select mode -- in single-select, selectedIdx alone tracks the
// selection. calloc initializes all bits to 0 (nothing selected).
static void allocSelBits(WidgetT *w) {
if (w->as.listBox.selBits) {
@ -362,13 +362,13 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
int32_t sel = w->as.listBox.selectedIdx;
// Ctrl+A select all (multi-select only)
// Ctrl+A -- select all (multi-select only)
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
wgtListBoxSelectAll(w);
return;
}
// Space toggle current item (multi-select only)
// Space -- toggle current item (multi-select only)
if (multi && key == ' ') {
if (sel >= 0 && w->as.listBox.selBits) {
w->as.listBox.selBits[sel] ^= 1;
@ -383,7 +383,7 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Enter activate selected item (same as double-click)
// Enter -- activate selected item (same as double-click)
if (key == '\r' || key == '\n') {
if (w->onDblClick && sel >= 0) {
w->onDblClick(w);
@ -557,7 +557,7 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// Paint: sunken bevel border, then iterate visible rows drawing text.
// Selected items get highlight background (full row width) with contrasting
// text color. In multi-select mode, the cursor item (selectedIdx) gets a
// dotted focus rect overlay this is separate from the selection highlight
// dotted focus rect overlay -- this is separate from the selection highlight
// so the user can see which item the keyboard cursor is on even when multiple
// items are selected. The scrollbar is drawn only when needed (item count
// exceeds visible rows), and the content width is reduced accordingly.

View file

@ -1,4 +1,4 @@
// widgetListView.c ListView (multi-column list) widget
// widgetListView.c -- ListView (multi-column list) widget
//
// A multi-column list with clickable/sortable column headers, horizontal
// and vertical scrolling, column resize by dragging header borders, and
@ -11,16 +11,16 @@
//
// ListView state is heap-allocated separately (ListViewDataT*) rather than
// inlined in the widget union because the state is too large for a union
// member it includes per-column resolved widths, sort index, selection
// member -- it includes per-column resolved widths, sort index, selection
// bits, and numerous scroll/drag state fields.
//
// Sort: clicking a column header toggles sort direction. Sorting uses an
// indirection array (sortIndex) rather than rearranging the cellData. This
// is critical because:
// 1. The application owns cellData and may not expect it to be mutated
// 2. Multi-select selBits are indexed by data row, not display row
// 2. Multi-select selBits are indexed by data row, not display row --
// rearranging data would invalidate all selection state
// 3. The indirection allows O(1) displaydata row mapping
// 3. The indirection allows O(1) display<->data row mapping
//
// Insertion sort is used because it's stable (preserves original order for
// equal keys) and has good performance for the typical case of partially-sorted
@ -44,7 +44,7 @@
// Horizontal scrolling is needed when total column width exceeds the widget's
// inner width. Both headers and data rows are offset by scrollPosH. The
// horizontal scrollbar appears automatically when needed, and its presence
// may trigger the vertical scrollbar to appear (and vice versa) this
// may trigger the vertical scrollbar to appear (and vice versa) -- this
// two-pass logic handles the interdependency correctly.
#include "widgetInternal.h"
@ -71,7 +71,7 @@ static void resolveColumnWidths(WidgetT *w, const BitmapFontT *font);
// allocListViewSelBits
// ============================================================
// Same allocation strategy as ListBox's selBits one byte per row,
// Same allocation strategy as ListBox's selBits -- one byte per row,
// only allocated when multiSelect is enabled.
static void allocListViewSelBits(WidgetT *w) {
if (w->as.listView->selBits) {
@ -97,16 +97,16 @@ static void allocListViewSelBits(WidgetT *w) {
// Build the sort indirection array. Rather than rearranging cellData (which
// the application owns and indexes into), we maintain a separate array that
// maps display-row data-row. Paint and keyboard navigation work in display-row
// maps display-row -> data-row. Paint and keyboard navigation work in display-row
// space, then map through sortIndex to get the data row for cell lookups and
// selection operations. This decoupling is essential because selBits are
// indexed by data row sorting must not invalidate selection state.
// indexed by data row -- sorting must not invalidate selection state.
static void listViewBuildSortIndex(WidgetT *w) {
int32_t rowCount = w->as.listView->rowCount;
int32_t sortCol = w->as.listView->sortCol;
int32_t colCount = w->as.listView->colCount;
// No sort active clear index
// No sort active -- clear index
if (sortCol < 0 || w->as.listView->sortDir == ListViewSortNoneE || rowCount <= 0) {
if (w->as.listView->sortIndex) {
free(w->as.listView->sortIndex);
@ -134,7 +134,7 @@ static void listViewBuildSortIndex(WidgetT *w) {
idx[i] = i;
}
// Insertion sort stable (equal elements keep their original order),
// Insertion sort -- stable (equal elements keep their original order),
// O(n^2) worst case but with very low constant factors. For typical
// ListView row counts (10s to low 100s), this beats quicksort because
// there's no recursion overhead, no stack usage, and the inner loop
@ -491,7 +491,7 @@ void wgtListViewSetReorderable(WidgetT *w, bool reorderable) {
w->as.listView->reorderable = reorderable;
// Disable sorting when reorderable sort order conflicts with manual order
// Disable sorting when reorderable -- sort order conflicts with manual order
if (reorderable) {
w->as.listView->sortCol = -1;
w->as.listView->sortDir = ListViewSortNoneE;
@ -596,7 +596,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
int32_t rowCount = w->as.listView->rowCount;
int32_t *sortIdx = w->as.listView->sortIndex;
// Enter activate selected item (same as double-click)
// Enter -- activate selected item (same as double-click)
if (key == '\r' || key == '\n') {
if (w->onDblClick && w->as.listView->selectedIdx >= 0) {
w->onDblClick(w);
@ -605,13 +605,13 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Ctrl+A select all (multi-select only)
// Ctrl+A -- select all (multi-select only)
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
wgtListViewSelectAll(w);
return;
}
// Space toggle current item (multi-select only)
// Space -- toggle current item (multi-select only)
if (multi && key == ' ') {
int32_t sel = w->as.listView->selectedIdx;
@ -738,7 +738,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
// distinct hit regions (checked in priority order):
// 1. Vertical scrollbar (right edge, if visible)
// 2. Horizontal scrollbar (bottom edge, if visible)
// 3. Dead corner (when both scrollbars present no action)
// 3. Dead corner (when both scrollbars present -- no action)
// 4. Column headers: resize drag (border +-3px) or sort toggle (click)
// 5. Data rows: item selection with Ctrl/Shift modifiers, double-click,
// and drag-reorder initiation
@ -950,7 +950,7 @@ void widgetListViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
colX += cw;
}
// Not on a border check for sort click (disabled when reorderable
// Not on a border -- check for sort click (disabled when reorderable
// since sorting conflicts with manual ordering). Clicking a column
// toggles between ascending/descending; clicking a different column
// starts ascending.

View file

@ -1,4 +1,4 @@
// widgetOps.c Paint dispatcher and public widget operations
// widgetOps.c -- Paint dispatcher and public widget operations
//
// This file contains two categories of functions:
// 1. The paint dispatcher (widgetPaintOne, widgetPaintOverlays, wgtPaint)
@ -57,7 +57,7 @@ static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
//
// Recursive paint walker. For each visible widget:
// 1. Call the widget's paint function (if any) via vtable.
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion --
// the widget's paint function already handled its children
// (e.g. TabControl only paints the active tab's children).
// 3. Otherwise, recurse into children (default child painting).
@ -190,7 +190,7 @@ void wgtDestroy(WidgetT *w) {
// ============================================================
// widgetHashName djb2 hash for widget name lookup
// widgetHashName -- djb2 hash for widget name lookup
// ============================================================
//
// The djb2 hash function (Dan Bernstein) is used for fast name-based
@ -212,7 +212,7 @@ static uint32_t widgetHashName(const char *s) {
// ============================================================
// wgtFindByHash recursive search with hash fast-reject
// wgtFindByHash -- recursive search with hash fast-reject
// ============================================================
//
// Depth-first search by name with hash pre-screening. The hash
@ -297,7 +297,7 @@ AppContextT *wgtGetContext(const WidgetT *w) {
// wgtGetText
// ============================================================
//
// Polymorphic text getter dispatches through the vtable to the
// Polymorphic text getter -- dispatches through the vtable to the
// appropriate getText implementation for the widget's type. Returns
// an empty string (not NULL) if the widget has no text or no getText
// handler, so callers don't need NULL checks.
@ -326,7 +326,7 @@ const char *wgtGetText(const WidgetT *w) {
// The root widget's userData points to the AppContextT, which is
// the bridge back to the display, font, colors, and blitOps needed
// for painting. This avoids threading the context through every
// widget function any widget can retrieve it via wgtGetContext().
// widget function -- any widget can retrieve it via wgtGetContext().
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
WidgetT *root = widgetAlloc(NULL, WidgetVBoxE);
@ -355,7 +355,7 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
// Full invalidation: re-measures the widget tree, manages scrollbars,
// re-lays out, repaints, and dirties the window on screen.
//
// This is the "something structural changed" path use when widget
// This is the "something structural changed" path -- use when widget
// sizes may have changed (text changed, children added/removed,
// visibility toggled). If only visual state changed (cursor blink,
// selection highlight), use wgtInvalidatePaint() instead to skip
@ -389,7 +389,7 @@ void wgtInvalidate(WidgetT *w) {
widgetManageScrollbars(w->window, ctx);
}
// Dirty the window dvxInvalidateWindow calls onPaint automatically
// Dirty the window -- dvxInvalidateWindow calls onPaint automatically
dvxInvalidateWindow(ctx, w->window);
}
@ -398,7 +398,7 @@ void wgtInvalidate(WidgetT *w) {
// wgtInvalidatePaint
// ============================================================
//
// Lightweight repaint skips measure/layout/scrollbar management.
// Lightweight repaint -- skips measure/layout/scrollbar management.
// Use when only visual state changed (slider value, cursor blink,
// selection highlight, checkbox toggle) but widget sizes are stable.
@ -419,7 +419,7 @@ void wgtInvalidatePaint(WidgetT *w) {
return;
}
// Dirty the window dvxInvalidateWindow calls onPaint automatically
// Dirty the window -- dvxInvalidateWindow calls onPaint automatically
dvxInvalidateWindow(ctx, w->window);
}

View file

@ -1,4 +1,4 @@
// widgetProgressBar.c ProgressBar widget
// widgetProgressBar.c -- ProgressBar widget
//
// A non-interactive fill-bar widget for displaying bounded progress.
// Supports both horizontal and vertical orientations via separate

View file

@ -1,4 +1,4 @@
// widgetRadio.c RadioGroup and Radio button widgets
// widgetRadio.c -- RadioGroup and Radio button widgets
//
// Two-level architecture: RadioGroupE is an invisible container that
// holds the selection state (selectedIdx), while RadioE children are
@ -130,7 +130,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
wgtInvalidatePaint(w);
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
// Down or Right next radio in group
// Down or Right -- next radio in group
if (w->parent && w->parent->type == WidgetRadioGroupE) {
WidgetT *next = NULL;
@ -155,7 +155,7 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
// Up or Left previous radio in group
// Up or Left -- previous radio in group
if (w->parent && w->parent->type == WidgetRadioGroupE) {
WidgetT *prev = NULL;
@ -238,7 +238,7 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
}
}
// Diamond border upper-left edges get highlight, lower-right get shadow
// Diamond border -- upper-left edges get highlight, lower-right get shadow
for (int32_t i = 0; i < mid; i++) {
int32_t left = mid - i;
int32_t right = mid + i;

View file

@ -1,4 +1,4 @@
// widgetScrollPane.c ScrollPane container widget
// widgetScrollPane.c -- ScrollPane container widget
//
// A clipping container that allows its children (laid out as a
// vertical box) to overflow and be scrolled into view. This is the
@ -150,7 +150,7 @@ static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
// ============================================================
// spCalcNeeds determine scrollbar needs and inner dimensions
// spCalcNeeds -- determine scrollbar needs and inner dimensions
// ============================================================
// Central sizing function called by layout, paint, and mouse handlers.
@ -234,7 +234,7 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
widgetCalcMinSizeTree(c, font);
}
// The scroll pane's own min size is small the whole point is scrolling
// The scroll pane's own min size is small -- the whole point is scrolling
w->calcMinW = SP_SB_W * 3 + SP_BORDER * 2;
w->calcMinH = SP_SB_W * 3 + SP_BORDER * 2;
}
@ -424,7 +424,7 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
wgtInvalidatePaint(w);
wgtInvalidate(w);
}
@ -508,7 +508,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
wgtInvalidatePaint(hit);
wgtInvalidate(hit);
return;
}
}
@ -552,7 +552,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
wgtInvalidatePaint(hit);
wgtInvalidate(hit);
return;
}
}
@ -567,7 +567,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
}
// Click on content area forward to child widgets
// Click on content area -- forward to child widgets
// Children are already positioned at scroll-adjusted coordinates
WidgetT *child = NULL;

View file

@ -1,4 +1,4 @@
// widgetScrollbar.c Shared scrollbar painting and hit-testing
// widgetScrollbar.c -- Shared scrollbar painting and hit-testing
//
// These are not widgets themselves -- they are stateless rendering and
// hit-testing utilities shared by ScrollPane, TreeView, TextArea,
@ -47,14 +47,14 @@ void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel);
// Left arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
// Left arrow triangle
{
int32_t cx = sbX + WGT_SB_W / 2;
int32_t cy = sbY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx - 2 + i, cy - i, 1 + i * 2, fg);
@ -69,7 +69,7 @@ void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
{
int32_t cx = rightX + WGT_SB_W / 2;
int32_t cy = sbY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) {
drawVLine(d, ops, cx + 2 - i, cy - i, 1 + i * 2, fg);
@ -103,14 +103,14 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel);
// Up arrow button
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel);
// Up arrow triangle
{
int32_t cx = sbX + WGT_SB_W / 2;
int32_t cy = sbY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy - 2 + i, 1 + i * 2, fg);
@ -125,7 +125,7 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
{
int32_t cx = sbX + WGT_SB_W / 2;
int32_t cy = downY + WGT_SB_W / 2;
uint32_t fg = colors->contentFg;
uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, cx - i, cy + 2 - i, 1 + i * 2, fg);
@ -405,9 +405,12 @@ void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int3
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
// Compute content min size by measuring children
// Compute content min size (must match spCalcNeeds in widgetScrollPane.c)
int32_t contentMinW = 0;
int32_t contentMinH = 0;
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
int32_t count = 0;
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
if (c->visible) {
@ -420,9 +423,17 @@ void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int3
}
contentMinH += c->calcMinH;
count++;
}
}
if (count > 1) {
contentMinH += gap * (count - 1);
}
contentMinW += pad * 2;
contentMinH += pad * 2;
int32_t innerH = w->h - SP_BORDER * 2;
int32_t innerW = w->w - SP_BORDER * 2;
bool needVSb = (contentMinH > innerH);

View file

@ -1,4 +1,4 @@
// widgetSeparator.c Separator widget (horizontal and vertical)
// widgetSeparator.c -- Separator widget (horizontal and vertical)
//
// A purely decorative widget that draws a 2px etched line (shadow +
// highlight pair) to visually divide groups of widgets. The etched

View file

@ -1,4 +1,4 @@
// widgetSlider.c Slider (trackbar) widget
// widgetSlider.c -- Slider (trackbar) widget
//
// A continuous-value selector with a draggable thumb on a groove track.
// Supports both horizontal and vertical orientation. The value maps
@ -127,9 +127,9 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
} else {
if (key == (0x4B | 0x100)) {
if (key == (0x4B | 0x100) || key == (0x48 | 0x100)) {
w->as.slider.value -= 1;
} else if (key == (0x4D | 0x100)) {
} else if (key == (0x4D | 0x100) || key == (0x50 | 0x100)) {
w->as.slider.value += 1;
} else if (key == (0x49 | 0x100)) {
w->as.slider.value -= pageStep;
@ -186,11 +186,11 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
mousePos = vy - hit->y;
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
// Click on thumb start drag
// Click on thumb -- start drag
sDragSlider = hit;
sDragOffset = mousePos - thumbPos;
} else {
// Click on track jump to position
// Click on track -- jump to position
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
@ -208,11 +208,11 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
mousePos = vx - hit->x;
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
// Click on thumb start drag
// Click on thumb -- start drag
sDragSlider = hit;
sDragOffset = mousePos - thumbPos;
} else {
// Click on track jump to position
// Click on track -- jump to position
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }

View file

@ -1,4 +1,4 @@
// widgetSpacer.c Spacer widget (invisible stretching element)
// widgetSpacer.c -- Spacer widget (invisible stretching element)
//
// A zero-sized invisible widget with weight=100, used purely for
// layout control. It absorbs leftover space in box layouts, pushing

View file

@ -1,4 +1,4 @@
// widgetSpinner.c Spinner (numeric up/down) widget
// widgetSpinner.c -- Spinner (numeric up/down) widget
//
// A hybrid widget combining a single-line text editor with up/down
// arrow buttons for numeric value entry. The user can either click
@ -155,7 +155,7 @@ const char *widgetSpinnerGetText(const WidgetT *w) {
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
int32_t step = w->as.spinner.step;
// Up arrow increment
// Up arrow -- increment
if (key == (0x48 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value += step;
@ -169,7 +169,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Down arrow decrement
// Down arrow -- decrement
if (key == (0x50 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value -= step;
@ -183,7 +183,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Page Up increment by step * 10
// Page Up -- increment by step * 10
if (key == (0x49 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value += step * 10;
@ -197,7 +197,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Page Down decrement by step * 10
// Page Down -- decrement by step * 10
if (key == (0x51 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value -= step * 10;
@ -211,7 +211,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Enter commit edit
// Enter -- commit edit
if (key == '\r' || key == '\n') {
if (w->as.spinner.editing) {
spinnerCommitEdit(w);
@ -226,7 +226,7 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Escape cancel edit, revert to current value
// Escape -- cancel edit, revert to current value
if (key == 27) {
if (w->as.spinner.editing) {
w->as.spinner.editing = false;
@ -266,6 +266,32 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
w->as.spinner.undoBuf, &w->as.spinner.undoLen,
&w->as.spinner.undoCursor);
// Validate buffer after paste -- reject non-numeric content.
// Allow optional leading minus and digits only.
bool valid = true;
for (int32_t i = 0; i < w->as.spinner.len; i++) {
char c = w->as.spinner.buf[i];
if (c == '-' && i == 0 && w->as.spinner.minValue < 0) {
continue;
}
if (c < '0' || c > '9') {
valid = false;
break;
}
}
if (!valid) {
// Revert to the undo buffer (pre-paste state)
memcpy(w->as.spinner.buf, w->as.spinner.undoBuf, sizeof(w->as.spinner.buf));
w->as.spinner.len = w->as.spinner.undoLen;
w->as.spinner.cursorPos = w->as.spinner.undoCursor;
w->as.spinner.selStart = 0;
w->as.spinner.selEnd = 0;
}
wgtInvalidatePaint(w);
}

View file

@ -1,4 +1,4 @@
// widgetSplitter.c Splitter (draggable divider between two panes)
// widgetSplitter.c -- Splitter (draggable divider between two panes)
//
// A container that divides its area between exactly two child widgets
// with a draggable divider bar between them. The divider position
@ -39,7 +39,7 @@ static WidgetT *spSecondChild(WidgetT *w);
// ============================================================
// spFirstChild get first visible child
// spFirstChild -- get first visible child
// ============================================================
// These helpers skip invisible children so the splitter works
@ -58,7 +58,7 @@ static WidgetT *spFirstChild(WidgetT *w) {
// ============================================================
// spSecondChild get second visible child
// spSecondChild -- get second visible child
// ============================================================
static WidgetT *spSecondChild(WidgetT *w) {
@ -79,7 +79,7 @@ static WidgetT *spSecondChild(WidgetT *w) {
// ============================================================
// widgetSplitterClampPos clamp divider to child minimums
// widgetSplitterClampPos -- clamp divider to child minimums
// ============================================================
void widgetSplitterClampPos(WidgetT *w, int32_t *pos) {
@ -316,14 +316,14 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
}
// Draw divider bar raised bevel
// Draw divider bar -- raised bevel
BevelStyleT bevel = BEVEL_RAISED(colors, 1);
if (w->as.splitter.vertical) {
int32_t barX = w->x + pos;
drawBevel(d, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
// Gripper row of embossed 2x2 bumps centered vertically
// Gripper -- row of embossed 2x2 bumps centered vertically
int32_t gx = barX + 1;
int32_t midY = w->y + w->h / 2;
int32_t count = 5;
@ -337,7 +337,7 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
int32_t barY = w->y + pos;
drawBevel(d, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
// Gripper row of embossed 2x2 bumps centered horizontally
// Gripper -- row of embossed 2x2 bumps centered horizontally
int32_t gy = barY + 1;
int32_t midX = w->x + w->w / 2;
int32_t count = 5;

View file

@ -1,4 +1,4 @@
// widgetStatusBar.c StatusBar widget
// widgetStatusBar.c -- StatusBar widget
//
// A horizontal container that draws a sunken border around each visible
// child to create the classic segmented status bar appearance. Children

View file

@ -1,4 +1,4 @@
// widgetTabControl.c TabControl and TabPage widgets
// widgetTabControl.c -- TabControl and TabPage widgets
//
// Two-level architecture: TabControlE is the container holding
// selection state and rendering the tab header strip. TabPageE
@ -46,7 +46,7 @@ static bool tabNeedScroll(const WidgetT *w, const BitmapFontT *font);
// ============================================================
// tabClosePopup close any open dropdown/combobox popup
// tabClosePopup -- close any open dropdown/combobox popup
// ============================================================
static void tabClosePopup(void) {
@ -63,7 +63,7 @@ static void tabClosePopup(void) {
// ============================================================
// tabEnsureVisible scroll so active tab is visible
// tabEnsureVisible -- scroll so active tab is visible
// ============================================================
static void tabEnsureVisible(WidgetT *w, const BitmapFontT *font) {
@ -119,7 +119,7 @@ static void tabEnsureVisible(WidgetT *w, const BitmapFontT *font) {
// ============================================================
// tabHeaderTotalW total width of all tab headers
// tabHeaderTotalW -- total width of all tab headers
// ============================================================
static int32_t tabHeaderTotalW(const WidgetT *w, const BitmapFontT *font) {
@ -136,7 +136,7 @@ static int32_t tabHeaderTotalW(const WidgetT *w, const BitmapFontT *font) {
// ============================================================
// tabNeedScroll do tab headers overflow?
// tabNeedScroll -- do tab headers overflow?
// ============================================================
static bool tabNeedScroll(const WidgetT *w, const BitmapFontT *font) {
@ -469,7 +469,7 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
}
}
// Tab headers clip to header area
// Tab headers -- clip to header area
int32_t headerLeft = w->x + 2 + (scroll ? TAB_ARROW_W : 0);
int32_t headerRight = scroll ? (w->x + w->w - TAB_ARROW_W) : (w->x + w->w);

View file

@ -1,13 +1,13 @@
// widgetTextInput.c TextInput and TextArea widgets
// widgetTextInput.c -- TextInput and TextArea widgets
//
// This file implements three text editing widgets plus shared
// infrastructure:
//
// 1. TextInput single-line text field with scroll, selection,
// 1. TextInput -- single-line text field with scroll, selection,
// undo, password masking, and masked input (e.g., phone/SSN).
// 2. TextArea multi-line text editor with row/col cursor,
// 2. TextArea -- multi-line text editor with row/col cursor,
// dual-axis scrolling, and full selection/clipboard support.
// 3. Shared infrastructure clipboard, multi-click detection,
// 3. Shared infrastructure -- clipboard, multi-click detection,
// word boundary logic, cross-widget selection clearing, and
// the single-line text editing engine (widgetTextEditOnKey).
//
@ -760,7 +760,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)shift;
// Ctrl+A select all
// Ctrl+A -- select all
if (key == 1) {
w->as.textInput.selStart = 0;
w->as.textInput.selEnd = maskLen;
@ -768,7 +768,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
goto done;
}
// Ctrl+C copy formatted text
// Ctrl+C -- copy formatted text
if (key == 3) {
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
@ -790,7 +790,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Ctrl+V paste valid chars into slots
// Ctrl+V -- paste valid chars into slots
if (key == 22) {
int32_t clipLen;
const char *clip = clipboardGet(&clipLen);
@ -835,7 +835,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
goto done;
}
// Ctrl+X copy and clear selected slots
// Ctrl+X -- copy and clear selected slots
if (key == 24) {
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
int32_t selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
@ -873,7 +873,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
goto done;
}
// Ctrl+Z undo
// Ctrl+Z -- undo
if (key == 26 && w->as.textInput.undoBuf) {
char tmpBuf[256];
int32_t tmpLen = maskLen + 1 < (int32_t)sizeof(tmpBuf) ? maskLen + 1 : (int32_t)sizeof(tmpBuf);
@ -897,7 +897,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
if (key >= 32 && key < 127) {
// Printable character place at current slot if valid
// Printable character -- place at current slot if valid
if (*pCur < maskLen && maskIsSlot(mask[*pCur]) && maskCharValid(mask[*pCur], (char)key)) {
if (w->as.textInput.undoBuf) {
textEditSaveUndo(buf, maskLen, *pCur, w->as.textInput.undoBuf, &w->as.textInput.undoLen, &w->as.textInput.undoCursor, w->as.textInput.bufSize);
@ -914,7 +914,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == 8) {
// Backspace clear previous slot
// Backspace -- clear previous slot
int32_t prev = maskPrevSlot(mask, *pCur);
if (prev != *pCur) {
@ -933,7 +933,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == (0x53 | 0x100)) {
// Delete clear current slot
// Delete -- clear current slot
if (*pCur < maskLen && maskIsSlot(mask[*pCur])) {
if (w->as.textInput.undoBuf) {
textEditSaveUndo(buf, maskLen, *pCur, w->as.textInput.undoBuf, &w->as.textInput.undoLen, &w->as.textInput.undoCursor, w->as.textInput.bufSize);
@ -949,7 +949,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == (0x4B | 0x100)) {
// Left arrow move to previous slot
// Left arrow -- move to previous slot
int32_t prev = maskPrevSlot(mask, *pCur);
if (shift) {
@ -966,7 +966,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
*pCur = prev;
}
} else if (key == (0x4D | 0x100)) {
// Right arrow move to next slot
// Right arrow -- move to next slot
int32_t next = maskNextSlot(mask, *pCur);
if (shift) {
@ -983,7 +983,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
*pCur = next;
}
} else if (key == (0x47 | 0x100)) {
// Home first slot
// Home -- first slot
if (shift) {
if (w->as.textInput.selStart < 0) {
w->as.textInput.selStart = *pCur;
@ -998,7 +998,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
*pCur = maskFirstSlot(mask);
}
} else if (key == (0x4F | 0x100)) {
// End past last slot
// End -- past last slot
int32_t last = maskLen;
if (shift) {
@ -1299,7 +1299,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
// Ctrl+A select all
// Ctrl+A -- select all
if (key == 1) {
*pSA = 0;
*pSC = *pLen;
@ -1310,7 +1310,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Ctrl+C copy
// Ctrl+C -- copy
if (key == 3) {
if (HAS_SEL()) {
clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO());
@ -1324,7 +1324,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
goto navigation;
}
// Ctrl+V paste
// Ctrl+V -- paste
if (key == 22) {
if (sClipboardLen > 0) {
textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
@ -1363,7 +1363,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Ctrl+X cut
// Ctrl+X -- cut
if (key == 24) {
if (HAS_SEL()) {
clipboardCopy(buf + SEL_LO(), SEL_HI() - SEL_LO());
@ -1390,7 +1390,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Ctrl+Z undo
// Ctrl+Z -- undo
if (key == 26) {
if (w->as.textArea.undoBuf && w->as.textArea.undoLen >= 0) {
// Swap current and undo
@ -1434,7 +1434,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
// Enter insert newline
// Enter -- insert newline
if (key == 0x0D) {
if (*pLen < bufSize - 1) {
textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize);
@ -1581,7 +1581,7 @@ navigation:
return;
}
// Ctrl+Left word left
// Ctrl+Left -- word left
if (key == (0x73 | 0x100)) {
SEL_BEGIN();
int32_t off = CUR_OFF();
@ -1594,7 +1594,7 @@ navigation:
return;
}
// Ctrl+Right word right
// Ctrl+Right -- word right
if (key == (0x74 | 0x100)) {
SEL_BEGIN();
int32_t off = CUR_OFF();
@ -1905,7 +1905,7 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
return;
}
// Click on text area place cursor
// Click on text area -- place cursor
int32_t relX = vx - innerX;
int32_t relY = vy - innerY;
@ -1978,7 +1978,7 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// ============================================================
// widgetTextDragUpdate update selection during mouse drag
// widgetTextDragUpdate -- update selection during mouse drag
// ============================================================
// Called by the event loop on mouse-move while sDragTextSelect is set.
@ -2164,7 +2164,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
selHi = w->as.textArea.selAnchor < w->as.textArea.selCursor ? w->as.textArea.selCursor : w->as.textArea.selAnchor;
}
// Draw lines compute first visible line offset once, then advance incrementally
// Draw lines -- compute first visible line offset once, then advance incrementally
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
int32_t textY = w->y + TEXTAREA_BORDER;
int32_t lineOff = textAreaLineStart(buf, len, w->as.textArea.scrollRow);
@ -2244,7 +2244,7 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
}
} else {
// No selection on this line single run
// No selection on this line -- single run
if (drawStart < drawEnd) {
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
}
@ -2559,7 +2559,7 @@ void widgetTextInputSetText(WidgetT *w, const char *text) {
// ============================================================
// widgetTextEditOnKey shared single-line text editing logic
// widgetTextEditOnKey -- shared single-line text editing logic
// ============================================================
// This is the core single-line text editing engine, parameterized by
@ -2603,7 +2603,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
}
}
// Ctrl+A select all
// Ctrl+A -- select all
if (key == 1 && pSelStart && pSelEnd) {
*pSelStart = 0;
*pSelEnd = *pLen;
@ -2611,7 +2611,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
goto adjustScroll;
}
// Ctrl+C copy
// Ctrl+C -- copy
if (key == 3) {
if (hasSel) {
clipboardCopy(buf + selLo, selHi - selLo);
@ -2620,7 +2620,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
return;
}
// Ctrl+V paste
// Ctrl+V -- paste
if (key == 22) {
if (sClipboardLen > 0) {
if (undoBuf) {
@ -2666,7 +2666,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
goto adjustScroll;
}
// Ctrl+X cut
// Ctrl+X -- cut
if (key == 24) {
if (hasSel) {
clipboardCopy(buf + selLo, selHi - selLo);
@ -2685,7 +2685,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
goto adjustScroll;
}
// Ctrl+Z undo
// Ctrl+Z -- undo
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
// Swap current and undo
char tmpBuf[CLIPBOARD_MAX];
@ -2827,7 +2827,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
}
}
} else if (key == (0x73 | 0x100)) {
// Ctrl+Left word left
// Ctrl+Left -- word left
int32_t newPos = wordBoundaryLeft(buf, *pCursor);
if (shift && pSelStart && pSelEnd) {
@ -2850,7 +2850,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
*pCursor = newPos;
}
} else if (key == (0x74 | 0x100)) {
// Ctrl+Right word right
// Ctrl+Right -- word right
int32_t newPos = wordBoundaryRight(buf, *pLen, *pCursor);
if (shift && pSelStart && pSelEnd) {

View file

@ -1,7 +1,7 @@
// widgetTimer.c Invisible timer widget
// widgetTimer.c -- Invisible timer widget
//
// Fires onChange callbacks at a configurable interval. Supports both
// one-shot and repeating modes. The timer is invisible it takes no
// one-shot and repeating modes. The timer is invisible -- it takes no
// space in layout and produces no visual output.
//
// Active timers are tracked via a module-level linked list (using the
@ -15,7 +15,7 @@
#include "thirdparty/stb_ds.h"
// Active timer list avoids walking the widget tree per frame
// Active timer list -- avoids walking the widget tree per frame
static WidgetT **sActiveTimers = NULL;

View file

@ -1,4 +1,4 @@
// widgetToolbar.c Toolbar widget
// widgetToolbar.c -- Toolbar widget
//
// A horizontal container with a raised 1px bevel background, used to
// hold buttons, separators, spacers, and other control widgets in a

View file

@ -1,4 +1,4 @@
// widgetTreeView.c TreeView and TreeItem widgets
// widgetTreeView.c -- TreeView and TreeItem widgets
//
// A hierarchical list with expand/collapse nodes, scrolling, multi-select,
// and drag-reorder support.
@ -419,7 +419,7 @@ static WidgetT *prevVisibleItem(WidgetT *item, WidgetT *treeView) {
return node;
}
// No previous sibling go to parent (if it's a tree item)
// No previous sibling -- go to parent (if it's a tree item)
if (item->parent && item->parent != treeView && item->parent->type == WidgetTreeItemE) {
return item->parent;
}
@ -496,7 +496,7 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
bool needVSb = (totalH > innerH);
bool needHSb = (totalW > innerW);
// V scrollbar reduces available width may trigger H scrollbar
// V scrollbar reduces available width -- may trigger H scrollbar
if (needVSb) {
innerW -= WGT_SB_W;
@ -505,7 +505,7 @@ static void treeCalcScrollbarNeeds(WidgetT *w, const BitmapFontT *font, int32_t
}
}
// H scrollbar reduces available height may trigger V scrollbar
// H scrollbar reduces available height -- may trigger V scrollbar
if (needHSb) {
innerH -= WGT_SB_W;
@ -662,11 +662,48 @@ void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
// wgtTreeView
// ============================================================
// Default double-click handler: toggle expand/collapse on the selected
// item if it has children, otherwise fire the item's onDblClick/onClick.
static void treeDefaultDblClick(WidgetT *w) {
WidgetT *sel = w->as.treeView.selectedItem;
if (!sel) {
return;
}
bool hasChildren = false;
for (WidgetT *c = sel->firstChild; c; c = c->nextSibling) {
if (c->type == WidgetTreeItemE) {
hasChildren = true;
break;
}
}
if (hasChildren) {
sel->as.treeItem.expanded = !sel->as.treeItem.expanded;
if (sel->onChange) {
sel->onChange(sel);
}
wgtInvalidatePaint(w);
} else {
if (sel->onDblClick) {
sel->onDblClick(sel);
} else if (sel->onClick) {
sel->onClick(sel);
}
}
}
WidgetT *wgtTreeView(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetTreeViewE);
if (w) {
w->weight = 100;
w->weight = 100;
w->onDblClick = treeDefaultDblClick;
}
return w;
@ -802,7 +839,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
WidgetT *sel = w->as.treeView.selectedItem;
if (key == (0x50 | 0x100)) {
// Down arrow next visible item
// Down arrow -- next visible item
if (!sel) {
w->as.treeView.selectedItem = firstVisibleItem(w);
} else {
@ -813,7 +850,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == (0x48 | 0x100)) {
// Up arrow previous visible item
// Up arrow -- previous visible item
if (!sel) {
w->as.treeView.selectedItem = firstVisibleItem(w);
} else {
@ -824,7 +861,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == (0x4D | 0x100)) {
// Right arrow expand if collapsed, else move to first child
// Right arrow -- expand if collapsed, else move to first child
if (sel) {
bool hasChildren = false;
@ -852,7 +889,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == (0x4B | 0x100)) {
// Left arrow collapse if expanded, else move to parent
// Left arrow -- collapse if expanded, else move to parent
if (sel) {
if (sel->as.treeItem.expanded) {
sel->as.treeItem.expanded = false;
@ -865,7 +902,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == 0x0D) {
// Enter toggle expand/collapse for parents, onClick for leaves
// Enter -- toggle expand/collapse for parents, onClick for leaves
if (sel) {
bool hasChildren = false;
@ -891,7 +928,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
}
} else if (key == ' ' && multi) {
// Space toggle selection of current item in multi-select
// Space -- toggle selection of current item in multi-select
if (sel) {
sel->as.treeItem.selected = !sel->as.treeItem.selected;
w->as.treeView.anchorItem = sel;
@ -1112,7 +1149,7 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
}
}
// Click in dead corner (both scrollbars present) ignore
// Click in dead corner (both scrollbars present) -- ignore
if (needVSb && needHSb) {
int32_t cornerX = hit->x + hit->w - TREE_BORDER - WGT_SB_W;
int32_t cornerY = hit->y + hit->h - TREE_BORDER - WGT_SB_W;
@ -1122,7 +1159,7 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
}
}
// Tree item click adjust for scroll offsets
// Tree item click -- adjust for scroll offsets
int32_t curY = hit->y + TREE_BORDER - hit->as.treeView.scrollPos;
WidgetT *item = treeItemAtY(hit, vy, &curY, font);

View file

@ -27,10 +27,10 @@ Requires `lib/libdvx.a` and `lib/libtasks.a` to be built first.
Each iteration of the main loop:
1. `dvxUpdate()` process input events, dispatch callbacks, composite dirty rects
2. `tsYield()` give CPU time to main-loop app tasks
3. `shellReapApps()` clean up apps that terminated this frame
4. `desktopUpdate()` notify the desktop app if anything changed
1. `dvxUpdate()` -- process input events, dispatch callbacks, composite dirty rects
2. `tsYield()` -- give CPU time to main-loop app tasks
3. `shellReapApps()` -- clean up apps that terminated this frame
4. `desktopUpdate()` -- notify the desktop app if anything changed
An idle callback (`idleYield`) yields to app tasks during quiet periods when
there are no events or dirty rects to process.

View file

@ -1,4 +1,4 @@
// shellApp.c DVX Shell application loading, lifecycle, and reaping
// shellApp.c -- DVX Shell application loading, lifecycle, and reaping
//
// Manages DXE app loading via dlopen/dlsym, resource tracking through
// sCurrentAppId, and clean teardown of both callback-only and main-loop apps.
@ -8,9 +8,11 @@
#include "platform/dvxPlatform.h"
#include <dlfcn.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <strings.h>
// ============================================================
// Module state
@ -32,7 +34,7 @@ static int32_t allocSlot(void);
static void appTaskWrapper(void *arg);
static const char *baseName(const char *path);
static void cleanupTempFile(ShellAppT *app);
static int32_t copyFile(AppContextT *ctx, const char *src, const char *dst);
static int32_t copyFile(const char *src, const char *dst);
static ShellAppT *findLoadedPath(const char *path);
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
void shellAppInit(void);
@ -69,7 +71,7 @@ static int32_t allocSlot(void) {
// state is set to Terminating so the shell's main loop will reap it.
//
// If the app crashes (signal), the signal handler longjmps to main and
// this function never completes the task is killed externally via
// this function never completes -- the task is killed externally via
// shellForceKillApp + tsKill.
static void appTaskWrapper(void *arg) {
ShellAppT *app = (ShellAppT *)arg;
@ -78,7 +80,7 @@ static void appTaskWrapper(void *arg) {
app->entryFn(&app->dxeCtx);
sCurrentAppId = 0;
// App returned from its main loop mark for reaping
// App returned from its main loop -- mark for reaping
app->state = AppStateTerminatingE;
}
@ -104,16 +106,18 @@ static void cleanupTempFile(ShellAppT *app) {
// Binary file copy. Returns 0 on success, -1 on failure.
static int32_t copyFile(AppContextT *ctx, const char *src, const char *dst) {
static int32_t copyFile(const char *src, const char *dst) {
FILE *in = fopen(src, "rb");
if (!in) {
shellLog("copyFile: failed to open source '%s' (errno=%d)", src, errno);
return -1;
}
FILE *out = fopen(dst, "wb");
if (!out) {
shellLog("copyFile: failed to create dest '%s' (errno=%d)", dst, errno);
fclose(in);
return -1;
}
@ -123,14 +127,12 @@ static int32_t copyFile(AppContextT *ctx, const char *src, const char *dst) {
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
if (fwrite(buf, 1, n, out) != n) {
shellLog("copyFile: write failed '%s' -> '%s' (errno=%d)", src, dst, errno);
fclose(in);
fclose(out);
remove(dst);
return -1;
}
// Yield so the UI stays responsive during large copies
dvxUpdate(ctx);
}
fclose(in);
@ -196,7 +198,25 @@ void shellAppInit(void) {
}
// Forcible kill — no shutdown hook is called. Used for crashed apps
// ============================================================
// shellConfigPath
// ============================================================
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize) {
snprintf(outPath, outSize, "%s/%s", ctx->configDir, filename);
}
// ============================================================
// shellEnsureConfigDir
// ============================================================
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx) {
return platformMkdirRecursive(ctx->configDir);
}
// Forcible kill -- no shutdown hook is called. Used for crashed apps
// (where running more app code would be unsafe) and for "End Task".
// Cleanup order matters: windows first (removes them from the compositor),
// then the task (frees the stack), then the DXE handle (unmaps the code).
@ -248,14 +268,14 @@ ShellAppT *shellGetApp(int32_t appId) {
// Load a DXE app: dlopen the module, resolve symbols, set up context, launch.
// DXE3 is DJGPP's dynamic linking system similar to dlopen/dlsym on Unix.
// DXE3 is DJGPP's dynamic linking system -- similar to dlopen/dlsym on Unix.
// Each .app file is a DXE3 shared object that exports _appDescriptor and
// _appMain (and optionally _appShutdown). The leading underscore is the
// COFF symbol convention; DJGPP's dlsym expects it.
//
// Multi-instance support: DXE3's dlopen returns the same handle for the
// same path (reference-counted), so two loads of the same .dxe share all
// global/static state only one main loop runs and closing one kills both.
// global/static state -- only one main loop runs and closing one kills both.
// If the app's descriptor sets multiInstance=true, we copy the .dxe to a
// unique temp file before dlopen, giving each instance its own code+data.
// If multiInstance=false (the default), a second load of the same path is
@ -271,7 +291,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
// Check if this DXE is already loaded. If so, check whether the app
// allows multiple instances. We read the descriptor from the existing
// slot directly no need to dlopen again.
// slot directly -- no need to dlopen again.
const char *loadPath = path;
char tempPath[260] = {0};
ShellAppT *existing = findLoadedPath(path);
@ -291,7 +311,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
// an independent code+data image.
makeTempPath(path, id, tempPath, sizeof(tempPath));
if (copyFile(ctx, path, tempPath) != 0) {
if (copyFile(path, tempPath) != 0) {
char msg[320];
snprintf(msg, sizeof(msg), "Failed to create instance copy of %s.", baseName(path));
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
@ -381,6 +401,25 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
app->dxeCtx.appDir[1] = '\0';
}
// Derive config directory: replace the APPS/ prefix with CONFIG/.
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
// If the path doesn't start with "apps/" or "APPS/", fall back to
// "CONFIG/<appname>" using the descriptor name.
const char *appDirStr = app->dxeCtx.appDir;
const char *appsPrefix = NULL;
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
appsPrefix = appDirStr + 5;
} else if (strncasecmp(appDirStr, "APPS/", 5) == 0 || strncasecmp(appDirStr, "APPS\\", 5) == 0) {
appsPrefix = appDirStr + 5;
}
if (appsPrefix && appsPrefix[0]) {
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", appsPrefix);
} else {
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", desc->name);
}
// Launch. Set sCurrentAppId before any app code runs so that window
// creation wrappers stamp the correct owner. Reset to 0 afterward so
// shell-initiated operations (e.g., message boxes) aren't misattributed.
@ -418,7 +457,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
}
// Graceful reap called from shellReapApps when an app has reached
// Graceful reap -- called from shellReapApps when an app has reached
// the Terminating state. Unlike forceKill, this calls the app's
// shutdown hook (if provided) giving it a chance to save state, close
// files, etc. The sCurrentAppId is set during the shutdown call so
@ -464,7 +503,7 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
// Called every frame from the shell's main loop. Scans for apps that have
// entered the Terminating state (either their task returned or their last
// window was closed) and cleans them up. The deferred-reap design avoids
// destroying resources in the middle of a callback chain the app marks
// destroying resources in the middle of a callback chain -- the app marks
// itself for termination, and cleanup happens at a safe top-level point.
bool shellReapApps(AppContextT *ctx) {
bool reaped = false;

View file

@ -1,4 +1,4 @@
// shellApp.h DVX Shell application lifecycle types and API
// shellApp.h -- DVX Shell application lifecycle types and API
//
// The shell supports two kinds of DXE apps:
//
@ -61,12 +61,13 @@ typedef struct {
// the shell's GUI context (for creating windows, drawing, etc.) and
// its own identity. appDir is derived from the .app file path at load
// time so the app can find its own resources (icons, data files) via
// relative paths important because the working directory is shared
// relative paths -- important because the working directory is shared
// by all apps and can't be changed per-app in DOS.
typedef struct {
AppContextT *shellCtx; // the shell's GUI context
int32_t appId; // this app's ID
char appDir[260]; // directory containing the .app file
char appDir[260]; // directory containing the .app file
char configDir[260]; // writable config directory (CONFIG/<apppath>/)
} DxeAppContextT;
// ============================================================
@ -76,7 +77,7 @@ typedef struct {
// State machine: Free -> Loaded -> Running -> Terminating -> Free
// LoadedE is transient (only during shellLoadApp before entry is called).
// TerminatingE means the app's task has exited but cleanup (window
// destruction, DXE unload) hasn't happened yet the shell's main loop
// destruction, DXE unload) hasn't happened yet -- the shell's main loop
// reaps these each frame via shellReapApps().
typedef enum {
AppStateFreeE, // slot available
@ -132,11 +133,11 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path);
// when appMain returns (via appTaskWrapper marking AppStateTerminatingE).
bool shellReapApps(AppContextT *ctx);
// Gracefully shut down a single app calls shutdownFn if present,
// Gracefully shut down a single app -- calls shutdownFn if present,
// destroys windows, kills task, closes DXE handle.
void shellReapApp(AppContextT *ctx, ShellAppT *app);
// Forcibly kill an app (Task Manager "End Task"). Skips shutdownFn
// Forcibly kill an app (Task Manager "End Task"). Skips shutdownFn --
// used when the app is hung or has crashed and cannot be trusted to
// run its own cleanup code.
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
@ -157,6 +158,15 @@ int32_t shellRunningAppCount(void);
// Write a printf-style message to DVX.LOG
void shellLog(const char *fmt, ...);
// Ensure an app's config directory exists (creates all parent dirs).
// Returns 0 on success, -1 on failure.
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx);
// Build a full path to a file in the app's config directory.
// e.g. shellConfigPath(ctx, "settings.ini", buf, sizeof(buf))
// -> "CONFIG/PROGMAN/settings.ini"
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);
// ============================================================
// DXE export table
// ============================================================

View file

@ -1,4 +1,4 @@
// shellExport.c DXE export table and wrapper functions for DVX Shell
// shellExport.c -- DXE export table and wrapper functions for DVX Shell
//
// Exports all dvx*/wgt*/ts* symbols that DXE apps need. A few functions
// are wrapped for resource tracking (window ownership via appId).
@ -15,7 +15,7 @@
// 1. Wrapped functions: dvxCreateWindow, dvxCreateWindowCentered,
// dvxDestroyWindow. These are intercepted to stamp win->appId for
// resource ownership tracking. The DXE sees them under their original
// names the app code calls dvxCreateWindow() normally and gets our
// names -- the app code calls dvxCreateWindow() normally and gets our
// wrapper transparently.
//
// 2. Direct exports: all other dvx/wgt/wm/ts functions. These are safe
@ -25,7 +25,7 @@
// libc, but DJGPP's DXE3 loader requires explicit re-export of any
// libc symbols the module references. Without these entries, the DXE
// would fail to load with unresolved symbol errors. This is a DXE3
// design limitation there's no automatic fallback to the host's libc.
// design limitation -- there's no automatic fallback to the host's libc.
#include "shellApp.h"
#include "shellInfo.h"
@ -55,12 +55,12 @@
#include <unistd.h>
#include <errno.h>
// stb headers (no IMPLEMENTATION symbols are in libdvx.a / libtasks.a)
// stb headers (no IMPLEMENTATION -- symbols are in libdvx.a / libtasks.a)
#include "thirdparty/stb_image.h"
#include "thirdparty/stb_image_write.h"
#include "thirdparty/stb_ds.h"
// DJGPP stdio internals the stdin/stdout/stderr macros dereference
// DJGPP stdio internals -- the stdin/stdout/stderr macros dereference
// these pointers. Without exporting them, any DXE that uses printf
// or fprintf gets an unresolved symbol.
extern FILE __dj_stdin;
@ -85,13 +85,13 @@ static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *titl
static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
// ============================================================
// Wrapper: dvxCreateWindow stamps win->appId
// Wrapper: dvxCreateWindow -- stamps win->appId
// ============================================================
// The wrapper calls the real dvxCreateWindow, then tags the result with
// sCurrentAppId. This is how the shell knows which app owns which window,
// enabling per-app window cleanup on crash/termination. The app never
// sees the difference the wrapper has the same signature and is
// sees the difference -- the wrapper has the same signature and is
// exported under the same name as the original function.
static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
WindowT *win = dvxCreateWindow(ctx, title, x, y, w, h, resizable);
@ -105,7 +105,7 @@ static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32
// ============================================================
// Wrapper: dvxCreateWindowCentered stamps win->appId
// Wrapper: dvxCreateWindowCentered -- stamps win->appId
// ============================================================
static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) {
@ -120,7 +120,7 @@ static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *titl
// ============================================================
// Wrapper: dvxDestroyWindow checks for last-window reap
// Wrapper: dvxDestroyWindow -- checks for last-window reap
// ============================================================
// Beyond just destroying the window, this wrapper implements the lifecycle
@ -162,7 +162,7 @@ static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) {
// ============================================================
// DXE_EXPORT_TABLE generates a DXE symbol table array. DXE_EXPORT(fn)
// expands to { "_fn", (void *)fn } the underscore prefix matches COFF
// expands to { "_fn", (void *)fn } -- the underscore prefix matches COFF
// symbol naming. For wrapped functions we use raw entries with explicit
// names so the DXE sees "_dvxCreateWindow" but gets our wrapper's address.
@ -172,10 +172,11 @@ DXE_EXPORT_TABLE(shellExportTable)
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
// dvxPlatform.h platform abstraction
// dvxPlatform.h -- platform abstraction
DXE_EXPORT(platformLineEnding)
DXE_EXPORT(platformChdir)
DXE_EXPORT(platformGetMemoryInfo)
DXE_EXPORT(platformMkdirRecursive)
DXE_EXPORT(platformMouseSetAccel)
DXE_EXPORT(platformMouseWarp)
DXE_EXPORT(platformPathDirEnd)
@ -183,7 +184,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(platformValidateFilename)
DXE_EXPORT(platformVideoEnumModes)
// dvxPrefs.h preferences
// dvxPrefs.h -- preferences
DXE_EXPORT(prefsGetBool)
DXE_EXPORT(prefsGetInt)
DXE_EXPORT(prefsGetString)
@ -195,9 +196,10 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(prefsSetInt)
DXE_EXPORT(prefsSetString)
// dvxApp.h direct exports
// dvxApp.h -- direct exports
DXE_EXPORT(dvxApplyColorScheme)
DXE_EXPORT(dvxChangeVideoMode)
DXE_EXPORT(dvxColorLabel)
DXE_EXPORT(dvxColorName)
DXE_EXPORT(dvxGetColor)
DXE_EXPORT(dvxInit)
@ -207,6 +209,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(dvxSetColor)
DXE_EXPORT(dvxSetMouseConfig)
DXE_EXPORT(dvxSetWallpaper)
DXE_EXPORT(dvxSetWallpaperMode)
DXE_EXPORT(dvxShutdown)
DXE_EXPORT(dvxUpdate)
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
@ -280,36 +283,37 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wmUpdateContentRect)
DXE_EXPORT(wmReallocContentBuf)
// dvxWidget.h window integration
// dvxWidget.h -- window integration
DXE_EXPORT(wgtInitWindow)
// dvxWidget.h containers
// dvxWidget.h -- containers
DXE_EXPORT(wgtVBox)
DXE_EXPORT(wgtHBox)
DXE_EXPORT(wgtFrame)
// dvxWidget.h basic widgets
// dvxWidget.h -- basic widgets
DXE_EXPORT(wgtLabel)
DXE_EXPORT(wgtLabelSetAlign)
DXE_EXPORT(wgtButton)
DXE_EXPORT(wgtCheckbox)
DXE_EXPORT(wgtTextInput)
DXE_EXPORT(wgtPasswordInput)
DXE_EXPORT(wgtMaskedInput)
// dvxWidget.h radio buttons
// dvxWidget.h -- radio buttons
DXE_EXPORT(wgtRadioGroup)
DXE_EXPORT(wgtRadio)
// dvxWidget.h spacing
// dvxWidget.h -- spacing
DXE_EXPORT(wgtSpacer)
DXE_EXPORT(wgtHSeparator)
DXE_EXPORT(wgtVSeparator)
// dvxWidget.h complex widgets
// dvxWidget.h -- complex widgets
DXE_EXPORT(wgtListBox)
DXE_EXPORT(wgtTextArea)
// dvxWidget.h dropdown/combo
// dvxWidget.h -- dropdown/combo
DXE_EXPORT(wgtDropdown)
DXE_EXPORT(wgtDropdownSetItems)
DXE_EXPORT(wgtDropdownGetSelected)
@ -319,35 +323,35 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtComboBoxGetSelected)
DXE_EXPORT(wgtComboBoxSetSelected)
// dvxWidget.h progress bar
// dvxWidget.h -- progress bar
DXE_EXPORT(wgtProgressBar)
DXE_EXPORT(wgtProgressBarV)
DXE_EXPORT(wgtProgressBarSetValue)
DXE_EXPORT(wgtProgressBarGetValue)
// dvxWidget.h slider
// dvxWidget.h -- slider
DXE_EXPORT(wgtSlider)
DXE_EXPORT(wgtSliderSetValue)
DXE_EXPORT(wgtSliderGetValue)
// dvxWidget.h spinner
// dvxWidget.h -- spinner
DXE_EXPORT(wgtSpinner)
DXE_EXPORT(wgtSpinnerSetValue)
DXE_EXPORT(wgtSpinnerGetValue)
DXE_EXPORT(wgtSpinnerSetRange)
DXE_EXPORT(wgtSpinnerSetStep)
// dvxWidget.h tab control
// dvxWidget.h -- tab control
DXE_EXPORT(wgtTabControl)
DXE_EXPORT(wgtTabPage)
DXE_EXPORT(wgtTabControlSetActive)
DXE_EXPORT(wgtTabControlGetActive)
// dvxWidget.h status bar / toolbar
// dvxWidget.h -- status bar / toolbar
DXE_EXPORT(wgtStatusBar)
DXE_EXPORT(wgtToolbar)
// dvxWidget.h tree view
// dvxWidget.h -- tree view
DXE_EXPORT(wgtTreeView)
DXE_EXPORT(wgtTreeViewGetSelected)
DXE_EXPORT(wgtTreeViewSetSelected)
@ -359,14 +363,14 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtTreeItemIsSelected)
DXE_EXPORT(wgtTreeItemSetSelected)
// dvxWidget.h timer
// dvxWidget.h -- timer
DXE_EXPORT(wgtTimer)
DXE_EXPORT(wgtTimerIsRunning)
DXE_EXPORT(wgtTimerSetInterval)
DXE_EXPORT(wgtTimerStart)
DXE_EXPORT(wgtTimerStop)
// dvxWidget.h list view
// dvxWidget.h -- list view
DXE_EXPORT(wgtListView)
DXE_EXPORT(wgtListViewSetColumns)
DXE_EXPORT(wgtListViewSetData)
@ -381,13 +385,13 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtListViewClearSelection)
DXE_EXPORT(wgtListViewSetReorderable)
// dvxWidget.h scroll pane / splitter
// dvxWidget.h -- scroll pane / splitter
DXE_EXPORT(wgtScrollPane)
DXE_EXPORT(wgtSplitter)
DXE_EXPORT(wgtSplitterSetPos)
DXE_EXPORT(wgtSplitterGetPos)
// dvxWidget.h image / image button
// dvxWidget.h -- image / image button
DXE_EXPORT(wgtImageButton)
DXE_EXPORT(wgtImageButtonFromFile)
DXE_EXPORT(wgtImageButtonSetData)
@ -395,7 +399,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtImageFromFile)
DXE_EXPORT(wgtImageSetData)
// dvxWidget.h canvas
// dvxWidget.h -- canvas
DXE_EXPORT(wgtCanvas)
DXE_EXPORT(wgtCanvasClear)
DXE_EXPORT(wgtCanvasSetMouseCallback)
@ -410,7 +414,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtCanvasSetPixel)
DXE_EXPORT(wgtCanvasGetPixel)
// dvxWidget.h ANSI terminal
// dvxWidget.h -- ANSI terminal
DXE_EXPORT(wgtAnsiTerm)
DXE_EXPORT(wgtAnsiTermWrite)
DXE_EXPORT(wgtAnsiTermClear)
@ -419,7 +423,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtAnsiTermPoll)
DXE_EXPORT(wgtAnsiTermRepaint)
// dvxWidget.h operations
// dvxWidget.h -- operations
DXE_EXPORT(wgtInvalidate)
DXE_EXPORT(wgtInvalidatePaint)
DXE_EXPORT(wgtSetDebugLayout)
@ -436,7 +440,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtFind)
DXE_EXPORT(wgtDestroy)
// dvxWidget.h list box ops
// dvxWidget.h -- list box ops
DXE_EXPORT(wgtListBoxSetItems)
DXE_EXPORT(wgtListBoxGetSelected)
DXE_EXPORT(wgtListBoxSetSelected)
@ -447,14 +451,14 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtListBoxClearSelection)
DXE_EXPORT(wgtListBoxSetReorderable)
// dvxWidget.h layout
// dvxWidget.h -- layout
DXE_EXPORT(wgtResolveSize)
DXE_EXPORT(wgtLayout)
DXE_EXPORT(wgtPaint)
// taskswitch.h only yield and query functions are exported.
// taskswitch.h -- only yield and query functions are exported.
// tsCreate/tsKill/etc. are NOT exported because apps should not
// manipulate the task system directly the shell manages task
// manipulate the task system directly -- the shell manages task
// lifecycle through shellLoadApp/shellForceKillApp.
DXE_EXPORT(tsActiveCount)
DXE_EXPORT(tsCreate)
@ -468,15 +472,17 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(tsSetPriority)
DXE_EXPORT(tsYield)
// dvxWm.h direct window management
// dvxWm.h -- direct window management
DXE_EXPORT(wmRaiseWindow)
DXE_EXPORT(wmSetFocus)
DXE_EXPORT(wmRestoreMinimized)
// Shell API
DXE_EXPORT(shellLog)
DXE_EXPORT(shellLoadApp)
DXE_EXPORT(shellConfigPath)
DXE_EXPORT(shellEnsureConfigDir)
DXE_EXPORT(shellGetApp)
DXE_EXPORT(shellLoadApp)
DXE_EXPORT(shellLog)
DXE_EXPORT(shellTaskMgrOpen)
DXE_EXPORT(shellForceKillApp)
DXE_EXPORT(shellRunningAppCount)
@ -711,7 +717,7 @@ DXE_EXPORT_END
// ============================================================
// dlregsym registers our export table with DJGPP's DXE3 runtime.
// Must be called once before any dlopen subsequent dlopen calls
// Must be called once before any dlopen -- subsequent dlopen calls
// will search this table to resolve DXE symbol references.
static void shellRegisterExports(void) {
dlregsym(shellExportTable);

View file

@ -1,4 +1,4 @@
// shellInfo.c System information wrapper for DVX Shell
// shellInfo.c -- System information wrapper for DVX Shell
//
// Delegates hardware detection to the platform layer via
// platformGetSystemInfo(), then logs the result line-by-line
@ -18,7 +18,7 @@
static const char *sCachedInfo = NULL;
// ============================================================
// shellGetSystemInfo return the cached info text
// shellGetSystemInfo -- return the cached info text
// ============================================================
const char *shellGetSystemInfo(void) {
@ -27,7 +27,7 @@ const char *shellGetSystemInfo(void) {
// ============================================================
// shellInfoInit gather info and log it
// shellInfoInit -- gather info and log it
// ============================================================
void shellInfoInit(AppContextT *ctx) {

View file

@ -1,4 +1,4 @@
// shellInfo.h System information display for DVX Shell
// shellInfo.h -- System information display for DVX Shell
//
// Thin wrapper around platformGetSystemInfo(). Calls the platform
// layer to gather hardware info, logs each line to DVX.LOG, and

View file

@ -1,4 +1,4 @@
// shellMain.c DVX Shell entry point and main loop
// shellMain.c -- DVX Shell entry point and main loop
//
// Initializes the GUI, task system, DXE export table, and loads
// the desktop app. Runs the cooperative main loop, yielding to
@ -21,7 +21,7 @@
// point in main(). This works because longjmp restores the main task's
// stack frame regardless of which task was running. tsRecoverToMain()
// then fixes the scheduler's bookkeeping, and the crashed app is killed.
// This gives the shell Windows 3.1-style fault tolerance one bad app
// This gives the shell Windows 3.1-style fault tolerance -- one bad app
// doesn't take down the whole system.
#include "shellApp.h"
@ -68,7 +68,7 @@ static void logCrash(int sig);
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
// ============================================================
// crashHandler catch page faults and other fatal signals
// crashHandler -- catch page faults and other fatal signals
// ============================================================
// Signal handler for fatal exceptions. DJGPP uses System V signal
@ -79,7 +79,7 @@ static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
// we're on (potentially a crashed app's task stack) and restores the
// main task's stack frame to the setjmp point in main(). This is safe
// because cooperative switching means the main task's stack is always
// intact it was cleanly suspended at a yield point. The crashed
// intact -- it was cleanly suspended at a yield point. The crashed
// task's stack is abandoned (and later freed by tsKill).
static void crashHandler(int sig) {
logCrash(sig);
@ -93,7 +93,7 @@ static void crashHandler(int sig) {
// ============================================================
// shellDesktopUpdate notify desktop app of state change
// shellDesktopUpdate -- notify desktop app of state change
// ============================================================
void shellDesktopUpdate(void) {
@ -104,7 +104,7 @@ void shellDesktopUpdate(void) {
// ============================================================
// ctrlEscHandler system-wide Ctrl+Esc callback
// ctrlEscHandler -- system-wide Ctrl+Esc callback
// ============================================================
static void ctrlEscHandler(void *ctx) {
@ -113,7 +113,7 @@ static void ctrlEscHandler(void *ctx) {
// ============================================================
// titleChangeHandler refresh listeners when a window title changes
// titleChangeHandler -- refresh listeners when a window title changes
// ============================================================
static void titleChangeHandler(void *ctx) {
@ -123,12 +123,12 @@ static void titleChangeHandler(void *ctx) {
// ============================================================
// idleYield called when no dirty rects need compositing
// idleYield -- called when no dirty rects need compositing
// ============================================================
// Registered as sCtx.idleCallback. dvxUpdate calls this when it has
// processed all pending events and there are no dirty rects to composite.
// Instead of busy-spinning, we yield to app tasks this is where most
// Instead of busy-spinning, we yield to app tasks -- this is where most
// of the CPU time for main-loop apps comes from when the UI is idle.
// The tsActiveCount > 1 check avoids the overhead of a tsYield call
// (which would do a scheduler scan) when the shell is the only task.
@ -153,12 +153,12 @@ static void installCrashHandler(void) {
// ============================================================
// logCrash write exception details to the log
// logCrash -- write exception details to the log
// ============================================================
// Dump as much diagnostic info as possible before longjmp destroys the
// crash context. This runs inside the signal handler, so only
// async-signal-safe functions should be used but since we're in
// async-signal-safe functions should be used -- but since we're in
// DJGPP (single-threaded DOS), reentrancy isn't a practical concern
// and vfprintf/fflush are safe to call here.
static void logCrash(int sig) {
@ -191,7 +191,7 @@ static void logCrash(int sig) {
// __djgpp_exception_state_ptr is a DJGPP extension that captures the
// full CPU register state at the point of the exception. This gives
// us the faulting EIP, stack pointer, and all GPRs invaluable for
// us the faulting EIP, stack pointer, and all GPRs -- invaluable for
// post-mortem debugging of app crashes from the log file.
jmp_buf *estate = __djgpp_exception_state_ptr;
@ -213,7 +213,7 @@ static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData) {
// ============================================================
// shellLog append a line to DVX.LOG
// shellLog -- append a line to DVX.LOG
// ============================================================
void shellLog(const char *fmt, ...) {
@ -297,7 +297,7 @@ int main(int argc, char *argv[]) {
int32_t videoW = prefsGetInt("video", "width", 640);
int32_t videoH = prefsGetInt("video", "height", 480);
int32_t videoBpp = prefsGetInt("video", "bpp", 32);
int32_t videoBpp = prefsGetInt("video", "bpp", 16);
shellLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp);
// Initialize GUI
@ -352,12 +352,22 @@ int main(int argc, char *argv[]) {
shellLog("Preferences: loaded custom color scheme");
}
// Apply saved wallpaper
// Apply saved wallpaper mode and image
const char *wpMode = prefsGetString("desktop", "mode", "stretch");
if (strcmp(wpMode, "tile") == 0) {
sCtx.wallpaperMode = WallpaperTileE;
} else if (strcmp(wpMode, "center") == 0) {
sCtx.wallpaperMode = WallpaperCenterE;
} else {
sCtx.wallpaperMode = WallpaperStretchE;
}
const char *wpPath = prefsGetString("desktop", "wallpaper", NULL);
if (wpPath) {
if (dvxSetWallpaper(&sCtx, wpPath)) {
shellLog("Preferences: loaded wallpaper %s", wpPath);
shellLog("Preferences: loaded wallpaper %s (%s)", wpPath, wpMode);
} else {
shellLog("Preferences: failed to load wallpaper %s", wpPath);
}
@ -416,7 +426,7 @@ int main(int argc, char *argv[]) {
return 1;
}
// Install crash handler after everything is initialized if
// Install crash handler after everything is initialized -- if
// initialization itself crashes, we want the default DJGPP behavior
// (abort with register dump) rather than our recovery path, because
// the system isn't in a recoverable state yet.
@ -454,7 +464,7 @@ int main(int argc, char *argv[]) {
shellDesktopUpdate();
}
// Main loop runs until dvxQuit() sets sCtx.running = false.
// Main loop -- runs until dvxQuit() sets sCtx.running = false.
// Two yield points per iteration: one explicit (below) and one via
// the idle callback inside dvxUpdate. The explicit yield here ensures
// app tasks get CPU time even during busy frames (lots of repaints).
@ -469,7 +479,7 @@ int main(int argc, char *argv[]) {
}
// Reap terminated apps and notify desktop if anything changed.
// This is the safe point for cleanup we're at the top of the
// This is the safe point for cleanup -- we're at the top of the
// main loop, not inside any callback or compositor operation.
if (shellReapApps(&sCtx)) {
shellDesktopUpdate();

View file

@ -1,4 +1,4 @@
// shellTaskMgr.c System Task Manager
// shellTaskMgr.c -- System Task Manager
//
// Shell-level Task Manager window. Accessible via Ctrl+Esc regardless
// of which app is focused or whether the desktop app is running. Lists
@ -271,7 +271,7 @@ void shellTaskMgrOpen(AppContextT *ctx) {
sCtx = ctx;
if (sTmWindow) {
// Already open raise and focus
// Already open -- raise and focus
for (int32_t i = 0; i < ctx->stack.count; i++) {
if (ctx->stack.windows[i] == sTmWindow) {
wmRaiseWindow(&ctx->stack, &ctx->dirty, i);

View file

@ -1,4 +1,4 @@
// shellTaskMgr.h System Task Manager
// shellTaskMgr.h -- System Task Manager
//
// The Task Manager is a shell-level component, not tied to any app.
// It is always available via Ctrl+Esc and persists even if the desktop

View file

@ -1,5 +1,5 @@
#!/bin/bash
# mkcd.sh Build DVX and create a CD-ROM ISO image for 86Box
# mkcd.sh -- Build DVX and create a CD-ROM ISO image for 86Box
#
# Usage: ./mkcd.sh
#
@ -22,7 +22,7 @@ make -C "$SCRIPT_DIR" all
# Verify build output exists
if [ ! -f "$SCRIPT_DIR/bin/dvx.exe" ]; then
echo "ERROR: bin/dvx.exe not found build failed?"
echo "ERROR: bin/dvx.exe not found -- build failed?"
exit 1
fi

View file

@ -1,4 +1,4 @@
# Packet Reliable Serial Transport
# Packet -- Reliable Serial Transport
Packetized serial transport with HDLC-style framing, CRC-16 error
detection, and a Go-Back-N sliding window protocol for reliable,
@ -71,7 +71,7 @@ The protocol uses Go-Back-N with a configurable sliding window
### Types
```c
// Receive callback called for each verified, in-order packet
// Receive callback -- called for each verified, in-order packet
typedef void (*PktRecvCallbackT)(void *ctx, const uint8_t *data, int len);
// Opaque connection handle
@ -106,10 +106,10 @@ PktConnT *pktOpen(int com, int windowSize,
Creates a packetized connection over an already-open COM port.
- `com` RS232 port index (`RS232_COM1`..`RS232_COM4`)
- `windowSize` sliding window size (1-8), 0 for default (4)
- `callback` called from `pktPoll()` for each received packet
- `callbackCtx` user pointer passed to callback
- `com` -- RS232 port index (`RS232_COM1`..`RS232_COM4`)
- `windowSize` -- sliding window size (1-8), 0 for default (4)
- `callback` -- called from `pktPoll()` for each received packet
- `callbackCtx` -- user pointer passed to callback
Returns a connection handle, or `NULL` on failure.
@ -129,8 +129,8 @@ int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block);
Sends a packet. `len` must be 1..`PKT_MAX_PAYLOAD`.
- `block = true` waits for window space, polling for ACKs internally
- `block = false` returns `PKT_ERR_TX_FULL` if the window is full
- `block = true` -- waits for window space, polling for ACKs internally
- `block = false` -- returns `PKT_ERR_TX_FULL` if the window is full
The packet is stored in the retransmit buffer until acknowledged.

View file

@ -60,7 +60,7 @@
#define RETRANSMIT_TIMEOUT_MS 500
// Receive state machine: three states for HDLC deframing.
// HUNT: discarding bytes until a flag (0x7E) is seen sync acquisition.
// HUNT: discarding bytes until a flag (0x7E) is seen -- sync acquisition.
// ACTIVE: accumulating frame bytes, watching for flag (end of frame) or
// escape (next byte is XOR'd).
// ESCAPE: the previous byte was 0x7D; XOR this byte with 0x20 to recover
@ -171,7 +171,7 @@ static int txSlotIndex(PktConnT *conn, uint8_t seq);
// Table-driven CRC-16-CCITT. Processing one byte per iteration with a
// 256-entry table is ~10x faster than bit-by-bit on a 486. The table
// costs 512 bytes of .rodata a worthwhile trade for a function called
// costs 512 bytes of .rodata -- a worthwhile trade for a function called
// on every frame received and transmitted.
static uint16_t crcCalc(const uint8_t *data, int len) {
uint16_t crc = 0xFFFF;
@ -258,7 +258,7 @@ static void sendRst(PktConnT *conn) {
// Check if a sequence number falls within a window starting at base.
// Works correctly with 8-bit wrap-around because unsigned subtraction
// wraps mod 256 if seq is "ahead" of base by less than size, diff
// wraps mod 256 -- if seq is "ahead" of base by less than size, diff
// will be a small positive number.
static int seqInWindow(uint8_t seq, uint8_t base, int size) {
uint8_t diff = seq - base;
@ -282,7 +282,7 @@ static int txSlotIndex(PktConnT *conn, uint8_t seq) {
// Handle a complete, de-stuffed frame. CRC is verified first; on failure,
// we NAK to request retransmission of the frame we actually expected.
//
// For DATA frames: Go-Back-N receiver logic only accept if seq matches
// For DATA frames: Go-Back-N receiver logic -- only accept if seq matches
// rxExpectSeq (strictly in-order). Out-of-order frames within the window
// trigger a NAK; duplicates and out-of-window frames are silently dropped.
//
@ -306,7 +306,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
calcCrc = crcCalc(frame, len - CRC_SIZE);
rxCrc = frame[len - 2] | ((uint16_t)frame[len - 1] << 8);
if (calcCrc != rxCrc) {
// CRC mismatch request retransmit of what we expect
// CRC mismatch -- request retransmit of what we expect
sendNak(conn, conn->rxExpectSeq);
return;
}
@ -330,7 +330,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
conn->rxExpectSeq++;
sendAck(conn, conn->rxExpectSeq);
} else if (seqInWindow(seq, conn->rxExpectSeq, conn->windowSize)) {
// Out of order but in window NAK the one we want
// Out of order but in window -- NAK the one we want
sendNak(conn, conn->rxExpectSeq);
}
// else: duplicate or out of window, silently discard
@ -361,7 +361,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
}
case FRAME_RST:
// Remote requested reset clear state and respond with RST
// Remote requested reset -- clear state and respond with RST
conn->txNextSeq = 0;
conn->txAckSeq = 0;
conn->txCount = 0;
@ -429,7 +429,7 @@ static void rxProcessByte(PktConnT *conn, uint8_t byte) {
// ========================================================================
// Timer-based retransmission for slots that haven't been ACK'd within the
// timeout. This handles the case where an ACK or NAK was lost without
// timeout. This handles the case where an ACK or NAK was lost -- without
// this, the connection would stall forever. Each slot is retransmitted
// independently and its timer is reset, creating exponential backoff
// behavior naturally (each retransmit resets the timer).
@ -504,7 +504,7 @@ PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *call
}
// Main polling function must be called frequently (each iteration of the
// Main polling function -- must be called frequently (each iteration of the
// app's main loop or event loop). It drains the serial port's RX buffer,
// feeds bytes through the deframing state machine, and checks for
// retransmit timeouts. The callback is invoked synchronously for each

View file

@ -16,7 +16,7 @@
// product of a serial link. On a 115200 bps local serial connection, the
// round-trip time is negligible, so the window rarely fills. GBN's
// retransmit-all-from-NAK behavior wastes bandwidth on lossy links, but
// serial links are nearly lossless the CRC check is primarily a safety
// serial links are nearly lossless -- the CRC check is primarily a safety
// net for electrical noise, not a routine error recovery mechanism.
//
// CRC-16-CCITT:

View file

@ -1,4 +1,4 @@
# SecLink Proxy Linux build
# SecLink Proxy -- Linux build
# Compiles the packet, security, and secLink layers against a socket
# shim instead of the DJGPP rs232 driver.
@ -27,15 +27,15 @@ $(OBJDIR)/sockShim.o: sockShim.c sockShim.h | $(OBJDIR)
$(OBJDIR)/proxy.o: proxy.c sockShim.h | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
# Packet layer block real rs232.h, inject socket shim
# Packet layer -- block real rs232.h, inject socket shim
$(OBJDIR)/packet.o: ../packet/packet.c sockShim.h | $(OBJDIR)
$(CC) $(CFLAGS) -I. -Istubs/ -include sockShim.h -c -o $@ $<
# Security layer stub DOS-specific headers
# Security layer -- stub DOS-specific headers
$(OBJDIR)/security.o: ../security/security.c | $(OBJDIR)
$(CC) $(CFLAGS) -Istubs/ -c -o $@ $<
# SecLink layer block real rs232.h, inject socket shim
# SecLink layer -- block real rs232.h, inject socket shim
$(OBJDIR)/secLink.o: ../seclink/secLink.c sockShim.h | $(OBJDIR)
$(CC) $(CFLAGS) -I. -include sockShim.h -c -o $@ $<

View file

@ -15,7 +15,7 @@ side is plain telnet over TCP.
TCP:2323 TCP:23
| |
+--- secproxy ----------------------------------+
secLink ←→ plaintext
secLink <-> plaintext
(encrypted, reliable)
```
@ -59,10 +59,10 @@ secproxy --help # show usage
The main loop uses `poll()` with a 10ms timeout to multiplex between
the two TCP connections:
- **86Box BBS**: `secLinkPoll()` reads from the 86Box socket via
- **86Box -> BBS**: `secLinkPoll()` reads from the 86Box socket via
the socket shim, decrypts incoming packets, and the receive callback
writes plaintext to the BBS socket.
- **BBS 86Box**: `read()` from the BBS socket, then
- **BBS -> 86Box**: `read()` from the BBS socket, then
`secLinkSend()` encrypts and sends to 86Box via the socket shim.
- **Maintenance**: `secLinkPoll()` also handles packet-layer retransmit
timers on every iteration.

View file

@ -1,7 +1,7 @@
// SecLink proxy bridges an 86Box serial connection to a telnet BBS
// SecLink proxy -- bridges an 86Box serial connection to a telnet BBS
//
// Architecture:
// 86Box (DOS terminal) ←→ TCP ←→ proxy ←→ TCP ←→ BBS
// 86Box (DOS terminal) <-> TCP <-> proxy <-> TCP <-> BBS
// secLink protocol plain telnet
//
// The proxy runs on Linux and sits between two TCP connections:
@ -172,7 +172,7 @@ static int createListenSocket(int port) {
}
// secLink receive callback called when decrypted data arrives from the DOS
// secLink receive callback -- called when decrypted data arrives from the DOS
// terminal. Before the BBS is connected, we watch for an ENTER keypress as
// a "ready" signal so the user can see the terminal is working before we
// connect to the BBS (which would immediately start sending ANSI art).
@ -203,7 +203,7 @@ static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t chann
// On Linux, /dev/urandom provides high-quality entropy without blocking.
// This is vastly better than the PIT-based entropy on DOS one of the
// This is vastly better than the PIT-based entropy on DOS -- one of the
// advantages of running the proxy on the host OS.
static void seedRng(void) {
uint8_t entropy[32];
@ -246,7 +246,7 @@ static void telnetRespond(int bbsFd, uint8_t cmd, uint8_t opt) {
// We accept ECHO and SGA (Suppress Go Ahead) because most BBSes require them
// for character-at-a-time mode. We accept TTYPE and NAWS for terminal type
// and window size negotiation. Everything else is refused. Subnegotiations
// (SB...SE) are consumed silently we don't actually respond with terminal
// (SB...SE) are consumed silently -- we don't actually respond with terminal
// type or window size data, but accepting the option prevents the BBS from
// falling back to line mode.
// Returns the number of clean data bytes written to 'out'.
@ -268,7 +268,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
case TS_IAC:
switch (b) {
case TEL_IAC:
// Escaped 0xFF emit literal
// Escaped 0xFF -- emit literal
out[outLen++] = 0xFF;
sTelState = TS_DATA;
break;
@ -295,7 +295,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
break;
case TS_WILL:
// Server offers to do something accept ECHO and SGA, refuse others
// Server offers to do something -- accept ECHO and SGA, refuse others
printf(" TEL RX: WILL %d\n", b);
if (b == TELOPT_ECHO || b == TELOPT_SGA) {
telnetRespond(bbsFd, TEL_DO, b);
@ -312,7 +312,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
break;
case TS_DO:
// Server asks us to do something accept TTYPE and NAWS, refuse others
// Server asks us to do something -- accept TTYPE and NAWS, refuse others
printf(" TEL RX: DO %d\n", b);
if (b == TELOPT_TTYPE || b == TELOPT_NAWS) {
telnetRespond(bbsFd, TEL_WILL, b);
@ -329,7 +329,7 @@ static int telnetFilter(int bbsFd, const uint8_t *in, int inLen, uint8_t *out) {
break;
case TS_SB:
// Inside subnegotiation skip until IAC SE
// Inside subnegotiation -- skip until IAC SE
if (b == TEL_IAC) {
sTelState = TS_SB_IAC;
}

View file

@ -1,11 +1,11 @@
// Socket shim rs232-compatible API over TCP sockets
// Socket shim -- rs232-compatible API over TCP sockets
//
// Maps up to 4 "COM ports" to TCP socket file descriptors. Reads use
// MSG_DONTWAIT (non-blocking) to match rs232Read's non-blocking semantics.
// Writes are blocking (loop until all bytes sent) to match rs232Write's
// guarantee of complete delivery.
//
// The shim does NOT close sockets in rs232Close socket lifecycle is
// The shim does NOT close sockets in rs232Close -- socket lifecycle is
// managed by the proxy's main() function. This avoids double-close bugs
// when secLinkClose calls rs232Close but the proxy still needs the fd.
@ -41,7 +41,7 @@ int rs232Close(int com) {
}
// Serial parameters are ignored TCP handles framing, flow control, and
// Serial parameters are ignored -- TCP handles framing, flow control, and
// error correction. We just validate that a socket has been assigned.
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) {
(void)bps;
@ -79,7 +79,7 @@ int rs232Read(int com, char *data, int len) {
return -1;
}
if (n == 0) {
// TCP FIN peer closed connection
// TCP FIN -- peer closed connection
return -1;
}

View file

@ -1,4 +1,4 @@
// Socket shim provides rs232-compatible API backed by TCP sockets
// Socket shim -- provides rs232-compatible API backed by TCP sockets
// Used by the Linux proxy to reuse the packet and secLink layers.
//
// Design: the packet and secLink layers call rs232Read/rs232Write assuming

View file

@ -1,4 +1,4 @@
// Stub for DJGPP <go32.h> Linux proxy build
// Stub for DJGPP <go32.h> -- Linux proxy build
#ifndef GO32_H_STUB
#define GO32_H_STUB

View file

@ -1,4 +1,4 @@
// Stub for DJGPP <pc.h> Linux proxy build
// Stub for DJGPP <pc.h> -- Linux proxy build
#ifndef PC_H_STUB
#define PC_H_STUB

View file

@ -1,4 +1,4 @@
// Stub for DJGPP <sys/farptr.h> Linux proxy build
// Stub for DJGPP <sys/farptr.h> -- Linux proxy build
#ifndef FARPTR_H_STUB
#define FARPTR_H_STUB

View file

@ -1,4 +1,4 @@
# RS232 Serial Port Library for DJGPP
# RS232 -- Serial Port Library for DJGPP
ISR-driven UART communication library supporting up to 4 simultaneous
COM ports with ring buffers and hardware/software flow control.
@ -35,10 +35,10 @@ All functions take a COM port index (`int com`) as their first argument:
| Constant | Value | Description |
|---------------------|-------|--------------------------------------|
| `RS232_UART_UNKNOWN`| 0 | Unknown or undetected |
| `RS232_UART_8250` | 1 | 8250 no FIFO, no scratch register |
| `RS232_UART_16450` | 2 | 16450 scratch register, no FIFO |
| `RS232_UART_16550` | 3 | 16550 broken FIFO (unusable) |
| `RS232_UART_16550A` | 4 | 16550A working 16-byte FIFO |
| `RS232_UART_8250` | 1 | 8250 -- no FIFO, no scratch register |
| `RS232_UART_16450` | 2 | 16450 -- scratch register, no FIFO |
| `RS232_UART_16550` | 3 | 16550 -- broken FIFO (unusable) |
| `RS232_UART_16550A` | 4 | 16550A -- working 16-byte FIFO |
### Handshaking Modes
@ -83,13 +83,13 @@ int rs232Open(int com, int32_t bps, int dataBits, char parity,
Opens a COM port. Detects the UART base address from the BIOS data
area, auto-detects the IRQ, installs the ISR, and configures the port.
- `bps` baud rate (50, 75, 110, 150, 300, 600, 1200, 1800, 2400,
- `bps` -- baud rate (50, 75, 110, 150, 300, 600, 1200, 1800, 2400,
3800, 4800, 7200, 9600, 19200, 38400, 57600, 115200)
- `dataBits` 5, 6, 7, or 8
- `parity` `'N'` (none), `'O'` (odd), `'E'` (even), `'M'` (mark),
- `dataBits` -- 5, 6, 7, or 8
- `parity` -- `'N'` (none), `'O'` (odd), `'E'` (even), `'M'` (mark),
`'S'` (space)
- `stopBits` 1 or 2
- `handshake` `RS232_HANDSHAKE_*` constant
- `stopBits` -- 1 or 2
- `handshake` -- `RS232_HANDSHAKE_*` constant
```c
int rs232Close(int com);
@ -154,9 +154,9 @@ int rs232GetUartType(int com); // UART type (RS232_UART_* constant)
`rs232GetUartType` probes the UART hardware to identify the chip:
1. **Scratch register test** writes two values to register 7 and
1. **Scratch register test** -- writes two values to register 7 and
reads them back. The 8250 lacks this register, so readback fails.
2. **FIFO test** enables the FIFO via the FCR, then reads IIR bits
2. **FIFO test** -- enables the FIFO via the FCR, then reads IIR bits
7:6. `0b11` = 16550A (working FIFO), `0b10` = 16550 (broken FIFO),
`0b00` = 16450 (no FIFO). The original FCR value is restored after
probing.

View file

@ -325,7 +325,7 @@ static void removeIrqHandler(int irq);
// 4. CLI, send EOI to PIC, re-enable COM IRQs, STI before IRET
//
// This "mask-then-STI" pattern is standard for slow device ISRs on PC
// hardware it prevents the same IRQ from re-entering while still allowing
// hardware -- it prevents the same IRQ from re-entering while still allowing
// the timer tick and keyboard to function during potentially long UART
// processing.
static void comGeneralIsr(void) {
@ -449,7 +449,7 @@ static void comGeneralIsr(void) {
// These functions wrap DJGPP's DPMI interface for installing protected-mode
// interrupt handlers. Under DPMI, the ISR code and data must be locked in
// physical memory to prevent page faults during interrupt handling a page
// physical memory to prevent page faults during interrupt handling -- a page
// fault inside an ISR would be fatal. The IRET wrapper is also allocated
// by DPMI to handle the real-mode-to-protected-mode transition.
@ -603,7 +603,7 @@ static int findIrq(int comport) {
// Release this port's IRQ. If another port shares the same IRQ line,
// the handler stays installed only the last user removes it. This is
// the handler stays installed -- only the last user removes it. This is
// critical for COM1/COM3 IRQ sharing.
static void freeIrq(int comport) {
Rs232StateT *com = &sComPorts[comport];
@ -998,8 +998,8 @@ int rs232GetTxBuffered(int com) {
// Detect UART type by probing hardware features. The detection sequence:
// 1. Write/read scratch register 8250 doesn't have one
// 2. Enable FIFO and check IIR bits 7:6 distinguishes 16450/16550/16550A
// 1. Write/read scratch register -- 8250 doesn't have one
// 2. Enable FIFO and check IIR bits 7:6 -- distinguishes 16450/16550/16550A
// This matters because only 16550A has a reliable 16-byte FIFO; the
// original 16550 FIFO is buggy and should not be enabled.
int rs232GetUartType(int com) {
@ -1014,7 +1014,7 @@ int rs232GetUartType(int com) {
return RS232_ERR_NOT_OPEN;
}
// Test scratch register 8250 lacks it
// Test scratch register -- 8250 lacks it
outportb(port->base + UART_SCR, 0xA5);
scratch = inportb(port->base + UART_SCR);
if (scratch != 0xA5) {
@ -1026,7 +1026,7 @@ int rs232GetUartType(int com) {
return RS232_UART_8250;
}
// Has scratch register at least 16450. Try enabling FIFO.
// Has scratch register -- at least 16450. Try enabling FIFO.
outportb(port->base + UART_FCR, FCR_ENABLE);
iir = inportb(port->base + UART_IIR);

View file

@ -12,7 +12,7 @@
// pace from application-level processing.
//
// Ring buffers are power-of-2 sized (2048 bytes) so head/tail index
// wrapping is a single AND, not a modulo critical for ISR speed.
// wrapping is a single AND, not a modulo -- critical for ISR speed.
//
// Flow control (XON/XOFF, RTS/CTS, DTR/DSR) operates entirely within
// the ISR using watermark thresholds. When the RX buffer is 80% full,
@ -20,7 +20,7 @@
// prevents buffer overflow without application involvement.
//
// The ISR and its data structures are locked in memory via DPMI to
// prevent page faults during interrupt handling a requirement for
// prevent page faults during interrupt handling -- a requirement for
// any ISR running under a DPMI host (DOS extender, Windows, etc.).
#ifndef RS232_H

View file

@ -1,12 +1,12 @@
# SecLink Secure Serial Link Library
# SecLink -- Secure Serial Link Library
SecLink is a convenience wrapper that ties together three lower-level
libraries into a single API for reliable, optionally encrypted serial
communication:
- **rs232** ISR-driven UART I/O with ring buffers and flow control
- **packet** HDLC-style framing with CRC-16 and sliding window reliability
- **security** 1024-bit Diffie-Hellman key exchange and XTEA-CTR encryption
- **rs232** -- ISR-driven UART I/O with ring buffers and flow control
- **packet** -- HDLC-style framing with CRC-16 and sliding window reliability
- **security** -- 1024-bit Diffie-Hellman key exchange and XTEA-CTR encryption
## Architecture
@ -51,7 +51,7 @@ Cleartext packets can be sent immediately after `secLinkOpen()`.
### Types
```c
// Receive callback called for each incoming packet with plaintext
// Receive callback -- called for each incoming packet with plaintext
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
// Opaque connection handle
@ -150,9 +150,9 @@ int secLinkSend(SecLinkT *link, const uint8_t *data, int len,
Sends up to `SECLINK_MAX_PAYLOAD` (255) bytes on the given channel.
- `channel` logical channel number (0-127)
- `encrypt` if `true`, encrypts the payload (requires completed handshake)
- `block` if `true`, waits for transmit window space; if `false`,
- `channel` -- logical channel number (0-127)
- `encrypt` -- if `true`, encrypts the payload (requires completed handshake)
- `block` -- if `true`, waits for transmit window space; if `false`,
returns `SECLINK_ERR_SEND` when the window is full
Cleartext packets (`encrypt = false`) can be sent before the handshake.
@ -288,8 +288,8 @@ Link against all four libraries:
SecLink requires these libraries (all in `../lib/`):
- `librs232.a` serial port driver
- `libpacket.a` packet framing and reliability
- `libsecurity.a` DH key exchange and XTEA cipher
- `librs232.a` -- serial port driver
- `libpacket.a` -- packet framing and reliability
- `libsecurity.a` -- DH key exchange and XTEA cipher
Target: DJGPP cross-compiler, 486+ CPU.

View file

@ -1,14 +1,14 @@
// Secure serial link ties rs232, packet, and security into one API
// Secure serial link -- ties rs232, packet, and security into one API
//
// Handshake protocol:
// 1. Both sides send their DH public key (128 bytes) as a single packet
// 2. On receiving the remote key, compute shared secret immediately
// 3. Derive separate TX/RX cipher keys based on public key ordering
// 4. Transition to READY all subsequent encrypted packets use the keys
// 4. Transition to READY -- all subsequent encrypted packets use the keys
//
// The handshake uses the packet layer's reliable delivery, so lost packets
// are automatically retransmitted. Both sides can send their public key
// simultaneously there's no initiator/responder distinction.
// simultaneously -- there's no initiator/responder distinction.
//
// Directionality: the side with the lexicographically lower public key
// uses master XOR 0xAA for TX and master XOR 0x55 for RX. The other
@ -79,7 +79,7 @@ static void internalRecv(void *ctx, const uint8_t *data, int len);
// Called when we've received the remote's public key. Computes the DH
// shared secret, derives directional cipher keys, and transitions to READY.
// After this, the DH context (containing the private key) is destroyed
// immediately forward secrecy principle: even if the long-term state is
// immediately -- forward secrecy principle: even if the long-term state is
// compromised later, past session keys can't be recovered.
static void completeHandshake(SecLinkT *link) {
uint8_t masterKey[SEC_XTEA_KEY_SIZE];
@ -103,7 +103,7 @@ static void completeHandshake(SecLinkT *link) {
memset(txKey, 0, sizeof(txKey));
memset(rxKey, 0, sizeof(rxKey));
// Destroy DH context private key no longer needed
// Destroy DH context -- private key no longer needed
secDhDestroy(link->dh);
link->dh = 0;

View file

@ -1,17 +1,17 @@
// Secure serial link convenience wrapper tying rs232 + packet + security
// Secure serial link -- convenience wrapper tying rs232 + packet + security
//
// This is the top-level API for the serial/networking stack. It composes
// three layers into one:
// rs232 ISR-driven UART I/O with ring buffers
// packet HDLC framing + CRC-16 + Go-Back-N ARQ (reliable delivery)
// security DH key exchange + XTEA-CTR encryption
// rs232 -- ISR-driven UART I/O with ring buffers
// packet -- HDLC framing + CRC-16 + Go-Back-N ARQ (reliable delivery)
// security -- DH key exchange + XTEA-CTR encryption
//
// Usage:
// 1. secLinkOpen() opens COM port, sets up packet framing
// 2. secLinkHandshake() DH key exchange (blocks until both sides complete)
// 3. secLinkSend() send data (optionally encrypted) on a channel
// 4. secLinkPoll() receive, decrypt if needed, deliver to callback
// 5. secLinkClose() tear everything down
// 1. secLinkOpen() -- opens COM port, sets up packet framing
// 2. secLinkHandshake() -- DH key exchange (blocks until both sides complete)
// 3. secLinkSend() -- send data (optionally encrypted) on a channel
// 4. secLinkPoll() -- receive, decrypt if needed, deliver to callback
// 5. secLinkClose() -- tear everything down
//
// Channel multiplexing:
// Each packet carries a one-byte header: bit 7 = encrypted flag,
@ -46,7 +46,7 @@
// Channel limits
#define SECLINK_MAX_CHANNEL 127
// Receive callback delivers plaintext with channel number
// Receive callback -- delivers plaintext with channel number
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
// Opaque handle

View file

@ -1,4 +1,4 @@
# Security DH Key Exchange and XTEA-CTR Cipher
# Security -- DH Key Exchange and XTEA-CTR Cipher
Cryptographic library providing Diffie-Hellman key exchange and XTEA
symmetric encryption, optimized for 486-class DOS hardware running
@ -16,8 +16,8 @@ under DJGPP/DPMI.
### XTEA Cipher (CTR Mode)
- 128-bit key, 64-bit block size, 32 rounds
- CTR mode encrypt and decrypt are the same XOR operation
- No lookup tables, no key schedule just shifts, adds, and XORs
- CTR mode -- encrypt and decrypt are the same XOR operation
- No lookup tables, no key schedule -- just shifts, adds, and XORs
- Ideal for constrained environments with small key setup cost
### Pseudo-Random Number Generator
@ -148,7 +148,7 @@ starts at zero.
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
```
Encrypts or decrypts `data` in place. CTR mode is symmetric the
Encrypts or decrypts `data` in place. CTR mode is symmetric -- the
same operation encrypts and decrypts.
```c
@ -200,7 +200,7 @@ uint8_t message[] = "Secret message";
secCipherCrypt(cipher, message, sizeof(message));
// message is now encrypted
// Decrypt (same operation CTR mode is symmetric)
// Decrypt (same operation -- CTR mode is symmetric)
// Reset counter first if using the same cipher context
secCipherSetNonce(cipher, 0, 0);
secCipherCrypt(cipher, message, sizeof(message));
@ -240,8 +240,8 @@ The CIOS (Coarsely Integrated Operand Scanning) variant computes
`a * b * R^-1 mod m` in a single pass with implicit division by the
word base. Constants are computed once on first DH use:
- `R^2 mod p` via 2048 iterations of shift-and-conditional-subtract
- `-p[0]^-1 mod 2^32` via Newton's method (5 iterations)
- `R^2 mod p` -- via 2048 iterations of shift-and-conditional-subtract
- `-p[0]^-1 mod 2^32` -- via Newton's method (5 iterations)
### Secure Zeroing

View file

@ -5,7 +5,7 @@
// are 256 bits for fast computation on 486-class hardware.
//
// XTEA in CTR mode provides symmetric encryption. No lookup tables,
// no key schedule just shifts, adds, and XORs.
// no key schedule -- just shifts, adds, and XORs.
#include <stdint.h>
#include <stdbool.h>
@ -393,7 +393,7 @@ static void dhInit(void) {
// Volatile pointer prevents the compiler from optimizing away the zeroing
// as a dead store. Critical for clearing key material without volatile,
// as a dead store. Critical for clearing key material -- without volatile,
// the compiler sees that ptr is about to be freed and removes the memset.
static void secureZero(void *ptr, int len) {
volatile uint8_t *p = (volatile uint8_t *)ptr;
@ -406,7 +406,7 @@ static void secureZero(void *ptr, int len) {
// XTEA block cipher: encrypts an 8-byte block in-place. The Feistel network
// uses 32 rounds (vs TEA's 32 or 64). Each round mixes the halves using
// shifts, adds, and XORs no S-boxes, no lookup tables, no key schedule.
// shifts, adds, and XORs -- no S-boxes, no lookup tables, no key schedule.
// The delta constant (golden ratio * 2^32) ensures each round uses a
// different effective key, preventing slide attacks.
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]) {
@ -494,7 +494,7 @@ void secRngBytes(uint8_t *buf, int len) {
// and provide ~10 bits of entropy per read (depending on timing jitter).
// The BIOS tick at 18.2 Hz adds a few more bits. Two PIT readings with
// the intervening code execution provide some jitter. Total: roughly 20
// bits of real entropy not enough alone, but sufficient to seed the DRBG
// bits of real entropy -- not enough alone, but sufficient to seed the DRBG
// when supplemented by user interaction timing.
int secRngGatherEntropy(uint8_t *buf, int len) {
int out = 0;
@ -524,7 +524,7 @@ int secRngGatherEntropy(uint8_t *buf, int len) {
// Initialize the RNG from entropy. The 64-byte discard at the end is
// standard DRBG practice it advances the state past any weak initial
// standard DRBG practice -- it advances the state past any weak initial
// output that might leak information about the seed material.
void secRngSeed(const uint8_t *entropy, int len) {
memset(&sRng, 0, sizeof(sRng));

View file

@ -7,7 +7,7 @@
// ~20 instructions per round (shifts, adds, XORs). This makes it ideal
// for a 486 where cache is tiny (8KB) and AES's 4KB S-boxes would
// thrash it. DES is similarly table-heavy and also has a complex key
// schedule. XTEA has no library dependencies the entire cipher fits
// schedule. XTEA has no library dependencies -- the entire cipher fits
// in a dozen lines of C. At 32 rounds, XTEA provides 128-bit security
// with negligible per-byte cost on even the slowest target hardware.
//
@ -46,7 +46,7 @@ typedef struct SecDhS SecDhT;
typedef struct SecCipherS SecCipherT;
// RNG seed before generating keys. Hardware entropy is weak (~20 bits);
// RNG -- seed before generating keys. Hardware entropy is weak (~20 bits);
// callers should supplement with keyboard timing, mouse jitter, etc.
int secRngGatherEntropy(uint8_t *buf, int len);
void secRngAddEntropy(const uint8_t *data, int len);

View file

@ -113,7 +113,7 @@ int main(void) {
// After all tasks from phases 1-4 have terminated, their slots are free.
// Creating new tasks should recycle those slots (IDs 1-4) rather than
// growing the array. Each "wave" creates 3 tasks, lets them finish,
// then creates 3 more the IDs should repeat across waves.
// then creates 3 more -- the IDs should repeat across waves.
printf("\n--- Phase 5: Slot reuse ---\n");
printf("[main] active before: %u\n", (unsigned)tsActiveCount());

View file

@ -10,7 +10,7 @@
// Why inline asm instead of setjmp/longjmp for context switching:
// setjmp/longjmp only save callee-saved registers and don't give us
// control over the stack pointer in a portable way. We need to set up
// a brand-new stack for each task and jump into a trampoline setjmp
// a brand-new stack for each task and jump into a trampoline -- setjmp
// can't bootstrap a fresh stack. The asm approach also avoids ABI
// differences in jmp_buf layout across DJGPP versions.
//
@ -65,10 +65,10 @@ typedef struct {
} TaskContextT;
#endif
// Task control block one per task slot. The 'allocated' flag tracks
// Task control block -- one per task slot. The 'allocated' flag tracks
// whether the slot is live or recyclable, separate from the state enum,
// because we need to distinguish "never used" from "terminated and reaped".
// The 'isMain' flag protects task 0 from kill/pause destroying the
// The 'isMain' flag protects task 0 from kill/pause -- destroying the
// main task would orphan all other tasks with no scheduler to resume them.
typedef struct {
char name[TS_NAME_MAX];
@ -167,7 +167,7 @@ static void __attribute__((noinline)) contextSwitch(TaskContextT *save, TaskCont
// RIP-relative lea captures the resume point address
"leaq 1f(%%rip), %%rax\n\t"
"movq %%rax, 56(%%rdi)\n\t"
// Restore new context once rsp is swapped we're on the other
// Restore new context -- once rsp is swapped we're on the other
// task's stack. The jmp completes the switch.
"movq 0(%%rsi), %%rbx\n\t"
"movq 8(%%rsi), %%r12\n\t"
@ -219,7 +219,7 @@ static void __attribute__((noinline)) contextSwitch(TaskContextT *save, TaskCont
// Find a free (terminated or unallocated) slot in the task array.
// Returns the index, or -1 if no free slot exists.
// Starts at 1 because slot 0 is always the main task and cannot be reused.
// Linear scan is fine SHELL_MAX_APPS caps the practical limit at ~32 tasks.
// Linear scan is fine -- SHELL_MAX_APPS caps the practical limit at ~32 tasks.
static int32_t findFreeSlot(void) {
ptrdiff_t count = arrlen(tasks);
for (ptrdiff_t i = 1; i < count; i++) {
@ -244,7 +244,7 @@ static int32_t findFreeSlot(void) {
// 4. Scan again after refill
//
// The round-robin scan starts at (currentIdx + 1) and wraps, ensuring
// fairness among tasks with equal priority no task gets picked twice
// fairness among tasks with equal priority -- no task gets picked twice
// in a row unless it's the only ready task.
//
// If no ready tasks exist at all (everything paused/terminated), return
@ -262,7 +262,7 @@ static uint32_t scheduleNext(void) {
}
}
// All credits exhausted start a new epoch by refilling every ready task
// All credits exhausted -- start a new epoch by refilling every ready task
bool anyReady = false;
for (uint32_t i = 0; i < count; i++) {
if (tasks[i].allocated && tasks[i].state == TaskStateReady) {
@ -291,7 +291,7 @@ static uint32_t scheduleNext(void) {
// Entry point for every new task. The first context switch into a new task
// jumps here (via the EIP/RIP set up in tsCreate). This is a trampoline
// rather than calling entry directly because we need to call tsExit() when
// the entry function returns if we just set EIP to the entry function,
// the entry function returns -- if we just set EIP to the entry function,
// it would return to a garbage address (the dummy 0 on the stack).
// The trampoline ensures clean task termination even if the app forgets
// to call tsExit() explicitly.
@ -364,7 +364,7 @@ int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSi
// Set up initial stack (grows downward, 16-byte aligned).
// The ABI requires 16-byte stack alignment at function entry. We align
// the top, then push a dummy return address (0) to simulate a CALL
// instruction this keeps the stack aligned for the trampoline.
// instruction -- this keeps the stack aligned for the trampoline.
// The dummy address is never used because taskTrampoline calls tsExit()
// which switches away without returning, but it satisfies debuggers
// and ABI checkers that expect a return address at the bottom of each frame.
@ -401,7 +401,7 @@ uint32_t tsCurrentId(void) {
// Self-termination. Frees resources and switches to the next task.
// This function never returns the terminated task's context is abandoned.
// This function never returns -- the terminated task's context is abandoned.
// We save to tasks[prev].context even though we'll never restore it because
// contextSwitch always writes to the save pointer; the data is harmless
// and will be overwritten when the slot is recycled.
@ -412,7 +412,7 @@ void tsExit(void) {
tasks[currentIdx].state = TaskStateTerminated;
// Free the stack immediately safe because we're about to switch
// Free the stack immediately -- safe because we're about to switch
// away and never return. The context switch itself doesn't touch
// the old stack after swapping ESP/RSP.
free(tasks[currentIdx].stack);
@ -465,7 +465,7 @@ TaskStateE tsGetState(uint32_t taskId) {
// Register the calling context as task 0 (main). No stack is allocated
// because the main task uses the process stack. The main task's context
// struct is filled in lazily by contextSwitch on the first tsYield()
// struct is filled in lazily by contextSwitch on the first tsYield() --
// until then, the saved EIP/ESP are zero, which is fine because we
// never restore task 0 from a cold start.
int32_t tsInit(void) {
@ -492,11 +492,11 @@ int32_t tsInit(void) {
// Forcibly terminate another task. This is safe in a cooperative system
// because the target is guaranteed to be suspended at a yield point it
// because the target is guaranteed to be suspended at a yield point -- it
// cannot be in the middle of a critical section. The stack is freed and
// the slot is recycled immediately.
//
// Cannot kill self (use tsExit instead) killing self would free the
// Cannot kill self (use tsExit instead) -- killing self would free the
// stack we're currently executing on. Cannot kill main (task 0) because
// the shell's main loop must always be runnable for crash recovery.
//
@ -546,7 +546,7 @@ int32_t tsPause(uint32_t taskId) {
tasks[taskId].state = TaskStatePaused;
// If we paused ourselves, must yield immediately a paused task
// If we paused ourselves, must yield immediately -- a paused task
// won't be selected by scheduleNext, so staying on CPU would deadlock.
// If pausing another task, no yield needed; it will simply be skipped
// the next time the scheduler scans.
@ -574,7 +574,7 @@ int32_t tsPause(uint32_t taskId) {
// app task. This function fixes the bookkeeping so the scheduler treats
// task 0 as the running task again.
//
// The crashed task's slot is NOT freed here its stack is corrupt and
// The crashed task's slot is NOT freed here -- its stack is corrupt and
// the caller (shellMain's crash recovery) must call shellForceKillApp
// to clean it up properly (destroying windows, closing DXE, etc.).
void tsRecoverToMain(void) {
@ -643,7 +643,7 @@ void tsShutdown(void) {
// The core cooperative yield. Called explicitly by app code (or implicitly
// via the shell's idle callback and main loop). If no other task is ready,
// returns immediately no context switch overhead when running solo.
// returns immediately -- no context switch overhead when running solo.
//
// The state transition: current task moves Running -> Ready (still
// schedulable), next task moves Ready -> Running. The previous task will
@ -660,7 +660,7 @@ void tsYield(void) {
uint32_t prev = currentIdx;
// Only transition to Ready if still Running a task that paused itself
// Only transition to Ready if still Running -- a task that paused itself
// will already be in Paused state when tsYield is called from tsPause.
if (tasks[prev].state == TaskStateRunning) {
tasks[prev].state = TaskStateReady;

View file

@ -3,7 +3,7 @@
// Cooperative (non-preemptive) multitasking for DOS protected mode (DJGPP/DPMI).
//
// Why cooperative instead of preemptive:
// 1. DOS is single-threaded there is no kernel scheduler to preempt us.
// 1. DOS is single-threaded -- there is no kernel scheduler to preempt us.
// 2. DPMI provides no thread or timer-based preemption primitives.
// 3. The DVX GUI event model is inherently single-threaded: one compositor,
// one input queue, one window stack. Preemption would require locking
@ -19,7 +19,7 @@
//
// This is a weighted fair-share scheduler: a priority-10 task gets 11
// turns per round while a priority-0 task gets 1, but the low-priority
// task is guaranteed to run eventually no starvation is possible.
// task is guaranteed to run eventually -- no starvation is possible.
#ifndef TASKSWITCH_H
#define TASKSWITCH_H
@ -35,20 +35,20 @@
#define TS_ERR_NOMEM (-4)
#define TS_ERR_STATE (-5)
// 32KB default stack generous for callback-style app tasks that spend
// 32KB default stack -- generous for callback-style app tasks that spend
// most of their time in the shell's GUI code. Apps needing deeper recursion
// or large stack allocations can request more via AppDescriptorT.stackSize.
#define TS_DEFAULT_STACK_SIZE 32768
#define TS_NAME_MAX 32
// Priority levels the credit count is (priority + 1), so LOW gets 1 turn
// Priority levels -- the credit count is (priority + 1), so LOW gets 1 turn
// per round, NORMAL gets 6, HIGH gets 11. The shell's main task runs at
// HIGH to keep UI responsive; app tasks default to NORMAL.
#define TS_PRIORITY_LOW 0
#define TS_PRIORITY_NORMAL 5
#define TS_PRIORITY_HIGH 10
// Task states only Ready tasks participate in scheduling. The Running
// Task states -- only Ready tasks participate in scheduling. The Running
// state is cosmetic (marks the currently executing task). Paused tasks
// are skipped until explicitly resumed. Terminated slots are recycled.
typedef enum {
@ -58,14 +58,14 @@ typedef enum {
TaskStateTerminated = 3
} TaskStateE;
// Task entry function signature the void* arg lets the caller pass
// Task entry function signature -- the void* arg lets the caller pass
// arbitrary context (e.g., a ShellAppT* for the DVX shell's app wrapper)
typedef void (*TaskEntryT)(void *arg);
// Initialize the task system. The calling context becomes task 0 (main).
// Task 0 is special: it cannot be killed or paused, and the crash recovery
// path (tsRecoverToMain) always returns control here. No separate stack is
// allocated for task 0 it uses the process stack directly.
// allocated for task 0 -- it uses the process stack directly.
int32_t tsInit(void);
// Shut down the task system and free all resources.
@ -77,7 +77,7 @@ void tsShutdown(void);
int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);
// Yield CPU to the next eligible ready task (credit-based round-robin).
// This is the sole mechanism for task switching every app must call
// This is the sole mechanism for task switching -- every app must call
// this (or a GUI function that calls it) periodically, or it will
// monopolize the CPU.
void tsYield(void);
@ -108,7 +108,7 @@ const char *tsGetName(uint32_t taskId);
// Terminate the calling task. Must not be called from the main task.
// The stack is freed immediately and the slot is marked for reuse.
// Internally performs a context switch to the next ready task this
// Internally performs a context switch to the next ready task -- this
// function never returns to the caller.
void tsExit(void);
@ -121,7 +121,7 @@ int32_t tsKill(uint32_t taskId);
// Crash recovery: force scheduler back to main task (id 0).
// Call after longjmp from a signal handler that fired in a non-main task.
// The crashed task is NOT cleaned up call tsKill() afterward.
// The crashed task is NOT cleaned up -- call tsKill() afterward.
// This exists because longjmp unwinds the crashed task's stack but the
// scheduler's currentIdx still points to it. We must fix the bookkeeping
// before doing anything else.

View file

@ -67,18 +67,18 @@ DOS console shows progress messages during connection setup.
Each iteration:
1. `dvxUpdate()` process mouse, keyboard, paint, and window events
2. `secLinkPoll()` read serial data, decrypt, deliver to ring buffer
3. `wgtAnsiTermPoll()` drain ring buffer into the ANSI parser
1. `dvxUpdate()` -- process mouse, keyboard, paint, and window events
2. `secLinkPoll()` -- read serial data, decrypt, deliver to ring buffer
3. `wgtAnsiTermPoll()` -- drain ring buffer into the ANSI parser
## Data Flow
```
BBS → proxy → serial → secLinkPoll() → onRecv() → ring buffer
→ commRead() → wgtAnsiTermWrite() → ANSI parser → screen
BBS -> proxy -> serial -> secLinkPoll() -> onRecv() -> ring buffer
-> commRead() -> wgtAnsiTermWrite() -> ANSI parser -> screen
Keyboard → widgetAnsiTermOnKey() → commWrite()
→ secLinkSend() → serial → proxy → BBS
Keyboard -> widgetAnsiTermOnKey() -> commWrite()
-> secLinkSend() -> serial -> proxy -> BBS
```
A 4KB ring buffer bridges the SecLink receive callback (which fires
@ -88,7 +88,7 @@ during `secLinkPoll()`) and the terminal widget's comm read interface
## GUI
- **Window**: resizable, titled "SecLink Terminal"
- **Menu bar**: File Quit
- **Menu bar**: File -> Quit
- **Terminal**: 80x25 ANSI terminal widget with 1000-line scrollback
- **Status bar**: shows COM port, baud rate, and encryption status

View file

@ -1,6 +1,6 @@
// termdemo.c SecLink terminal emulator demo
// termdemo.c -- SecLink terminal emulator demo
//
// A standalone DVX GUI application (NOT a DXE app this has its own main())
// A standalone DVX GUI application (NOT a DXE app -- this has its own main())
// that combines the ANSI terminal widget with the SecLink encrypted serial
// stack to create a BBS terminal client.
//
@ -20,8 +20,8 @@
// during its paint cycle.
//
// Usage: termdemo [com_port] [baud_rate]
// com_port 1-4 (default 1)
// baud_rate baud rate (default 115200)
// com_port -- 1-4 (default 1)
// baud_rate -- baud rate (default 115200)
#include "dvxApp.h"
#include "dvxWidget.h"
@ -71,7 +71,7 @@ static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel);
// ============================================================
// commRead drain receive ring buffer for terminal widget
// commRead -- drain receive ring buffer for terminal widget
// ============================================================
static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
@ -88,7 +88,7 @@ static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
// ============================================================
// commWrite send keystrokes via secLink (encrypted, channel 0)
// commWrite -- send keystrokes via secLink (encrypted, channel 0)
// ============================================================
static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
@ -100,7 +100,7 @@ static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
// ============================================================
// idlePoll poll serial link instead of yielding CPU
// idlePoll -- poll serial link instead of yielding CPU
// ============================================================
static void idlePoll(void *ctx) {
@ -110,7 +110,7 @@ static void idlePoll(void *ctx) {
// ============================================================
// onCloseCb quit when the terminal window is closed
// onCloseCb -- quit when the terminal window is closed
// ============================================================
static void onCloseCb(WindowT *win) {
@ -123,7 +123,7 @@ static void onCloseCb(WindowT *win) {
// ============================================================
// onMenuCb handle menu commands
// onMenuCb -- handle menu commands
// ============================================================
static void onMenuCb(WindowT *win, int32_t menuId) {
@ -140,7 +140,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
// ============================================================
// onRecv secLink receive callback, fills ring buffer
// onRecv -- secLink receive callback, fills ring buffer
// ============================================================
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {

Some files were not shown because too many files have changed in this diff Show more