Another DOS GUI library.
Find a file
2026-03-09 23:15:44 -05:00
dvx Reformatted wrapped lines of code. 2026-03-09 23:15:44 -05:00
dvxdemo Cleaning up project layout. 2026-03-09 22:55:00 -05:00
.gitignore Cleaning up project layout. 2026-03-09 22:55:00 -05:00
dosbox-x.conf Setting up repo. 2026-03-09 20:52:29 -05:00
README.md More widgets! 2026-03-09 22:50:06 -05:00
run.sh Setting up repo. 2026-03-09 20:52:29 -05:00

DV/X GUI

A DESQview/X-style windowed GUI compositor for DOS, targeting DJGPP/DPMI with VESA VBE 2.0+ linear framebuffer.

Motif-style beveled chrome, dirty-rectangle compositing, draggable and resizable windows, dropdown menus, scrollbars, and a declarative widget/layout system.

Building

Requires the DJGPP cross-compiler (i586-pc-msdosdjgpp-gcc).

make            # builds bin/demo.exe
make clean      # removes obj/ and bin/

Set DJGPP_PREFIX in the Makefile if your toolchain is installed somewhere other than ~/djgpp/djgpp.

Architecture

The library is organized in five layers. Each layer is a .h/.c pair. Application code only needs to include dvxApp.h (which pulls in the rest) and optionally dvxWidget.h.

Layer 1  dvxVideo   VESA init, LFB mapping, backbuffer, pixel format
Layer 2  dvxDraw    Spans, rects, bevels, text, bitmaps (asm inner loops)
Layer 3  dvxComp    Dirty rectangle list, merge, LFB flush
Layer 4  dvxWm      Window stack, chrome, drag, resize, focus, menus, scrollbars
Layer 5  dvxApp     Event loop, mouse/keyboard input, public API
         dvxWidget  Widget/layout system (optional, standalone)

Supporting files:

File Purpose
dvxTypes.h Shared type definitions used by all layers
dvxFont.h Built-in 8x14 bitmap font glyph data
dvxCursor.h Mouse cursor bitmask data (5 shapes)
dvxPalette.h Default VGA palette for 8-bit mode
dvxIcon.c stb_image implementation unit (BMP/PNG/JPEG/GIF)
thirdparty/stb_image.h Third-party single-header image loader

Quick start

#include "dvxApp.h"

static void onPaint(WindowT *win, RectT *dirty) {
    AppContextT *ctx = (AppContextT *)win->userData;
    const BlitOpsT *ops = dvxGetBlitOps(ctx);
    const DisplayT *d   = dvxGetDisplay(ctx);
    uint32_t blue = packColor(d, 0, 0, 200);

    for (int32_t y = 0; y < win->contentH; y++) {
        ops->spanFill(win->contentBuf + y * win->contentPitch,
                      blue, win->contentW);
    }
}

int main(void) {
    AppContextT ctx;

    if (dvxInit(&ctx, 1024, 768, 16) != 0) {
        return 1;
    }

    WindowT *win = dvxCreateWindow(&ctx, "Hello", 100, 100, 300, 200, true);
    win->userData = &ctx;
    win->onPaint  = onPaint;

    RectT r = {0, 0, win->contentW, win->contentH};
    win->onPaint(win, &r);

    dvxRun(&ctx);
    dvxShutdown(&ctx);

    return 0;
}

Quick start (widgets)

#include "dvxApp.h"
#include "dvxWidget.h"

static void onButtonClick(WidgetT *w) {
    WidgetT *root = w;
    while (root->parent) root = root->parent;

    WidgetT *lbl = wgtFind(root, "status");
    if (lbl) {
        wgtSetText(lbl, "Clicked!");
        wgtInvalidate(lbl);
    }
}

int main(void) {
    AppContextT ctx;
    dvxInit(&ctx, 1024, 768, 16);

    WindowT *win = dvxCreateWindow(&ctx, "Widgets", 100, 100, 260, 200, true);
    win->userData = &ctx;

    WidgetT *root = wgtInitWindow(&ctx, win);

    WidgetT *lbl = wgtLabel(root, "Ready.");
    strncpy(lbl->name, "status", MAX_WIDGET_NAME);

    wgtHSeparator(root);

    WidgetT *row = wgtHBox(root);
    wgtLabel(row, "Name:");
    wgtTextInput(row, 64);

    WidgetT *btn = wgtButton(root, "Go");
    btn->onClick = onButtonClick;

    wgtInvalidate(root);   // initial layout + paint

    dvxRun(&ctx);
    dvxShutdown(&ctx);

    return 0;
}

Application API (dvxApp.h)

Lifecycle

int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH,
                int32_t preferredBpp);

Initialize VESA video, input, fonts, color scheme, and cursors. Finds a mode matching the requested resolution and bit depth (8, 15, 16, or 32). Returns 0 on success, -1 on failure.

void dvxRun(AppContextT *ctx);

Enter the main event loop. Handles mouse movement, button clicks, keyboard input, window management, dirty-rectangle compositing, and LFB flush. Returns when dvxQuit() is called or ESC is pressed.

void dvxShutdown(AppContextT *ctx);

Restore text mode, release LFB mapping, and free the backbuffer.

void dvxQuit(AppContextT *ctx);

Request exit from the main loop. dvxRun() returns on the next iteration.

Windows

WindowT *dvxCreateWindow(AppContextT *ctx, const char *title,
                          int32_t x, int32_t y, int32_t w, int32_t h,
                          bool resizable);

Create a window at screen position (x, y) with outer dimensions w x h. If resizable is true, the window gets resize handles and a maximize button. The window is raised to the top and given focus. Returns NULL on failure.

After creation, set win->userData and install callbacks:

Callback Signature When called
onPaint void (WindowT *win, RectT *dirtyArea) Content needs redrawing
onKey void (WindowT *win, int32_t key, int32_t mod) Key press (focused window)
onMouse void (WindowT *win, int32_t x, int32_t y, int32_t buttons) Mouse event in content area
onResize void (WindowT *win, int32_t newW, int32_t newH) Window resized
onClose void (WindowT *win) Close button double-clicked
onMenu void (WindowT *win, int32_t menuId) Menu item selected
onScroll void (WindowT *win, ScrollbarOrientE orient, int32_t value) Scrollbar moved

Mouse/key coordinates are relative to the content area. buttons is a bitmask (bit 0 = left, bit 1 = right, bit 2 = middle).

void dvxDestroyWindow(AppContextT *ctx, WindowT *win);

Remove a window from the stack and free its resources.

void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title);

Change the title bar text.

int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path);

Load a BMP, TGA, or PNG image and assign it as the window's minimized icon. The image is converted to the display pixel format and scaled to 64x64. Returns 0 on success.

Invalidation

void dvxInvalidateRect(AppContextT *ctx, WindowT *win,
                        int32_t x, int32_t y, int32_t w, int32_t h);
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win);

Mark a region (or the entire content area) as needing repaint. The compositor flushes dirty rectangles to the LFB on the next frame.

Accessors

DisplayT         *dvxGetDisplay(AppContextT *ctx);
const BlitOpsT   *dvxGetBlitOps(const AppContextT *ctx);
const BitmapFontT *dvxGetFont(const AppContextT *ctx);
const ColorSchemeT *dvxGetColors(const AppContextT *ctx);

Window properties

Set these directly on the WindowT struct after creation:

Field Type Default Description
maxW int32_t -1 Maximum width when maximized (-1 = screen width)
maxH int32_t -1 Maximum height when maximized (-1 = screen height)
userData void * NULL Application data pointer, passed through to callbacks

Content buffer

Each window has a persistent content backbuffer:

Field Description
contentBuf Pixel data in display format
contentPitch Bytes per scanline
contentW Width in pixels
contentH Height in pixels

Paint callbacks write directly into contentBuf. The compositor copies visible portions to the screen backbuffer, then flushes dirty rects to the LFB.


Menu bars

MenuBarT *wmAddMenuBar(WindowT *win);

Add a menu bar to a window. Call wmUpdateContentRect() and wmReallocContentBuf() after adding the menu bar (the menu bar reduces the content area).

MenuT *wmAddMenu(MenuBarT *bar, const char *label);

Add a top-level menu to the bar. Returns a MenuT * to populate with items.

void wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
void wmAddMenuSeparator(MenuT *menu);

Add items to a menu. id is an application-defined command passed to onMenu. Up to 16 items per menu, 8 menus per bar.


Scrollbars

ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max,
                             int32_t pageSize);
ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max,
                             int32_t pageSize);

Add a scrollbar to a window. pageSize controls the thumb size relative to the range. Call wmUpdateContentRect() and wmReallocContentBuf() afterward.

The onScroll callback fires when the user drags the thumb or clicks the arrow buttons / trough.

Widget windows manage their own scrollbars automatically -- do not add them manually.


Video and drawing (dvxVideo.h, dvxDraw.h)

These are lower-level APIs. Application code typically only needs packColor.

uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b);

Pack an RGB triple into the display's pixel format.

void rectFill(DisplayT *d, const BlitOpsT *ops,
              int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);

Fill a rectangle with a solid color (clipped to the display clip rect).

void drawBevel(DisplayT *d, const BlitOpsT *ops,
               int32_t x, int32_t y, int32_t w, int32_t h,
               const BevelStyleT *style);

Draw a beveled frame. BevelStyleT specifies highlight, shadow, face colors and border width.

void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font,
              int32_t x, int32_t y, const char *text,
              uint32_t fg, uint32_t bg, bool opaque);
int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font,
                 int32_t x, int32_t y, char ch,
                 uint32_t fg, uint32_t bg, bool opaque);
int32_t textWidth(const BitmapFontT *font, const char *text);

Draw text using the built-in 8-pixel-wide bitmap font. opaque controls whether background pixels are drawn. drawChar returns the advance width.

void drawHLine(DisplayT *d, const BlitOpsT *ops,
               int32_t x, int32_t y, int32_t w, uint32_t color);
void drawVLine(DisplayT *d, const BlitOpsT *ops,
               int32_t x, int32_t y, int32_t h, uint32_t color);
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);

Blit from a source buffer to the backbuffer with clipping.

void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h);
void resetClipRect(DisplayT *d);

Widget system (dvxWidget.h)

An optional declarative layout system inspired by Amiga MUI and PC GEOS. Widgets are arranged in a tree of VBox/HBox containers. A two-pass layout engine (measure minimum sizes, then distribute space) handles geometry automatically. Scrollbars appear when the window is too small for the content.

Initialization

WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win);

Attach the widget system to a window. Returns the root container (a VBox filling the content area). Installs onPaint, onMouse, onKey, and onResize handlers on the window. Build the widget tree by passing the returned root (or its children) as parent to the widget creation functions below.

Call wgtInvalidate(root) after the tree is fully built to trigger the initial layout and paint.

Containers

WidgetT *wgtVBox(WidgetT *parent);    // vertical stack
WidgetT *wgtHBox(WidgetT *parent);    // horizontal stack
WidgetT *wgtFrame(WidgetT *parent, const char *title);  // titled border

Containers hold child widgets and control layout direction. wgtFrame draws a styled border with a title label and lays out children vertically.

Frame styles (set w->as.frame.style after creation):

Style Description
FrameInE Beveled inward / sunken (default)
FrameOutE Beveled outward / raised
FrameFlatE Solid color line; set w->as.frame.color (0 = windowShadow)

The title text is vertically centered on the top border line.

Container properties (set directly on the returned WidgetT *):

Field Type Default Description
align WidgetAlignE AlignStartE Main-axis alignment when no children have weight
spacing int32_t 0 (4px) Tagged size: gap between children
padding int32_t 0 (4px) Tagged size: internal padding

Alignment values:

Value HBox meaning VBox meaning
AlignStartE left top
AlignCenterE center center
AlignEndE right bottom

Widgets

WidgetT *wgtLabel(WidgetT *parent, const char *text);

Static text label. Sized to fit its text.

WidgetT *wgtButton(WidgetT *parent, const char *text);

Beveled push button. Set onClick to handle clicks.

WidgetT *wgtCheckbox(WidgetT *parent, const char *text);

Toggle checkbox. Read/write w->as.checkbox.checked. Set onChange to be notified.

WidgetT *wgtRadioGroup(WidgetT *parent);
WidgetT *wgtRadio(WidgetT *parent, const char *text);

Radio buttons. Create a group, then add options as children. The group tracks the selected index in w->as.radioGroup.selectedIdx. Set onChange on the group to be notified. Radio indices are auto-assigned.

WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);

Single-line text input field. Supports typing, cursor movement (arrows, Home, End), backspace, and delete. maxLen is the buffer capacity. Default weight is 100 (stretches to fill). Set onChange to be notified on edits.

WidgetT *wgtListBox(WidgetT *parent);

List box (basic -- set items with wgtListBoxSetItems()).

WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);

Multi-line text area (basic).

Spacing and dividers

WidgetT *wgtSpacer(WidgetT *parent);

Invisible spacer. Default weight is 100 -- pushes siblings apart.

WidgetT *wgtHSeparator(WidgetT *parent);   // horizontal line
WidgetT *wgtVSeparator(WidgetT *parent);   // vertical line

Visual divider lines (shadow + highlight, 2px).

Size specifications

Size fields (minW, minH, maxW, maxH, prefW, prefH, spacing, padding) accept tagged values created with:

wgtPixels(120)     // 120 pixels
wgtChars(15)       // 15 character widths
wgtPercent(50)     // 50% of parent

A raw 0 means auto (use the widget's natural/computed size).

Weight

The weight field controls how extra space is distributed among siblings. When a container is larger than its children's combined minimum size, the surplus is divided proportionally by weight.

  • weight = 0 -- fixed size, does not stretch (default for most widgets)
  • weight = 100 -- normal stretch (default for spacers and text inputs)
  • Relative values work: weights of 100, 200, 100 give a 1:2:1 split

When all children have weight 0, the container's align property determines where children are placed within the extra space.

Operations

void wgtSetText(WidgetT *w, const char *text);
const char *wgtGetText(const WidgetT *w);

Get/set text for labels, buttons, checkboxes, radios, and text inputs.

void wgtSetEnabled(WidgetT *w, bool enabled);
void wgtSetVisible(WidgetT *w, bool visible);

Disabled widgets are drawn grayed out and ignore input. Hidden widgets are excluded from layout.

void wgtInvalidate(WidgetT *w);

Trigger a full relayout and repaint of the widget tree. Call after modifying widget properties, adding/removing widgets, or changing text.

WidgetT *wgtFind(WidgetT *root, const char *name);

Search the subtree for a widget by name. Set w->name (up to 31 chars) to make a widget findable.

void wgtDestroy(WidgetT *w);

Remove a widget and all its children from the tree and free memory.

List box operations

void    wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
int32_t wgtListBoxGetSelected(const WidgetT *w);
void    wgtListBoxSetSelected(WidgetT *w, int32_t idx);

Types reference (dvxTypes.h)

WindowT

Core window structure. Key fields:

Field Type Description
id int32_t Unique window identifier
x, y, w, h int32_t Outer frame position and size
contentX, contentY int32_t Content area offset within frame
contentW, contentH int32_t Content area dimensions
contentBuf uint8_t * Pixel buffer (display format)
contentPitch int32_t Bytes per scanline in content buffer
title char[128] Title bar text
visible bool Window is shown
focused bool Window has input focus
minimized bool Window is minimized to icon
maximized bool Window is maximized
resizable bool Window has resize handles
maxW, maxH int32_t Max dimensions for maximize (-1 = screen)
userData void * Application data pointer

ColorSchemeT

All colors are pre-packed via packColor():

Field Usage
desktop Desktop background
windowFace Window frame fill
windowHighlight Bevel light edge
windowShadow Bevel dark edge
activeTitleBg/Fg Focused title bar
inactiveTitleBg/Fg Unfocused title bar
contentBg/Fg Window content area
menuBg/Fg Menu bar and popups
menuHighlightBg/Fg Highlighted menu item
buttonFace Button interior
scrollbarBg/Fg/Trough Scrollbar elements

BitmapFontT

Fixed-width bitmap font (8px wide, 14 or 16px tall). Glyphs are packed 1bpp, charHeight bytes per glyph, MSB-first.

BevelStyleT

typedef struct {
    uint32_t highlight;   // top/left edge color
    uint32_t shadow;      // bottom/right edge color
    uint32_t face;        // interior fill (0 = no fill)
    int32_t  width;       // border thickness in pixels
} BevelStyleT;

Window chrome

Windows use a Motif/GEOS-style frame:

  • 4px beveled outer border with perpendicular groove breaks
  • 20px title bar (dark charcoal background when focused)
  • Close button on the left edge (requires double-click)
  • Minimize button on the right edge (always present)
  • Maximize button to the left of minimize (resizable windows only)
  • Optional menu bar below the title bar (20px)
  • Optional scrollbars along the right and bottom edges (16px)

Minimized windows appear as 64x64 beveled icons along the bottom of the screen. If a window has an icon image set via dvxSetWindowIcon(), that image is shown; otherwise a nearest-neighbor-scaled snapshot of the window content is used. Double-click an icon to restore.


Hardware requirements

  • 486 or later CPU
  • VESA VBE 2.0+ compatible video card with linear framebuffer support
  • PS/2 mouse (or compatible mouse driver)
  • DPMI host (CWSDPMI, Windows DOS box, DOSBox, 86Box)

Tested on 86Box with PCI video cards. DOSBox is a trusted reference platform.