Welcome to DVX

help.png

DVX is a graphical desktop for DOS computers. Instead of typing commands at a prompt, you work with windows, menus, icons, and a mouse, much like Windows 3.1.

With DVX you can run several programs at the same time, each in its own window. You can move and resize those windows, switch between them, and copy text from one program to another.

About This Guide

This guide explains the things that work the same way everywhere in DVX: starting programs, using windows, menus, and dialog boxes, opening and saving files, and getting out of trouble.

Every program also has its own help, which explains that program's commands. Open a program and press F1, or choose Contents from its Help menu.

Where to Go Next

If you are new to DVX, read these pages in order:

Getting Started

Using the Mouse and Keyboard

Working with Windows

Menus and Dialog Boxes

Running Programs

Tip: Press F1 at any time to open help. Use the list on the left side of the help window to jump to any page, or click the Search button to look for a word.

Getting Started

What You Need

Starting DVX

At the DOS prompt, change to the folder where DVX is installed, type DVX, and press Enter. You can also start DVX from any folder by typing its full path, for example C:\DVX\DVX.

While DVX loads you see a title screen with a bar that fills from left to right. Sometimes the bar fills a second time. That happens after programs or parts of DVX were added or removed, while DVX brings its help files up to date. It only takes a moment.

When loading is done, the screen changes to the DVX desktop and the Program Manager window opens.

The Desktop and the Program Manager

The desktop is the background behind all the windows. It can show a solid color or a picture.

The Program Manager is your home base. Its window shows your programs as icons, sorted into groups you can arrange as you like. Double-click an icon to start that program. The Installed Programs group always lists every program installed in DVX: newly installed programs appear there without any setup.

Running Programs

The Program Manager

Leaving DVX

Choose Exit DVX from the Program Manager's File menu, or close the Program Manager window. DVX asks "Are you sure you want to exit DVX?". Click Yes to return to DOS.

Before it exits, DVX closes each running program the same way you would by closing its windows. A program with unsaved work asks whether you want to save it first:

Tip: If a program does not respond when DVX asks it to close, DVX tells you so and asks whether to end it. Click Yes to end the program and continue exiting (anything it had not saved is lost), or No to stay in DVX.

If a dialog box is open when you choose Exit DVX, DVX brings it to the front and does not exit. Answer or close the dialog box first.

Next: Using the Mouse and Keyboard

Using the Mouse and Keyboard

The Mouse

Moving the mouse moves the pointer on the screen. These are the basic mouse actions:

  Action          How to do it
  ------          ------------
  Point           Move the pointer over something.
  Click           Press and release the left mouse button once.
  Double-click    Click the left button twice, quickly.
  Drag            Hold the left button down while you move the mouse, then let go.
  Right-click     Press and release the right mouse button. Some programs show a short menu of choices.

If you hold the pointer still over some buttons and icons, a small tip appears that tells you what they do.

The speed of the pointer, the double-click speed, and the scroll wheel can be changed. See Changing Settings.

The Scroll Wheel

If your mouse has a scroll wheel, turning it scrolls the window you are working in (the active window), not the window under the pointer.

The Keyboard

Most things you can do with the mouse you can also do with the keyboard:

Letters that are underlined in menus, buttons, and labels are shortcuts. Hold Alt and press the letter to choose that item.

See the full list of keyboard shortcuts

Changing Settings

Working with Windows

Every program shows its work in one or more windows. Only one window is active at a time: the one you are typing into. The title bar of the active window is drawn in a different color from the others, so you can always tell which one it is.

Parts of a Window

Making a Window Active

Click anywhere in the window. It comes to the front and becomes active. You can also press Alt+Tab to go to the next window, or Shift+Alt+Tab to go the other way.

Moving a Window

Drag the title bar. DVX always keeps part of the window on the screen so you cannot lose it.

Changing a Window's Size

Point at any edge or corner of the window, then drag. Dragging a corner changes the width and the height together. The small notches near each corner show where the corners begin. Some windows, such as dialog boxes, have a fixed size.

Maximizing and Restoring

Click the Maximize button to make the window fill the whole screen. Click the same button again (it now shows a smaller box) to put the window back to its old size. Double-clicking the title bar does the same thing.

Minimizing

Click the Minimize button to put the window away. It becomes a small square icon along the bottom of the screen, showing the program's picture or a tiny copy of the window. The program keeps running.

To bring the window back, double-click its icon. You can also use the Task Manager (Ctrl+Esc) and click Switch To. Icons can be hidden behind other windows; move or minimize those windows to reach them.

Closing a Window

Any of these closes a window:

Closing a program's last window usually ends the program.

The Window Menu

Every window has a window menu. Open it by clicking the Close box once, or by pressing Alt+Space. It contains:

  Item            What it does
  ----            ------------
  Restore         Puts a maximized window back to its old size.
  Move            Move the window with the arrow keys. Press Enter when done, or Esc to put it back.
  Size            Resize the window with the arrow keys. Right and Down make it bigger, Left and Up make it smaller. Press Enter when done, or Esc to cancel.
  Minimize        Turns the window into an icon.
  Maximize        Makes the window fill the screen.
  Screenshot...   Saves a picture of the whole screen to a file.
  Window Shot...  Saves a picture of just this window's contents to a file.
  Close           Closes the window.

Items that do not apply right now are grayed out.

Arranging Windows

The Program Manager's Window menu can arrange all open windows for you:

Minimized windows are left where they are.

Saving a Picture of the Screen

Press Ctrl+F12 to save a picture of the whole screen, or Ctrl+Shift+F12 to save a picture of the active window. DVX asks where to save it. Name the file ending in .png or .bmp to choose the picture format.

Menus and Dialog Boxes

Using Menus

Many windows have a menu bar under the title bar, with words such as File, Edit, and Help. Each word opens a menu of commands.

With the mouse, click a menu name to open it, then click a command. While a menu is open, move the pointer along the menu bar to look at the other menus. Click anywhere outside the menu to close it without choosing anything.

With the keyboard, press F10 to open the first menu, or hold Alt and press the underlined letter of a menu name. Then:

Some commands show extra signs:

Some programs also show a short menu when you right-click.

Menus Most Programs Share

Many programs use the same menu names for the same kinds of commands:

  Menu   Usual commands
  ----   --------------
  File   New, Open..., Save, Save As..., Page Setup..., Print..., and Exit.
  Edit   Cut, Copy, Paste, and Select All.
  Help   Contents opens the program's own help. About shows the program's name and copyright.

Dialog Boxes

A dialog box is a small window that asks you a question or asks for details. While a dialog box is open you must answer it before you can use the rest of the program.

Message boxes show a short message with a picture: an "i" for information, a "!" for a warning, an "X" for an error, or a "?" for a question. Click one of the buttons to answer.

Saving Changes

When you close a program, or start a new file, while you have unsaved work, most programs ask "Save changes?":

Next: Buttons, Lists, and Other Controls

Opening and Saving Files

Buttons, Lists, and Other Controls

Windows and dialog boxes are built from a small set of controls. They all work the same way in every program.

Buttons

Click a button to use it. When a button is highlighted, Enter or the Space bar presses it too.

Check Boxes

A check box turns one option on or off. Click it, or press the Space bar when it is highlighted. A check mark means the option is on.

Option Buttons

Option buttons come in groups, and only one in a group can be chosen. Click the one you want, or use the arrow keys to move through the group.

Lists

A list shows several items. Click an item to select it, and double-click it (or press Enter) to open or use it. Up, Down, Page Up, Page Down, Home, and End move through the list. Typing a letter jumps to the next item that starts with that letter.

Some lists let you select more than one item. Hold Ctrl while clicking to add or remove single items, or hold Shift while clicking to select everything between two items.

Some lists have column headings. Click a heading to sort the list by that column; click it again to sort the other way. Drag the line between two headings to make a column wider or narrower.

Drop-down Lists

A drop-down list shows one choice with an arrow button beside it. Click it to see all the choices, then click the one you want. With the keyboard, Up and Down change the choice, and Enter, the Space bar, F4, or Alt+Down opens the list. In an open list, press Enter, F4, or Alt+Down to take the highlighted choice.

Some drop-down lists also let you type your own entry in the box.

Text Boxes

Click in a text box and type. These keys help you edit:

  Key                      What it does
  ---                      ------------
  Left, Right              Move one character.
  Ctrl+Left, Ctrl+Right    Move one word.
  Home, End                Go to the start or end of the line.
  Shift + a moving key     Select text as you move.
  Backspace, Del           Delete text.
  Ctrl+A                   Select everything.
  Ctrl+Z                   Undo the last change. Press it again to redo.

You can also drag across text with the mouse to select it. Double-click selects a word, and a triple-click selects the whole line.

Cut, Copy, and Paste work in every text box; see Copying and Pasting.

Copying and Pasting

Number Boxes

A number box has small up and down arrows. Click the arrows, press Up or Down, or type a number.

Sliders

Drag the slider's knob, or click it and use the arrow keys. Home and End jump to the lowest and highest values.

Tabs

Some windows have several pages, with tabs along the top like file folders. Click a tab to show its page, or use Left and Right when the tabs are highlighted.

Trees

A tree shows items inside other items, like folders. Click the small + box to open an item and the - box to close it. With the keyboard, Right opens an item and Left closes it.

Scroll Bars

A scroll bar lets you see the part of a window that does not fit. Click the arrow at either end to move a little, click the empty track to move a whole page, or drag the box in the middle. The mouse wheel also scrolls.

Opening and Saving Files

When a program opens or saves a file, it shows the same Open or Save As dialog box.

Parts of the Dialog Box

Opening a File

Double-click a folder to look inside it. Click a file to put its name in the File name box, then click Open, or just double-click the file. If you type the name of a file that does not exist, the dialog asks whether you want to create it.

Saving a File

Go to the folder you want, type a name in the File name box, and click Save. If you leave off the ending (such as .txt), the dialog adds the one from the Filter box. If a file with that name already exists, DVX asks whether to replace it.

Choosing a Folder

Where a program needs a folder rather than a file, the Browse... button beside it opens the Choose a Folder dialog. It works like the Open dialog but lists folders only. Go into the folder you want and click OK. To choose a new folder, type its name in the Folder name box and click OK: the program makes it when it needs it.

Tip: Alt+D opens the Drive list from the keyboard. You can also change drives by typing the drive letter and a colon, such as A:, in the Path box and pressing Enter.

Copying and Pasting

The clipboard is a holding place for text. You put text on it with Cut or Copy, and take it back out with Paste. There is one clipboard shared by every program, so you can copy text in one program and paste it into another.

  Key       Command   What it does
  ---       -------   ------------
  Ctrl+X    Cut       Removes the selected text and puts it on the clipboard.
  Ctrl+C    Copy      Puts a copy of the selected text on the clipboard.
  Ctrl+V    Paste     Puts the clipboard text where the cursor is. If text is selected, the pasted text replaces it.

These keys work in every text box. Many programs also have the same commands in their Edit menu, and some show them when you right-click.

Copying Text Between Programs

The clipboard keeps its text until you cut or copy something else, so you can paste the same text many times.

Printing

Programs that can print have Print... in their File menu, and programs that print text also have Page Setup.... Every program prints to the same printer, so you only tell DVX once which printer you have and where it is plugged in (see Changing Settings). You can also send printouts into a file instead of a printer.

The Print Dialog

File > Print... opens the Print dialog. Choose where the printout goes (a printer port such as LPT1, a serial port such as COM1, or a file), how many copies you want, and (for a document of several pages) whether to print all of it or only some pages. Then choose Print. When the printout goes into a file, type the file's name, or click Browse... to pick it.

While a printout is going to the printer, a small Printing window shows it is busy. You can keep working in other programs, and Cancel stops the printout.

When the Printer Has a Problem

If the printer is out of paper, switched off, off line, or not answering, a message says what is wrong. Fix the problem and choose Retry to carry on, or Cancel to stop printing. Nothing waits forever.

Page Setup

File > Page Setup... chooses how text is laid out on paper: the margins, how many lines fit on a page, how wide a line is, and a header and footer printed on every page (for example the file name and the page number). Your choices are kept for next time and used by every program that prints text.

Pictures

Pictures print as large as fits on the page: in color on a color printer (one with a color ribbon or cartridge), and in shades of grey on the others. Printing pictures needs your printer's model to be set (see Changing Settings); with the setting for text only, a program tells you so.

Some programs draw whole pages of their own, such as reports with charts. On a PostScript printer the printer draws those pages itself, as sharp as it can print. On other printers their text is made from DVX's fonts: DejaVu Sans, Sans Mono, and Serif have sizes made for printing, so they print crisp from 6 to 24 points; other fonts, and larger text, are enlarged and smoothed, a little softer.

Running Programs

Finding and Starting a Program

Every program installed in DVX appears as an icon in the Program Manager, in its Installed Programs group, and you can put icons for the programs you use most into groups of your own. Programs you add later come as packages you install with the Software program (see Adding and Removing Software). Select an icon to see a short description of the program in the Program Manager's status bar.

Double-click an icon to start that program, or select it with the arrow keys and press Enter. The program opens in a new window. While it is loading, the mouse pointer shows an hourglass. The Program Manager's Search box finds a program in every group as you type its name.

To start a program file that has no icon, choose Run... from the Program Manager's File menu and type or pick the file.

Opening Documents

A document can be opened the same way as a program: DVX starts the program set up for its kind of file (the letters after the dot in its name, such as TXT) and gives it the document, which the program opens right away: a picture opens in the Image Viewer, and a music or sound file (MOD, S3M, XM, IT, MID, RMI, WAV, or VOC) plays in Media Player. Which program opens which kind of file is set in the settings program (see Changing Settings).

Running Several Programs

You can run many programs at the same time, each in its own window. They all keep running while you work in another one.

Most programs can only run once at a time. If you start a program that is already running, DVX tells you so; find its window instead with Alt+Tab or the Task Manager. A few take the document you opened instead: a music file opened while Media Player is running is added to its list and plays. Some programs can be opened more than once, each copy in its own window.

Switching Between Programs

Programs That Use the Whole Screen

Some programs, games especially, can fill the whole screen instead of a window (many use Alt+Enter to switch). Your other programs keep running while they do. Alt+Tab and Ctrl+Esc still work and bring the desktop back; when you return to the program, it fills the screen again.

Running DOS Programs

DVX can start ordinary DOS programs too (files ending in .EXE, .COM, or .BAT): from an icon in the Program Manager, from Run..., or by double-clicking one in the File Manager. Such a program has the whole screen to itself. Before it starts, DVX names the programs that will close and asks whether to go on; each one closes as it would if you closed it, asking about unsaved work. When the DOS program ends, DVX comes back with the Program Manager. The Program Manager's own help (DOS Programs) tells how to add one as an icon.

Closing a Program

Close the program's main window, or choose Exit from its File menu. A program that has unsaved work usually asks whether you want to save it first. See Working with Windows for all the ways to close a window.

Help for Each Program

Every program has its own help, which explains its menus and commands. To open it:

The Help menu usually also has an About command, which shows the program's name and copyright.

If a program has no help of its own, F1 opens this guide instead.

Getting Help

Task Manager

Adding and Removing Software

The Program Manager

The Program Manager opens when DVX starts and stays open the whole time DVX runs. It is where you find and start your programs:

You can make your own groups, add, move, and copy icons, give an icon a shortcut key that starts it from any program, and choose large icons, small icons, or a list. Everything you arrange is remembered the next time you start DVX. The Program Manager's own help explains all of this: press F1 while it is the active window, or choose Contents from its Help menu.

Warning: Closing the Program Manager window leaves DVX, after asking you first.

Adding and Removing Software

Programs, fonts, sounds, themes, desktop pictures, MIDI instruments, and the libraries and widgets other programs use can be added to DVX after it is installed. Each comes as a package: one file whose name ends in .DPK.

Installing a Package

Open the .DPK file in File Manager, or start the Software program and choose Install from File. Software shows what will be installed first -- including other packages it needs from the same folder -- who signed it, and how much disk space it takes, then installs it when you choose Install. A new program appears in the Program Manager's Installed Programs group; new fonts, sounds, themes, desktop pictures, and instruments appear where you choose them in the settings program. Libraries and widgets work from the next time DVX starts.

Software can also get packages from the package server over the network: its Available page lists every package the server offers. Choose one and Install; Software fetches it with any packages it needs, then shows what will be installed exactly as for a file.

A package can be signed by the people who made it, like a seal on a letter. Software tells you when a package is not signed by anyone you trust: install it only if you trust where it came from. To trust a publisher, get their key file (it ends in .KEY) and choose Trust a Publisher's Key in Software's File menu.

Keeping DVX Up to Date

Software's Updates page asks the package server what is newer than what you have, DVX itself included, and Update All installs it. When DVX itself is updated, the new version runs from the next time DVX starts; Software lists any add-ons that will stop working with it until they are updated too. The package server's address and port are set with Package Server in Software's File menu.

Publishing Your Own Packages

With an account on the package server (its email and password given in the same Package Server window), Software's File > Publish a Package sends a package you made to the server. It is offered to everyone once the server's operator has reviewed it; Software's Published page shows how the review went.

Removing a Package

Software's Installed list shows every package in DVX. Choose one and Remove. If other packages need it, Software names them and removes them too (or nothing); files you changed since they were installed are kept unless you say otherwise.

Making Your Own Packages

Package Builder makes a package of your own program, fonts, or other files, for other people to install with Software, and signs it with a key of yours. The DVX BASIC IDE makes a package of a BASIC program in one step: File > Make Package. Their help explains how.

If DVX Will Not Start

If DVX does not start after you installed something, hold down a Shift key while DVX starts. DVX then starts without the libraries and widgets add-ons installed, so you can remove the one that causes the trouble with Software. See Troubleshooting.

Running Programs

Troubleshooting

Changing Settings

The mouse, the screen colors, the desktop picture and the screen saver, the screen resolution, which program opens each kind of document, the network, the printer, joysticks, and the sounds are changed with the settings program, which has an icon in the Installed Programs group of the Program Manager. Start it like any other program. Its own help, opened with F1 or its Help menu, explains every setting.

Settings you keep are saved, and DVX uses them again the next time it starts.

DVX plays a short sound when it starts and when it ends, and when a message appears (a different one for errors, warnings, questions, and information). Each sound can be changed or turned off, and the volume set, in the settings program.

A screen saver can fill the screen with a moving picture when the computer has not been used for a while; pressing a key or moving the mouse brings your work back. Choose one, and how many minutes to wait, on the Desktop page of the settings program. There is none until you choose one.

Tip: If a new screen resolution leaves the screen unreadable, just wait: DVX goes back to the old one after 15 seconds unless you confirm the change.

Where your settings and files are kept

Everything DVX and its programs save for you goes in one folder, the USER folder inside the DVX folder (for example C:\DVX\USER): your settings, each program's settings and the files it saves (such as saved games), Terminal's downloads, and DVX's log. To back up your work, copy this one folder. Installing a new version of DVX never changes it.

The fonts, sounds, themes, desktop pictures, and screen savers that come with DVX are in the SHARED folder. To add your own, put them in the folder of the same name in your USER folder: FONTS, SOUNDS, THEMES, WPAPER, or SAVERS. The settings program lists yours together with DVX's, and a file of yours with the same name as one of DVX's is used instead of it. Themes you save go there too.

To keep the USER folder somewhere else (on another disk, or one for each person who uses the computer), set DVXUSER to that folder before starting DVX, for example SET DVXUSER=D:\MYDVX in AUTOEXEC.BAT.

Task Manager

The Task Manager shows every program that is running and lets you switch to one or stop one. Press Ctrl+Esc from anywhere to open it, or choose Task Manager from the Program Manager's Help menu.

The list shows each program's name, its window title, its file, how much memory it is using, and its status. The line at the bottom shows how many programs are running and how much memory is in use.

  Button       What it does
  ------       ------------
  Switch To    Brings the selected program's window to the front, even if it was minimized. Double-clicking a program does the same.
  End Task     Stops the selected program immediately.
  Run...       Starts a program you type or pick, or opens a document in its program.

When a Program Stops Responding

If a program stops reacting to the mouse and keyboard, press Ctrl+Esc. After a few seconds the Task Manager shows its status as Not responding (a program that is only waiting for you to answer one of its own messages is not counted). Select the program and click End Task. The other programs keep running, and the selection moves to the program next to the one you ended.

Warning: End Task does not ask first, and it does not give the program a chance to save. Any unsaved work in that program is lost.

If the whole screen is frozen and even the mouse pointer does not move, DVX cannot recover. Restart the computer.

When a Program Crashes

If a program makes a serious mistake, DVX stops it and shows an Application Error message saying the program "has caused a fault and will be terminated". Click OK. The rest of DVX and your other programs keep running, but unsaved work in the stopped program is lost.

Getting Help

Press F1 at any time. F1 opens the help of the program you are working in; a program with no help of its own opens this guide instead. To come back to this guide from anywhere, choose DVX Help from the Program Manager's Help menu.

Help opens in the DVX Help window. If DVX Help is already open, F1 shows the new page in the same window.

Finding Your Way Around

Press F1 in the DVX Help window itself to learn more about using it.

Keyboard Shortcuts

These keys work in every program:

  Key              What it does
  ---              ------------
  F1               Open help for the program you are using.
  Alt+Tab          Switch to the next window.
  Shift+Alt+Tab    Switch to the previous window.
  Ctrl+Esc         Open the Task Manager.
  Alt+F4           Close the active window.
  Alt+Space        Open the window menu of the active window.
  F10              Open the menu bar of the active window.
  Alt+letter       Open the menu, or press the button, whose underlined letter you typed.
  Tab, Shift+Tab   Move to the next or previous control in a window.
  Enter            Press the highlighted button, or the main button of a dialog box.
  Esc              Close a menu or drop-down list, or cancel a dialog box.
  F4, Alt+Down     Open a drop-down list, or take the highlighted choice.
  Ctrl+F12         Save a picture of the whole screen.
  Ctrl+Shift+F12   Save a picture of the active window.

Editing Text

  Key              What it does
  ---              ------------
  Ctrl+X           Cut
  Ctrl+C           Copy
  Ctrl+V           Paste
  Ctrl+A           Select everything
  Ctrl+Z           Undo (press again to redo)

In DVX Help

  Key              What it does
  ---              ------------
  Alt+Left         Back
  Alt+Right        Forward
  Ctrl+F           Search
  Esc              Close the help window

Programs can have their own shortcut keys. Look for them at the right edge of each program's menus, and in each program's help. A shortcut key given to a program icon works everywhere, whichever window is in front (but not while a menu or a message is open).

Troubleshooting

The mouse pointer does not move

DVX uses your DOS mouse driver. Load the mouse driver (usually from AUTOEXEC.BAT, or by typing its name at the DOS prompt) before you start DVX. If the scroll wheel does nothing, your mouse driver may not support wheels; everything else still works. If the pointer is too fast or too slow, change the mouse settings (see Changing Settings).

DVX will not start, or the screen stays blank

DVX needs a video card with a VESA video BIOS and a mode of at least 640x480 at 256 colors or more, which nearly every card from about 1992 on has. Many older cards need their VESA driver program loaded first (from AUTOEXEC.BAT, or by typing its name at the DOS prompt): UniVBE (Scitech Display Doctor) works with most cards, and some cards came with their own, such as S3VBE20 for S3 cards and M64VBE for ATI Mach64 cards. Older cards work too, but the screen updates more slowly on them. If DVX still will not start, DVX.LOG in the USER folder says why.

If the screen goes wrong right after you picked a new screen resolution, wait 15 seconds and DVX puts the old one back.

Safe video mode

If the screen resolution DVX is set to use does not work on your computer (for example after changing video cards), DVX starts in safe video mode instead: 640x480, which every video card can show. A message tells you so and asks whether to use this mode from now on. Answer Yes to keep it, or No to try the old resolution again at the next start.

If DVX starts but the screen cannot be read (the monitor cannot show the saved resolution), hold down a Shift key while DVX starts. DVX then starts in safe video mode and asks the same question. Typing DVX /SAFE at the DOS prompt does the same.

Starting this way is a safe start: DVX also leaves out the libraries and widgets that add-on packages installed, in case one of them is what stops DVX from starting. A message says so once the desktop is up. Start DVX the usual way to use them again, or remove the add-on that causes the trouble with the Software program.

Add-ons that could not be loaded

When an add-on package's library or widget cannot be loaded (it was made for another version of DVX, it needs something that is not installed, or its file is damaged), DVX starts without it and a message once the desktop is up lists what was left out and why. The rest of DVX works as usual. Reinstall the add-on (a version made for this DVX), or remove it with the Software program.

You can pick another resolution at any time with the settings program (see Changing Settings).

There is no sound

DVX finds the sound hardware by itself. A Sound Blaster needs its BLASTER setting in AUTOEXEC.BAT, for example SET BLASTER=A220 I5 D1 H5, and a Gravis UltraSound its ULTRASND setting, for example SET ULTRASND=220,1,1,11,5 (the card's manual or setup program gives the numbers; a Sound Blaster AWE's setting includes E620 for its music chip). A Windows Sound System card is found at its usual addresses; a sound device on the printer port (a Disney Sound Source or a Stereo-on-1) is found on its own, while a Covox Speech Thing must be chosen in the settings file (DVX.INI [audio] lptDac = covox). Without a sound card DVX uses the PC speaker, which plays tones and beeps but not recorded sounds. The settings program shows the sound hardware in use, lets you pick it yourself, and sets the volume; if a sound is set to none there, nothing is heard for it. Without any sound hardware DVX and its programs work the same, silently.

MIDI music plays on the sound card's own recorded instruments (a Gravis UltraSound or a Sound Blaster AWE) when DVX's General MIDI SoundFont is installed (the Software program offers it) or the card's own instruments are there; otherwise on its music chip, or on DVX's own synthesizer. Music modules (MOD, S3M, XM, and IT files) play on a Gravis UltraSound's or a Sound Blaster AWE's own voices, which takes the computer little time; on other cards the computer mixes them, which takes much of a slow computer's time (the Control Panel's Sounds page can have them always mixed).

An empty floppy or CD-ROM drive

When a program tries to read a drive that has no disk in it, or to write to a write-protected disk, the program simply reports that it could not read or write the file. DVX never stops at the DOS "Abort, Retry, Fail" question. Put the disk in and try again.

Installing, upgrading, and removing DVX

DVX is installed with SETUP, from the DVX CD or from Disk 1 of the DVX floppy disks: change to that drive and type SETUP. After the licence, Setup asks what you would like to do: Install DVX, Upgrade or repair DVX, or Remove DVX. Click the picture or the button of your choice. To install, choose the folder for DVX (type it, or click Browse... to pick it; a folder that does not exist yet is made), then Next: what to install, that is the fonts, the parts of DVX, and the add-ons you want. Setup checks that there is room and unpacks DVX there, asking for each floppy disk it needs in turn. It can also make DVX start when the computer starts (it shows the line it adds to AUTOEXEC.BAT and keeps a copy of the old file as AUTOEXEC.DVX). Setup never writes to the CD or the floppy disks.

The fonts are up to you. None keeps DVX to its own font; the base set adds DejaVu Sans, Sans Mono, and Serif in all their styles, in sizes for the screen and for printing (about 8 MB). A program that asks for a font you did not install shows its text in DVX's own font instead, unless it carries that font itself (some programs do, such as Welcome and the DVX Demo). Every font file holds its own licence: the Licence button of the Font dialog shows it. With None, Setup does not ask for the last floppy disks, which hold only fonts; you can add the base set later from the package server in Software (as DVX Fonts), or by running Setup again. Software lists DVX and DVX Fonts among the installed software, and offers updates to them as they are published.

DVX comes in parts. The desktop and the everyday programs (Program Manager, File Manager, Notepad, Clock, the Image Viewer, the Help Viewer, Control Panel, and Software) are always installed; the other parts each have a box to tick: DVX BASIC (for writing your own programs, with BASCOMP to build them at the DOS prompt), Databases (for BASIC programs that keep their data in a database), Networking (network cards, the web, and the package server), Communications (the Terminal; it needs Networking), Media (the Media Player and the Sound Recorder), Developer Tools (the Icon Editor, the Resource Editor, the Help Editor, the Package Builder, and the Widget Showcase, with DVXRES and DVXHLPC for the DOS prompt), and Demos (the DVX Demo and the BASIC Demo). Point at one to see what it holds. A part you leave out takes no disk space and leaves more memory for your programs. To add or remove parts later, run Setup again. Software lists each part as one package, whose details name what it holds; without Networking, Software installs packages from files only.

The CD also has add-ons, which Setup offers with a box to tick for each: More Fonts (more than forty more typefaces, about 8 MB) and DVX General MIDI (instruments for MIDI music on a Gravis UltraSound or a Sound Blaster AWE32 or AWE64, about 4 MB). They are installed like any other add-on, so Software lists them and can remove them; to add one later, choose Install from File in Software and open it from the ADDONS folder of the CD. The floppy disks have no add-ons: get them from the package server in Software instead.

To upgrade or repair DVX, run Setup again and choose Upgrade or repair DVX, with the folder DVX is in. (When DVX is in the usual folder, such as C:\DVX, Setup says so and suggests this choice.) Your settings and saved files (the USER folder) are kept, unless you tick Start over to remove them. Setup starts with the fonts, parts, and add-ons that folder already has; choose others to add or remove them. A part you untick is removed, unless other software in the folder needs it (Setup then tells you it keeps it). If you choose Install DVX for a folder that already has DVX, Setup offers to upgrade it instead.

To remove DVX, run Setup and choose Remove DVX, with the folder DVX is in. The SETUP in the DVX folder (for example C:\DVX\SETUP) can remove DVX too, from its own folder only. Files you added to the folder yourself are not removed, and the USER folder is only removed if you ask.

Running DVX from a CD-ROM or a write-protected disk

DVX also runs straight from a disk it cannot write to. It then keeps your settings and files in a DVXUSER folder in your TEMP folder, and tells you so once. To keep them somewhere of your choosing, set DVXUSER to a folder on your hard disk before starting DVX (see Changing Settings). Without a TEMP folder, DVX tells you that changed settings last only until you exit DVX. Either way the help files are used as they are. To keep everything on your hard disk, install DVX there with Setup.

A program is already running

Most programs can only be open once. Use Alt+Tab or the Task Manager (Ctrl+Esc) to find its window. If you cannot see it, it may be minimized to an icon at the bottom of the screen.

"This program was built for a different version of DVX"

The program was made for an older or newer version of DVX and cannot run on this one. Get a copy of the program made for your version of DVX. The same thing can happen to an add-on (a file in the LIBS or WIDGETS folder): DVX then stops while starting and names the file. Remove that file or replace it with one made for your version, then start DVX again.

A program stopped responding or crashed

Press Ctrl+Esc and use End Task. See the Task Manager page for details.

Running low on memory

Each running program uses memory. If DVX reports that it is out of memory, close programs you are not using. The Task Manager shows how much memory each program uses, and System Information in the Program Manager's Help menu shows how much memory your computer has free. A lower screen resolution or fewer colors also frees memory.

A window is lost off the screen or behind others

Press Alt+Tab until it comes to the front, or use the Program Manager's Window menu and choose Cascade to bring every window into view.

DVX will not exit

When you exit DVX, each program is closed as if you had closed it yourself. If you answer Cancel when a program asks to save its work, DVX stays running. Save or close that program, then choose Exit DVX again.

Reporting a problem

DVX writes a record of what happened to the file DVX.LOG, in your USER folder (for example C:\DVX\USER\DVX.LOG). If a program crashes, this file contains details that help the program's author find the problem. The file is started fresh each time DVX starts, so copy it before starting DVX again.

Changing Settings

Task Manager

For Developers

The rest of this help file is a technical reference for programmers who write programs, controls, or add-ons for DVX in C. It is listed under Technical Reference (for Developers) in the table of contents.

Start with the Developer Overview

Programs can also be written in BASIC, if the BASIC programming environment is installed. It is easier to learn and has its own help: open it and press F1.

Developer Overview

help.png

Note: This part of the help is written for programmers. If you just want to use DVX, start with the user guide instead.

Welcome to DVX (the user guide)

DVX (DOS Visual eXecutive) is a graphical user interface environment for DOS, designed for 486-class hardware and above. This help file covers the system architecture, the core libdvx API, the widget toolkit, and the platform abstraction layer.

What Is libdvx?

libdvx is the heart of DVX -- a DXE3 library that provides:

Applications written in C link against libdvx (and the widget DXEs they need) to build native DVX programs. Applications written in DVX BASIC compile to bytecode that runs on top of libdvx via the form runtime.

Target Audience

This document is aimed at developers writing native C code for DVX:

All examples and signatures assume the DJGPP cross-compiler toolchain.

What's Covered

Use the table of contents on the left to navigate. The API reference is organised by header file; each function has a one-line summary, parameter table, and (where useful) a working example.

Conventions

Getting Started

To write a program, widget, or library right away, start with the SDK Quick Start: it creates a working project from a template and walks through building, running, debugging, and installing it.

SDK Quick Start

To understand how DVX works, read these topics in order:

Then dip into the API Reference to find specific functions. The public entry point for any application is dvxInit / dvxRun in dvxApp.h.

License

DVX is distributed under the MIT License (see the copyright notice at the top of every source file). Third-party code (stb_image, stb_ds, stb_image_write in thirdparty/) is used under its own permissive license.

DVX Architecture Overview

DOS Visual eXecutive -- A Windowing GUI for DJGPP/DPMI

DVX (DOS Visual eXecutive) is a complete windowing GUI compositor targeting DJGPP/DPMI on DOS. It provides overlapping windows with Motif-style chrome, a retained-mode widget toolkit, cooperative multitasking of DXE-loaded applications, and a dirty-rectangle compositor optimized for 486/Pentium hardware.

Key Design Constraints

The runtime environment consists of a bootstrap loader (dvx.exe) that loads core DXE libraries, widget plugins, and the shell, which in turn loads and manages DXE application modules.

Contents

Five-Layer Architecture

Display Pipeline

Window System

Widget System

Modal Dialogs

Resource System

Preferences System

DVX Folders

Per-App Memory Tracking

DXE Module System

Event Model

Font System

Color System

Platform Layer

Build System

Five-Layer Architecture

DVX is organized into five layers, each implemented as a single .h/.c pair. Every header includes dvxTypes.h (the shared type definitions) to avoid circular dependencies. The layers are strictly stacked: each layer depends only on the layers below it.

 Applications (DXE .app modules)
 ==================================================
 |                                                |
 |  +------------------------------------------+  |
 |  |  Layer 5: dvxApp  (Application API)      |  |  dvxApp.h / dvxApp.c
 |  |  Event loop, window creation, public API  |  |
 |  +------------------------------------------+  |
 |  |  Layer 4: dvxWm   (Window Manager)       |  |  dvxWm.h / dvxWm.c
 |  |  Window stack, chrome, drag, resize      |  |
 |  +------------------------------------------+  |
 |  |  Layer 3: dvxComp  (Compositor)          |  |  dvxComp.h / dvxComp.c
 |  |  Dirty rect tracking, merge, LFB flush   |  |
 |  +------------------------------------------+  |
 |  |  Layer 2: dvxDraw  (Drawing Primitives)  |  |  dvxDraw.h / dvxDraw.c
 |  |  Rects, bevels, text, blits, cursors     |  |
 |  +------------------------------------------+  |
 |  |  Layer 1: dvxVideo (Video Backend)       |  |  dvxVideo.h / dvxVideo.c
 |  |  VESA VBE, LFB mapping, pixel format     |  |
 |  +------------------------------------------+  |
 |                                                |
 |  +------------------------------------------+  |
 |  |  Platform Layer (dvxPlat.h)              |  |  dvxPlatformDos.c
 |  |  OS-specific: video, input, asm spans    |  |
 |  +------------------------------------------+  |
 |                                                |
 |  +------------------------------------------+  |
 |  |  Shared Types (dvxTypes.h)               |  |
 |  |  DisplayT, WindowT, RectT, ColorSchemeT  |  |
 |  +------------------------------------------+  |
 ==================================================

Layer Summary

  Layer               Header         Responsibility
  -----               ------         --------------
  1 - Video           dvxVideo.h     VESA VBE mode negotiation, LFB mapping via DPMI, backbuffer allocation, packColor() (RGB to native pixel format), display-wide clip rectangle.
  2 - Draw            dvxDraw.h      All 2D drawing: rectFill, rectCopy, drawBevel, drawText/drawTextN, drawMaskedBitmap (cursor), drawTermRow (batch terminal row). Stateless beyond clip rect. Dispatches hot inner loops through BlitOpsT function pointers.
  3 - Compositor      dvxComp.h      Dirty rectangle tracking (dirtyListAdd), pairwise merge of overlapping rects (dirtyListMerge), and flushRect to copy dirty regions from backBuf to LFB.
  4 - Window Manager  dvxWm.h        Window lifecycle, Z-order stack, chrome drawing (title bars, bevels, close/minimize/maximize gadgets), hit testing, drag/resize, menu bars, scrollbars, system menu, keyboard move/resize, minimized icon bar.
  5 - Application     dvxApp.h       Public API aggregating all layers into AppContextT. Provides dvxInit/dvxShutdown, dvxRun/dvxUpdate, window creation helpers, image loading, clipboard, accelerator tables, theme management, wallpaper, video mode switching, screenshot capture.

Display Pipeline

The double-buffer strategy is the single most important performance decision in DVX. All drawing goes to a system RAM backbuffer (DisplayT.backBuf); only dirty rectangles are flushed to the linear framebuffer (DisplayT.lfb) in video memory.

This matters because writes to video memory over the PCI bus are 10-50x slower than writes to main RAM on 486/Pentium hardware for random-access patterns.

Per-Frame Compositing Pipeline

  1. Input poll (mouse, keyboard)
         |
  2. Event dispatch (focus window callbacks)
         |
  3. Layers call dirtyListAdd() for changed regions
         |
  4. dirtyListMerge() consolidates overlapping rects
         |
  5. For each merged dirty rect:
     a. Clip and redraw desktop background (or wallpaper)
     b. For each window (back-to-front, painter's algorithm):
        - wmDrawChrome()   -- frame, title bar, gadgets, menu bar
        - wmDrawContent()  -- blit per-window content buffer
        - wmDrawScrollbars()
     c. Draw minimized window icons
     d. Draw popup menus / tooltips (overlay pass)
     e. Draw software mouse cursor
         |
  6. flushRect() -- copy each dirty rect from backBuf to LFB
         |
  7. Yield (platformYield)

Key Data Structures

DisplayT -- Central display context: width, height, pitch, pixel format, LFB pointer, backbuffer pointer, palette, clip rectangle. Passed by pointer through every layer -- no globals.

BlitOpsT -- Vtable of span fill/copy function pointers resolved at init time for the active pixel depth. On DOS these dispatch to hand-written rep stosl / rep movsd asm inner loops.

DirtyListT -- Fixed-capacity dynamic array of RectT. Linear scanning for merge candidates is cache-friendly at typical sizes (under 128 rects). If the list fills up, the compositor merges aggressively or falls back to full-screen repaint.

Why This Works on a 486

Window System

WindowT Structure

Each WindowT is the central object of the window manager. Key fields:

  Field Group                              Purpose
  -----------                              -------
  Geometry (x, y, w, h)                   Outer frame rectangle (including chrome).
  Content area (contentX/Y/W/H)           Computed from frame minus chrome. Where application content lives.
  Content buffer (contentBuf, contentPitch) Per-window backbuffer in native pixel format. Persists across frames.
  Chrome state (menuBar, vScroll, hScroll) Optional menu bar and scrollbars. Affect content area computation.
  Widget tree (widgetRoot)                 Root of the retained-mode widget tree (NULL if using raw callbacks).
  Callbacks                                onPaint, onKey, onKeyUp, onMouse, onResize, onClose, onMenu, onScroll, onFocus, onBlur, onCursorQuery.

Window Stack (Z-Order)

WindowStackT is an array of WindowT* ordered front-to-back: index count-1 is the topmost window. This allows:

Only one drag/resize/scroll operation can be active system-wide at a time (single mouse), so that state lives on the stack, not on individual windows.

Chrome Layout

  +-------------------------------------------+
  |  4px outer border (raised bevel)          |
  |  +-------------------------------------+  |
  |  | [X] Title Bar Text     [_] [^] [X]  |  |  20px title height
  |  +-------------------------------------+  |
  |  | 2px inner border                    |  |
  |  +-------------------------------------+  |
  |  | Menu Bar (optional, 20px)           |  |
  |  +-------------------------------------+  |
  |  |                                     |  |
  |  |          Content Area               |  |
  |  |                                     |  |
  |  |                               |  S  |  |  S = vertical scrollbar
  |  |                               |  B  |  |      (16px wide)
  |  +-------------------------------------+  |
  |  |  Horizontal Scrollbar (optional)    |  |  16px tall
  |  +-------------------------------------+  |
  |  4px outer border                         |
  +-------------------------------------------+

Chrome constants are compile-time defines:

  CHROME_BORDER_WIDTH   = 4px
  CHROME_TITLE_HEIGHT   = 20px
  CHROME_INNER_BORDER   = 2px
  CHROME_MENU_HEIGHT    = 20px
  SCROLLBAR_WIDTH       = 16px

Hit Test Regions

wmHitTest() iterates the stack front-to-back and returns a hit-part identifier: HIT_CONTENT, HIT_TITLE, HIT_CLOSE, HIT_RESIZE, HIT_MENU, HIT_VSCROLL, HIT_HSCROLL, HIT_MINIMIZE, HIT_MAXIMIZE. Resize edge detection returns a bitmask of RESIZE_LEFT, RESIZE_RIGHT, RESIZE_TOP, RESIZE_BOTTOM (corners combine two edges).

Menu System

Menus use fixed-size inline char buffers for labels (MAX_MENU_LABEL = 32, no heap strings). Both menus per bar and items per menu are stored in dynamic arrays that grow on demand. Supports cascading submenus via MenuItemT.subMenu pointer. Item types: normal, checkbox, radio. Separators are non-interactive items. The popup state (PopupStateT) tracks a stack of parent frames for cascading submenu nesting.

Minimized Windows

Minimized windows display as 64x64 icons at the bottom of the screen with beveled borders, similar to a classic desktop icon bar. Icons show a scaled-down preview of the window's content buffer, refreshed one per frame in a round-robin fashion to amortize the scaling cost.

Widget System

The widget system (dvxWgt.h) is a retained-mode toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container.

WidgetT Base Structure

Every widget shares the same WidgetT struct. The type field is a runtime-assigned integer ID. The wclass pointer references the widget's WidgetClassT vtable. Widget-specific private data is stored in w->data (opaque void*).

Tree linkage: parent, firstChild, lastChild, nextSibling. No prevSibling -- this halves pointer overhead and removal is still O(n) for typical tree depths of 5-10.

Layout Engine

Two-pass flexbox-like algorithm:

Size hints use a tagged encoding: the top 2 bits of an int32_t select the unit (pixels, character widths, or percentage of parent), the low 30 bits hold the value. Macros: wgtPixels(v), wgtChars(v), wgtPercent(v).

Widget Class Dispatch (WidgetClassT)

Each widget type provides a WidgetClassT with a handlers[] array indexed by stable method IDs. Method IDs are never reordered or reused -- new methods append at the end. A change to the slots that would break a compiled widget bumps DVX_ABI_VERSION instead, and the loader then refuses widgets built for the old number (see Binary Interface Version).

Methods include: PAINT, PAINT_OVERLAY, CALC_MIN_SIZE, LAYOUT, ON_MOUSE, ON_KEY, ON_ACCEL_ACTIVATE, DESTROY, GET_TEXT, SET_TEXT, POLL, ON_BLUR, ON_WHEEL, and more (23 defined, IDs 0-22, room for 32). A widget class whose version is not WGT_CLASS_VERSION is rejected by wgtRegisterClass.

Class Flags

  Flag                    Meaning
  ----                    -------
  WCLASS_FOCUSABLE        Can receive keyboard focus (Tab navigation)
  WCLASS_HORIZ_CONTAINER  Lays out children horizontally (HBox)
  WCLASS_PAINTS_CHILDREN  Widget handles child rendering itself
  WCLASS_SCROLLABLE       Mouse wheel sends Up/Down arrow keys
  WCLASS_SCROLL_CONTAINER ScrollPane -- scrolling viewport
  WCLASS_NEEDS_POLL       Needs periodic polling (e.g. AnsiTerm comms)
  WCLASS_SWALLOWS_TAB     Tab key goes to widget, not focus navigation
  WCLASS_PRESS_RELEASE    Click = press + release (buttons)

Available Widget Types

Each widget is a separate .wgt DXE module. 29 widget types are included:

  Widget                  Description
  ------                  -----------
  Box (VBox/HBox)         Vertical and horizontal layout containers
  Button                  Clickable push button with label
  Canvas                  Raw drawing surface for custom painting
  Checkbox                Boolean toggle with checkmark
  ComboBox                Text input with dropdown list
  DataCtrl                Data-bound control for database operations
  DbGrid                  Database grid (tabular data display)
  Dropdown                Dropdown selection list
  Image                   Static image display
  ImageButton             Button with bitmap icon
  Label                   Static text label
  ListBox                 Scrollable selection list
  ListView                Multi-column list with headers and sorting
  ProgressBar             Determinate progress indicator
  Radio                   Radio button (mutual exclusion group)
  ScrollPane              Scrollable viewport container
  Separator               Visual divider line
  Slider                  Value selection via draggable thumb
  Spacer                  Empty space for layout
  Spinner                 Numeric input with up/down arrows
  Splitter                Resizable split pane
  StatusBar               Window status bar with sections
  TabControl              Tabbed page container
  Terminal (AnsiTerm)     ANSI terminal emulator widget
  TextInput               Single-line text entry field
  Timer                   Periodic timer events
  Toolbar                 Toolbar with icon buttons
  TreeView                Hierarchical tree display
  WrapBox                 Flow layout (wrapping horizontal container)

Widget API Registry

Each widget DXE registers a small API struct under a name during wgtRegister(). Callers retrieve it via wgtGetApi("button", sizeof(ButtonApiT)) and cast to the widget-specific API type. Every API struct starts with a WgtApiHeaderT (the provider's DVX_ABI_VERSION and struct size), so wgtGetApi refuses a table built for another ABI or shorter than the caller expects. Per-widget headers provide typed accessors so callers avoid manual casts. Adding a new widget requires zero changes to the core.

Widget Interface Descriptors (WgtIfaceT)

Each widget can register an interface descriptor that describes its BASIC-facing properties, methods, and events. These descriptors are used by the form runtime and IDE for generic dispatch and property panel enumeration. Properties have typed getters/setters (WGT_IFACE_STRING, WGT_IFACE_INT, WGT_IFACE_BOOL, WGT_IFACE_ENUM).

Modal Dialogs (dvxDlg.h)

Pre-built modal dialog boxes block the caller and run their own event loop via dvxUpdate() until the user dismisses them. Each dialog sets ctx->modalWindow to prevent input from reaching other windows while open.

Dialog Types

  Function              Purpose
  --------              -------
  dvxMessageBox         Generic message box with button/icon flags (MB_OK, MB_YESNO, MB_ICONERROR, etc.)
  dvxErrorBox           Shortcut for MB_OK + MB_ICONERROR
  dvxInfoBox            Shortcut for MB_OK + MB_ICONINFO
  dvxFileDialog         File open/save dialog with directory navigation, a drive dropdown, and a filter dropdown
  dvxInputBox           Single-line text input with OK/Cancel
  dvxIntInputBox        Integer input with spinner, clamp range, and step
  dvxChoiceDialog       List selection with OK/Cancel
  dvxPromptSave         Canonical "Save changes?" Yes/No/Cancel dialog

Flag Encoding

dvxMessageBox flags split into two nibbles: button configuration (low) and icon type (high). This matches the Win16 MessageBox() convention so OR'd flags read naturally:

dvxMessageBox(ctx, "Confirm", "Delete file?", MB_YESNO | MB_ICONQUESTION);

Return values are ID_OK / ID_CANCEL / ID_YES / ID_NO / ID_RETRY.

Resource System (dvxRes.h)

Resources are appended to DXE3 files (.app, .wgt, .lib) after the normal DXE content. The DXE loader never reads past the DXE header, so the appended data is invisible to dlopen. Every DXE can carry its own icons, text strings, and binary data without separate resource files.

On-Disk Layout

[DXE3 content]           -- untouched, loaded by dlopen
[resource data entries]  -- sequential, variable length
[resource directory]     -- fixed-size entries (48 bytes each)
[footer]                 -- magic + directory offset + count (16 bytes)

Readers start from the end: seek to EOF - sizeof(footer), verify the magic (DVX_RES_MAGIC = "DVXR"), then seek to the directory. Entries are sorted at open time so lookups are O(log n) via bsearch.

Types

  Type ID           Value   Payload
  -------           -----   -------
  DVX_RES_ICON      1       Image data (BMP or PNG, typically 16x16 or 32x32 icons)
  DVX_RES_TEXT      2       Null-terminated string (localisable UI text, author, copyright)
  DVX_RES_BINARY    3       Arbitrary data (app-specific)
  DVX_RES_FONT      4       A .DFN font the program carries (the shell adds it for the program when it loads it)

Runtime API

  Function           Description
  --------           -----------
  dvxResOpen         Open a handle by reading the footer and directory
  dvxResRead         Look up a resource by name and read its data into a malloc'd buffer
  dvxResFind         Look up a resource's directory entry (no data copy)
  dvxResClose        Release the handle
  dvxResAppend       Append a new resource to a DXE file
  dvxResRemove       Remove a resource by name

The dvxApp.h wrappers (dvxResLoadIcon, dvxResLoadText, dvxResLoadData) combine open + read + close for the common case.

Preferences System (dvxPrefs.h)

Handle-based API over classic INI files. Multiple files can be open at once. Each prefsLoad / prefsCreate returns a PrefsHandleT opaque pointer passed to subsequent calls and freed with prefsClose. The system INI is DVX.INI in the user folder: prefsLoad(platformDvxIniPath()).

Usage Pattern

PrefsHandleT *h = prefsLoad(platformDvxIniPath());
int32_t  w      = prefsGetInt(h, "video", "width", 640);
bool     sound  = prefsGetBool(h, "audio", "enable", true);
const char *bg  = prefsGetString(h, "desktop", "wallpaper", "");

prefsSetInt(h, "video", "width", 800);
prefsSave(h);
prefsClose(h);

Boolean getters recognise "true"/"yes"/"1" and "false"/"no"/"0" (case-insensitive). If the file does not exist, prefsLoad still returns a valid empty handle with the path captured so prefsSave can write a new file.

DVX Folders

DVX Folders (dvxPlat.h)

Files are kept apart by who writes them. The DVX folder holds the program: Setup installs it, an upgrade replaces all of it, and DVX only reads it. Everything written while DVX runs goes in the user folder, the one folder to back up.

C:\DVX\
  DVX.EXE  SETUP.EXE  SETUP.LST
  SYSTEM\                 tools, and the shipped DVX.INI and ASSOC.INI
  LIBS\  WIDGETS\
  APPS\<vendor>\<program>\  a program, its help, and DATA\ (files it ships)
  SHARED\                 shipped for every program
    FONTS\  SOUNDS\  THEMES\  WPAPER\
  USER\                   the user folder
    DVX.INI  ASSOC.INI  DVX.LOG
    FONTS\  SOUNDS\  THEMES\  WPAPER\   the user's own files
    APPS\<vendor>\<program>\  each program's settings and saved files
    DOWNLOAD\               Terminal's downloads

Every folder name is defined once in dvxPlat.h (DVX_SYSTEM_DIR, DVX_APPS_DIR, DVX_SHARED_DIR, DVX_USER_DIR, DVX_DATA_DIR, and the kinds' names), with the functions that find them (platformUserInit, platformUserPath, platformDvxIniPath, platformAppUserDir, platformKindFind, platformKindReadDir, and the rest; see the API reference).

Per-App Memory Tracking (dvxMem.h)

All allocations route through dvxMalloc/dvxFree wrappers that prepend a 16-byte header recording the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected at app termination.

Transparent Interception

DXE code does NOT need to include dvxMem.h. The DXE export table maps malloc / free / calloc / realloc / strdup to the dvxXxx wrappers, so standard C code is tracked automatically.

Explicit use (e.g. in the Task Manager) can include dvxMem.h to call:

  Function               Purpose
  --------               -------
  dvxMemGetAppUsage      Query current bytes allocated for an app
  dvxMemResetApp         Free every tracked allocation charged to an app

The dvxMemAppIdPtr pointer is set by the shell to &ctx->currentAppId so the allocator always knows which app to charge.

DXE Module System

DVX uses DJGPP's DXE3 (Dynamic eXtension) format for all loadable modules. DXE3 supports RTLD_GLOBAL symbol sharing -- symbols exported by one module are visible to all subsequently loaded modules. This is critical: widget DXEs call core API functions (e.g. rectFill, wgtInvalidate) that are exported by the core library DXE.

Module Types

  Extension  Directory   Purpose                                             Examples
  ---------  ---------   -------                                             --------
  .lib       LIBS/       Core libraries loaded first. Provide infrastructure APIs. Each exports an ABI stamp.  libtasks.lib, libdvx.lib, dvxshell.lib
  .wgt       WIDGETS/    Widget type plugins. Each exports wgtRegister() and an ABI stamp.    button.wgt, listview.wgt, terminal.wgt
  .app       APPS/*/     Application modules. Each exports appDescriptor (which records its ABI) and appMain(). Loaded on demand by the shell.  progman.app, notepad.app, cpanel.app

Binary Interface Version

DVX_ABI_VERSION (dvxTypes.h) is one number for the binary interface between all modules. Each .lib and .wgt exports <baseName>AbiVersion (written by DVX_ABI_STAMP(baseName) in its main source file); each app's AppDescriptorT starts with abiVersion (SHELL_APP_DESCRIPTOR fills it); each widget API struct starts with a WgtApiHeaderT; each widget class carries WGT_CLASS_VERSION, which is the same number. The loader checks every .lib and .wgt right after loading it and stops the boot with a message naming a module whose stamp is missing or different; the shell refuses a mismatched app with a message box; wgtGetApi and wgtRegisterClass refuse mismatched tables and classes. Details always go to DVX.LOG. The bump rule and the size-table test that enforces it are described with DVX_ABI_VERSION in the API reference (dvxTypes.h).

Boot Sequence

  dvx.exe (loader)
     |
     +-- Enter VGA mode 13h, display splash screen with progress bar
     |
     +-- Scan LIBS/ for *.lib, WIDGETS/ for *.wgt
     |
     +-- Read .dep files for each module (dependency base names)
     |
     +-- Topological sort: load modules in dependency order
     |     - dlopen() with RTLD_GLOBAL
     |     - ABI stamp checked; a mismatch stops the boot
     |     - Each .wgt that exports wgtRegister() has it called
     |
     +-- Find and call shellMain() (exported by dvxshell.lib)
           |
           +-- dvxInit() -- video mode, input, font, colors, cursors
           |
           +-- Load desktop app (progman.app)
           |
           +-- Main loop:
                 dvxUpdate() -> tsYield() -> shellReapApps()

Application Lifecycle

Two kinds of DXE apps:

Callback-only (hasMainLoop = false)

appMain() creates windows, registers callbacks, and returns. The app lives through GUI callbacks driven by the shell's main loop. Lifecycle ends when the last window is closed. No extra task stack needed -- simpler and cheaper.

Main-loop (hasMainLoop = true)

A dedicated cooperative task is created. appMain() runs in that task with its own loop, calling tsYield() to share CPU. Needed for apps with continuous work (terminal emulators, games). Lifecycle ends when appMain() returns.

Crash Recovery

The platform layer installs signal handlers for SIGSEGV, SIGFPE, SIGILL. On crash, the handler logs platform-specific diagnostics (register dump on DJGPP), then longjmps back to the shell's main loop. The crashed app is killed; other apps and the shell survive. This provides Windows 3.1-style fault tolerance.

Per-App Memory Tracking

All allocations route through dvxMalloc/dvxFree wrappers that prepend a 16-byte header recording the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected at app termination.

Event Model

DVX uses a cooperative polling model. The main loop (dvxRun / dvxUpdate) runs this cycle each frame:

Event Dispatch Chain

  Mouse/Keyboard Input
       |
  Global handlers (Ctrl+Esc, modal filter)
       |
  System-wide hotkeys (dvxRegisterHotkey)
       |
  Menu keys (Alt+letter), accelerator table check (focused window)
       |
  Window callback (onMouse / onKey)
       |
  [If widget tree installed:]
       |
  widgetOnMouse / widgetOnKey
       |
  Widget hit test (widgetHitTest)
       |
  wclsOnMouse / wclsOnKey (vtable dispatch)
       |
  Universal callbacks (onClick, onChange, etc.)

Mouse events use two hit tests. widgetHitTest finds the widget whose class handles the press; it stops at a WCLASS_NO_HIT_RECURSE container (Splitter, ScrollPane), so the container's ON_MOUSE handler still gets the press (divider drag, scrollbar clicks, forwarding into its children). widgetCallbackTarget continues through non-focusable NO_HIT_RECURSE containers into the child whose paintClip (the visible part when it was last painted) contains the point. The universal callbacks (onClick, onDblClick, onMouseDown/Up/Move), context menus, tooltips, and the cursor shape use that target, so a control inside a Splitter pane or ScrollPane viewport gets its own events only where it is visible; on the container's own chrome the container is the target.

Accelerator Tables

Per-window accelerator tables map key + modifier combinations to command IDs. The runtime normalizes key/modifier at registration time with the same rule it applies to every keystroke (letters uppercased, Ctrl+letter and Alt+letter mapped to the letter, Ctrl/Alt always significant, Shift significant except for a printable character typed without Ctrl/Alt), so matching at dispatch time is two integer comparisons per entry. Matched accelerators fire the window's onMenu callback with the command ID, unifying the menu and hotkey code paths.

Mouse Wheel

The wheel goes to the focused window. It is first offered to a WGT_METHOD_ON_WHEEL handler on the focused widget and then on each ancestor (delta in notches, positive = down, reverse setting applied); a handler that returns true consumes it. Otherwise a WCLASS_SCROLLABLE focused widget, or its nearest scroll-container ancestor, receives Up/Down arrow keys (wheelStep per notch), and failing that the window's own scrollbar moves. The focused widget's onScroll callback fires in every case.

Mouse Cursor

Software-rendered cursor using the classic AND/XOR mask approach. Seven cursor shapes are compiled in: arrow, horizontal resize, vertical resize, NW-SE diagonal resize, NE-SW diagonal resize, busy (hourglass), and crosshair. The cursor is painted into the backbuffer on top of the composited frame and the affected region is flushed to the LFB each frame.

Double-Click Detection

Timestamp-based: two clicks on the same target (title bar, minimized icon, close gadget) within the configurable double-click interval trigger the double-click action. Separate tracking for each target type.

Font System

Every text call takes a BitmapFontT, and there are two kinds of them. The system font is the 8x16 VGA ROM font (code page 437, 256 glyphs) built into libdvx: 8-pixel cells, one byte per glyph row, so character positions are a multiplication, glyph lookup an index, and every scanline a single byte -- the hot paths of drawChar, drawTextN, and drawTermRow depend on it. A loaded font (dvxText.h) is one size of a font family from the FONTS folder (Setup installs the base set of DejaVu Sans, Sans Mono, and Serif or none, and the More Fonts add-on the others; a family that is not there opens as the system font): per-glyph advances and bitmap boxes, any width, proportional or fixed-pitch, and in up to four styles. The same calls draw and measure both; a loaded font's glyphs field is what sends them to the general paths.

BitmapFontT

  typedef struct {
      int32_t           charWidth;    // cell width (fixed pitch) or the digit 0's advance
      int32_t           charHeight;   // line height
      int32_t           firstChar;    // 0
      int32_t           numChars;     // 256
      const uint8_t    *glyphData;    // system font: charHeight bytes per glyph; loaded: bitmaps
      const FontGlyphT *glyphs;       // loaded font: per-glyph metrics; NULL: the system font
      const uint32_t   *glyphAt;      // loaded font: each glyph's bitmap offset
      int32_t           ascent;       // top of a line to the baseline
      int32_t           pixelSize;
      uint32_t          style;        // FONT_STYLE_BOLD / ITALIC
      uint32_t          flags;        // FONT_FLAG_PROPORTIONAL, _SYNTH_BOLD, _SYNTH_ITALIC, _GRAY
      int32_t           underlineY;   // underline and accelerator row
      int32_t           strikeY;      // strike-through row
      int32_t           lineThick;
      void             *priv;         // the font cache's entry
      uint32_t          serial;       // new whenever the glyphs are built (again)
  } BitmapFontT;

A FontGlyphT is six bytes: advance, bitmap width and height, the bitmap's offset from the pen (left, and top up from the baseline), and flags. A glyph's bitmap is height rows of (width + 7) / 8 bytes, the leftmost pixel in the top bit.

Text Rendering Functions

drawChar() -- Renders a single character. Supports opaque (background fill) and transparent modes.

drawTextN() -- Optimized batch rendering for a known character count. Clips once for the entire run, fills background in a single rectFill, then overlays glyph foreground pixels. Significantly faster than per-character rendering for long runs. In a loaded font each glyph is drawn at the pen; every bitmap byte whose 8 pixels lie inside the clip rectangle goes through the same unrolled row writers as the system font, so only the bytes on a clip edge are drawn pixel by pixel.

drawTermRow() -- Renders an 80-column terminal row in a single pass, with per-cell foreground/background from a 16-color palette, blink attribute support, and cursor rendering. Exists because per-character terminal rendering is unacceptably slow on target hardware.

drawTextAccel() -- Renders text with & accelerator markers. The character after & is marked with the font's underline across its advance to indicate the keyboard shortcut.

drawTextStyled() -- Text with an underline and / or strike-through as wide as it, at the font's rows.

textWidth(), textWidthN(), textWidthAccel() -- Widths: for the system font a multiplication, for a loaded font the sum of the advances.

Loading Fonts

dvxFontOpen(family, size, style) reads the family list from FONTS (once, at the first call: each .DFN file's header and size table), picks the face for the style -- the exact one, else one with fewer style bits whose missing bits are made -- and that face's size nearest the one asked for. It never scales. The font is built once into a glyph table and bitmaps of its own, so drawing never decides anything per glyph:

Fonts are cached and counted: the same family, size, and style opened again is the same font. Up to eight fonts no one holds stay loaded, the least recently used unloaded first. Everything the font system allocates is charged to the system, not to the program that happened to open a font.

Fonts a Program Carries

A family is installed (the FONTS folder, read by its full path so a program that changes the current folder still finds them) or added for an owner: a program's app id, or 0 for every program. The shell calls dvxFontAddResources for each program it loads (its DVX_RES_FONT resources, read in place: a font source is a file and the font's offset and length in it) and dvxFontRemoveOwner when its resources are released; a program can add .DFN files or fonts in memory itself. Every lookup is for the running program (*dvxMemAppIdPtr, the shell's currentAppId; window callbacks run as the window's program).

Opening a font picks its face per style from the program's own family and the installed one together: the style's faces in sFaceOrder's order, the first style either has; then the nearest size of either face's sizes; then the program's face when it has that size, else the installed one. So a program's faces and sizes win, and the installed family fills in the rest. A program with no families of its own shares the cache with every other; one with its own gets fonts built for it alone (their fallback glyphs may come from its families too). A program's fonts that change or go are detached from the cache: the ones no one holds unloaded, held ones kept as they are.

Each face holds its source counted, and each font built from it holds it too, so a font's licence (dvxFontLicence) is always the one of the face it was built from, its program ended or not.

The building works on one level per pixel (0 to FONT_GRAY_FULL) whatever the source, and packs the finished glyph at the font's own depth, so made styles, box characters, and fallback glyphs are the same code for both kinds of font.

Smoothed Text

A .DFN size can come twice: 1-bit, and smoothed -- FONT_GRAY_BPP (2) bits per pixel, four levels from none to full, rendered by FreeType with light hinting. mkfont smooths the sizes fonts.ini lists under smooth. A 1-bit size is FreeType's 1-bit hinted rendering, except at the sizes listed under graycut, where it is the light-hinted rendering with every pixel at least half covered set: some faces' 1-bit hinting draws them too light (DejaVu Serif Bold at its smoothed sizes, which comes out near regular weight, and DejaVu Sans Bold at 8 pixels). When smoothing is on (dvxFontSetSmooth), such a size opens from its smoothed strike and the font has FONT_FLAG_GRAY; every other size, and every face without one, stays 1-bit.

Drawing a full-level pixel is a store of the text color, as for a 1-bit font. A partial one is the text color blended into the pixel under it, channel by channel through the display's PixelFormatT masks and shifts; text mostly stands on one color, so each glyph keeps its last blend per level and reuses it while the color under it stays the same. Only partial pixels cost more than 1-bit text. At 8 bits per pixel there is nothing to blend with: the pixels of FONT_GRAY_HALF and more are drawn and the rest dropped. The scaled text path (drawTextScaled) does the same.

The application decides (dvxSetFontSmoothing): smoothing is on when the user wants it (DVX.INI [fonts] smoothing, on by default; Control Panel's Video page) and the screen has more than 8 bits per pixel, and a video mode change applies it again. Turning it on or off builds every open font again in place: widgets keep their font pointers, the glyphs change under them, and the font's serial changes so caches of measurements (list column widths and the like) know to measure again. Every window is then laid out and painted again.

Widget Fonts

Any widget can have a font of its own (wgtSetFont; BASIC's FontName, FontSize, FontBold ... properties), and the widgets inside a container draw with the container's font unless they have their own, so a form's font is the default for everything on it. wgtFontFor(w, inherited) walks up the parent chain to the nearest font set, else takes the one passed down (the application's). The class dispatch helpers hand every paint, measure, and layout method that font, so widget code just uses its font parameter -- the same code serves the system font, a proportional font, and a fixed-pitch one of any size. Event handlers, which are given no font, call wgtFont(w). Widgets measure text with textWidth / textWidthN, never with character counts. A class that edits text by columns (TextBox, Terminal, ComboBox, Spinner: WCLASS_FIXED_FONT) given a proportional font draws with the system font instead; a fixed-pitch font of any size works there, its column math using that font's charWidth and charHeight.

Underline and strike-through are part of a font, as in Visual Basic: a font opened with them shares the glyphs of the same font without them, and every text call draws its lines. A widget holds its font open while it is set and closes it when destroyed (widgetFree, the one place a widget is freed).

Rich Text

Text in several fonts in one control is built on the text flow (dvxFlow.h): runs of text, each in a font and a color, laid out into wrapped, aligned lines as tall as the tallest font on them, with offsets for carets, hit tests, and selections. The RichLabel widget is one flow built by calls. The RichEdit widget keeps its document in the richtext library (styled text: runs of interned styles over code page 437 text, an alignment per paragraph, copy / replace / restyle, and RTF in and out) and lays each paragraph out as a flow of its own, so an edit lays out only the paragraphs it touched and repaints only the rows they hold (its quick repaint); a change of height repaints the widget. Its undo steps are pieces of styled text swapped back in, and Copy keeps a styled copy for a paste of the same text.

Font Files (.DFN)

One file holds one family in one style at every size it comes in -- its screen sizes, their smoothed copies, and its print sizes (mkfont's print key; the SDK guide's Making Fonts) -- made on the build host by mkfont (src/tools/mkfont.c) with FreeType from TrueType, OpenType, Type 1, BDF, and PCF fonts, as listed in src/fonts/fonts.ini. Opening a font reads only the header and the strike table, so sizes nothing uses cost disk space, not memory. A .DFN is a pure format conversion: the converter never adds or changes a glyph, it only marks the characters a face lacks. Little-endian, every record read whole:

  Part                      Size            Holds
  ----                      ----            -----
  DfnHeaderT                60 bytes        "DVXF", format version (2), style, face flags (fixed pitch; box characters drawn by DVX), strike count (1 to 64), family name (31 characters), where the notice and licence texts are and their lengths
  DfnStrikeT x count        24 bytes each   per size, smallest first: where its glyph table and bitmaps are, the bitmaps' size, the pixel size, ascent, descent, line height, widest advance
  FontGlyphT x 256          6 bytes each    per size, at its glyph table offset
  glyph bitmaps             as listed       per size, back to back in character order
  notice text               as listed       the family and style, the source fonts it was converted from, and their copyright notices
  licence text              as listed       the licence of the font it came from

Each glyph's bitmap starts where the one before ends, so the reader (dvxFontFmt.c) works out the offsets and checks that they fill the strike's bitmaps exactly. Every count, offset, and size is checked against the font's length before it is used: a damaged file is refused, never read past.

Each file carries what its licence asks to go with every copy of the font: the notice and licence texts, transliterated to ASCII with CR LF line ends. They are read only when asked for (dvxFontLicence, the Font dialog's Licence button), never when text is drawn, so they cost nothing at run time. A font goes nowhere without its licence: copied, installed, or carried inside a program as a resource.

The reader takes a DfnSourceT: a file of its own, a font inside another file at an offset (a program's font resource), or a font in memory. dfnOpen reads the header and strike table, dfnLoadStrike one size, and dfnLoadText a text.

Performance Optimization

AppContextT stores a fixed-point 16.16 reciprocal of font.charHeight (charHeightRecip) so that dividing by charHeight (the pixel-to-row mapping for popup menu and system menu hover/click) becomes a multiply+shift instead of an integer divide, which costs 40+ cycles on a 486.

Color System

Pixel Format

PixelFormatT describes the active VESA mode's pixel encoding. Populated once from the VBE mode info block. Stores shift, mask, and bit count for each channel so packColor() can convert RGB to native format with shift-and-mask arithmetic -- no per-pixel computation.

Supported depths:

  Depth    Bytes/Pixel  Notes
  -----    -----------  -----
  8 bpp    1            Palette mode. Nearest-index via 6x6x6 color cube + grey ramp.
  15 bpp   2            5-5-5 RGB (1 bit unused).
  16 bpp   2            5-6-5 RGB.
  32 bpp   4            8-8-8 RGB (8 bits unused).

ColorSchemeT -- Theming

All 20 UI colors are pre-packed into display pixel format at init time. Every color is a uint32_t that can be written directly to the framebuffer with zero per-pixel conversion. The scheme must be regenerated on video mode change, but mode changes require re-init anyway.

Color roles mirror classic Motif/Windows 3.x conventions:

Source RGB values are kept in AppContextT.colorRgb[] for theme save/load. Themes are stored as INI files with a [colors] section. The API provides dvxLoadTheme(), dvxSaveTheme(), dvxSetColor(), and dvxResetColorScheme().

Bevel Styles

Bevels are the defining visual element of the Motif aesthetic. Convenience macros create bevel style descriptors by swapping highlight and shadow colors:

  BEVEL_RAISED(colorScheme, borderWidth)       -- raised 3D look
  BEVEL_SUNKEN(colorScheme, face, borderWidth) -- sunken/inset look
  BEVEL_TROUGH(colorScheme)                    -- 1px scrollbar trough
  BEVEL_SB_BUTTON(colorScheme)                 -- scrollbar button

Platform Layer

All OS-specific and CPU-specific code is isolated behind platform/dvxPlat.h. To port DVX, implement a new dvxPlatformXxx.c against this header.

Implementations

  File                Target        Details
  ----                ------        -------
  dvxPlatformDos.c    DJGPP/DPMI    Real VESA VBE, INT 33h mouse, INT 16h keyboard, rep movsd/rep stosl asm spans, DPMI physical memory mapping for LFB, INT 9 hook for key-up, CuteMouse Wheel API.
  dvxPlatformHost.c   Linux (test)  Host test backend: in-memory framebuffer, scripted mouse/keyboard queues, fake clock. Used only by the host test suites.
  dvxPlatformUtil.c   Shared        Code common to both backends (paths, globbing, F-key normalization, string helpers).

Abstraction Areas

Video

platformVideoInit() -- mode probe and framebuffer setup. platformVideoShutdown() -- restore previous mode. platformVideoEnumModes() -- enumerate available modes.

Framebuffer Flush

platformFlushRect() -- copy dirty rect from backBuf to video memory. On DOS, each scanline uses rep movsd for near-optimal aligned 32-bit writes over the bus, to the linear framebuffer or, on a card without one, through the 64 KB bank window (platformBankChunk cuts the runs at the window's end).

Optimized Memory Spans

Six functions: platformSpanFill8/16/32() and platformSpanCopy8/16/32(). Called once per scanline of every rectangle fill, blit, and text draw. On DOS these use inline assembly for critical inner loops.

Mouse Input

Polling model. platformMousePoll() returns position and button bitmask. Wheel support via CuteMouse API. platformMouseInit() can be called again after a video-mode change; it re-applies the speed, acceleration, and wheel settings after the driver reset.

Keyboard Input

platformKeyboardRead() -- non-blocking key read; Shift/Ctrl/Alt+F1..F12 are reported as the plain F-key scancode (platformNormalizeFKeyScan). The keyboard hook (a chain on INT 9 on DOS; its code and data are pinned with platformLockRegion) decodes every key going down and up into a held-key table and a queue of raw events (platformKeyEventRead, platformKeyIsDown), which feed onKeyUp and game input windows. platformAltScanToChar() -- scancode-to-ASCII lookup for Alt+key combinations.

Timing

platformClock() / platformClockHz() -- monotonic UI tick counter. platformMicros() -- high-resolution monotonic clock (PIT, with the TSC between interrupts when there is one). The timer service owns the timer interrupt: callbacks at up to 1000 Hz (platformTimerAdd) while the BIOS keeps its 18.2 Hz tick. platformEntropyStamp() -- fine-grained timestamp (TSC, or BIOS tick + PIT counter) for input-timing entropy.

Crash Recovery

platformInstallCrashHandler() -- signal handlers + longjmp for fault tolerance. platformOutOfMemory() -- stb_ds allocation-failure hook; logs and raises SIGSEGV so it takes the same recovery path. platformAppExit() / platformAppAbort() -- exit() and abort() as loaded modules see them: the shell's hook ends only the calling program, by the same recovery path, without a fault message.

DXE Support

platformRegisterDxeExports() -- register C runtime and platform symbols for DXE resolution. platformRegisterSymOverrides() -- register function pointer overrides for module loader (SDK API for embedders and test harnesses; DVX itself does not call it).

Build System

Cross-Compilation

DVX is cross-compiled from Linux using a DJGPP cross-compiler (i586-pc-msdosdjgpp-gcc). The top-level Makefile orchestrates building all subsystems in dependency order.

  make               -- build everything
  ./mkcd.sh          -- build + create ISO for the target emulator

Build Targets

  all: core tasks loader texthelp listhelp tools widgets shell taskmgr serial sql apps
  Target     Output                    Description
  ------     ------                    -----------
  core       bin/libs/libdvx.lib       GUI core library (draw, comp, wm, app, widget infrastructure)
  tasks      bin/libs/libtasks.lib     Cooperative task switcher
  loader     bin/dvx.exe               Bootstrap loader (the DOS executable)
  widgets    bin/widgets/*.wgt         29 widget type plugins
  shell      bin/libs/dvxshell.lib     DVX Shell (app management, desktop)
  taskmgr    bin/libs/taskmgr.lib      Task Manager (loaded as a separate DXE)
  texthelp   shared library            Shared text editing helpers (clipboard, word boundaries)
  listhelp   shared library            Shared dropdown/list helpers
  apps       bin/apps/*/*.app          Application modules (progman, notepad, clock, etc.)
  tools      bin/dvxres                Resource compiler (runs on Linux, builds resource sections into DXEs)
  serial     serial DXE libs           UART driver, HDLC packets, security, seclink
  sql        SQL DXE lib               SQLite integration

DXE3 Build Process

Each DXE module is compiled to an object file with GCC, then linked with dxe3gen:

  # Compile
  i586-pc-msdosdjgpp-gcc -O2 -march=i486 -mtune=i586 -c -o widget.o widget.c

  # Link as DXE (exports all non-static symbols)
  dxe3gen -o widget.wgt -U widget.o

  # Optionally append resources
  dvxres build widget.wgt widget.res

The -E flag specifies exported symbols (prefixed with underscore per DJGPP convention). -U marks unresolved symbols as OK (they'll be resolved at load time from previously loaded DXEs).

Deployment (mkcd.sh)

Compiler Flags

  -O2                  Optimization level 2
  -march=i486          486 instruction set baseline
  -mtune=i586          Optimize scheduling for Pentium
  -Wall -Wextra        Full warnings

Directory Layout

  dvxgui/
  +-- core/              Core library sources (dvxVideo, dvxDraw, dvxComp, dvxWm, dvxApp, widget infra)
  |   +-- platform/      Platform abstraction (dvxPlat.h, dvxPlatformDos.c, dvxPlatformUtil.c)
  |   +-- thirdparty/    stb_image, stb_ds, stb_image_write
  +-- loader/            Bootstrap loader (dvx.exe)
  +-- tasks/             Cooperative task switcher (libtasks.lib)
  +-- shell/             DVX Shell (dvxshell.lib)
  +-- widgets/           Widget DXE modules (*.wgt), each in its own subdirectory
  |   +-- box/           VBox/HBox layout containers
  |   +-- button/        Push button
  |   +-- textInput/     Text entry field
  |   +-- listView/      Multi-column list
  |   +-- ...            (29 widget types total)
  +-- texthelp/          Shared text editing helpers
  +-- listhelp/          Shared dropdown/list helpers
  +-- apps/              Application DXE modules (*.app)
  |   +-- progman/       Program Manager (desktop)
  |   +-- notepad/       Text editor
  |   +-- cpanel/        Control Panel
  |   +-- imgview/       Image viewer
  |   +-- clock/         Clock
  |   +-- dvxdemo/       Demo / showcase app
  |   +-- dvxbasic/      DVX BASIC compiler and VM
  +-- tools/             Build tools (dvxres resource compiler)
  +-- rs232/             ISR-driven UART driver
  +-- packet/            HDLC framing, CRC-16, sliding window
  +-- security/          DH key exchange, XTEA cipher, DRBG RNG
  +-- seclink/           Encrypted channel wrapper
  +-- serial/            Combined serial stack DXE
  +-- proxy/             Linux proxy (emulator <-> secLink <-> telnet)
  +-- sql/               SQLite integration
  +-- bin/               Build output (dvx.exe, system/, libs/, widgets/, apps/, shared/)
  +-- obj/               Intermediate object files
  +-- docs/              Documentation

DVX GUI API Reference

DOS Visual eXecutive -- Complete public API documentation covering every public function, type, and constant in the libdvx headers.

The DVX GUI is built as a five-layer architecture. Each layer is defined in its own header file. Additional headers cover dialogs, resources, preferences, memory tracking, the platform abstraction, and the widget system.

Headers

dvxTypes.h -- Shared Type Definitions

dvxCur.h -- Cursor Definitions

dvxFont.h -- Bitmap Font Data

dvxText.h -- Fonts

dvxFlow.h -- Flowing Text

dvxPal.h -- 8-bit Palette

dvxVideo.h -- Layer 1: Video Backend

dvxDraw.h -- Layer 2: Drawing Primitives

dvxComp.h -- Layer 3: Compositor

dvxWm.h -- Layer 4: Window Manager

dvxApp.h -- Layer 5: Application API

dvxDlg.h -- Modal Dialogs

dvxRes.h -- Resource System

dvxPrefs.h -- Preferences

dvxMem.h -- Memory Tracking

dvxDrv.h -- Driver Modules

dvxWgt.h -- Widget System

dvxWgtP.h -- Widget Plugin API

platform/dvxPlat.h -- Platform Layer

dvxTypes.h -- Shared Type Definitions

Central type definitions shared across all five layers of the DVX GUI stack. Every header includes this file. Contains no function definitions -- only structs, enums, typedefs, and compile-time constants.

Binary Interface Version

DVX_ABI_VERSION is one number for the whole binary interface between dvx.exe, libraries (.lib), widgets (.wgt), and apps (.app). Every module records the value it was compiled with, and a module whose value differs from the running system's is refused instead of being allowed to call the wrong functions:

  Module / table   Where the value is recorded                                    Checked by
  --------------   ---------------------------                                    ----------
  .lib, .wgt       DVX_ABI_STAMP(baseName) exports <baseName>AbiVersion           The loader, right after loading the module. A missing or different stamp stops the boot with a message naming the file (details in DVX.LOG).
  .app             AppDescriptorT.abiVersion, set by SHELL_APP_DESCRIPTOR          The shell, before appMain. The user sees "This program was built for a different version of DVX and cannot run."; DVX.LOG has both versions.
  Widget API       WgtApiHeaderT.abiVersion, set by WGT_API_HEADER                 wgtGetApi (which also checks the table's size).
  Widget class     WidgetClassT.version (WGT_CLASS_VERSION is DVX_ABI_VERSION)     wgtRegisterClass.
  Driver (.drv)    DvxDriverHeaderT.abiVersion, set by DVX_DRIVER_HDR              dvxDriversLoad, when the library of its device class loads it (a mismatched driver is skipped and logged).

Every library and widget module writes the stamp once at file scope in its main source file; the argument is the module file's base name (libdvx for LIBDVX.LIB, box for BOX.WGT):

#include "dvxTypes.h"

DVX_ABI_STAMP(mylib);

Bump rule: increment DVX_ABI_VERSION in the same change that alters anything an already compiled module depends on -- the size, member order, or member types of a public struct or widget API struct in an SDK header; a WGT_METHOD_* slot or the meaning of a WidgetClassT slot; the parameters, return type, or calling contract of a public or exported function (or its removal); the value of a public constant modules compile in. Adding a function, a constant, or a struct no existing struct embeds needs no bump. After a bump, run make -C src/test/abi update to regenerate the committed size table (src/test/abi/abiSizes.txt). The abi host test fails when a public struct changes size without a bump, or when the table was not regenerated.

Core Structures

PixelFormatT

Describes the pixel encoding for the active VESA video mode. Populated once at startup from the VBE mode info block, then treated as read-only.

  Field                                        Description
  -----                                        -----------
  int32_t bitsPerPixel                         8, 15, 16, or 32
  int32_t bytesPerPixel                        1, 2, 2, or 4
  uint32_t redMask, greenMask, blueMask        Bitmasks for each color channel
  int32_t redShift, greenShift, blueShift      Bit position of each color field
  int32_t redBits, greenBits, blueBits         Number of bits per channel

DisplayT

Single display context passed by pointer through every layer. All drawing targets the backBuf; only dirty rects are flushed to lfb.

  Field                                  Description
  -----                                  -----------
  int32_t width, height                  Screen dimensions in pixels
  int32_t pitch                          Bytes per scanline
  PixelFormatT format                    Active pixel format
  uint8_t *lfb                           Mapped linear framebuffer (VESA LFB)
  uint8_t *backBuf                       System RAM backbuffer
  uint8_t *palette                       768 bytes for 8-bit mode, NULL otherwise
  int32_t clipX, clipY, clipW, clipH     Current clip rectangle

RectT

Rectangle in origin + extent form. Used throughout the compositor, window manager, and widget layout engine.

  Field              Description
  -----              -----------
  int32_t x, y       Top-left corner
  int32_t w, h       Width and height

BlitOpsT

Vtable for hot-path span operations. Resolved at init time based on pixel depth. On DOS, these dispatch to hand-written asm (rep stosl / rep movsd).

  Field                    Description
  -----                    -----------
  SpanFillFnT spanFill     Fill a horizontal span with a solid color
  SpanCopyFnT spanCopy     Copy a horizontal span between buffers
  int32_t bytesPerPixel    Bytes per pixel for the active mode
  int32_t pitch            Bytes per scanline

BevelStyleT

Bevel drawing parameters. Swapping highlight/shadow flips raised vs. sunken appearance.

  Field                  Description
  -----                  -----------
  uint32_t highlight     Lighter color (top/left edges)
  uint32_t shadow        Darker color (bottom/right edges)
  uint32_t face          Interior fill color (0 = no fill)
  int32_t width          Border thickness in pixels (typically 2)

BitmapFontT

The one font type every text call takes. Two kinds share it: the built-in system font (dvxFont8x16: 8-pixel cells of the VGA 8x16 font, one byte per glyph row, glyphs NULL; text takes the byte-aligned fast paths) and a loaded font (dvxFontOpen, dvxText.h: a size of a .DFN font file, or the system font made bold or italic), whose glyphs give each character's advance and bitmap box. Every character of code page 437 draws in either kind.

  Field                          Description
  -----                          -----------
  int32_t charWidth              Cell width of a fixed-pitch font; the digit 0's width in a proportional one (for "so many characters wide")
  int32_t charHeight             Line height
  int32_t firstChar              Code of the first glyph (0)
  int32_t numChars               Number of glyphs (256)
  const uint8_t *glyphData       System font: charHeight bytes per glyph; loaded font: the glyph bitmaps
  const FontGlyphT *glyphs       Loaded font: each character's glyph; NULL for the system font
  const uint32_t *glyphAt        Loaded font: where each glyph's bitmap starts in glyphData
  int32_t ascent                 Rows from the top of a line to the baseline
  int32_t pixelSize              The size the font was made at (16 for the system font)
  uint32_t style                 FONT_STYLE_BOLD / FONT_STYLE_ITALIC the font draws
  uint32_t flags                 FONT_FLAG_PROPORTIONAL, FONT_FLAG_SYNTH_BOLD, FONT_FLAG_SYNTH_ITALIC, FONT_FLAG_GRAY
  int32_t underlineY             Row of an underline and of accelerator marks, from the top of a line
  int32_t strikeY                Row of a strike-through line
  int32_t lineThick              Rows in those lines
  void *priv                     The font cache's (dvxFontClose); NULL for fonts not from dvxFontOpen
  uint32_t serial                A loaded font's own number, never reused; new when its glyphs are built again (dvxFontSetSmooth). 0 for the system font as compiled in

Measure text with textWidth / textWidthN, never strlen * charWidth: a loaded font may be proportional.

FontGlyphT

One character of a loaded font, the same 6-byte record a .DFN file stores. The glyph's bitmap is height rows of FONT_ROW_BYTES(width) bytes, the leftmost pixel in the top bit, at glyphData + glyphAt[character]. In a smoothed font (FONT_FLAG_GRAY) each pixel is FONT_GRAY_BPP bits instead: rows of FONT_ROW_BYTES_BPP(width, FONT_GRAY_BPP) bytes, a pixel's level 0 (none) to FONT_GRAY_FULL read with FONT_GRAY_LEVEL(row, x); FONT_BPP(font) is a font's bits per pixel.

  Field                          Description
  -----                          -----------
  uint8_t advance                Pen movement after the glyph
  uint8_t width, height          Bitmap size
  int8_t left                    Pen position to the bitmap's left column (negative: the glyph reaches back)
  int8_t top                     Baseline to the bitmap's top row (up is positive)
  uint8_t flags                  FONT_GLYPH_MISSING in a file (a loaded font has every glyph)

Font styles and flags

  Define                   Value   Description
  ------                   -----   -----------
  FONT_STYLE_BOLD          0x01    Bold face (dvxFontOpen)
  FONT_STYLE_ITALIC        0x02    Italic face (dvxFontOpen)
  FONT_STYLE_UNDERLINE     0x04    Underline drawn with the text (drawTextStyled)
  FONT_STYLE_STRIKE        0x08    Strike-through drawn with the text (drawTextStyled)
  FONT_STYLE_FACE          0x03    The face bits (bold and italic)
  FONT_FLAG_PROPORTIONAL   0x01    Characters differ in width
  FONT_FLAG_SYNTH_BOLD     0x02    Bold made from the regular face (thickened)
  FONT_FLAG_SYNTH_ITALIC   0x04    Italic made by slanting the upright face
  FONT_FLAG_GRAY           0x08    Smoothed: glyph pixels of FONT_GRAY_BPP bits (dvxFontSetSmooth)

ColorSchemeT

All UI colors pre-packed into display pixel format at init time. Theme support is achieved by swapping this struct.

  Field                                              Description
  -----                                              -----------
  uint32_t desktop                                   Desktop background color
  uint32_t windowFace                                Window body / chrome face
  uint32_t windowHighlight                           Bevel highlight (top/left edge)
  uint32_t windowShadow                              Bevel shadow (bottom/right edge)
  uint32_t activeTitleBg, activeTitleFg              Focused window title bar
  uint32_t inactiveTitleBg, inactiveTitleFg          Unfocused window title bar
  uint32_t contentBg, contentFg                      Window content area default colors
  uint32_t menuBg, menuFg                            Menu bar and popup background/text
  uint32_t menuHighlightBg, menuHighlightFg          Menu item highlight
  uint32_t buttonFace                                Button face color
  uint32_t scrollbarBg, scrollbarFg, scrollbarTrough Scrollbar element colors
  uint32_t cursorFg, cursorBg                        Mouse cursor colors

ColorIdE

Enum for addressing individual colors in ColorSchemeT. Order matches struct field order.

Values: ColorDesktopE, ColorWindowFaceE, ColorWindowHighlightE, ColorWindowShadowE, ColorActiveTitleBgE, ColorActiveTitleFgE, ColorInactiveTitleBgE, ColorInactiveTitleFgE, ColorContentBgE, ColorContentFgE, ColorMenuBgE, ColorMenuFgE, ColorMenuHighlightBgE, ColorMenuHighlightFgE, ColorButtonFaceE, ColorScrollbarBgE, ColorScrollbarFgE, ColorScrollbarTroughE, ColorCursorFgE, ColorCursorBgE, ColorCountE.

DirtyListT

List of dirty rectangles. The rect array is preallocated to a soft cap (128) by dirtyListInit; at the cap dirtyListAdd merges or collapses rects instead of growing, so the array is only (re)allocated when the initial allocation failed.

  Field              Description
  -----              -----------
  RectT *rects       Dynamic array of dirty rectangles
  int32_t count      Current number of dirty rects
  int32_t cap        Allocated capacity

WindowT

Central window object. Each window owns a persistent content backbuffer and receives events through callback function pointers.

  Field                                                          Description
  -----                                                          -----------
  int32_t id                                                     Unique window identifier
  int32_t appId                                                  Shell app ID (0 = shell itself)
  int32_t x, y, w, h                                            Outer frame position and dimensions
  int32_t contentX, contentY, contentW, contentH                 Content area inset from frame
  char title[MAX_TITLE_LEN]                                      Window title text (max 128 chars)
  bool visible, focused, minimized, maximized, resizable, modal  Window state flags
  bool iconNeedsRefresh                                          true when contentBuf changed (for minimized icon refresh)
  bool gameInput                                                 Game input: see below
  uint8_t paintNeeded                                            PAINT_NONE / PAINT_PARTIAL / PAINT_FULL
  int32_t maxW, maxH                                             Maximum dimensions
  int32_t preMaxX, preMaxY, preMaxW, preMaxH                    Saved geometry before maximize
  uint8_t *contentBuf                                            Per-window content backbuffer
  int32_t contentPitch                                           Content buffer bytes per row
  uint8_t *iconData                                              Icon pixel data, NULL if none
  int32_t iconW, iconH, iconPitch                                Icon image dimensions and pitch
  MenuBarT *menuBar                                              Menu bar (NULL if no menus)
  ScrollbarT *vScroll, *hScroll                                  Scrollbars (NULL if not present)
  struct WidgetT *widgetRoot                                     Widget tree root (NULL if none)
  MenuT *contextMenu                                             Right-click context menu
  AccelTableT *accelTable                                        Keyboard accelerator table
  void *userData                                                 Application-defined data pointer

Callbacks:

  Callback                                                     Description
  --------                                                     -----------
  onPaint(WindowT *win, RectT *dirtyArea)                              Content repaint requested
  onKey(WindowT *win, int32_t key, int32_t mod)                        Key press
  onKeyUp(WindowT *win, int32_t scancode, int32_t mod)                 Key release
  onMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons)         Mouse event (content-relative)
  onResize(WindowT *win, int32_t newW, int32_t newH)                   Window resized
  onClose(WindowT *win)                                                Close requested
  onMenu(WindowT *win, int32_t menuId)                                 Menu item or accelerator activated
  onScroll(WindowT *win, ScrollbarOrientE orient, int32_t value)       Scrollbar value changed
  int32_t onCursorQuery(WindowT *win, int32_t x, int32_t y)            Return CURSOR_* for hit position
  onFocus(WindowT *win)                                                Window gained focus
  onBlur(WindowT *win)                                                 Window lost focus
  onRawKey(WindowT *win, const RawKeyEventT *evt)                      Key pressed or released (gameInput windows)

Game input: while a window with gameInput set has the keyboard focus, DVX keeps only Alt+Tab, Ctrl+Esc, and Alt+F4 for itself. Every other keystroke, F1, F10, Alt+letter, Ctrl+F12, accelerators, and hotkeys included, goes straight to onKey (except while a menu opened with the mouse, or the keyboard move/size mode, is taking keys). onRawKey then receives each key going down and up (RawKeyEventT: set-1 scancode, extended flag, repeat flag, modifiers from the held-key table), after the frame's onKey calls. When the focus moves away the window gets a release for every key still held (with no modifiers), and the held-key table starts over for the next window. onKeyUp keeps working as for any window. Mouse input is unchanged. The game library (game.h) adds gameKeyIsDown, frame pacing, and timers.

WindowStackT

Z-ordered window stack (front-to-back: index count-1 is topmost). Owns system-wide drag/resize/scroll interaction state.

  Field                                              Description
  -----                                              -----------
  WindowT **windows                                  Dynamic array of window pointers
  int32_t count, cap                                 Current count and allocated capacity
  int32_t focusedIdx                                 Stack index of focused window
  int32_t dragWindow, dragOffX, dragOffY             Active drag state
  int32_t dragStartX, dragStartY                     Mouse position at drag begin (for deadzone)
  bool dragActive                                    True once mouse moved past deadzone
  int32_t resizeWindow, resizeEdge                   Active resize state
  int32_t scrollWindow, scrollOrient, scrollDragOff  Active scroll drag state

MenuT / MenuItemT / MenuBarT

Menu system types. Fixed-size label buffers (MAX_MENU_LABEL = 32). Cascading submenus supported via MenuItemT.subMenu pointer.

  Field                            Description
  -----                            -----------
  MenuItemT.label                  Item text (supports & accelerator markers)
  MenuItemT.id                     Application-defined command ID
  MenuItemT.type                   MenuItemNormalE, MenuItemCheckE, or MenuItemRadioE
  MenuItemT.separator              true = horizontal divider line
  MenuItemT.enabled, checked       Item state
  MenuItemT.accelKey               Lowercase accelerator character (0 = none)
  MenuItemT.subMenu                Child menu for cascading (NULL if leaf)
  MenuT.label                      Menu bar label (e.g. "File")
  MenuT.items                      Dynamic array of MenuItemT
  MenuT.itemCount, itemCap         Current count and allocated capacity
  MenuT.barX, barW                 Computed position/width on the menu bar
  MenuT.accelKey                   Lowercase accelerator character (0 = none)
  MenuBarT.menus                   Dynamic array of MenuT
  MenuBarT.menuCount, menuCap      Current count and allocated capacity
  MenuBarT.activeIdx               Open popup index (-1 = none)
  MenuBarT.positionsDirty          True when barX/barW need recomputation

ScrollbarT

Window-level scrollbar state. Managed by the WM layer, drawn after content.

  Field                        Description
  -----                        -----------
  ScrollbarOrientE orient      ScrollbarVerticalE or ScrollbarHorizontalE
  int32_t min, max             Scroll range
  int32_t value                Current position
  int32_t pageSize             Visible portion (for proportional thumb sizing)
  int32_t x, y, length         Computed screen position and track length

AccelTableT / AccelEntryT

Per-window keyboard accelerator table. Entries are matched against keystrokes in the event loop and fire onMenu(cmdId) on match. Only the normalized form is stored (see dvxAddAccel), so matching is two integer comparisons per entry.

  Field                      Description
  -----                      -----------
  AccelEntryT.cmdId          Command ID passed to onMenu
  AccelEntryT.normKey        Normalized key (see dvxAddAccel)
  AccelEntryT.normMods       Normalized ACCEL_* modifiers (see dvxAddAccel)
  AccelTableT.entries        Dynamic array of AccelEntryT
  AccelTableT.count, cap     Current count and allocated capacity

VideoModeInfoT

Describes an available video mode (enumerated at init).

  Field            Description
  -----            -----------
  int32_t w, h     Resolution
  int32_t bpp      Bits per pixel

CursorT

Software-rendered 16x16 cursor using AND/XOR mask encoding.

  Field                          Description
  -----                          -----------
  int32_t width, height          Cursor dimensions (always 16x16)
  int32_t hotX, hotY             Hot spot coordinates
  const uint16_t *andMask        AND mask (0 = draw pixel, 1 = transparent)
  const uint16_t *xorData        XOR data (0 = black, 1 = white where AND = 0)

Bevel Convenience Macros

  Macro                      Description
  -----                      -----------
  BEVEL_RAISED(cs, bw)       Raised bevel style from ColorSchemeT ptr and border width
  BEVEL_SUNKEN(cs, face, bw) Sunken bevel style with explicit face color
  BEVEL_TROUGH(cs)           1px sunken trough (for scrollbar tracks)
  BEVEL_SB_BUTTON(cs)        1px raised scrollbar button

Chrome Constants

  Define                  Value   Description
  ------                  -----   -----------
  CHROME_BORDER_WIDTH     4       Outer frame border width
  CHROME_TITLE_HEIGHT     20      Title bar height
  CHROME_TITLE_PAD        4       Title text padding
  CHROME_INNER_BORDER     2       Inner chrome border
  CHROME_MENU_HEIGHT      20      Menu bar height
  CHROME_TOTAL_TOP        26      Total inset from top of frame to content
  CHROME_TOTAL_SIDE       6       Total inset from side of frame to content
  CHROME_TOTAL_BOTTOM     6       Total inset from bottom of frame to content

Hit Test Constants

  Define          Value   Description
  ------          -----   -----------
  HIT_CONTENT     0       Content area
  HIT_TITLE       1       Title bar
  HIT_CLOSE       2       Close gadget
  HIT_RESIZE      3       Resize border
  HIT_MENU        4       Menu bar
  HIT_VSCROLL     5       Vertical scrollbar
  HIT_HSCROLL     6       Horizontal scrollbar
  HIT_MINIMIZE    7       Minimize gadget
  HIT_MAXIMIZE    8       Maximize gadget
  HIT_NONE        -1      No window hit (desktop)

Mouse Button Flags

  Define          Value   Description
  ------          -----   -----------
  MOUSE_LEFT      1       Left mouse button
  MOUSE_RIGHT     2       Right mouse button
  MOUSE_MIDDLE    4       Middle mouse button

Accelerator Modifier Flags

The keyboard modifier flags are the BIOS shift-state bits. KEY_MOD_* (dvxTypes.h) is the single definition; ACCEL_* are aliases of the same values for accelerator tables.

  Define          Value   Description
  ------          -----   -----------
  KEY_MOD_SHIFT   0x03    Either Shift key (BIOS shift state bits)
  KEY_MOD_CTRL    0x04    Ctrl key
  KEY_MOD_ALT     0x08    Alt key
  ACCEL_SHIFT     0x03    Alias of KEY_MOD_SHIFT
  ACCEL_CTRL      0x04    Alias of KEY_MOD_CTRL
  ACCEL_ALT       0x08    Alias of KEY_MOD_ALT

Extended Key Codes

Non-ASCII keys are delivered as (scancode | KEY_EXT_FLAG). The platform layer reports Shift/Ctrl/Alt+F1..F12 as the plain F-key code with the modifier in the shift-state flags (e.g. Alt+F4 arrives as KEY_F4 with KEY_MOD_ALT).

  Define                  Description
  ------                  -----------
  KEY_EXT_FLAG            0x100, marks an extended (scancode) key
  KEY_F1 .. KEY_F10       Function keys (KEY_SCAN_F1..F10 = 0x3B..0x44, | KEY_EXT_FLAG)
  KEY_F11, KEY_F12        0x85 | KEY_EXT_FLAG, 0x86 | KEY_EXT_FLAG
  KEY_INSERT              Insert key
  KEY_DELETE              Delete key
  KEY_HOME                Home key
  KEY_END                 End key
  KEY_PGUP                Page Up key
  KEY_PGDN                Page Down key
  KEY_UP, KEY_DOWN        Arrow keys
  KEY_LEFT, KEY_RIGHT     Arrow keys
  KEY_CTRL_LEFT           Ctrl+Left (0x73 | KEY_EXT_FLAG)
  KEY_CTRL_RIGHT          Ctrl+Right (0x74 | KEY_EXT_FLAG)
  KEY_CTRL_END            Ctrl+End (0x75 | KEY_EXT_FLAG)
  KEY_CTRL_HOME           Ctrl+Home (0x77 | KEY_EXT_FLAG)

Raw Scancodes

KEY_SCAN_* are the raw BIOS scancodes (without KEY_EXT_FLAG): KEY_SCAN_ESC, KEY_SCAN_TAB, KEY_SCAN_SPACE, KEY_SCAN_F1 .. KEY_SCAN_F12, KEY_SCAN_HOME, KEY_SCAN_END, KEY_SCAN_PGUP, KEY_SCAN_PGDN, KEY_SCAN_INSERT, KEY_SCAN_DELETE, KEY_SCAN_UP, KEY_SCAN_DOWN, KEY_SCAN_LEFT, KEY_SCAN_RIGHT, KEY_SCAN_CTRL_LEFT, KEY_SCAN_CTRL_RIGHT, KEY_SCAN_CTRL_END, KEY_SCAN_CTRL_HOME, KEY_SCAN_ALT_TAB.

Raw Key Codes

A raw key code names one physical key: its set-1 make code (1-127), plus KEY_RAW_EXT (0x80) for keys sent behind an 0xE0 prefix (grey arrows and navigation keys, right Ctrl and Alt, keypad Enter). KEY_RAW_CODE(scancode, extended) builds one. The code is the same whatever modifier is held; letters, digits, and punctuation use their KEY_SCAN_* / BIOS scancode. Used by the held-key table (platformKeyIsDown, gameKeyIsDown) and RawKeyEventT.

  Define                          Value   Key
  ------                          -----   ---
  KEY_RAW_LSHIFT, KEY_RAW_RSHIFT  0x2A, 0x36   Left / right Shift
  KEY_RAW_LCTRL, KEY_RAW_RCTRL    0x1D, 0x9D   Left / right Ctrl
  KEY_RAW_LALT, KEY_RAW_RALT      0x38, 0xB8   Left / right Alt
  KEY_RAW_F11, KEY_RAW_F12        0x57, 0x58   F11, F12
  KEY_RAW_UP, KEY_RAW_DOWN        0xC8, 0xD0   Grey arrows (the keypad's are 0x48, 0x50)
  KEY_RAW_LEFT, KEY_RAW_RIGHT     0xCB, 0xCD
  KEY_RAW_KP_ENTER                0x9C         Keypad Enter
  KEY_RAW_PAUSE                   0xC5         Pause (a press and a release at once)
  KEY_RAW_COUNT                   256          Size of the held-key snapshot

RawKeyEventT: uint8_t scancode (the make code), bool extended, bool down, bool repeat (a typematic repeat of a key already down), int32_t modifiers (KEY_MOD_* from the held-key table after the event, with KEY_MOD_LSHIFT 0x02 and KEY_MOD_RSHIFT 0x01 apart).

ASCII Key Codes

  Define                  Value   Description
  ------                  -----   -----------
  KEY_ESCAPE              0x1B    Escape
  KEY_ENTER               0x0D    Enter / Return
  KEY_TAB                 0x09    Tab
  KEY_SPACE               0x20    Space
  KEY_BACKSPACE           0x08    Backspace
  KEY_ASCII_DEL           0x7F    ASCII DEL (not KEY_DELETE)
  KEY_ASCII_PRINT_FIRST   0x20    First printable character
  KEY_ASCII_PRINT_LAST    0x7E    Last printable character
  KEY_CTRL_A              0x01    Ctrl+A (BIOS reports Ctrl+letter as 0x01..0x1A)
  KEY_CTRL_C              0x03    Ctrl+C
  KEY_CTRL_V              0x16    Ctrl+V
  KEY_CTRL_X              0x18    Ctrl+X
  KEY_CTRL_Z              0x1A    Ctrl+Z

Resize Edge Flags

  Define          Value   Description
  ------          -----   -----------
  RESIZE_NONE     0       No resize edge
  RESIZE_LEFT     1       Left edge
  RESIZE_RIGHT    2       Right edge
  RESIZE_TOP      4       Top edge
  RESIZE_BOTTOM   8       Bottom edge (combinable via OR for corners)

Utility Macros

  Macro           Description
  -----           -----------
  DVX_MIN(a, b)   Return the smaller of two values
  DVX_MAX(a, b)   Return the larger of two values

dvxCur.h -- Cursor Definitions

Embedded 16x16 mouse cursor bitmaps compiled as static const data. No external cursor files. Uses the standard AND/XOR mask encoding from the IBM VGA hardware cursor spec. The AND mask selects transparency (1 = transparent, 0 = opaque) and the XOR data selects black vs. white for opaque pixels.

Cursor Shape IDs

  Define                      Value   Description
  ------                      -----   -----------
  CURSOR_ARROW                0       Standard arrow (hot spot at tip)
  CURSOR_RESIZE_H             1       Horizontal resize (left/right arrows)
  CURSOR_RESIZE_V             2       Vertical resize (up/down arrows)
  CURSOR_RESIZE_DIAG_NWSE     3       NW-SE diagonal resize
  CURSOR_RESIZE_DIAG_NESW     4       NE-SW diagonal resize
  CURSOR_BUSY                 5       Hourglass (wait)
  CURSOR_CROSSHAIR            6       Crosshair for placement
  CURSOR_NO_DROP              7       Circle with a slash: a drag cannot drop here (see dvxDragCursorSet)
  CURSOR_COUNT                8       Total number of cursor shapes

Data

dvxCursors[CURSOR_COUNT]

Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each entry includes the AND mask, XOR data (both as 16-element uint16_t arrays), dimensions (16x16), and hot spot coordinates.

dvxCursor

Legacy alias for backward compatibility with code that predates multi-cursor support. Equivalent to dvxCursors[CURSOR_ARROW].

dvxFont.h -- Embedded Bitmap Font Data

Raw glyph bitmaps for the standard 8x16 VGA ROM font covering the full IBM Code Page 437 character set (256 glyphs). Compiled in as a static const array, avoiding a real-mode INT 10h call at startup.

Glyph format: 1 bit per pixel, 8 pixels wide, MSB = leftmost pixel. Each glyph occupies 16 consecutive bytes (one byte per scanline). The 8-pixel width means per-scanline rendering never needs bit shifting across byte boundaries.

CP437 covers:

Data

font8x16

const uint8_t array of FONT_NUM_CHARS * FONT_CHAR_HEIGHT = 4096 bytes containing the packed 1bpp glyph bitmaps, in ASCII code order. It has external linkage with a single definition in the core library (the translation unit that defines DVX_FONT_IMPL before including dvxFont.h); every other includer sees only the extern declaration.

dvxFont8x16

const BitmapFontT struct describing the 8x16 font, ready to pass to drawText/drawTextN/drawChar. Use this as the default font for all text rendering. Declared extern like font8x16.

Constants

  Define              Value   Description
  ------              -----   -----------
  FONT_NUM_CHARS      256     Glyph count (full 8-bit character range)
  FONT_CHAR_WIDTH     8       Fixed glyph width in pixels (defined in dvxTypes.h)
  FONT_CHAR_HEIGHT    16      Glyph height in pixels (defined in dvxTypes.h)
  FONT_SYS_ASCENT     12      Rows above the baseline (letters sit on row 11)
  FONT_SYS_UNDERLINE_Y 15     Underline and accelerator mark row (the cell's last)
  FONT_SYS_STRIKE_Y   8       Strike-through row (the middle of the lower-case letters)
  FONT_SYS_LINE_THICK 1       Rows in those lines

dvxText.h -- Fonts

The font families DVX draws with: "System", the built-in 8x16 font, and the families in the FONTS folder. Which families that folder holds depends on the install -- Setup puts in the base set (DejaVu Sans, Sans Mono, and Serif) or none, and the More Fonts add-on the rest -- and dvxFontOpen of a family that is not there gives the system font. A family has up to four faces (regular, bold, italic, bold italic), each a .DFN file holding the face at the pixel sizes it was made at; the files are made on the build host from TrueType, Type 1, and bitmap fonts (src/tools/mkfont.c), so DVX only copies bitmaps. A face's file holds its screen sizes, their smoothed copies, and its print sizes together (the base set's have 25 to 100 pixels for printing; FONT_SIZES_MAX sizes at most). More files of the same family and style (up to four in all) add the sizes the face lacks, each size read from the file it came in: a program's own copy of a face adds its sizes that way.

dvxFontOpen gives a BitmapFontT every text call draws and measures with. It never scales: it picks the face's size nearest the one asked for. A style the family has no face for is made from the nearest face -- bold by thickening each stroke a column (two from 24 pixels), italic by slanting a column per four rows -- and the font's flags say so. Every character of code page 437 draws: what a face lacks is filled in when the font loads. Box drawing, block, and shade characters are drawn to fill the character cell (the pitch, or the digit 0's width) so lines join, also in place of a face's own when its file asks; other characters come from FONT_FALLBACK_FAMILY (FONT_FALLBACK_FIXED for a fixed-pitch face, centered in its pitch) at the nearest size and the same style, or from the system font when that family is not installed.

A face can also hold a size smoothed (anti-aliased): each pixel one of four levels, from none to full, drawn as that blend of the text color and what is under it. When smoothing is on (dvxFontSetSmooth) such a size opens smoothed, with FONT_FLAG_GRAY set and FONT_GRAY_BPP bits per pixel; other sizes and faces stay 1-bit. At 8 bits per pixel there are no colors to blend: the pixels of FONT_GRAY_HALF and more are drawn, the rest not.

Fonts are shared and counted: the same family, size, and style opened again is the same font, and dvxFontClose gives one up. A few fonts no one holds stay loaded so reopening them is quick. A font's memory belongs to DVX, not to the program that opened it. The family list is read from DVX_FONTS_DIR the first time it is needed; a file that cannot be read is logged and skipped.

Fonts a Program Carries

A program can carry fonts of its own: font resources in its file (DVX_RES_FONT, dvxRes.h; a .res line "name font FILE.DFN", or a BASIC project's Fonts), .DFN files it adds with dvxFontAddFile, or fonts in memory (dvxFontAddMemory). The shell adds a program's font resources when it loads the program and forgets them when the program ends; whatever a program adds itself goes then too. Its fonts are its own: every call here works for the running program (the app id the shell sets while a program's code runs, window callbacks included), and other programs never see them.

The program sees the installed families and its own. Where it has a family that is installed too, its faces and sizes come first, and the installed family fills in the styles and sizes it lacks: a program that carries DejaVu Sans Bold at 36 pixels gets that size from its own file and every other size and style of DejaVu Sans from the installed fonts. The nearest size is chosen from both together. Fonts built for a program that carries fonts are cached for it alone; programs that carry none share every font.

A .DFN carries its notice (where the font came from and its copyright notices) and its licence, so they go wherever the font goes; dvxFontLicence reads them, and the Font dialog shows them (its Licence button).

Constants

  Define                  Value                 Description
  ------                  -----                 -----------
  DVX_FONTS_DIR           "FONTS"               The fonts folder (dvxPlat.h), in SHARED and in the user folder
  FONT_SYSTEM_FAMILY      "System"              The built-in 8x16 font's family name
  FONT_FALLBACK_FAMILY    "DejaVu Sans"         Fills what a proportional face lacks
  FONT_FALLBACK_FIXED     "DejaVu Sans Mono"    Fills what a fixed-pitch face lacks
  FONT_SIZES_MAX          64                    The most sizes a face has (dvxFontSizes)

dvxFontOpen

BitmapFontT *dvxFontOpen(const char *family, int32_t pixelSize, uint32_t style);

The font of family at the size nearest pixelSize in style. NULL, "", and a family there is none of give the system font (16 pixels; plain, it is dvxFont8x16 itself, drawn by the fast fixed paths).

  Parameter   Description
  ---------   -----------
  family      Family name (any case)
  pixelSize   Size wanted, in pixels
  style       FONT_STYLE_BOLD and / or FONT_STYLE_ITALIC (the line styles are ignored)

Returns: The font, or NULL when out of memory. Close it with dvxFontClose.

dvxFontClose

void dvxFontClose(BitmapFontT *font);

Give up a font dvxFontOpen gave. NULL and fonts not from dvxFontOpen (dvxFont8x16, the application context's font) are ignored.

dvxFontFamilyCount / dvxFontFamilyName

int32_t     dvxFontFamilyCount(void);
const char *dvxFontFamilyName(int32_t index);

The number of families (the system font's included) and family index: "System" first, then the others in alphabetical order. dvxFontFamilyName returns NULL for an index out of range.

dvxFontFamilyOf

const char *dvxFontFamilyOf(const BitmapFontT *font);

The family a font was opened from; "System" for the system font and for fonts not from dvxFontOpen.

dvxFontFamilyFixed

bool dvxFontFamilyFixed(const char *family);

True when every face of family is fixed-pitch (the system font is; a family there is none of is not): the families an editor or terminal can use.

dvxFontSizes

int32_t dvxFontSizes(const char *family, uint32_t style, int32_t *sizes, int32_t max);

The pixel sizes family has in style -- those of the face the style is drawn from -- smallest first, into sizes (at most max).

Returns: How many sizes there are (which may be more than max); 0 for a family there is none of.

dvxFontRetain

BitmapFontT *dvxFontRetain(BitmapFontT *font);

Hold font once more, for keeping a font someone else opened (each hold is given up with dvxFontClose). Returns font; fonts not from dvxFontOpen are returned as they are.

dvxFontRescan

void dvxFontRescan(void);

Read the FONTS folder again on the next call, after fonts were added or removed. The fonts no one holds are unloaded; open fonts stay valid.

dvxFontSetSmooth / dvxFontSmooth

void dvxFontSetSmooth(bool smooth);
bool dvxFontSmooth(void);

Smooth fonts where their face has the size smoothed, or stop; dvxFontSmooth says which it is (off at first). The fonts open are built again in place: the same BitmapFontT pointers with new glyphs and a new serial, so code that keeps measurements of a font compares its serial too, and text is laid out again. The fonts no one holds are unloaded. Programs call dvxSetFontSmoothing (dvxApp.h) instead, which follows the user's choice and the screen's depth.

dvxFontAddFile / dvxFontAddMemory

bool dvxFontAddFile(int32_t owner, const char *path);
bool dvxFontAddMemory(int32_t owner, const void *data, uint32_t length, const char *name);

Add the .DFN at path, or held in data (length bytes, copied at once; name names it in the log), as a face of its family for program owner (an app id; 0 for every program, as SETUP.EXE adds the fonts it carries). Families added for owner 0 are seen by every program and kept by dvxFontRescan.

Returns: True when added; false, logged, when the font cannot be read, calls its family "System", or brings no size that owner's face of that family and style lacks (a second file of a face adds its sizes to it).

dvxFontAddResources

int32_t dvxFontAddResources(int32_t owner, const char *path);

Add every font resource (DVX_RES_FONT) of the file at path -- a program, a library, or a resource file -- for owner. The shell calls it for each program it loads.

Returns: How many fonts were added.

dvxFontRemoveOwner

void dvxFontRemoveOwner(int32_t owner);

Forget every face added for owner (the shell does when a program ends; for 0, the faces added for every program, the installed ones staying). Its fonts no one holds are unloaded; a held one stays as it is until it is closed.

dvxFontLicence

char *dvxFontLicence(const BitmapFontT *font);

The texts that go with the face font was built from: its notice -- the family and style, the fonts it was converted from, and their copyright notices -- then a blank line and its licence. ASCII with CR LF line ends; malloc'd (the caller frees it).

Returns: The texts; NULL for the system font, and when the font's file cannot be read.

dvxFlow.h -- Flowing Text

A flow is a document built in order: runs of text, each in a font and a color, line breaks, and paragraph starts, each with an alignment. dvxFlowLayout wraps it to a width into lines; dvxFlowPaint draws them. The RichLabel widget is a flow with calls around it; the RichEdit widget lays out each of its paragraphs as a flow and edits through the offset calls below.

Layout is greedy: a word and the spaces after it go on the line when the word fits, else the line ends and the word starts the next one, without its leading spaces. A word wider than a whole line is broken where the line is full. A line is as tall as the tallest font on it -- the largest ascent above one baseline and the deepest descent below it -- and its trailing spaces neither count toward its width (alignment) nor draw (an underline would show them). A word may go on across runs (a style changed inside it, or punctuation in a run of its own); it wraps as one word, and only a word wider than a whole line breaks, where the line is full. Underline and strike-through come with a run's font (dvxText.h).

Offsets number the text added from 0, each run's characters after the one before (breaks and paragraphs take none); an offset is the caret before that character. Each line owns the offsets from its start to the next line's start -- the spaces it wrapped at included -- and the last line those to the text's end. Text that is all empty lays out as one empty line in its first run's font, so an empty paragraph keeps a height and a caret place.

Constants

  Define               Value          Description
  ------               -----          -----------
  FLOW_ALIGN_LEFT      0              Paragraph alignment
  FLOW_ALIGN_CENTER    1
  FLOW_ALIGN_RIGHT     2
  FLOW_COLOR_DEFAULT   0xFFFFFFFF     A run in the color dvxFlowPaint is given
  FLOW_WIDTH_NATURAL   INT32_MAX      dvxFlowLayout's width for the natural size: lines break only at breaks and paragraphs

Functions

  Function                                                     Description
  --------                                                     -----------
  TextFlowT *dvxFlowCreate(void)                               An empty flow (one left-aligned paragraph); NULL out of memory.
  void dvxFlowDestroy(flow)                                    Free it and give up the fonts it holds. NULL is ignored.
  void dvxFlowClear(flow)                                      Empty it again.
  bool dvxFlowAddText(flow, text, len, font, color)            Add len bytes of text (len below 0: up to the NUL) in font and color (0x00RRGGBB or FLOW_COLOR_DEFAULT). The flow holds the font (dvxFontRetain) and copies the text.
  bool dvxFlowAddBreak(flow)                                   Start a new line in the same paragraph (two in a row make an empty line, as tall as the font before it).
  bool dvxFlowAddParagraph(flow, align)                        Start a new paragraph aligned FLOW_ALIGN_*; it ends the line before it when that has text, and on an empty flow it sets the first paragraph's alignment.
  int32_t dvxFlowLayout(flow, width)                           Lay it out width pixels wide; returns its height. The same width again costs nothing.
  int32_t dvxFlowHeight(flow) / dvxFlowWidth(flow)             The last layout's height and widest line.
  void dvxFlowPaint(flow, d, ops, x, y, defaultFg)             Draw the lines that meet the clip rectangle, top-left at x, y, aligned within the width laid out at (the widest line's for FLOW_WIDTH_NATURAL); FLOW_COLOR_DEFAULT runs in defaultFg (a packed pixel). Transparent.
  void dvxFlowPaintSel(flow, d, ops, x, y, defaultFg, selStart, selEnd, selFg, selBg)   dvxFlowPaint with offsets selStart to selEnd selected: their text in selFg on selBg (packed pixels), a line's undrawn trailing spaces too.
  int32_t dvxFlowTextLength(flow)                              The characters added (the last offset).
  int32_t dvxFlowLineCount(flow)                               The last layout's lines.
  int32_t dvxFlowLineOf(flow, offset)                          The line the caret before offset is on (a line's end is the next line's start); -1 with no lines.
  int32_t dvxFlowLineStart(flow, line)                         The offset a line starts at; past the last line, the text's length.
  void dvxFlowLineBox(flow, line, &y, &h)                      A line's top and height.
  void dvxFlowCaret(flow, offset, &x, &y, &h)                  The caret before offset: its x, and its line's top and height (alignment included).
  int32_t dvxFlowHitTest(flow, x, y)                           The offset nearest a point: on the line at y (the first or last beyond them), the character boundary nearest x. A line that wrapped at a space ends before it.
TextFlowT   *flow = dvxFlowCreate();
BitmapFontT *bold = dvxFontOpen("DejaVu Sans", 14, FONT_STYLE_BOLD);

dvxFlowAddText(flow, "Warning: ", -1, bold, DVX_RGB(160, 0, 0));
dvxFlowAddText(flow, "the disk is almost full.", -1, &ctx->font, FLOW_COLOR_DEFAULT);
dvxFontClose(bold);
dvxFlowLayout(flow, 200);
dvxFlowPaint(flow, d, ops, x, y, fg);

dvxPal.h -- 256-Color Palette for 8-bit Mode

Defines the 256-color VGA/VESA palette used in 8-bit video modes. Layout:

  Range     Purpose
  -----     -------
  0-215     6x6x6 color cube -- uniformly distributed RGB (6 levels per channel: 0, 51, 102, 153, 204, 255). Index = r*36 + g*6 + b.
  216-231   16-step grey ramp for smooth gradients
  232-239   Dedicated UI chrome colors (highlight, shadow, title bar, desktop)
  240-255   Reserved (black)

The cube layout enables O(1) color lookup: snapping an RGB triplet to the nearest cube vertex is integer division by 51. The grey ramp and chrome slots hold colors that need exact values not available in the cube.

Layout Constants

  Define               Value   Description
  ------               -----   -----------
  PAL_ENTRIES          256     Palette size
  PAL_CHANNELS         3       Bytes per entry (R, G, B)
  PAL_CUBE_LEVELS      6       Levels per channel in the color cube
  PAL_CUBE_STEP        51      Distance between cube levels (255 / 5)
  PAL_CUBE_ROUND       25      Rounding bias when snapping to a cube level
  PAL_CUBE_COUNT       216     Number of cube entries (indices 0-215)
  PAL_GREY_FIRST       216     First grey-ramp index
  PAL_GREY_COUNT       16      Grey-ramp entries
  PAL_GREY_STEP        17      Distance between grey levels (255 / 15)
  PAL_CHROME_FIRST     232     First chrome color index
  PAL_RESERVED_FIRST   240     First reserved (black) index

Chrome Color Indices

  Define                     Value   Description
  ------                     -----   -----------
  PAL_CHROME_HIGHLIGHT       232     White (bevel highlight)
  PAL_CHROME_SHADOW          233     Dark grey (bevel shadow)
  PAL_CHROME_ACTIVE_BG       234     Navy (focused title bar)
  PAL_CHROME_INACTIVE_BG     235     Grey (unfocused title bar)
  PAL_CHROME_DESKTOP         236     Steel blue (desktop)
  PAL_CHROME_SELECTION       237     Navy (selection highlight)
  PAL_CHROME_TEXT            238     Black (text)
  PAL_CHROME_WHITE           239     Bright white

Inline Functions

dvxPalSet

static inline void dvxPalSet(uint8_t *pal, int32_t idx, uint8_t r, uint8_t g, uint8_t b);

Store one RGB entry at index idx of a PAL_ENTRIES * PAL_CHANNELS palette buffer.

dvxGeneratePalette

static inline void dvxGeneratePalette(uint8_t *pal);

Populate a 768-byte (256 * 3) RGB buffer with the default DVX 8-bit palette. Called once at init in 8-bit mode to program the DAC.

  Parameter   Description
  ---------   -----------
  pal         Output RGB buffer (768 bytes)

dvxNearestPalEntry

static inline uint8_t dvxNearestPalEntry(const uint8_t *pal, uint8_t r, uint8_t g, uint8_t b);

Find the nearest palette entry for an RGB color using minimum Euclidean distance. Two-phase: snap to color cube vertex (O(1)), then scan grey ramp and chrome entries (216-239) for a closer match. Called by packColor() in 8-bit mode.

  Parameter   Description
  ---------   -----------
  pal         Active 768-byte RGB palette
  r, g, b     Color components (0-255)

Returns: Palette index (0-239) of the nearest match.

dvxVideo.h -- Layer 1: VESA VBE Video Backend

The lowest layer. Responsible for VESA VBE mode negotiation, LFB mapping via DPMI (or, for a card with no linear framebuffer, its bank window), system RAM backbuffer allocation, pixel format discovery, and color packing.

videoInit

int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);

Probe VBE for a mode matching the requested resolution and depth, enable it, map the LFB into DPMI linear address space, and allocate a system RAM backbuffer. preferredBpp is a hint; the closest available depth is used if an exact match is not found.

  Parameter        Description
  ---------        -----------
  d                Display context to initialize
  requestedW/H     Desired screen resolution
  preferredBpp     Preferred bits per pixel (8, 15, 16, or 32)

Returns: 0 on success, negative on failure.

videoShutdown

void videoShutdown(DisplayT *d);

Restore VGA text mode (mode 3), unmap the LFB, and free the backbuffer. Safe to call even if videoInit() failed.

  Parameter   Description
  ---------   -----------
  d           Display context to shut down

packColor

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

Pack an RGB triplet into the display's native pixel format. For direct-color modes (15/16/32 bpp), returns a packed pixel value using shift/mask fields. For 8-bit mode, returns the nearest palette index via Euclidean distance in RGB space.

  Parameter   Description
  ---------   -----------
  d           Display context (provides pixel format)
  r, g, b     Color components (0-255)

Returns: Native pixel value suitable for direct framebuffer write.

unpackColor

void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b);

Reverse of packColor -- decode a native pixel value to 8-bit RGB components. For direct-color modes, reverses the shift/mask arithmetic and widens a field narrower than 8 bits by repeating its bits, so black and full channels come back exact (white in a 16-bit mode reads as 255, 255, 255). For 8-bit mode, looks up the palette entry at the given index.

  Parameter   Description
  ---------   -----------
  d           Display context
  color       Packed native pixel value
  r, g, b     Output: color components (0-255)

setClipRect

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

Set the clip rectangle on the display. All subsequent draw operations clip to this rectangle. The caller must save and restore the clip rect around scoped operations.

  Parameter   Description
  ---------   -----------
  d           Display context
  x, y, w, h Clip rectangle in screen coordinates

resetClipRect

void resetClipRect(DisplayT *d);

Reset the clip rectangle to the full display dimensions.

  Parameter   Description
  ---------   -----------
  d           Display context

dvxDraw.h -- Layer 2: Drawing Primitives

All 2D drawing operations: rectangle fills, bitmap blits, text rendering, bevels, lines, and cursor rendering. Every function draws into the display's backbuffer and clips to the current clip rectangle. This layer is stateless beyond the clip rect on DisplayT.

drawInit

void drawInit(BlitOpsT *ops, const DisplayT *d);

Populate a BlitOpsT with the correct span functions for the display's pixel depth (1, 2, or 4 bytes per pixel). Must be called once after videoInit(). For any other pixel size the span functions are left NULL so misuse fails loudly instead of drawing with the wrong depth.

  Parameter   Description
  ---------   -----------
  ops         BlitOpsT to populate
  d           Initialized display context

rectFill

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. Clips to the display clip rect. Workhorse for backgrounds, window fills, and clear operations.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  x, y, w, h Rectangle to fill
  color       Packed pixel color

rectCopy

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);

Copy a rectangle from an arbitrary source buffer into the backbuffer. Used to blit per-window content buffers during compositing. Rows are copied with ops->spanCopy (rep movsd on DOS); when both pitches equal the row size the whole block is one span. Source and destination must not overlap.

  Parameter     Description
  ---------     -----------
  d             Display context
  ops           Blit operations vtable
  dstX, dstY    Destination position in backbuffer
  srcBuf        Source pixel buffer
  srcPitch      Source buffer bytes per row
  srcX, srcY    Origin within source buffer
  w, h          Rectangle dimensions to copy

rectCopyGrayscale

void rectCopyGrayscale(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);

Copy a rectangle with grayscale conversion. Each pixel's RGB is converted to luminance (0.299R + 0.587G + 0.114B) for a disabled/grayed appearance. In 8-bit mode the gray is mapped back to the nearest palette entry (cached per index); on an 8-bit display without a palette the indices are copied unchanged.

  Parameter       Description
  ---------       -----------
  d               Display context
  ops             Blit operations vtable
  dstX, dstY      Destination position
  srcBuf, srcPitch Source buffer and pitch
  srcX, srcY      Source origin
  w, h            Rectangle dimensions

rectCopyTransparent

void rectCopyTransparent(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, uint32_t keyColor);

Copy with color-key transparency. Pixels matching keyColor are skipped, letting the destination show through. Used to draw image buttons and icons with transparent backgrounds.

  Parameter        Description
  ---------        -----------
  d                Display context
  ops              Blit operations vtable
  dstX, dstY       Destination position
  srcBuf, srcPitch Source buffer and pitch
  srcX, srcY       Source origin
  w, h             Rectangle dimensions
  keyColor         Packed color treated as transparent

drawBevel

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. Top/left edges in highlight color, bottom/right in shadow. Interior filled with the face color unless face is BEVEL_NO_FILL (0 is packed black and fills normally).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  x, y, w, h Outer bevel rectangle
  style       Bevel colors and width

drawChar

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);

Draw a single character glyph. When opaque is true, the background fills the entire cell; when false, only foreground pixels are drawn (transparent background).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font
  x, y        Character position
  ch          Character to draw
  fg, bg      Foreground and background packed colors
  opaque      true = fill background, false = transparent

Returns: Advance width (charWidth for the system font, the glyph's advance for a loaded font).

drawText

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);

Draw a null-terminated string. Equivalent to drawTextN over the whole string (one background span per scanline, character-range clipping).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font
  x, y        Start position
  text        Null-terminated string
  fg, bg      Foreground and background packed colors
  opaque      true = fill background, false = transparent

drawTextN

void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque);

Optimized batch text rendering for a known character count. Computes clip bounds once, fills background in a single rectFill, then overlays glyph foreground pixels. Significantly faster than per-character drawChar for long runs (terminal rows, list items).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font
  x, y        Start position
  text        Character buffer (not required to be null-terminated)
  count       Number of characters to render
  fg, bg      Foreground and background packed colors
  opaque      true = fill background, false = transparent

drawTextScaled

void drawTextScaled(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t scale, uint32_t fg);

Draw a null-terminated string scale times as large: every glyph pixel becomes a scale x scale block in fg, and the background shows through. For titles and banners in the 8x16 font (DVX Setup's title, the Welcome window). The text is textWidth(font, text) * scale wide and font->charHeight * scale tall. Each run of set pixels in a glyph row is one rectFill, which clips. Scale 1 is drawText; a scale below 1 draws nothing. BASIC: FontScale on a form or PictureBox (Print, TextWidth, TextHeight).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font
  x, y        Top-left corner
  text        Null-terminated string
  scale       Size multiple (1 = normal)
  fg          Packed text color

textWidth

int32_t textWidth(const BitmapFontT *font, const char *text);

Return the pixel width of a null-terminated string: the sum of its characters' advances (for the system font strlen(text) * charWidth).

  Parameter   Description
  ---------   -----------
  font        Bitmap font
  text        Null-terminated string

Returns: Width in pixels.

textWidthN

int32_t textWidthN(const BitmapFontT *font, const char *text, int32_t count);

The pixel width of the first count characters of text (0 when count is 0 or less): where a caret after them goes, how much of a line fits.

  Parameter   Description
  ---------   -----------
  font        Bitmap font
  text        Characters (not required to be null-terminated)
  count       How many to measure

Returns: Width in pixels.

drawTextStyled

void drawTextStyled(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t style, uint32_t fg, uint32_t bg, bool opaque);
void drawTextStyledN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t style, uint32_t fg, uint32_t bg, bool opaque);

Draw text (drawText, drawTextN) with the lines style asks for: FONT_STYLE_UNDERLINE at the font's underlineY and FONT_STYLE_STRIKE at its strikeY, lineThick rows, as wide as the text, in fg. Bold and italic are the font's own (dvxFontOpen); those bits of style are ignored here.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font
  x, y        Top-left of the line
  text        Text (drawTextStyledN: count characters of it)
  style       FONT_STYLE_UNDERLINE and / or FONT_STYLE_STRIKE
  fg, bg      Foreground and background packed colors
  opaque      true = fill background, false = transparent

accelParse

char accelParse(const char *text);

Scan text for an & prefix and return the following character as a lowercase accelerator key. "&File" returns 'f', "E&xit" returns 'x'.

  Parameter   Description
  ---------   -----------
  text        Text with optional & accelerator marker

Returns: Lowercase accelerator character, or 0 if none.

drawTextAccel

void drawTextAccel(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);

Draw text with & accelerator markers. The character after & is marked with the font's underline (underlineY; the system font's last row) across its advance to indicate the keyboard shortcut. && produces a literal &. Used for menu items and button labels.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font
  x, y        Start position
  text        Text with & markers
  fg, bg      Foreground and background packed colors
  opaque      true = fill background, false = transparent

textWidthAccel

int32_t textWidthAccel(const BitmapFontT *font, const char *text);

Measure text width excluding & markers (so "&File" measures as the width of "File").

  Parameter   Description
  ---------   -----------
  font        Bitmap font
  text        Text with optional & markers

Returns: Width in pixels.

drawMaskedBitmap

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);

Draw a 1-bit AND/XOR masked bitmap. Used for software-rendered mouse cursors.

  Parameter       Description
  ---------       -----------
  d               Display context
  ops             Blit operations vtable
  x, y            Screen position
  w, h            Bitmap dimensions
  andMask         AND transparency mask (one uint16_t per row)
  xorData         XOR color data
  fgColor, bgColor Cursor foreground and background packed colors

drawTermRow

void drawTermRow(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, int32_t cols, const uint8_t *lineData, const uint32_t *palette, bool blinkVisible, int32_t cursorCol);

Render an entire row of terminal character cells (ch/attr byte pairs) in a single pass. Colors looked up from a 16-color palette. The attribute byte uses the CGA layout described by the TERM_ATTR_* constants.

  Define               Value   Description
  ------               -----   -----------
  TERM_ATTR_FG_MASK    0x0F    Foreground palette index (low nibble)
  TERM_ATTR_BG_SHIFT   4       Shift of the background index
  TERM_ATTR_BG_MASK    0x07    Background palette index (bits 4-6, after shifting)
  TERM_ATTR_BLINK      0x80    Blink attribute
  Parameter       Description
  ---------       -----------
  d               Display context
  ops             Blit operations vtable
  font            Bitmap font
  x, y            Row start position
  cols            Number of columns
  lineData        Packed ch/attr byte pairs (2 bytes per cell)
  palette         16-entry packed color palette
  blinkVisible    false = hide blinking characters
  cursorCol       Column for inverted text cursor (-1 = none)

drawArrowGlyph

void drawArrowGlyph(DisplayT *d, const BlitOpsT *ops, int32_t cx, int32_t cy, int32_t rows, ScrollArrowDirE dir, uint32_t color);

Draw a solid triangle of rows rows (base 2 * rows - 1 pixels) pointing in direction dir (ScrollArrowUpE/DownE/LeftE/RightE), centered on (cx, cy). The shared direction glyph for scrollbar arrows, spinners, tab scroll buttons, navigation bars, and sort indicators.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  cx, cy      Center of the glyph
  rows        Number of rows (tip to base)
  dir         Direction the tip points
  color       Fill color

drawScrollbar

void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, ScrollbarOrientE orient, int32_t x, int32_t y, int32_t length, int32_t barW, int32_t thumbPos, int32_t thumbSize);

Paint a complete scrollbar from a ready-made thumb geometry: the sunken trough, an arrow button (drawScrollbarArrowButton) at each end, and a raised thumb thumbPos pixels past the first arrow button. The window scrollbars, the widget scrollbars, and wmDrawScrollbarAt (which computes the thumb from a scroll state) all paint through it.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  colors      Color scheme
  orient      ScrollbarVerticalE (arrows up and down) or ScrollbarHorizontalE (arrows left and right)
  x, y        Top-left corner of the bar
  length      Bar length in pixels along its axis, both arrow buttons included
  barW        Bar thickness, which is also the side of the square arrow buttons (SCROLLBAR_WIDTH for the standard look)
  thumbPos    Thumb offset in pixels from the end of the first arrow button
  thumbSize   Thumb length in pixels; 0 or less draws no thumb

drawScrollbarArrow

void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size, ScrollArrowDirE dir);

Draw the arrow glyph of a scrollbar button whose top-left is (x, y) and side is size, using drawArrowGlyph centered in the button with colors->scrollbarFg.

drawScrollbarArrowButton

void drawScrollbarArrowButton(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size, ScrollArrowDirE dir);

Draw one complete arrow button of a scrollbar, as the scrollbar painter does at each end of a bar: a size x size raised button (1px bevel on colors->scrollbarBg) whose top-left is (x, y), with the drawScrollbarArrow glyph pointing dir centered on it. The BASIC DrawScrollArrow method draws with it.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  colors      Color scheme
  x, y        Top-left corner of the button
  size        Side of the square button in pixels
  dir         Direction the arrow points (ScrollArrowUpE/DownE/LeftE/RightE)

drawFocusRect

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

Draw a 1px dotted rectangle (alternating pixels). Used for keyboard focus indicators, matching the Windows 3.x focus rectangle convention.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  x, y, w, h Focus rectangle bounds
  color       Dot color (packed)

drawFocusRectDots

typedef void (*DrawDotFnT)(void *ctx, int32_t x, int32_t y);
void drawFocusRectDots(int32_t x, int32_t y, int32_t w, int32_t h, DrawDotFnT fn, void *ctx);

The dots drawFocusRect draws, one fn call each and without clipping: the one definition of the pattern, for drawing it somewhere other than the back buffer. drawFocusRect plots them in the back buffer inside the clip rect; the BASIC DrawFocusRect method of forms and PictureBoxes sets them on the canvas.

drawHLine

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

Draw a horizontal line (1px tall).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  x, y        Start position
  w           Width in pixels
  color       Packed pixel color

drawVLine

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

Draw a vertical line (1px wide).

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  x, y        Start position
  h           Height in pixels
  color       Packed pixel color

dvxComp.h -- Layer 3: Dirty Rectangle Compositor

Tracks changed screen regions and ensures only dirty regions are redrawn and flushed to video memory. The compositing pipeline: mark dirty, merge overlapping rects, redraw desktop + windows (back-to-front, painter's algorithm), flush to LFB.

dirtyListInit

void dirtyListInit(DirtyListT *dl);

One-time initialization: empty the list and preallocate its rect array to the soft cap (128) so adding rects never allocates. Calling it again keeps the existing array.

  Parameter   Description
  ---------   -----------
  dl          Dirty list to initialize

dirtyListAdd

void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h);

Enqueue a dirty rectangle (empty rects are ignored). At the soft cap the list is merged first; if it is still full, every rect (including the new one) is collapsed into one bounding rect. A rect is never dropped while the list has any storage: if the array cannot be allocated it is unioned into the first entry.

  Parameter   Description
  ---------   -----------
  dl          Dirty list
  x, y, w, h Dirty rectangle in screen coordinates

dirtyListMerge

void dirtyListMerge(DirtyListT *dl);

Consolidate the dirty list by merging overlapping and adjacent rects. Uses iterative pairwise merge: if combining two rects does not increase total area beyond a threshold, they are merged. Reduces compositor passes and LFB flush operations.

  Parameter   Description
  ---------   -----------
  dl          Dirty list to merge

dirtyListClear

void dirtyListClear(DirtyListT *dl);

Reset the dirty list to empty (sets count to 0).

  Parameter   Description
  ---------   -----------
  dl          Dirty list to clear

flushRect

void flushRect(DisplayT *d, const RectT *r);

Copy a rectangle from the system RAM backbuffer to the LFB (video memory). This is the only place the real framebuffer is written. Uses platform-specific fast copy (rep movsd on DOS) for each scanline.

  Parameter   Description
  ---------   -----------
  d           Display context
  r           Rectangle to flush

rectIntersect

bool rectIntersect(const RectT *a, const RectT *b, RectT *result);

Compute the intersection of two rectangles.

  Parameter   Description
  ---------   -----------
  a, b        Input rectangles
  result      Output: intersection rectangle (valid only when return is true)

Returns: true if the rectangles overlap, false if disjoint.

dvxWm.h -- Layer 4: Window Manager

Manages the window lifecycle, Z-order stack, chrome drawing, hit testing, and interactive operations (drag, resize, scroll). The WM owns window geometry and chrome; content is owned by the application via callbacks or the widget system.

Initialization

wmInit

void wmInit(WindowStackT *stack);

Zero the window stack. Must be called before any other WM function.

  Parameter   Description
  ---------   -----------
  stack       Window stack to initialize

Window Lifecycle

wmCreateWindow

WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);

Allocate a new window, initialize its geometry and content buffer, and push it to the top of the Z-order stack. Returns with all callbacks NULL; the caller should set onPaint/onKey/etc. before the next event loop iteration.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  d           Display context
  title       Window title text
  x, y        Initial position
  w, h        Initial outer frame dimensions
  resizable   true = allow user resize

Returns: Pointer to new WindowT, or NULL on failure.

wmDestroyWindow

void wmDestroyWindow(WindowStackT *stack, WindowT *win);

Free the window's content buffer and all attached resources (menu bar, scrollbars, widget tree), remove it from the stack, and dirty the vacated region.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  win         Window to destroy

Z-Order and Focus

wmRaiseWindow

void wmRaiseWindow(WindowStackT *stack, DirtyListT *dl, int32_t idx);

Move window at stack index idx to the top of the Z-order. Dirties both old and new top positions so overlapping windows get repainted.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list for repaint marking
  idx         Stack index of window to raise

wmSetFocus

void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx);

Transfer keyboard focus to the window at stack index idx. Unfocuses the previously focused window and dirties both title bars.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list
  idx         Stack index of window to focus

Geometry

wmUpdateContentRect

void wmUpdateContentRect(WindowT *win);

Recompute contentX/Y/W/H from the window's outer frame dimensions, accounting for chrome borders, title bar, menu bar, and scrollbars. Must be called after any change to frame size or chrome configuration.

  Parameter   Description
  ---------   -----------
  win         Window to update

wmReallocContentBuf

int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);

Reallocate the per-window content backbuffer to match current contentW/H. Old buffer contents are lost; caller should trigger a full repaint via onPaint afterward.

  Parameter   Description
  ---------   -----------
  win         Window to reallocate
  d           Display context (for bytes-per-pixel)

Returns: 0 on success, -1 on allocation failure.

wmMinWindowSize

void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);

Get the minimum window size. Accounts for chrome, gadgets, and menu bar.

  Parameter    Description
  ---------    -----------
  win          Window
  minW, minH   Output: minimum width and height

wmClampWindowPos

void wmClampWindowPos(const WindowT *win, int32_t *x, int32_t *y, int32_t screenW, int32_t screenH);

Clamp a candidate window position so the window's title bar stays reachable on a screenW x screenH screen. Shared by mouse dragging (wmDragMove) and keyboard move.

  Parameter          Description
  ---------          -----------
  win                Window being moved
  x, y               In/out: candidate position, clamped in place
  screenW, screenH   Screen size

Menu Bar

wmAddMenuBar

MenuBarT *wmAddMenuBar(WindowT *win);

Allocate and attach a menu bar to a window. Adjusts content area to make room (CHROME_MENU_HEIGHT pixels). One menu bar per window.

  Parameter   Description
  ---------   -----------
  win         Window to add menu bar to

Returns: Pointer to the new MenuBarT.

wmDestroyMenuBar

void wmDestroyMenuBar(WindowT *win, const DisplayT *d);

Free the menu bar and reclaim the content area. The content buffer is reallocated to fit the grown content area.

  Parameter   Description
  ---------   -----------
  win         Window to remove menu bar from
  d           Display context; pass NULL only during window teardown

wmAddMenu

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

Append a dropdown menu to the menu bar. The label supports & accelerator markers (e.g. "&File").

  Parameter   Description
  ---------   -----------
  bar         Menu bar
  label       Menu label text

Returns: Pointer to the new MenuT to populate with items.

wmAddMenuItem

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

Append a clickable item to a menu. The id is passed to the window's onMenu callback when selected.

  Parameter   Description
  ---------   -----------
  menu        Menu to append to
  label       Item label (supports & markers)
  id          Application-defined command ID

wmAddMenuCheckItem

void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked);

Add a checkbox-style menu item. Check state toggles on click; rendered with a checkmark glyph.

  Parameter   Description
  ---------   -----------
  menu        Menu to append to
  label       Item label
  id          Command ID
  checked     Initial checked state

wmAddMenuRadioItem

void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked);

Add a radio-style menu item. Radio groups are defined implicitly by consecutive radio items; selecting one unchecks the others in the group.

  Parameter   Description
  ---------   -----------
  menu        Menu to append to
  label       Item label
  id          Command ID
  checked     Initial checked state

wmAddMenuSeparator

void wmAddMenuSeparator(MenuT *menu);

Insert a horizontal separator line. Separators are not interactive.

  Parameter   Description
  ---------   -----------
  menu        Menu to append separator to

wmRemoveMenu

bool wmRemoveMenu(WindowT *win, MenuT *menu, const DisplayT *d);

Take one menu (a wmAddMenu result) off the window's menu bar and free it with its items and submenus. Removing the last menu removes the bar, and the content buffer is reallocated for d as by wmDestroyMenuBar. The content area changes size when the bar goes, so the caller lays the window out again (dvxResizeWindow with the same size does it and fires onResize). The BASIC RemoveMenu method uses it.

  Parameter   Description
  ---------   -----------
  win         Window whose menu bar holds the menu
  menu        The menu to remove
  d           Display, for reallocating the content buffer when the bar goes

Returns: true if the menu was on the bar.

wmRemoveMenuItem

bool wmRemoveMenuItem(MenuT *menu, int32_t id);

Remove a menu item by command ID. Searches the menu's items array and shifts remaining items down to close the gap.

  Parameter   Description
  ---------   -----------
  menu        Menu to remove item from
  id          Command ID to remove

Returns: true if a matching item was found and removed.

wmClearMenuItems

void wmClearMenuItems(MenuT *menu);

Remove all items from a menu while preserving the menu itself on the menu bar. Use when rebuilding a menu's contents dynamically (e.g. a recent-files submenu).

  Parameter   Description
  ---------   -----------
  menu        Menu to empty

wmMenuItemIsChecked

bool wmMenuItemIsChecked(MenuBarT *bar, int32_t id);

Query the checked state of a menu item by command ID. Searches all menus in the bar.

  Parameter   Description
  ---------   -----------
  bar         Menu bar
  id          Command ID to query

Returns: true if checked.

wmMenuItemSetChecked

void wmMenuItemSetChecked(MenuBarT *bar, int32_t id, bool checked);

Set the checked state of a menu item by command ID. For radio items, setting checked=true also unchecks other radio items in the same group.

  Parameter   Description
  ---------   -----------
  bar         Menu bar
  id          Command ID
  checked     New checked state

wmMenuItemSetEnabled

void wmMenuItemSetEnabled(MenuBarT *bar, int32_t id, bool enabled);

Enable or disable a menu item by command ID.

  Parameter   Description
  ---------   -----------
  bar         Menu bar
  id          Command ID
  enabled     true = enabled, false = grayed out

wmMenuItemSetLabel

void wmMenuItemSetLabel(MenuItemT *item, const char *label);

Give an item a new label, written as wmAddMenuItem takes it: & before the accelerator letter, and the key text after a tab ("&Open\tCtrl+O"). The accelerator follows the new label; the item's ID, type, and state stay. An open popup shows the new label the next time it is drawn. A NULL item or label does nothing.

wmAddSubMenu

MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label);

Create a cascading submenu attached to a parent menu. The child MenuT is heap-allocated and freed when the parent window is destroyed.

  Parameter    Description
  ---------    -----------
  parentMenu   Parent menu to attach submenu to
  label        Submenu label text

Returns: Pointer to the child MenuT, or NULL on allocation failure.

wmCreateMenu

MenuT *wmCreateMenu(void);

Allocate a heap-resident MenuT for use as a context menu (right-click). Unlike menu bar menus, context menus are standalone allocations. Free with wmFreeMenu().

Returns: Pointer to the new MenuT.

wmFreeMenu

void wmFreeMenu(MenuT *menu);

Free a standalone menu allocated with wmCreateMenu(). Also frees any heap-allocated submenu children recursively.

  Parameter   Description
  ---------   -----------
  menu        Menu to free

Scrollbars

wmAddVScrollbar

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

Attach a vertical scrollbar to the right edge of the window's content area. Shrinks contentW by SCROLLBAR_WIDTH pixels. If the window already has a vertical scrollbar, that ScrollbarT is reused (same pointer) and reset to the new range with value = min. A negative pageSize is treated as 0.

  Parameter   Description
  ---------   -----------
  win         Window
  min, max    Scroll value range
  pageSize    Visible portion (controls thumb size)

Returns: Pointer to the window's ScrollbarT, or NULL on allocation failure.

wmAddHScrollbar

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

Attach a horizontal scrollbar to the bottom edge. Shrinks contentH by SCROLLBAR_WIDTH pixels. If the window already has a horizontal scrollbar, that ScrollbarT is reused and reset. A negative pageSize is treated as 0.

  Parameter   Description
  ---------   -----------
  win         Window
  min, max    Scroll value range
  pageSize    Visible portion

Returns: Pointer to the window's ScrollbarT, or NULL on allocation failure.

Drawing

wmDrawChrome

void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win, const RectT *clipTo);

Draw the window frame: outer bevel, title bar with text, close/minimize/maximize gadgets, and menu bar if present. Drawing is clipped to the intersection with clipTo. When the window has a content buffer, the content well (content area and scrollbar strips) is not painted: follow with wmDrawContent and wmDrawScrollbars, as the compositor does.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  font        Bitmap font for title text
  colors      Color scheme
  win         Window to draw chrome for
  clipTo      Dirty rectangle to clip drawing to

wmDrawContent

void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo);

Blit the window's content backbuffer into the display backbuffer, clipped to the dirty rect. Pure copy operation (no drawing); rows are copied with ops->spanCopy.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  win         Window
  clipTo      Dirty rectangle

wmDrawScrollbars

void wmDrawScrollbars(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, WindowT *win, const RectT *clipTo);

Draw scrollbars (track, arrows, proportional thumb) for a window. Drawn after content so scrollbars overlay the content area edge.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  colors      Color scheme
  win         Window
  clipTo      Dirty rectangle

wmDrawMinimizedIcons

void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, const WindowStackT *stack, const RectT *clipTo);

Draw icons for all minimized windows along the bottom of the screen. Each icon shows a scaled preview of the window's content with a beveled border.

  Parameter   Description
  ---------   -----------
  d           Display context
  ops         Blit operations vtable
  colors      Color scheme
  stack       Window stack
  clipTo      Dirty rectangle

wmDrawScrollbarAt

void wmDrawScrollbarAt(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, ScrollbarOrientE orient, int32_t x, int32_t y, int32_t length, int32_t scrollPos, int32_t visibleItems, int32_t totalItems);

Draw a standalone scrollbar of either orientation at the given screen coordinates, without a ScrollbarT struct. A vertical bar (ScrollbarVerticalE) is SCROLLBAR_WIDTH wide and length tall with up and down arrow buttons; a horizontal one (ScrollbarHorizontalE) is length wide and SCROLLBAR_WIDTH tall with left and right arrow buttons. Thumb size and position are computed from scrollPos / visibleItems / totalItems with the window scrollbars' rule; there is no thumb when everything is visible. wmVScrollbarAtClick maps a click on the bar to a new scrollPos. The BASIC DrawScrollBar method draws with it.

  Parameter       Description
  ---------       -----------
  d               Display context
  ops             Blit operations vtable
  colors          Color scheme
  orient          ScrollbarVerticalE or ScrollbarHorizontalE
  x, y            Screen position (top-left corner of the bar)
  length          Bar length in pixels along its axis, both arrow buttons included
  scrollPos       Current first visible item index
  visibleItems    Visible item count (for thumb size)
  totalItems      Total item count

wmDrawVScrollbarAt

void wmDrawVScrollbarAt(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t h, int32_t scrollPos, int32_t visibleItems, int32_t totalItems);

wmDrawScrollbarAt for a vertical bar h tall. Used by popup lists (dropdown, combobox) that need a scrollbar inline within a popup overlay.

  Parameter       Description
  ---------       -----------
  d               Display context
  ops             Blit operations vtable
  colors          Color scheme
  x, y            Screen position (top of scrollbar)
  h               Bar height in pixels, both arrow buttons included
  scrollPos       Current top visible item index
  visibleItems    Visible item count (for thumb size)
  totalItems      Total item count

wmVScrollbarAtClick

int32_t wmVScrollbarAtClick(int32_t length, int32_t offset, int32_t scrollPos, int32_t visibleItems, int32_t totalItems);

Apply a click on a scrollbar drawn by wmDrawScrollbarAt (either orientation) or wmDrawVScrollbarAt with the same length and scroll state; offset is the click's offset along the bar from its start: down from the top of a vertical bar, right from the left end of a horizontal one. Only that offset matters, so the function serves both orientations. Returns the new scrollPos: the arrow buttons step one item, the trough steps a page (visibleItems) toward the click, and the thumb leaves the position unchanged. The result is clamped to 0 .. totalItems - visibleItems. The thumb comes from the same geometry the paint uses, so a click always matches what is drawn.

Hit Testing

wmHitTest

int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hitPart);

Determine which window and chrome region is under the given screen coordinates. Iterates front-to-back (highest Z first) so the topmost window wins.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  mx, my      Screen coordinates
  hitPart     Output: HIT_CONTENT, HIT_TITLE, HIT_CLOSE, etc.

Returns: Stack index of hit window, or -1 for desktop.

wmResizeEdgeHit

int32_t wmResizeEdgeHit(const WindowT *win, int32_t mx, int32_t my);

Determine which edge(s) of a window's border zone are targeted for resize.

  Parameter   Description
  ---------   -----------
  win         Window
  mx, my      Screen coordinates

Returns: Bitmask of RESIZE_LEFT / RESIZE_RIGHT / RESIZE_TOP / RESIZE_BOTTOM.

wmMinimizedIconHit

int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t mx, int32_t my);

Hit-test minimized icons at the bottom of the screen.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  d           Display context
  mx, my      Screen coordinates

Returns: Stack index of the minimized window, or -1.

Drag and Resize

wmDragBegin

void wmDragBegin(WindowStackT *stack, int32_t idx, int32_t mouseX, int32_t mouseY);

Begin a window drag operation. Records the mouse offset from the window origin.

  Parameter     Description
  ---------     -----------
  stack         Window stack
  idx           Stack index of window to drag
  mouseX/Y      Current mouse position

wmDragMove

void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mouseY, int32_t screenW, int32_t screenH);

Update window position during an active drag. Dirties both old and new positions.

  Parameter      Description
  ---------      -----------
  stack          Window stack
  dl             Dirty list
  mouseX/Y       Current mouse position
  screenW/H      Screen dimensions (for clamping)

wmDragEnd

void wmDragEnd(WindowStackT *stack);

End the current drag operation. Clears dragWindow state.

  Parameter   Description
  ---------   -----------
  stack       Window stack

wmResizeBegin

void wmResizeBegin(WindowStackT *stack, int32_t idx, int32_t edge, int32_t mouseX, int32_t mouseY);

Begin a window resize operation. Records which edge(s) are being dragged.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  idx         Stack index
  edge        Bitmask of RESIZE_xxx flags
  mouseX/Y    Current mouse position

wmResizeMove

void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, int32_t *mouseX, int32_t *mouseY);

Update window dimensions during an active resize. Enforces MIN_WINDOW_W/H and maxW/maxH constraints. Reallocates content buffer and calls onResize if size changed. mouseX/mouseY are in/out: clamped on return for cursor warping.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list
  d           Display context
  mouseX/Y    In/out: mouse position (clamped on return)

wmResizeEnd

void wmResizeEnd(WindowStackT *stack);

End the current resize operation. Clears resizeWindow state.

  Parameter   Description
  ---------   -----------
  stack       Window stack

Scrollbar Interaction

wmScrollbarClick

void wmScrollbarClick(WindowStackT *stack, DirtyListT *dl, int32_t idx, ScrollbarOrientE orient, int32_t mx, int32_t my);

Handle an initial click on a scrollbar. Determines what was hit (arrows, trough, or thumb) and either adjusts the value immediately or begins a thumb drag.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list
  idx         Stack index of window
  orient      ScrollbarVerticalE or ScrollbarHorizontalE
  mx, my      Click screen coordinates

wmScrollbarDrag

void wmScrollbarDrag(WindowStackT *stack, DirtyListT *dl, int32_t mx, int32_t my);

Update the scroll value during an active thumb drag. Maps mouse position along the track to a proportional scroll value.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list
  mx, my      Current mouse position

wmScrollbarEnd

void wmScrollbarEnd(WindowStackT *stack);

End an active scrollbar thumb drag.

  Parameter   Description
  ---------   -----------
  stack       Window stack

Minimize / Maximize / Restore

wmMaximize

void wmMaximize(DirtyListT *dl, const DisplayT *d, WindowT *win);

Maximize a window. Saves current geometry, then expands to screen or maxW/maxH bounds.

  Parameter   Description
  ---------   -----------
  dl          Dirty list
  d           Display context
  win         Window to maximize

wmMinimize

void wmMinimize(WindowStackT *stack, DirtyListT *dl, WindowT *win);

Minimize a window. Hides the window and shows an icon at the bottom of the screen.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list
  win         Window to minimize

wmRestore

void wmRestore(DirtyListT *dl, const DisplayT *d, WindowT *win);

Restore a maximized window to its pre-maximize geometry.

  Parameter   Description
  ---------   -----------
  dl          Dirty list
  d           Display context
  win         Maximized window to restore

wmRestoreMinimized

void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);

Restore a minimized window (show it again, remove the icon) and raise it to the top. It does NOT change focus, so no onBlur/onFocus fires here; applications use dvxRestoreWindow, which also focuses the window with correct callback attribution.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  dl          Dirty list
  d           Display context
  win         Minimized window to restore

Minimized Icon Layout

wmMinimizedIconPos

void wmMinimizedIconPos(const DisplayT *d, int32_t index, int32_t *x, int32_t *y);

Compute the screen position of a minimized icon by ordinal index. Icons wrap into rows from bottom to top when the screen fills up.

  Parameter   Description
  ---------   -----------
  d           Display context
  index       Ordinal index of the minimized icon
  x, y        Output: screen position

wmMinimizedIconRect

void wmMinimizedIconRect(const WindowStackT *stack, const DisplayT *d, int32_t *y, int32_t *h);

Compute the screen rect covering all minimized icon rows. Used to dirty the icon area when windows are minimized or restored.

  Parameter   Description
  ---------   -----------
  stack       Window stack
  d           Display context
  y, h        Output: vertical extent of icon area

Miscellaneous

wmSetTitle

void wmSetTitle(WindowT *win, DirtyListT *dl, const char *title);

Set the window title and dirty the title bar for repaint.

  Parameter   Description
  ---------   -----------
  win         Window
  dl          Dirty list
  title       New title text

wmSetIconData

void wmSetIconData(WindowT *win, uint8_t *pixels, int32_t w, int32_t h, int32_t pitch);

Install an icon that is already in display pixel format (for example from dvxLoadImage). Takes ownership of the malloc'd pixel buffer; it is freed with the window or when the next icon replaces it.

  Parameter   Description
  ---------   -----------
  win         Window
  pixels      Display-format pixels (ownership transfers to the window)
  w, h        Icon size in pixels
  pitch       Bytes per row

dvxApp.h -- Layer 5: Application API

The topmost layer and the public-facing API. Aggregates all lower layers into a single AppContextT. Applications interact exclusively through dvx*() functions and window callbacks. The event loop follows a cooperative model: poll, dispatch, composite, yield.

AppContextT

Single monolithic context that owns all GUI state. Contains the display, window stack, dirty list, blit ops, font, color scheme, popup state, cursor state, mouse/keyboard state, tooltip state, wallpaper buffer, video mode list, and various configuration fields. Allocated on the caller's stack or statically.

Fields applications and the shell commonly read (all others are internal state):

  Field                        Description
  -----                        -----------
  DisplayT display             Screen display context (use dvxGetDisplay)
  WindowStackT stack           Window stack (z-order, focus, drag state)
  int32_t mouseX, mouseY       Current mouse position
  int32_t mouseButtons         Current MOUSE_* button bitmask
  int32_t mouseWheel           Wheel delta this frame (positive = down)
  int32_t keyModifiers         Current BIOS shift state (KEY_MOD_*)
  int32_t keyFocusWinId        Window the held-key table was last cleared for (0 = none; internal)
  int32_t wheelDirection       1 = normal, -1 = reversed
  int32_t wheelStep            Lines per wheel notch (1-10, default 3)
  PlatformTicksT dblClickTicks Double-click interval in platformClock ticks
  int32_t dblClickMs           Double-click interval in milliseconds (as given to dvxSetMouseConfig)
  int32_t mouseAccel           Acceleration threshold last applied (MOUSE_ACCEL_*)
  int32_t mouseMickeys         Mickeys per 8 pixels last applied (MOUSE_SPEED_DEFAULT at start)
  uint32_t charHeightRecip     16.16 reciprocal of font.charHeight (popup row mapping)
  WindowT *modalWindow         If non-NULL, only this window receives input
  int32_t dispatchDepth        Callback nesting depth; destroys are deferred while > 0
  uint32_t windowsCreated      Windows created by dvxCreateWindow so far (the shell compares it before and after a close request to see whether the app showed a prompt)
  int32_t currentAppId         App charged for allocations and callbacks (0 = shell)
  bool busy                    Busy cursor shown and input blocked
  bool fontSmoothing           Text smoothing wanted (dvxSetFontSmoothing; on at start)
  idleCallback / idleCtx       Called instead of yielding when a frame had no events
  onCtrlEsc, onF1, onTitleChange   Shell hooks (with their ctx pointers)
  VideoModeInfoT *videoModes   stb_ds array of settable modes
  char wallpaperPath[]         Current wallpaper source path (empty = none)
  WallpaperModeE wallpaperMode Stretch, tile, or center

dvxSetMouseConfig programs the mouse speed and acceleration into the platform layer, which re-applies them after every mouse driver reset; mouseAccel and mouseMickeys only record the last values applied (for dvxMouseToPrefs and settings pages).

Initialization and Shutdown

dvxInit

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

Initialize the entire GUI stack: video mode, input devices, font, color scheme, cursor shapes, and internal state. Single entry point for starting a DVX application.

  Parameter        Description
  ---------        -----------
  ctx              Application context to initialize
  requestedW/H     Desired screen resolution
  preferredBpp     Preferred bits per pixel

Returns: 0 on success, negative on failure.

dvxShutdown

void dvxShutdown(AppContextT *ctx);

Tear down the GUI stack in reverse order: destroy all windows, restore text mode, release input devices. Safe to call after a failed dvxInit().

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxChangeVideoMode

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

Switch to a new video mode live. Reallocates the backbuffer, all window content buffers, repacks colors, rescales wallpaper, and repositions off-screen windows.

  Parameter        Description
  ---------        -----------
  ctx              Application context
  requestedW/H     New resolution
  preferredBpp     New bits per pixel

Returns: 0 on success, -1 on failure (old mode restored).

Event Loop

dvxRun

void dvxRun(AppContextT *ctx);

Enter the main event loop. Polls input, dispatches events, composites dirty regions, and yields on each iteration. Returns when ctx->running becomes false.

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxUpdate

bool dvxUpdate(AppContextT *ctx);

Process exactly one frame of the event loop. For applications that integrate the GUI into their own main loop (e.g. polling serial ports between frames).

  Parameter   Description
  ---------   -----------
  ctx         Application context

Returns: false when the GUI wants to exit.

dvxQuit

void dvxQuit(AppContextT *ctx);

Request exit from the main event loop (sets ctx->running = false).

  Parameter   Description
  ---------   -----------
  ctx         Application context

Window Management

Every dvx* function that takes a WindowT pointer accepts NULL as a no-op (functions that return a status return failure).

dvxCreateWindow

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 an explicit screen position. The window is raised to the top, focused, and its entire region is dirtied.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Window title
  x, y        Screen position
  w, h        Outer frame dimensions
  resizable   true = allow user resize

Returns: Pointer to new WindowT.

dvxCreateWindowCentered

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

Convenience wrapper that centers the window on screen.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Window title
  w, h        Outer frame dimensions
  resizable   true = allow user resize

Returns: Pointer to new WindowT.

dvxDestroyWindow

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

Destroy a window, free all its resources, and dirty its former region.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to destroy

dvxRaiseWindow

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

Raise a window to the top of the Z-order and give it focus.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to raise

dvxRequestWindowClose

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

Ask a window to close exactly as its close box, Alt+F4, and the system menu's Close do. The window's onClose runs (under the window's app ID) and decides -- a save prompt may keep the window open; a window without onClose is destroyed. A clean frame is composited first so an open menu is erased before onClose shows a dialog. When no dispatch is in progress the window may already be freed on return, so look it up again by its id rather than using the pointer. The shell's Exit DVX sequence uses this to ask each app to close.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to ask to close

dvxRestoreWindow

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

Restore a minimized window (or un-maximize a maximized one), then raise and focus it. Focus-change callbacks (onBlur of the old window, onFocus of the restored one) run under their own app's ID. No-op for NULL or a window pending destruction.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to restore

dvxFitWindow

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

Resize a window to exactly fit its widget tree's computed minimum size (plus chrome), never smaller than the window manager's minimum window size. Used for dialog boxes and fixed-layout windows.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to fit

dvxFitWindowW

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

Resize window width only to fit widget tree's minimum width (plus chrome), never below the window manager's minimum width.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to fit

dvxFitWindowH

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

Resize window height only to fit widget tree's minimum height (plus chrome), never below the window manager's minimum height.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to fit

dvxResizeWindow

void dvxResizeWindow(AppContextT *ctx, WindowT *win, int32_t newW, int32_t newH);

Programmatically resize a window to the specified outer dimensions.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to resize
  newW, newH  New outer frame dimensions

dvxMinimizeWindow

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

Minimize a window (show as icon at bottom of screen).

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to minimize

dvxMaximizeWindow

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

Maximize a window (expand to fill screen or maxW/maxH).

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to maximize

dvxHideWindow

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

Hide a window without destroying it. Marks the exposed region dirty.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to hide

dvxShowWindow

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

Show a previously hidden window. Marks its region dirty for repaint.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to show

Invalidation

dvxInvalidateRect

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

Mark a sub-region of a window's content area as needing repaint. Coordinates are relative to the content area, not the screen. Triggers onPaint during the next composite pass.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window
  x, y, w, h Dirty rectangle in content-relative coordinates

dvxInvalidateWindow

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

Mark the entire window content area as dirty.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to invalidate

Window Properties

dvxSetTitle

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

Set a window's title text and dirty the title bar.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window
  title       New title text

dvxSetWindowIcon

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

Load an icon for a window from an image file. The image is converted to the display format with the same (dithered) conversion as dvxLoadImage and installed with wmSetIconData.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window
  path        Image file path

Returns: 0 on success, -1 on failure.

dvxSetBusy

void dvxSetBusy(AppContextT *ctx, bool busy);

Set or clear busy state. While busy, the hourglass cursor is shown and input is blocked.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  busy        true = show hourglass, false = normal

Accessors

dvxGetFont

const BitmapFontT *dvxGetFont(const AppContextT *ctx);

Get a pointer to the default font.

  Parameter   Description
  ---------   -----------
  ctx         Application context

Returns: Pointer to the active BitmapFontT.

dvxGetColors

const ColorSchemeT *dvxGetColors(const AppContextT *ctx);

Get a pointer to the current color scheme.

  Parameter   Description
  ---------   -----------
  ctx         Application context

Returns: Pointer to the active ColorSchemeT.

dvxGetDisplay

DisplayT *dvxGetDisplay(AppContextT *ctx);

Get a pointer to the display context.

  Parameter   Description
  ---------   -----------
  ctx         Application context

Returns: Pointer to the DisplayT.

dvxGetBlitOps

const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx);

Get a pointer to the blit operations vtable.

  Parameter   Description
  ---------   -----------
  ctx         Application context

Returns: Pointer to the active BlitOpsT.

dvxContentDisplay

void dvxContentDisplay(const AppContextT *ctx, const WindowT *win, DisplayT *out);

Fill *out with a copy of the display redirected at the window's content buffer: backBuf and lfb point at win->contentBuf, width/height/pitch are the content size and pitch, and the clip rect covers the whole buffer. Pixel format fields come from the real display. Use it to draw straight into a window's content buffer with the normal drawing primitives, for example from an onPaint handler that does not use widgets. Because lfb is redirected too, nothing drawn through the copy can reach the screen.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window whose content buffer is the target
  out         Output: display context for drawing into the buffer
static void onPaint(WindowT *win, RectT *dirty) {
    DisplayT cd;

    dvxContentDisplay(ctx, win, &cd);
    rectFill(&cd, dvxGetBlitOps(ctx), 0, 0, win->contentW, win->contentH, dvxGetColors(ctx)->contentBg);
}

dvxGetVideoModes

const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx, int32_t *count);

Return the list of available video modes enumerated at init time.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  count       Output: number of mode entries

Returns: Pointer to the VideoModeInfoT array.

Color Scheme

dvxSetColor

void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b);

Set a single color by ID. Repacks to native pixel format and invalidates the entire screen.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  id          Color ID (ColorIdE)
  r, g, b     RGB values (0-255)

dvxGetColor

void dvxGetColor(const AppContextT *ctx, ColorIdE id, uint8_t *r, uint8_t *g, uint8_t *b);

Get a color's RGB values by ID.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  id          Color ID (ColorIdE)
  r, g, b     Output: RGB values

dvxApplyColorScheme

void dvxApplyColorScheme(AppContextT *ctx);

Apply all colors from ctx->colorRgb[] at once (repack + full repaint).

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxResetColorScheme

void dvxResetColorScheme(AppContextT *ctx);

Reset all colors to the built-in defaults and repaint.

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxLoadTheme

bool dvxLoadTheme(AppContextT *ctx, const char *filename);

Load a theme file (INI format with [colors] section) and apply it through dvxColorsFromPrefs. Each color is a "name = r,g,b" key in [colors]; keys outside that section, unknown names, and malformed values are ignored, and channels are clamped to 0..255. A missing file returns false.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  filename    Path to theme INI file

Returns: true on success.

dvxSaveTheme

bool dvxSaveTheme(const AppContextT *ctx, const char *filename);

Save the current color scheme to a theme file (the keys dvxColorsToPrefs writes). When the file already exists its comments and other lines are kept and only the [colors] keys are updated. The new copy is written to a temporary file first and swapped in only when every write succeeded, so a failed save leaves the old theme intact.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  filename    Output file path

Returns: true on success.

dvxColorsFromPrefs / dvxColorsToPrefs

bool dvxColorsFromPrefs(AppContextT *ctx, PrefsHandleT *h);
void dvxColorsToPrefs(const AppContextT *ctx, PrefsHandleT *h);

The [colors] section of an INI handle, the one place its keys are read and written: dvxLoadTheme / dvxSaveTheme, the shell at start (DVX.INI), and Control Panel (OK, and its Cancel snapshot in a prefsCreate handle) all use these. FromPrefs reads every "name = r,g,b" key (malformed ones ignored, channels clamped to 0..255), repaints only when a color changed and returns true when any color key was read. ToPrefs writes every color.

dvxColorName

const char *dvxColorName(ColorIdE id);

Return the INI key name for a color ID (e.g. "desktop", "windowFace").

  Parameter   Description
  ---------   -----------
  id          Color ID

Returns: Static string.

dvxColorLabel

const char *dvxColorLabel(ColorIdE id);

Return a human-readable display label (e.g. "Desktop", "Cursor Color").

  Parameter   Description
  ---------   -----------
  id          Color ID

Returns: Static string.

Wallpaper

dvxSetWallpaper

bool dvxSetWallpaper(AppContextT *ctx, const char *path);

Load and apply a wallpaper image using the current wallpaperMode (stretch/tile/center). Pass NULL to clear the wallpaper. If the image cannot be loaded, the current wallpaper and its saved path are kept and false is returned.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  path        Image file path, or NULL to clear

Returns: true on success.

dvxSetWallpaperMode

void dvxSetWallpaperMode(AppContextT *ctx, WallpaperModeE mode);

Change the wallpaper display mode; a loaded wallpaper is rendered again. With none loaded the mode is kept for the next one.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  mode        WallpaperStretchE, WallpaperTileE, or WallpaperCenterE

dvxWallpaperFromPrefs / dvxWallpaperToPrefs

bool dvxWallpaperFromPrefs(AppContextT *ctx, PrefsHandleT *h);
void dvxWallpaperToPrefs(const AppContextT *ctx, PrefsHandleT *h);

The [desktop] wallpaper and mode keys of an INI handle (mode through prefsWallpaperChoices). FromPrefs applies them: a missing mode is WALLPAPER_MODE_DEFAULT and a missing wallpaper means none; the image is built again only when the path or mode differs from the current one. It returns false when the named image could not be loaded. ToPrefs writes the path (removes the key when there is none) and the mode.

Text Smoothing

dvxSetFontSmoothing

void dvxSetFontSmoothing(AppContextT *ctx, bool on);

Smooth text, or stop (ctx->fontSmoothing; on after dvxInit). Text is smoothed when on and the screen has more than 8 bits per pixel (dvxFontSetSmooth, dvxText.h), in the faces and sizes that have a smoothed strike; dvxChangeVideoMode applies it again at the new depth. When what is drawn changes, every window is laid out and painted again.

dvxFontsFromPrefs / dvxFontsToPrefs

void dvxFontsFromPrefs(AppContextT *ctx, PrefsHandleT *h);
void dvxFontsToPrefs(const AppContextT *ctx, PrefsHandleT *h);

The [fonts] keys of an INI handle: smoothing (true or false; true when missing). FromPrefs applies it with dvxSetFontSmoothing; ToPrefs writes the setting. The shell uses FromPrefs at start and Control Panel both for OK and for its Cancel snapshot.

Mouse Configuration

dvxSetMouseConfig

void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio, int32_t wheelStep);

Configure mouse behavior. Updates the in-memory AppContextT fields and programs any INT 33h settings that need driver-side adjustment.

  Parameter        Description
  ---------        -----------
  ctx              Application context
  wheelDir         1 = normal, -1 = reversed
  dblClickMs       Double-click speed in milliseconds (e.g. 500)
  accelThreshold   Double-speed threshold in mickeys/sec (0 = don't change)
  mickeyRatio      Mickeys per 8 pixels (0 = don't change, 8 = default speed)
  wheelStep        Lines to scroll per wheel notch (1-10, default 3)

The speed, acceleration, and wheel settings are re-applied by the platform layer after every mouse driver reset, so they survive a live video-mode change. The values are kept in AppContextT (wheelDirection, dblClickMs, mouseAccel, mouseMickeys, wheelStep).

dvxMouseFromPrefs / dvxMouseToPrefs

void dvxMouseFromPrefs(AppContextT *ctx, PrefsHandleT *h);
void dvxMouseToPrefs(const AppContextT *ctx, PrefsHandleT *h);

The [mouse] keys of an INI handle: wheel and acceleration (by name, through prefsWheelDirChoices and prefsAccelChoices), doubleclick (milliseconds), speed (mickeys per 8 pixels), and wheelspeed. FromPrefs applies them with dvxSetMouseConfig, defaults for missing keys (MOUSE_*_DEFAULT); ToPrefs writes the configuration in effect (an acceleration no choice has leaves that key alone). The shell uses FromPrefs at start and Control Panel both for OK and for its Cancel snapshot.

Input Entropy

dvxSetInputEntropyHook

typedef void (*DvxEntropyHookT)(const uint8_t *data, int32_t len);

void dvxSetInputEntropyHook(DvxEntropyHookT fn);

Install a sink for input-timing entropy (for example a random-number generator's add-entropy routine). While a hook is set, every keyboard event and every mouse change (movement, button, wheel) is recorded as a 32-bit platformEntropyStamp value followed by the event bytes (scancode, ASCII, and shift state; or position, buttons, and wheel delta). Records are batched and handed to fn once 64 bytes have accumulated, so the cost per event stays negligible. An idle mouse is not recorded. Changing or clearing the hook discards any pending partial batch. The core library has no link-time dependency on any consumer; the shell resolves one at run time.

  Parameter   Description
  ---------   -----------
  fn          Entropy sink, or NULL to disable

Desktop Sounds

The sounds DVX plays for its events. Which file each event plays (or none) is set on the Control Panel's Sounds page and kept in DVX.INI [sounds]; the sound library plays them (see its developer help). Without the sound library, or without sound hardware, nothing is heard and nothing else changes.

typedef enum {
    SoundDefaultE,          // the default beep (dvxBeep)
    SoundStartE,            // DVX started
    SoundExitE,             // DVX is ending
    SoundErrorE,
    SoundWarningE,
    SoundQuestionE,
    SoundInfoE,
    SoundWindowOpenE,
    SoundWindowCloseE,
    SoundCountE
} DvxSoundE;

dvxPlaySound / dvxPlaySoundWait / dvxBeep

void dvxPlaySound(DvxSoundE event);
void dvxPlaySoundWait(DvxSoundE event);
void dvxBeep(void);

Play the sound of an event. dvxPlaySound returns at once while the sound plays on; dvxPlaySoundWait returns once it has played (at most a few seconds); dvxBeep is dvxPlaySound(SoundDefaultE). An event without a sound is silent.

dvxSetSoundHook

typedef void (*DvxSoundHookT)(DvxSoundE event, bool wait);

void dvxSetSoundHook(DvxSoundHookT fn);

The function that plays the sounds, set by the sound library when it loads (NULL: silent). Programs do not call it.

Full-Screen Exclusive Mode

A window's program takes the whole screen in a video mode of its own (a game at 320x200, say) while every other program keeps running: windows keep painting into their own buffers, and the desktop is drawn again when the program lets go. The owner's window must have the focus: when it loses it (Alt+Tab, Ctrl+Esc, a dialog, another window coming to the front) the desktop comes back and onChange hears ExclusiveSuspendedE; when the window has the focus again the mode is set again and onChange hears ExclusiveActiveE (draw the whole frame: the screen and the backbuffer start black). While active every key but Alt+Tab, Ctrl+Esc, and Alt+F4 goes to the window as in game input mode, the mouse cursor is not drawn, onMouse gets full-screen coordinates, dvxScreenshot saves the full screen, and dvxChangeVideoMode fails. Closing the window, the program ending or being ended, and dvxExclusiveEnd give the screen back. See the Writing Games guide.

typedef enum {
    ExclusiveActiveE,       // the screen is the program's: draw the whole frame
    ExclusiveSuspendedE,    // its window lost the focus: the desktop is back until it has the focus again
    ExclusiveEndedE         // the mode could not be set again: full screen is over
} ExclusiveStateE;

typedef void (*ExclusiveFnT)(WindowT *win, ExclusiveStateE state, void *userData);

dvxExclusiveBegin

int32_t dvxExclusiveBegin(AppContextT *ctx, WindowT *win, int32_t width, int32_t height, int32_t bpp, ExclusiveFnT onChange, void *userData);

Take the whole screen for win (raised and focused): the smallest mode of at least width x height, at bpp when there is one (0: any depth). At 8 bpp (or 0) a request that fits 320x200 gets VGA mode 13h, and one that fits 320x240 VGA Mode X (square pixels, two pages flipped at the retrace), unless the card has a VBE mode no larger with a linear frame buffer. onChange (may be NULL) hears ExclusiveActiveE before this returns. A window that already has full screen gets the new size.

Returns: DVX_EXCL_OK; DVX_EXCL_ERR_ARGS (no such window, or a bad size); DVX_EXCL_ERR_BUSY (another window has full screen); DVX_EXCL_ERR_MODE (no mode could be set; the desktop is back).

dvxExclusiveEnd

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

Give the screen back: the desktop's mode, palette, and mouse, and a full repaint. onChange is not called. Nothing when win does not have full screen.

dvxExclusiveDisplay / dvxExclusiveBlitOps

DisplayT       *dvxExclusiveDisplay(AppContextT *ctx);
const BlitOpsT *dvxExclusiveBlitOps(AppContextT *ctx);

The full-screen display (its size may be larger than asked for) and the span operations for its pixel format while active; NULL while suspended or without full screen. Draw into the display's backBuf, with DVX's draw functions if you like, then dvxExclusiveFlush.

dvxExclusiveFlush

void dvxExclusiveFlush(AppContextT *ctx, const RectT *r);

Copy a rectangle of the backbuffer to the screen (NULL: all of it). Nothing when not active. In Mode X a whole-screen flush goes to the hidden page, which is shown from the next vertical retrace (the call waits for it): no tearing; a smaller rectangle goes to the page on show.

dvxExclusivePresent8

bool dvxExclusivePresent8(AppContextT *ctx, const uint8_t *src, int32_t srcW, int32_t srcH, int32_t srcPitch);

Show an 8-bit picture of palette indexes on the whole screen: scaled by the largest whole factor that fits, centred on black, through the palette in any pixel format, and flushed (the whole screen, so Mode X flips pages). Returns false (and does nothing) when not active, so a caller falls back to drawing in its window.

dvxExclusiveSetPalette

void dvxExclusiveSetPalette(AppContextT *ctx, const uint8_t *pal, int32_t first, int32_t count);

Set palette entries first..first+count-1 (pal is the whole 256-entry table, R, G, B bytes per entry). At 8 bpp they go to the DAC at once, so a picture on screen changes colour; at other depths the next dvxExclusivePresent8 uses them. The palette is kept while suspended and set again on resuming; the desktop's palette comes back whenever the desktop does.

dvxExclusiveWaitRetrace

void dvxExclusiveWaitRetrace(void);

Wait for the start of the next vertical retrace (at most two frames' time), before a flush, so the frame is not torn.

DOS Programs: dvxRunDos

int32_t dvxRunDos(AppContextT *ctx, const char *program, const char *args, const char *folder, bool pause);
int32_t platformDosRun(const char *command, const char *folder, bool pause, const DisplayT *desktop);
bool    platformPathAbsolute(const char *path, char *out, int32_t outSize);

dvxRunDos runs a DOS program (an .EXE, .COM, or .BAT file, or a command DOS knows) with DVX stepped aside, and comes back. Full screen, menus, and drags end; program is made absolute (a bare name stays one, for the PATH) and given to DOS by its short name, with args after it, in folder (NULL or "": the program's own folder). platformDosRun then runs the suspend hooks (newest first), lets the keyboard hook and the timer service go (the timers are kept and stand still), sets text mode, gives the INT 24h handler and Ctrl+Break back to DOS, makes the folder current, and runs the command line with system() (as at the DOS prompt: a batch file through the command interpreter, PATH, redirection); with pause it prints "Press any key to return to DVX." and waits for a key. Everything is taken back in the opposite order, the desktop's mode (and palette) set again, and the resume hooks run (oldest first); dvxRunDos sets the mouse up again where it was, forgets held keys, and repaints the whole screen. DVX logs "DOS: running" and "DOS: back from" with the command line. Nothing of DVX runs meanwhile: close the other programs first (the shell's shellRunDos asks them). Returns the program's exit code, or -1 when it could not be run (errno: ENOENT, ENOMEM, E2BIG for a command line longer than DOS's 126 characters). DVX stays in memory: the program gets the conventional memory DVX's DOS part and the DPMI host leave. On the host, platformDosRun is system() in the folder with the hooks around it.

platformPathAbsolute makes a path absolute against the current folder (it need not exist).

Optional Libraries: dvxLibraryLoaded

bool dvxLibraryLoaded(const char *name);

Whether the library (or widget) name, its file's base name in any case ("pkgnet", "dvxsql"), is loaded: installed and started with DVX, which loads every library and widget it has at start. It looks for the module's ABI stamp (DVX_ABI_STAMP: name and DVX_ABI_SUFFIX), which the loader checked before it took the module; a module that is not there, or did not load, has none. Some parts of DVX are optional (Setup's "What to install" page installs them as bundles: src/packages.ini), so a program that uses one of their libraries only when it is there asks first, and does not import it: an import of a missing library stops the program from loading. BASIC: SysLibraryLoaded (system.bas).

Idle Time: dvxIdleMs

uint32_t dvxIdleMs(const AppContextT *ctx);

The milliseconds since the last key or mouse input: a key (pressed, released, or typed), the mouse moving, a button, or the wheel, injected input (a test script, SysSendKey) included, and input that came while DVX was busy and was thrown away. DVX coming back from a DOS program or from full-screen mode counts as input too. The shell's screen saver starts after DVX.INI [desktop] saverWait minutes of it (shellSaver.h); BASIC: SysIdleSeconds. The time is kept in AppContextT.lastInputTime (platformClock ticks).

Accelerators

dvxCreateAccelTable

AccelTableT *dvxCreateAccelTable(void);

Allocate a new accelerator table. Attach to a window via win->accelTable.

Returns: Pointer to new AccelTableT.

dvxFreeAccelTable

void dvxFreeAccelTable(AccelTableT *table);

Free an accelerator table and its entries.

  Parameter   Description
  ---------   -----------
  table       Table to free

dvxAddAccel

void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId);

Register a keyboard shortcut. On match, fires the window's onMenu callback with cmdId.

The key and modifiers are normalized at registration with the same rule applied to every keystroke:

  Parameter   Description
  ---------   -----------
  table       Accelerator table (NULL is a no-op)
  key         ASCII character or KEY_Fxx / KEY_* constant
  modifiers   Bitmask of ACCEL_CTRL / ACCEL_SHIFT / ACCEL_ALT
  cmdId       Command ID passed to onMenu

dvxRemoveAccel

void dvxRemoveAccel(AccelTableT *table, int32_t cmdId);

Remove every shortcut of command cmdId from the table: a menu item that lost its key or went away. The BASIC menu Shortcut property and RemoveMenuItem use it.

  Parameter   Description
  ---------   -----------
  table       Accelerator table (NULL is a no-op)
  cmdId       Command whose shortcuts go

dvxAccelFormat

void dvxAccelFormat(int32_t key, int32_t modifiers, char *out, int32_t outSize);

Write a key and its modifiers (given as for dvxAddAccel) as text, the way menus show it after a tab: the modifiers Ctrl, Alt, and Shift in that order, each followed by +, then the key -- a letter or digit, F1 to F12, or one of the names Backspace, Del, Down, End, Enter, Esc, Home, Ins, Left, PgDn, PgUp, Right, Space, Tab, Up. Key 0 gives "".

dvxAccelFormat(KEY_ENTER, ACCEL_ALT, text, sizeof(text));   // "Alt+Enter"

dvxAccelParse

bool dvxAccelParse(const char *text, int32_t *outKey, int32_t *outMods);

Read key text as dvxAccelFormat writes it ("F2", "Ctrl+X", "Alt+Enter", "Shift+Del") into a key and ACCEL_* modifiers for dvxAddAccel or dvxRegisterHotkey. Case does not matter, blanks around the parts are ignored, and Delete and Insert are accepted for Del and Ins. Returns false, with *outKey 0, for empty text, an unknown modifier, or an unknown key.

System-Wide Hotkeys

A hotkey is a keystroke that works whichever window has the focus (or when none has): its callback runs instead of anything the focused window would do with the keys, including its menu keys and accelerators. Program Manager's item shortcut keys are hotkeys. Hotkeys are not checked while a menu or a modal dialog is open, and keys the system handles first (Alt+Tab, Alt+F4, Ctrl+F12, Ctrl+Esc, F1, F10, Alt+Space) never reach them.

A hotkey belongs to the app that was current when it was registered (ctx->currentAppId, set by the shell whenever app code runs). When an app exits, crashes, or is ended from the Task Manager, the shell removes its hotkeys with dvxUnregisterAppHotkeys, so a callback never outlives the code it points into. The callback also runs with currentAppId set to that app.

dvxRegisterHotkey

typedef void (*DvxHotkeyFnT)(void *userCtx);

int32_t dvxRegisterHotkey(AppContextT *ctx, int32_t key, int32_t modifiers, DvxHotkeyFnT fn, void *userCtx);

Register a system-wide hotkey. key and modifiers are given and normalized exactly as for dvxAddAccel. A key needs Ctrl or Alt unless it is a function or navigation key (KEY_EXT_FLAG set), so a hotkey cannot swallow typing.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  key         ASCII character or KEY_Fxx / KEY_* constant
  modifiers   Bitmask of ACCEL_CTRL / ACCEL_SHIFT / ACCEL_ALT
  fn          Called with userCtx when the keystroke is pressed
  userCtx     Passed to fn

Returns: the hotkey's id (greater than zero), or DVX_HOTKEY_NONE when refused: NULL ctx or fn, a character key without Ctrl or Alt, or a keystroke that is already a hotkey (first come, first served).

dvxUnregisterHotkey

bool dvxUnregisterHotkey(AppContextT *ctx, int32_t hotkeyId);

Remove one hotkey (a callback may remove its own). Returns false when ctx has no hotkey with that id.

dvxUnregisterAppHotkeys

void dvxUnregisterAppHotkeys(int32_t appId);

Remove every hotkey registered while appId was the current app. The shell calls it when an app goes away; apps do not need to.

Hung Apps

dvxAppIsHung

#define DVX_HUNG_APP_MS 5000

bool dvxAppIsHung(const AppContextT *ctx, int32_t appId);

True when appId has been inside one of its window callbacks (an event handler that has not returned to the event loop, such as a button handler in an endless loop) for DVX_HUNG_APP_MS or longer. An app with a modal dialog of its own open is waiting for the user, not hung. The Task Manager shows such an app as "Not responding". Every window callback dvxUpdate dispatches is booked per app (nested calls count once, from the outermost); widget Timer callbacks are not window callbacks and are not tracked.

dvxAppHungForget

void dvxAppHungForget(int32_t appId);

Drop appId's callback bookkeeping. The shell calls it when an app goes away (even from inside one of its callbacks, when End Task stops it); apps do not need to.

Window Arrangement

dvxCascadeWindows

void dvxCascadeWindows(AppContextT *ctx);

Cascade all visible, non-minimized windows. Each is offset diagonally by the title bar height; the diagonal wraps back to the top-left corner when the next window would leave the screen.

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxTileWindows

void dvxTileWindows(AppContextT *ctx);

Arrange visible windows in an NxM grid filling the screen. Windows are never placed off screen: when there are more windows than fit at the minimum window size, the extra tiles overlap.

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxTileWindowsH

void dvxTileWindowsH(AppContextT *ctx);

Tile windows horizontally (side by side, equal width, full height).

  Parameter   Description
  ---------   -----------
  ctx         Application context

dvxTileWindowsV

void dvxTileWindowsV(AppContextT *ctx);

Tile windows vertically (stacked, full width, equal height).

  Parameter   Description
  ---------   -----------
  ctx         Application context

Image I/O

dvxLoadImage

uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch);

Load an image file (BMP, PNG, JPEG, GIF) and convert to the display's native pixel format. A file that is no image but a DVX program, widget, or library with an icon resource gives that icon (dvxResLoadIconData), so every picture that takes a file name can show a program's icon. Caller must free with dvxFreeImage().

  Parameter     Description
  ---------     -----------
  ctx           Application context
  path          Image file path
  outW, outH    Output: image dimensions
  outPitch      Output: row pitch in bytes

Returns: Pixel buffer, or NULL on failure. An image without pixels (a width or height of 0, which some BMP headers declare) is a failure for every image loader.

dvxLoadImageFromMemory

uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch);

Load an image from a memory buffer (typically resource data). Same output format as dvxLoadImage(). Caller must free with dvxFreeImage().

  Parameter     Description
  ---------     -----------
  ctx           Application context
  data          Image data buffer
  dataLen       Buffer size in bytes
  outW, outH    Output: image dimensions
  outPitch      Output: row pitch in bytes

Returns: Pixel buffer, or NULL on failure.

dvxLoadImageAlpha

uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor);

Load an image from memory with alpha transparency. Pixels with alpha < 128 are replaced with a packed magenta key color. If any transparent pixels are found, *outHasAlpha is set to true and *outKeyColor receives the packed key color for use with rectCopyTransparent. Caller must free with dvxFreeImage.

  Parameter     Description
  ---------     -----------
  ctx           Application context
  data          Image data buffer
  dataLen       Buffer size in bytes
  outW, outH    Output: image dimensions
  outPitch      Output: row pitch
  outHasAlpha   Output: true if any transparent pixels found
  outKeyColor   Output: packed key color (valid when outHasAlpha is true)

Returns: Pixel buffer, or NULL on failure.

dvxFreeImage

void dvxFreeImage(uint8_t *data);

Free a pixel buffer returned by dvxLoadImage() or dvxLoadImageFromMemory().

  Parameter   Description
  ---------   -----------
  data        Buffer to free

dvxImageInfo

bool dvxImageInfo(const char *path, int32_t *outW, int32_t *outH);

Query image dimensions without decoding the full file.

  Parameter     Description
  ---------     -----------
  path          Image file path
  outW, outH    Output: image dimensions

Returns: true on success; false for an unreadable image or one without pixels.

dvxSaveImage

int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data, int32_t w, int32_t h, int32_t pitch, const char *path);

Save native-format pixel data to an image file: BMP when path ends in .bmp (case-insensitive), PNG otherwise.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  data        Pixel data in display native format
  w, h        Image dimensions
  pitch       Row pitch in bytes
  path        Output file path

Returns: 0 on success, -1 on failure.

Screenshots

dvxScreenshot

int32_t dvxScreenshot(AppContextT *ctx, const char *path);

Save the entire screen (backbuffer contents) to an image file (BMP for a .bmp path, PNG otherwise). Converts from native pixel format to RGB (15/16-bit channels are expanded to full intensity). Shows the busy cursor while writing and restores the caller's busy state afterwards.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  path        Output file path

Returns: 0 on success, -1 on failure.

dvxWindowScreenshot

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

Save a window's content to an image file (BMP for a .bmp path, PNG otherwise). A repaint still pending from wgtInvalidate is run first, so the image shows the current state. Restores the caller's busy state afterwards.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window
  path        Output file path

Returns: 0 on success, -1 on failure.

Clipboard

dvxClipboardClear

void dvxClipboardClear(void);

Empty the clipboard; dvxClipboardGet then returns NULL.

dvxClipboardCopy

void dvxClipboardCopy(const char *text, int32_t len);

Copy text to the process-wide clipboard buffer. Simple static buffer (not inter-process). Copying nothing (NULL text or a len of 0 or less) empties the clipboard, like dvxClipboardClear.

  Parameter   Description
  ---------   -----------
  text        Text to copy
  len         Length in bytes

dvxClipboardGet

const char *dvxClipboardGet(int32_t *outLen);

Retrieve the current clipboard contents. Returns a pointer to the internal buffer (valid until the next dvxClipboardCopy), or NULL if empty.

  Parameter   Description
  ---------   -----------
  outLen      Output: length of clipboard text

Returns: Clipboard text, or NULL.

Drag Feedback

For programs that implement drag and drop: an outline or a picture that follows the mouse over every window (drawn under the cursor) until it is hidden. One is shown at a time; showing another replaces it. The program still tracks the mouse and decides where a drop lands; this only draws.

dvxDragOutlineShow

void dvxDragOutlineShow(AppContextT *ctx, int32_t w, int32_t h, int32_t offX, int32_t offY);

Show a w x h dotted outline (two-tone, so it shows on any background) whose top-left corner stays (offX, offY) from the mouse pointer -- typically the dragged object's size and its offset from where it was grabbed.

dvxDragImageShow

void dvxDragImageShow(AppContextT *ctx, const uint8_t *pixels, int32_t w, int32_t h, int32_t pitch, bool hasKey, uint32_t keyColor);

Show a picture centered on the mouse pointer. pixels are in the display's native format, as dvxLoadImage or dvxLoadImageAlpha return them, and must stay valid until the feedback is hidden or replaced. With hasKey, pixels of keyColor are transparent (pass dvxLoadImageAlpha's outputs).

dvxDragFeedbackHide

void dvxDragFeedbackHide(AppContextT *ctx);

Remove the outline or picture. The usual cursor shapes come back (see dvxDragCursorSet).

dvxDragCursorSet

void dvxDragCursorSet(AppContextT *ctx, int32_t cursorId);

Choose the mouse cursor shown while the outline or picture is up: CURSOR_NO_DROP where a drop cannot land, CURSOR_ARROW (the default) where it can. The program calls it as the mouse moves, since only it knows its drop targets. While drag feedback is shown this cursor replaces the usual shapes (resize arrows, a widget's own cursor); hiding the feedback puts them back and resets the choice to CURSOR_ARROW. Values outside the cursor table are ignored.

Resource Loading

dvxResLoadIcon

uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch);

Load an icon/image resource from a DXE file and decode to native pixel format. Caller must free with dvxFreeImage().

  Parameter     Description
  ---------     -----------
  ctx           Application context
  dxePath       Path to DXE file
  resName       Resource name within the DXE
  outW, outH    Output: image dimensions
  outPitch      Output: row pitch

Returns: Pixel buffer, or NULL if not found.

dvxResLoadIconData

void *dvxResLoadIconData(const char *dxePath, uint32_t *outSize);

The icon of a DVX program, widget, or library file as the bytes of its image file: its DVX_RES_NAME_ICON resource ("icon32", a program's icon), else DVX_RES_NAME_WIDGET_ICON ("icon24", a widget's toolbox icon). Decode it with dvxLoadImageFromMemory or dvxLoadImageAlpha. Caller must free() the buffer.

  Parameter     Description
  ---------     -----------
  dxePath       Path to the program, widget, or library file
  outSize       Output: size of the data in bytes

Returns: The data, or NULL when the file has neither resource (or is no DVX file).

dvxResLoadText

bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t bufSize);

Load a text resource from a DXE file into a caller-provided buffer. Null-terminated and truncated to fit bufSize.

  Parameter   Description
  ---------   -----------
  dxePath     Path to DXE file
  resName     Resource name
  buf         Output buffer
  bufSize     Buffer capacity

Returns: true on success.

dvxResLoadData

void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize);

Load a raw binary resource from a DXE file. Returns a malloc'd buffer that the caller must free.

  Parameter   Description
  ---------   -----------
  dxePath     Path to DXE file
  resName     Resource name
  outSize     Output: data size in bytes

Returns: Data buffer, or NULL if not found.

Utilities

dvxTextHash

uint32_t dvxTextHash(const char *text);

Compute a djb2-xor hash for cheap dirty detection. Compare at save time with the current hash to detect changes without a shadow copy. Not cryptographic.

  Parameter   Description
  ---------   -----------
  text        Null-terminated string to hash

Returns: 32-bit hash value.

dvxDrv.h -- Driver Modules

One model for the hardware drivers of every device class (sound, joysticks, ...). A driver is a module of its own, a file <name>.drv in its class's folder under bin/libs (built like a library, but the loader does not load it). The library of the device class loads its drivers only when a program first needs that kind of device, probes them, keeps the ones it uses, and unloads the rest, so drivers for hardware that is not there, or for a device class nobody uses, never stay in memory. Nothing lists drivers: adding one is dropping its file into the folder.

Each driver exports one descriptor under a name its class chooses (audioDriver, joyDriver, ...). The descriptor is the class's own struct and starts with a DvxDriverHeaderT, written with DVX_DRIVER_HDR (the classes wrap it in their own macro, such as AUDIO_DRIVER or JOY_DRIVER).

Types

  Type               Members
  ----               -------
  DvxDriverHeaderT   abiVersion (DVX_ABI_VERSION the driver was built for), size (sizeof the whole descriptor), name (short name, lower case, no blanks; DVX.INI settings use it), priority (higher is tried first).
  DvxDriverModT      handle (NULL once kept out of the list or unloaded), desc (the descriptor), kind (index of the descriptor symbol found).
  DvxDriverClassT    symbols (descriptor symbol of each kind, as dlsym wants it: DVX_SYM("name")), minSizes (smallest descriptor accepted per kind), kinds, valid (the class's own check of a descriptor, such as its functions being present; NULL for none).

DVX_DRIVER_HDR(type, name, priority) fills the header: { DVX_ABI_VERSION, sizeof(type), name, priority }.

Functions

dvxDriversLoad

DvxDriverModT *dvxDriversLoad(const char *dir, const DvxDriverClassT *cls);

Loads every *.drv in dir (relative to the DVX directory), looks each descriptor up, and checks it: the ABI version, the size, a name, and the class's valid function. The valid ones come back as an stb_ds array sorted by priority (highest first, by name on a tie); a module that does not load, has no valid descriptor, or repeats a name a higher priority driver already has is logged to DVX.LOG and unloaded. NULL when there are none.

dvxDriverKeep

DvxDriverModT dvxDriverKeep(DvxDriverModT *mods, int32_t index);

Takes mods[index] out of the list (so dvxDriversFree leaves it loaded) and returns it; unload it with dvxDriverUnload when done.

dvxDriversFree

void dvxDriversFree(DvxDriverModT *mods);

Unloads every module still in the list and frees the array.

dvxDriverUnload

void dvxDriverUnload(DvxDriverModT *mod);

Unloads one module (nothing when it is not loaded) and clears its handle and descriptor.

Writing a Device Class

A library that has drivers puts them in drivers/<name>/ in its source folder; its Makefile sets OBJDIR and DRVDIR and writes $(foreach d,$(DVX_DRIVERS),$(eval $(call DVX_DRIVER_RULE,$d))) (common.mk), which builds each folder into $(DRVDIR)/<name>.drv. The library defines its descriptor struct (starting with DvxDriverHeaderT), a macro that writes it, and a DvxDriverClassT; it calls dvxDriversLoad when it first needs devices, dvxDriverKeep for the drivers it uses, and dvxDriversFree for the rest. Driver code resolves the library's functions when it loads, since the libraries are loaded for everyone to use.

dvxWgt.h -- Widget System

Retained-mode widget toolkit layered on the DVX window manager. Widgets form a tree (parent-child) rooted at a per-window VBox container. Layout is automatic: measure minimum sizes bottom-up, then allocate space top-down with flexbox-like weighted distribution. Widget types are registered dynamically at runtime via DXE plugins.

WidgetT Structure

Core widget structure. Generic across all widget types; type-specific data lives in the void *data pointer managed by each widget's DXE.

  Field                                       Description
  -----                                       -----------
  int32_t type                                Widget type ID (assigned by wgtRegisterClass)
  const WidgetClassT *wclass                  Vtable for this widget type
  char name[MAX_WIDGET_NAME]                  Widget name for lookup via wgtFind
  parent, firstChild, lastChild, nextSibling  Tree linkage pointers
  WindowT *window                             Owning window
  int32_t x, y, w, h                         Computed geometry (relative to content area)
  int32_t calcMinW, calcMinH                  Computed minimum size (from layout pass)
  int32_t minW, minH, maxW, maxH              Size hints (tagged sizes): the least and the most the layout gives the widget
  int32_t weight                              Extra-space distribution weight (0=fixed, 100=normal)
  WidgetAlignE align                          Main-axis alignment for children
  int32_t spacing, padding                    Tagged sizes for child spacing and padding
  uint32_t fgColor, bgColor                   Custom colors (0 = use scheme defaults)
  BitmapFontT *font                           The widget's own font (wgtSetFont); NULL = its container's (wgtFontFor)
  bool visible, enabled, readOnly             State flags
  bool swallowTab                             Tab key goes to widget, not focus navigation
  bool paintDirty                             Needs repaint (set by wgtInvalidatePaint)
  bool childDirty                             A descendant needs repaint (for WCLASS_PAINTS_CHILDREN)
  bool pressed                                WCLASS_PRESS_RELEASE: set while button is pressed
  char accelKey                               Accelerator character (0 = none)
  int32_t contentOffX, contentOffY            Content offset for mouse event coordinates
  void *userData, *data                       Application data and widget-private data
  const char *tooltip                         Tooltip text (NULL = none)
  MenuT *contextMenu                          Right-click menu (NULL = none)
  uint32_t serial                             Unique per allocation, never reused while the process runs (tells "the same widget" from a new widget at a recycled address)
  RectT paintClip                             Display clip when last painted (content-buffer coords, w = 0 until first paint); used by mouse dispatch inside clipping containers
  onEvent(w, name, args, argc)                Custom (named) events: every event wgtFireEvent fires that has no universal callback below (see Custom Events)

Universal Callbacks:

  Callback                                           Description
  --------                                           -----------
  onClick(WidgetT *w)                                Widget clicked
  onDblClick(WidgetT *w)                             Widget double-clicked
  onChange(WidgetT *w)                               Value changed
  onFocus(WidgetT *w)                                Widget gained focus
  onBlur(WidgetT *w)                                 Widget lost focus
  onKeyPress(WidgetT *w, int32_t keyAscii)           ASCII key press
  onKeyDown(WidgetT *w, int32_t keyCode, int32_t shift)  Key down
  onKeyUp(WidgetT *w, int32_t keyCode, int32_t shift)    Key up
  onMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y)  Mouse button pressed
  onMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y)    Mouse button released
  onMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y)  Mouse moved
  onScroll(WidgetT *w, int32_t delta)                Mouse wheel
  onValidate(WidgetT *w)                             Return false to cancel a write

The universal callbacks belong to the widget's host (the application or the form runtime). A widget class sees the same input earlier through its own class methods: WGT_METHOD_ON_MOUSE_EVENT, WGT_METHOD_ON_KEY, WGT_METHOD_ON_KEY_UP, WGT_METHOD_ON_FOCUS, WGT_METHOD_ON_BLUR, and WGT_METHOD_ON_WHEEL run before the matching callback (see Method IDs).

Size Specification Macros

  Macro            Description
  -----            -----------
  wgtPixels(v)     Size in pixels
  wgtChars(v)      Size in character widths (multiplied by charWidth at layout)
  wgtPercent(v)    Size as percentage of parent dimension

Widget Class Flags

  Flag                        Description
  ----                        -----------
  WCLASS_FOCUSABLE            Can receive keyboard focus (Tab navigation)
  WCLASS_HORIZ_CONTAINER      Lays out children horizontally (vs. vertical)
  WCLASS_PAINTS_CHILDREN      Widget handles child rendering itself
  WCLASS_NO_HIT_RECURSE       Class hit testing stops here (the container's ON_MOUSE gets the press); universal callbacks still reach visible children of a non-focusable container
  WCLASS_FOCUS_FORWARD        Accel hit forwards focus to next focusable sibling
  WCLASS_HAS_POPUP            Has dropdown popup overlay
  WCLASS_SCROLLABLE           Mouse wheel sends Up/Down arrow keys
  WCLASS_SCROLL_CONTAINER     Scroll container (ScrollPane)
  WCLASS_NEEDS_POLL           Needs periodic polling
  WCLASS_SWALLOWS_TAB         Tab key goes to widget, not focus navigation
  WCLASS_RELAYOUT_ON_SCROLL   Full relayout on scrollbar drag
  WCLASS_PRESS_RELEASE        Click = press + release (Button, ImageButton)
  WCLASS_ACCEL_WHEN_HIDDEN    Accelerator matching works even when invisible
  WCLASS_HAS_TEXT             The first field of w->data is the widget's own text (strdup / free)
  WCLASS_RAISES_EVENTS        The class fires the universal mouse, click, key, and scroll callbacks itself (wgtFireEvent); the dispatcher does not call them for its widgets. Its class methods still get every event. A BASIC UserControl has it: its host sees only what the control's code raises. GotFocus / LostFocus still reach onFocus / onBlur.
  WCLASS_FIXED_FONT           The class edits text by character columns (TextBox, TextArea, Terminal, ComboBox, Spinner): given a proportional font it draws with the system font instead (wgtFontFor); a fixed-pitch font of any size is used.

Window Integration

wgtInitWindow

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

Initialize the widget system for a window. Creates a root VBox container that fills the content area, and installs callback handlers (onPaint, onMouse, onKey, onResize) for widget-based event dispatch. The root widget's userData is set to the AppContextT pointer (the window's userData is left for the application). Calling it again on a window that already has a widget root returns that root.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  win         Window to initialize

Returns: Root VBox widget (add children to this).

Widget Operations

wgtGetContext

AppContextT *wgtGetContext(const WidgetT *w);

Retrieve the AppContextT stored in the root widget's userData: in O(1) through the widget's window when it is attached, otherwise by walking up to the root of its tree. Lets any widget access the full application context.

  Parameter   Description
  ---------   -----------
  w           Any widget in the tree

Returns: Pointer to the AppContextT.

wgtInvalidate

void wgtInvalidate(WidgetT *w);

Re-measure and re-lay out the widget's whole window immediately (adding or removing window scrollbars as needed, so widget geometry can be read right away) and schedule a full repaint. The paint happens once per frame in the deferred paint flush of dvxUpdate, so several invalidations in one event handler cost one paint. Use after structural changes (adding/removing children, text changes that affect size).

  Parameter   Description
  ---------   -----------
  w           Widget to invalidate

wgtInvalidatePaint

void wgtInvalidatePaint(WidgetT *w);

Mark a widget as needing repaint only, without re-layout. Use for visual-only changes (checkbox toggle, selection highlight, cursor blink).

  Parameter   Description
  ---------   -----------
  w           Widget to repaint

wgtSetText

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

Set widget text content (dispatches to the widget class's SET_TEXT handler).

  Parameter   Description
  ---------   -----------
  w           Widget
  text        New text

wgtGetText

const char *wgtGetText(const WidgetT *w);

Get the widget's current text content.

  Parameter   Description
  ---------   -----------
  w           Widget

Returns: Text string (empty string if no handler).

wgtSetFont / wgtFont / wgtFontFor

bool               wgtSetFont(WidgetT *w, const char *family, int32_t size, uint32_t style);
const BitmapFontT *wgtFont(const WidgetT *w);
const BitmapFontT *wgtFontFor(const WidgetT *w, const BitmapFontT *inherited);

wgtSetFont gives w the font of family at the size nearest size in style (dvxFontOpen, dvxText.h: FONT_STYLE_BOLD, ITALIC, UNDERLINE, STRIKE); the widgets inside w draw with it too unless they have their own -- a form's font is its root widget's. A NULL family gives the font up, so w uses its container's again. The window is laid out again. Returns false when out of memory (w keeps the font it had). The font is closed when w is destroyed.

wgtFontFor is the font w draws with: its own, else the nearest container's up the parent chain, else inherited (the font passed down: the application's, or NULL for a metrics query that needs none). A class with WCLASS_FIXED_FONT given a proportional font gets the system font. The class dispatch helpers (wclsPaint, wclsPaintOverlay, wclsCalcMinSize, wclsLayout, wclsGetLayoutMetrics, wclsGetPopupRect) hand every method this font, so a widget's paint, measure, and layout methods simply use their font parameter. Code that is given no font -- mouse and key handlers, helpers -- calls wgtFont(w), the same with the application's font. Size hints in characters (wgtChars) count the widget's own font.

  Parameter   Description
  ---------   -----------
  w           Widget
  family      Family name (NULL: inherit again)
  size        Pixel size wanted
  style       FONT_STYLE_* bits
  inherited   The font the caller would otherwise use

wgtSetEnabled

void wgtSetEnabled(WidgetT *w, bool enabled);

Enable or disable a widget. Disabled widgets are grayed out and do not receive input; disabling a container also blocks mouse and keyboard input to every widget inside it (see widgetIsEnabledChain). Setting the current value is a no-op.

  Parameter   Description
  ---------   -----------
  w           Widget
  enabled     true = enabled, false = disabled

wgtSetReadOnly

void wgtSetReadOnly(WidgetT *w, bool readOnly);

Set read-only mode. Allows scrolling and selection but blocks editing.

  Parameter   Description
  ---------   -----------
  w           Widget
  readOnly    true = read-only

wgtSetFocused

void wgtSetFocused(WidgetT *w);

Set keyboard focus to a widget.

  Parameter   Description
  ---------   -----------
  w           Widget to focus

wgtGetFocused

WidgetT *wgtGetFocused(void);

Get the currently focused widget.

Returns: Focused widget, or NULL.

wgtSetVisible

void wgtSetVisible(WidgetT *w, bool visible);

Show or hide a widget. Hiding a subtree that holds keyboard focus drops the focus. Setting the current value is a no-op.

  Parameter   Description
  ---------   -----------
  w           Widget
  visible     true = visible, false = hidden

wgtSetName

void wgtSetName(WidgetT *w, const char *name);

Set a widget's name for lookup via wgtFind().

  Parameter   Description
  ---------   -----------
  w           Widget
  name        Name string (max MAX_WIDGET_NAME chars)

wgtFind

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

Find a widget by name. Searches the subtree rooted at root.

  Parameter   Description
  ---------   -----------
  root        Root of subtree to search
  name        Widget name to find

Returns: Matching widget, or NULL.

wgtDestroy

void wgtDestroy(WidgetT *w);

Destroy a widget and all its children. Removes from parent's child list.

  Parameter   Description
  ---------   -----------
  w           Widget to destroy

wgtSetTooltip

void wgtSetTooltip(WidgetT *w, const char *text);

Set tooltip text for a widget. Pass NULL to remove. Caller owns the string and it must outlive the widget.

  Parameter   Description
  ---------   -----------
  w           Widget
  text        Tooltip text, or NULL

widgetOnResize

void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);

Default window resize handler installed by wgtInitWindow(). Re-evaluates scrollbars and relayouts the widget tree. Call from custom onResize handlers to chain to the widget system.

  Parameter    Description
  ---------    -----------
  win          Window being resized
  newW, newH   New content dimensions

Layout

wgtResolveSize

int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth);

Decode a tagged size value (WGT_SIZE_PIXELS/CHARS/PERCENT) into a concrete pixel count. Returns 0 for a raw 0 input (meaning "auto").

  Parameter     Description
  ---------     -----------
  taggedSize    Tagged size value
  parentSize    Parent dimension (for PERCENT mode)
  charWidth     Font character width (for CHARS mode)

Returns: Size in pixels.

wgtGetPaddingPx / wgtSetPaddingPx / wgtGetSpacingPx / wgtSetSpacingPx

int32_t wgtGetPaddingPx(const WidgetT *w);
void    wgtSetPaddingPx(WidgetT *w, int32_t pixels);
int32_t wgtGetSpacingPx(const WidgetT *w);
void    wgtSetSpacingPx(WidgetT *w, int32_t pixels);

A container's padding (space inside its edge) and spacing (gap between children) in pixels. The getters return what the layout uses: the container's default while the field is WGT_GAP_DEFAULT (every widget starts so), and 0 for a size given in another unit. The setters clamp to 0..WGT_GAP_PIXELS_MAX (0 is no gap) and re-lay out the window. The BASIC Padding and Spacing properties of VBox, HBox, Frame, and ScrollPane use these.

wgtLayout

void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT *font);

Execute the full two-pass layout algorithm on a tree at an explicit size. Pass 1 (bottom-up): compute minimum sizes; a cap (maxW/maxH) wins over a larger minimum, so a widget whose content asks for more than its cap is given its cap and the rest of the space goes to its siblings. Pass 2 (top-down): allocate space with weighted distribution; a weighted child capped by maxW/maxH stops at its cap and the space it cannot take goes to the other weighted children. Window-managed trees are laid out by wgtInvalidate and the paint path; this is for laying out a tree on demand.

  Parameter     Description
  ---------     -----------
  root          Root widget
  availW/H      Available space
  font          Bitmap font (for character-based sizing)

wgtPaint

void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint);

Paint the widget tree by depth-first traversal (every widget, or only dirty ones) using the display's current clip rect. Popup overlays are not painted here; the window paint handler paints them afterwards so they are on top.

  Parameter     Description
  ---------     -----------
  root          Root widget
  d             Display context
  ops           Blit operations vtable
  font          Bitmap font
  colors        Color scheme
  fullRepaint   true = repaint every widget; false = only paintDirty widgets

Debug

wgtSetDebugLayout

void wgtSetDebugLayout(AppContextT *ctx, bool enabled);

Draw colored borders around layout containers for debugging.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  enabled     true = draw debug borders

wgtGetDebugLayout

bool wgtGetDebugLayout(void);

Returns true while the debug borders are drawn (the last value given to wgtSetDebugLayout; false at start).

Dynamic Widget Registration

wgtRegisterClass

int32_t wgtRegisterClass(const WidgetClassT *wclass);

Register a new widget class at runtime. Stores the class in the first free widgetClassTable slot (or appends one). The WidgetClassT must remain valid until wgtUnregisterClass is called for it (typically static const in a DXE). A class whose version is not WGT_CLASS_VERSION is rejected (logged, returns -1).

  Parameter   Description
  ---------   -----------
  wclass      Widget class vtable to register

Returns: Assigned type ID, or -1 if wclass is NULL or its version does not match.

wgtUnregisterClass

void wgtUnregisterClass(int32_t classId);

Release a class registered with wgtRegisterClass, typically from an app's shutdown before its module is unloaded. Every widget of the class must already be destroyed. The slot is cleared: creating a widget with the freed ID fails (returns NULL), and a later wgtRegisterClass may reuse the ID. Out-of-range or already-free IDs are ignored.

  Parameter   Description
  ---------   -----------
  classId     Type ID returned by wgtRegisterClass

wgtUnregisterIface

void wgtUnregisterIface(const char *name);

Remove the interface registered under name (wgtRegisterIface). A widget class made at run time -- a BASIC program's own UserControl -- leaves the registry this way when its owner is done with it, so the name can be registered again. Nothing happens when no interface has the name.

wgtUnregisterAppClasses

void wgtUnregisterAppClasses(int32_t appId);

Release every class, API (wgtRegisterApi), and interface (wgtRegisterIface) registered while appId was the current app (AppContextT.currentAppId at registration time), since all of them point into the app's module. The shell calls it whenever it unloads an app (normal exit or End Task), once the app's windows and widgets are gone. appId 0 (boot-time widget modules) is ignored.

  Parameter   Description
  ---------   -----------
  appId       App whose classes are released

wgtRegisterApi

void wgtRegisterApi(const char *name, const void *api);

Register a widget API struct under a name. Each widget DXE registers its API during initialization. Callers retrieve it via wgtGetApi() and cast to the widget-specific type.

  Parameter   Description
  ---------   -----------
  name        Widget type name (e.g. "button", "listbox")
  api         Pointer to the widget's API struct

Every API struct starts with a WgtApiHeaderT member named header, which the provider fills with WGT_API_HEADER(its API type):

typedef struct {
    WgtApiHeaderT header;
    WidgetT *(*create)(WidgetT *parent, const char *text);
} ButtonApiT;

static const ButtonApiT sApi = {
    .header = WGT_API_HEADER(ButtonApiT),
    .create = wgtButton
};
  Type / Macro              Description
  ------------              -----------
  WgtApiHeaderT             { uint32_t abiVersion; uint32_t size; }: the DVX_ABI_VERSION the provider was built with and the sizeof its API struct.
  WGT_API_HEADER(apiType)   Initializer for the header member: { DVX_ABI_VERSION, sizeof(apiType) }.
  WgtApiPrefixT             { WgtApiHeaderT header; WgtApiFnT create; }: the part every API struct shares. Generic callers that do not know the widget's own API type use it.
Warning: The create function must be the first field after the header. wgtCreateByName (which the form runtime and the designer use) instantiates widgets through WgtApiPrefixT.create, cast to the signature WgtIfaceT.createSig names, so whichever field comes first is what gets invoked. If the struct has multiple constructor variants, the one matching createSig must come first; put helper constructors, index accessors, and other entry points after it.

wgtGetApi

const void *wgtGetApi(const char *name, uint32_t minSize);

Retrieve a registered widget API struct by name, checked against the caller's view of it. A struct built for another DVX_ABI_VERSION, or smaller than minSize (an older, shorter table), is refused and the refusal logged.

  Parameter   Description
  ---------   -----------
  name        Widget type name
  minSize     sizeof the API type the caller will use: sizeof(ButtonApiT) for a typed accessor, sizeof(WgtApiPrefixT) when only create is called

Returns: Pointer to the API struct, or NULL if not found or refused. Per-widget headers wrap the call in a typed accessor (dvxButtonApi() and friends).

Widget Interface Descriptors

wgtBasicColor

static inline uint32_t wgtBasicColor(uint32_t color);

Converts a colour between DVX's 0x00RRGGBB layout (WidgetT fgColor / bgColor, packColor's channels, the colour dialog) and DVX BASIC's Visual Basic layout &HBBGGRR (RGB(), QBColor, ForeColor / BackColor, drawing-method colour arguments) by swapping the red and blue bytes; the top byte is kept, so the function is its own inverse. A widget's BASIC methods call it once where a BASIC colour comes in and once where one goes back out.

  Parameter   Description
  ---------   -----------
  color       A colour in either layout

Returns: The colour in the other layout.

wgtRegisterIface

void wgtRegisterIface(const char *name, const WgtIfaceT *iface);

Register an interface descriptor for a widget type. Used by the BASIC form runtime and IDE for generic property/method dispatch.

  Parameter   Description
  ---------   -----------
  name        Widget type name
  iface       Interface descriptor

wgtGetIface

const WgtIfaceT *wgtGetIface(const char *name);

Retrieve an interface descriptor by widget type name.

  Parameter   Description
  ---------   -----------
  name        Widget type name

Returns: Interface descriptor, or NULL.

wgtFindByBasName

const char *wgtFindByBasName(const char *basName);

Find a widget type name by its VB-style name (e.g. "CommandButton" -> "button"). Case-insensitive search.

  Parameter   Description
  ---------   -----------
  basName     VB-style widget name

Returns: Internal type name, or NULL.

wgtIfaceCount

int32_t wgtIfaceCount(void);

Return the number of registered widget interfaces.

Returns: Count of registered interfaces.

wgtIfaceAt

const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName);

Get a registered widget interface by index.

  Parameter   Description
  ---------   -----------
  idx         Index (0-based)
  outName     Output: type name

Returns: Interface descriptor.

wgtIfaceGetPath

const char *wgtIfaceGetPath(const char *name);

Get the .wgt DXE file path for a registered widget.

  Parameter   Description
  ---------   -----------
  name        Widget type name

Returns: File path string.

wgtIfaceSetPath

void wgtIfaceSetPath(const char *name, const char *path);

Set the .wgt DXE file path for a registered widget (called by the loader).

  Parameter   Description
  ---------   -----------
  name        Widget type name
  path        DXE file path

wgtIfaceGetPathIndex

int32_t wgtIfaceGetPathIndex(const char *name);

Get the 1-based index of this widget within its .wgt file. Used to construct suffixed resource names (e.g. "name-2", "icon16-2").

  Parameter   Description
  ---------   -----------
  name        Widget type name

Returns: 1-based index within the DXE file.

Typed Dispatch Helpers

The following inline functions provide type-safe dispatch through the WidgetClassT handler table. Each checks for a non-NULL handler before calling.

  Function                                Method ID                          Description
  --------                                ---------                          -----------
  wclsHas(w, methodId)                    --                                 Check if handler exists
  wclsPaint(w, d, ops, font, colors)      WGT_METHOD_PAINT                   Paint the widget
  wclsPaintOverlay(w, d, ops, font, colors) WGT_METHOD_PAINT_OVERLAY         Paint overlay (popups)
  wclsCalcMinSize(w, font)                WGT_METHOD_CALC_MIN_SIZE           Compute minimum size
  wclsLayout(w, font)                     WGT_METHOD_LAYOUT                  Layout children
  wclsGetLayoutMetrics(w, font, ...)      WGT_METHOD_GET_LAYOUT_METRICS      Get pad, gap, extraTop, borderW
  wclsOnMouse(w, root, vx, vy)           WGT_METHOD_ON_MOUSE                Handle mouse event
  wclsOnKey(w, key, mod)                  WGT_METHOD_ON_KEY                  Handle key event
  wclsOnAccelActivate(w, root)            WGT_METHOD_ON_ACCEL_ACTIVATE       Handle accelerator
  wclsDestroy(w)                          WGT_METHOD_DESTROY                 Destroy widget data
  wclsOnChildChanged(parent, child)       WGT_METHOD_ON_CHILD_CHANGED        Notify parent of change
  wclsGetText(w)                          WGT_METHOD_GET_TEXT                Get widget text
  wclsSetText(w, text)                    WGT_METHOD_SET_TEXT                Set widget text
  wclsClearSelection(w)                   WGT_METHOD_CLEAR_SELECTION         Clear text selection
  wclsClosePopup(w)                       WGT_METHOD_CLOSE_POPUP             Close dropdown popup
  wclsGetPopupRect(w, font, ...)          WGT_METHOD_GET_POPUP_RECT          Get popup screen rect
  wclsOnDragUpdate(w, root, x, y)         WGT_METHOD_ON_DRAG_UPDATE          Update during drag
  wclsOnDragEnd(w, root, x, y)            WGT_METHOD_ON_DRAG_END             End drag operation
  wclsGetCursorShape(w, vx, vy)           WGT_METHOD_GET_CURSOR_SHAPE        Get cursor for position
  wclsPoll(w, win)                        WGT_METHOD_POLL                    Periodic polling
  wclsQuickRepaint(w, outY, outH)         WGT_METHOD_QUICK_REPAINT           Fast partial repaint
  wclsScrollChildIntoView(parent, child)  WGT_METHOD_SCROLL_CHILD_INTO_VIEW  Scroll child visible
  wclsOnBlur(w)                           WGT_METHOD_ON_BLUR                 Widget lost keyboard focus
  wclsOnWheel(w, delta)                   WGT_METHOD_ON_WHEEL                Mouse wheel; true = consumed
  wclsOnMouseEvent(w, action, button, x, y, shift)  WGT_METHOD_ON_MOUSE_EVENT  Every press, release, and move (hover too)
  wclsOnKeyUp(w, keyCode, shift)          WGT_METHOD_ON_KEY_UP               Key released on the focused widget
  wclsOnFocus(w)                          WGT_METHOD_ON_FOCUS                Widget gained keyboard focus

The central dispatcher calls WGT_METHOD_ON_MOUSE_EVENT for every widget whose class has it, at exactly the points where it calls the universal mouse callbacks and on the same widget (the one under the cursor; inside a non-focusable WCLASS_NO_HIT_RECURSE container, a child with the method gets them itself, and a container with the method keeps them), and always before the callback. action is a WgtMouseActionE: WgtMouseDownE and WgtMouseUpE carry the 1-based button ordinal (1 left, 2 right, 3 middle), WgtMouseMoveE the MOUSE_* mask of the held buttons (0 is a hover move). x and y are content-relative (contentOffX / contentOffY subtracted), shift is the KEY_MOD_* Shift / Ctrl / Alt state. A class that captures the mouse on a press (sets sDragWidget in its WGT_METHOD_ON_MOUSE handler) also gets the moves while the button is held and the left release, wherever the pointer is. WGT_METHOD_ON_KEY_UP runs before onKeyUp with the same arguments, WGT_METHOD_ON_FOCUS before onFocus (the counterpart of ON_BLUR). If a class method destroys widgets, the callback after it is skipped. Classes without these methods see no change.

The mouse wheel is offered first to an ON_WHEEL handler on the focused widget, then on each ancestor, with delta in notches (positive = down, the user's reverse-wheel setting already applied; the lines-per-notch preference is AppContextT.wheelStep). Returning true consumes the event. Otherwise a WCLASS_SCROLLABLE focused widget (or a scroll-container ancestor) receives Up/Down arrow keys, and failing that the window scrollbar moves. The widget's onScroll callback fires in every case.

Method IDs

Fixed constants used to index into WidgetClassT.handlers[]. Method IDs are stable ABI -- new methods are appended at the next sequential ID, never reordered or reused.

  ID   Symbol                              Signature
  --   ------                              ---------
  0    WGT_METHOD_PAINT                    void (w, d, ops, font, colors)
  1    WGT_METHOD_PAINT_OVERLAY            void (w, d, ops, font, colors)
  2    WGT_METHOD_CALC_MIN_SIZE            void (w, font)
  3    WGT_METHOD_LAYOUT                   void (w, font)
  4    WGT_METHOD_GET_LAYOUT_METRICS       void (w, font, pad, gap, extraTop, borderW)
  5    WGT_METHOD_ON_MOUSE                 void (w, root, vx, vy)
  6    WGT_METHOD_ON_KEY                   void (w, key, mod)
  7    WGT_METHOD_ON_ACCEL_ACTIVATE        void (w, root)
  8    WGT_METHOD_DESTROY                  void (w)
  9    WGT_METHOD_ON_CHILD_CHANGED         void (parent, child)
  10   WGT_METHOD_GET_TEXT                 const char *(w)
  11   WGT_METHOD_SET_TEXT                 void (w, text)
  12   WGT_METHOD_CLEAR_SELECTION          bool (w)
  13   WGT_METHOD_CLOSE_POPUP              void (w)
  14   WGT_METHOD_GET_POPUP_RECT           void (w, font, contentH, popX, popY, popW, popH)
  15   WGT_METHOD_ON_DRAG_UPDATE           void (w, root, x, y)
  16   WGT_METHOD_ON_DRAG_END              void (w, root, x, y)
  17   WGT_METHOD_GET_CURSOR_SHAPE         int32_t (w, vx, vy)
  18   WGT_METHOD_POLL                     void (w, win)
  19   WGT_METHOD_QUICK_REPAINT            int32_t (w, outY, outH)
  20   WGT_METHOD_SCROLL_CHILD_INTO_VIEW   void (parent, child)
  21   WGT_METHOD_ON_BLUR                  void (w)
  22   WGT_METHOD_ON_WHEEL                 bool (w, delta)
  23   WGT_METHOD_ON_MOUSE_EVENT           void (w, action, button, x, y, shift)
  24   WGT_METHOD_ON_KEY_UP                void (w, keyCode, shift)
  25   WGT_METHOD_ON_FOCUS                 void (w)
  --   WGT_METHOD_MAX = 32 (room for future methods)

WGT_CLASS_VERSION is DVX_ABI_VERSION (see Binary Interface Version). The framework rejects WidgetClassT structs whose version does not match.

Widget Class Dispatch Table

WidgetClassT

typedef struct WidgetClassT {
    uint32_t version;
    uint32_t flags;
    void    *handlers[WGT_METHOD_MAX];
} WidgetClassT;
  Field       Description
  -----       -----------
  version     Must equal WGT_CLASS_VERSION at registration
  flags       Bitmask of WCLASS_xxx flags
  handlers    Array of 32 function pointers indexed by WGT_METHOD_xxx

Size Encoding Constants

  Define              Value        Description
  ------              -----        -----------
  WGT_SIZE_TYPE_MASK  0xC0000000   Top 2 bits: unit type selector
  WGT_SIZE_VAL_MASK   0x3FFFFFFF   Bottom 30 bits: numeric value
  WGT_SIZE_PIXELS     0x00000000   Unit tag: pixels
  WGT_SIZE_CHARS      0x40000000   Unit tag: character widths
  WGT_SIZE_PERCENT    0x80000000   Unit tag: percentage of parent
  WGT_GAP_DEFAULT     0xC0000000   padding / spacing: the container's own default (wgtPixels(0) is no gap)
  WGT_WEIGHT_FILL     100          Default "flexible" weight value

Widget Interface Descriptors

WgtPropDescT

Describes a BASIC-exposed property on a widget type. Used by the form runtime and IDE for generic get/set dispatch.

  Field       Description
  -----       -----------
  name        BASIC property name (e.g. "Caption", "Value")
  type        Data type: WGT_IFACE_STRING, WGT_IFACE_INT, WGT_IFACE_BOOL, WGT_IFACE_FLOAT, or WGT_IFACE_ENUM
  getFn       Getter function pointer (NULL if write-only)
  setFn       Setter function pointer (NULL if read-only)
  enumNames   For WGT_IFACE_ENUM: NULL-terminated array of value names

A property is readable when getFn is not NULL and writable when setFn is not NULL -- the one rule for every interface (the form runtime, the designer, and the property sheet all apply it). A dynamic interface (WgtIfaceT.dyn) puts WGT_DYN_ACCESS in getFn / setFn for "present"; they are never called. A FLOAT getter returns float and its setter takes float.

WgtMethodDescT

Describes an invokable method on a widget type.

  Field       Description
  -----       -----------
  name        BASIC method name (e.g. "Clear", "SetFocus")
  sig         Calling convention ID (WGT_SIG_xxx)
  fn          Function pointer

Calling convention IDs (method sig field). IDs are stable ABI; new ones are appended.

  Define                     ID   Handler signature
  ------                     --   -----------------
  WGT_SIG_VOID               0    void fn(WidgetT *)
  WGT_SIG_INT                1    void fn(WidgetT *, int32_t)
  WGT_SIG_BOOL               2    void fn(WidgetT *, bool)
  WGT_SIG_STR                3    void fn(WidgetT *, const char *)
  WGT_SIG_INT_INT            4    void fn(WidgetT *, int32_t, int32_t)
  WGT_SIG_INT_BOOL           5    void fn(WidgetT *, int32_t, bool)
  WGT_SIG_RET_INT            6    int32_t fn(const WidgetT *)
  WGT_SIG_RET_BOOL           7    bool fn(const WidgetT *)
  WGT_SIG_RET_BOOL_INT       8    bool fn(const WidgetT *, int32_t)
  WGT_SIG_INT3               9    void fn(WidgetT *, int32_t, int32_t, int32_t)
  WGT_SIG_INT4               10   void fn(WidgetT *, int32_t, int32_t, int32_t, int32_t)
  WGT_SIG_RET_INT_INT_INT    11   int32_t fn(const WidgetT *, int32_t, int32_t)
  WGT_SIG_INT_INT_STR        12   void fn(WidgetT *, int32_t, int32_t, const char *)
  WGT_SIG_INT5               13   void fn(WidgetT *, int32_t, int32_t, int32_t, int32_t, int32_t)
  WGT_SIG_RET_STR_INT        14   const char *fn(const WidgetT *, int32_t)
  WGT_SIG_RET_STR_INT_INT    15   const char *fn(const WidgetT *, int32_t, int32_t)
  WGT_SIG_STR_BOOL_BOOL      16   bool fn(WidgetT *, const char *, bool, bool)
  WGT_SIG_STR_STR_BOOL       17   int32_t fn(WidgetT *, const char *, const char *, bool)
  WGT_SIG_RET_STR            18   const char *fn(const WidgetT *)
  WGT_SIG_STR_INT            19   void fn(WidgetT *, const char *, int32_t)
  WGT_SIG_INT_STR            20   void fn(WidgetT *, int32_t, const char *)
  WGT_SIG_STR_STR            21   void fn(WidgetT *, const char *, const char *)
  WGT_SIG_STR_BOOL           22   void fn(WidgetT *, const char *, bool)
  WGT_SIG_RET_INT_STR        23   int32_t fn(WidgetT *, const char *) -- status-returning string method (e.g. Load/Save)
  WGT_SIG_STR_STR_BOOL_BOOL  24   bool fn(WidgetT *, const char *, const char *, bool, bool)
  WGT_SIG_RET_INT_INT        25   int32_t fn(const WidgetT *, int32_t)
  WGT_SIG_DYNAMIC            26   bool fn(WidgetT *, const WgtValueT *args, int32_t argc, WgtValueT *ret) (WgtDynMethodFnT): any arguments, as they come

wgtSigInfo describes each signature (WgtSigInfoT: argc, retType, and argTypes[], WGT_SIG_MAX_ARGS = 5; NULL for WGT_SIG_DYNAMIC). wgtIfaceCallMethod is the one place that calls through these signatures.

WgtEventDescT

Describes an event beyond the standard common set (Click, DblClick, Change, GotFocus, LostFocus, the key and mouse events ...). The widget fires it with wgtFireEvent.

  Field       Description
  -----       -----------
  name        Event name (e.g. "ColumnClick", "Timer")
  params      The handler's parameter list as BASIC writes it ("Topic As String", "" for none); NULL when the event is one of the BASIC master list's (Timer, Validate, Reposition) and takes its parameters from there

params is the only place a widget's own event parameters are written down: the IDE's handler stubs and designer.bas CtlEventParams$ read it. The shipped widgets' own events: ListView ColumnClick (Column As Integer, 0-based), TextArea GutterClick (LineNumber As Integer, 1-based), HelpPane LinkClick (Topic As String).

WgtIfaceT

Top-level interface descriptor registered by each widget DXE.

  Field         Description
  -----         -----------
  basName       VB-style name (e.g. "CommandButton"), or NULL
  props         Array of WgtPropDescT
  propCount     Length of props array
  methods       Array of WgtMethodDescT
  methodCount   Length of methods array
  events        Array of WgtEventDescT (extras beyond common set)
  eventCount    Length of events array
  createSig     How to call the widget's create() function (WgtCreateSigE)
  createArgs    Default numeric args for design-time instantiation
  isContainer   true = widget can hold child widgets
  defaultEvent  Default event name for form designer
  namePrefix    Auto-name prefix (NULL = derive from basName)
  dyn           Dynamic interface hooks (WgtDynIfaceT), NULL for a widget written in C

WgtDynIfaceT

typedef struct {
    WidgetT *(*create)(WidgetT *parent, const WgtIfaceT *iface);
    bool     (*getProp)(WidgetT *w, int32_t propIdx, WgtValueT *out);
    bool     (*setProp)(WidgetT *w, int32_t propIdx, const WgtValueT *value);
    bool     (*callMethod)(WidgetT *w, int32_t methodIdx, const WgtValueT *args, int32_t argc, WgtValueT *ret);
    void      *ctx;
} WgtDynIfaceT;

A widget class whose properties and methods are not C functions with fixed signatures (a BASIC UserControl, a scripted widget) sets WgtIfaceT.dyn. The generic access functions then call these hooks instead of the descriptors' getFn / setFn / fn: create makes a widget for wgtCreateByName, propIdx and methodIdx index the interface's props[] and methods[]. Each hook returns false when it could not do the job. A value read is converted to the property's type; a method whose sig is a fixed WGT_SIG_* gets its arguments converted to that signature and its result converted to the signature's type, a WGT_SIG_DYNAMIC method gets them as they come. Strings returned follow the WgtValueT lifetime rule. ctx is the provider's own pointer.

Generic Values

WgtValueT

typedef struct {
    uint8_t type;   // WGT_IFACE_*
    union {
        int32_t     i;   // INT, BOOL (0 / 1), ENUM
        double      f;   // FLOAT, DOUBLE
        const char *s;   // STRING
    };
} WgtValueT;

One property value, method argument or result, or event argument. WGT_IFACE_NONE (5) is "no value" (a void method's result). A STRING value never owns its text: an argument's text belongs to the caller for the duration of the call; a returned one (a property read, a method result) belongs to the widget and stays valid until the next call on that widget or until the widget changes or is destroyed -- copy it to keep it. wgtValueInt, wgtValueBool, wgtValueFloat, wgtValueDouble, and wgtValueStr (NULL becomes "") build values. FLOAT is BASIC's Single, DOUBLE its Double: a DOUBLE property or argument keeps every digit (a value through FLOAT is rounded to a float by a static widget's getter and setter, and the form runtime shows it as a Single); as text a DOUBLE has 15 significant digits.

wgtValueToInt / wgtValueToBool / wgtValueToFloat / wgtValueToText

int32_t     wgtValueToInt(const WgtValueT *v);
bool        wgtValueToBool(const WgtValueT *v);
double      wgtValueToFloat(const WgtValueT *v);
const char *wgtValueToText(const WgtValueT *v, char *buf, int32_t bufSize);

Convert any value: numbers convert to each other (a FLOAT truncates toward zero and is clamped to the int32_t range), a BOOL is 0 / 1, a STRING is parsed (decimal, strtod, wgtParseBool), NONE is 0 / false / "". wgtValueToText returns the value's own text for a STRING, otherwise formats into buf (INT / ENUM decimal, BOOL "True" / "False", FLOAT %g); WGT_VALUE_TEXT_MAX (32) bytes hold any non-STRING value.

wgtParseBool

bool wgtParseBool(const char *text, bool defaultValue);

The .frm and property-sheet boolean rule: "True" or "-1" is true, "False" or "0" false (case-insensitive), anything else (and NULL) defaultValue.

Generic Widget Access

One implementation of "create, read, write, and call by name" for every widget, static or dynamic. The form runtime, the IDE designer, and C programs all use it; nothing else switches on WGT_SIG_* or WgtCreateSigE.

wgtIfaceSetType / wgtGetIfaceOf

void             wgtIfaceSetType(const char *name, int32_t typeId);
const WgtIfaceT *wgtGetIfaceOf(const WidgetT *w, const char **outName);

wgtIfaceSetType binds the interface registered under name to the widget class typeId (from wgtRegisterClass); every shipped widget calls it in wgtRegister after wgtRegisterIface. wgtGetIfaceOf then finds the interface of any widget of that class (NULL when none is bound); outName (may be NULL) receives the registered type name. wgtUnregisterClass drops the binding.

wgtResolveTypeName

const char *wgtResolveTypeName(const char *name);

The registered type name for a BASIC name ("CommandButton" -> "button", tried first) or for a type name that has an API or an interface; NULL when neither is registered.

wgtIfaceFindProp / wgtIfaceFindMethod / wgtIfaceFindEvent

const WgtPropDescT   *wgtIfaceFindProp(const WgtIfaceT *iface, const char *propName);
const WgtMethodDescT *wgtIfaceFindMethod(const WgtIfaceT *iface, const char *methodName);
const WgtEventDescT  *wgtIfaceFindEvent(const WgtIfaceT *iface, const char *eventName);

Case-insensitive lookups of a descriptor by name (the first match); NULL when iface is NULL or nothing matches.

wgtCreateByName

WidgetT *wgtCreateByName(const char *name, WidgetT *parent);

Create a widget of type name (a BASIC or type name, wgtResolveTypeName) under parent: a dynamic interface's create hook, otherwise the API's create function called by the interface's createSig with its createArgs ("" for a text, an empty picture for WGT_CREATE_PARENT_DATA; a type without an interface is created as fn(parent)). Returns NULL when the type is unknown or creation failed.

wgtGetProp / wgtSetProp / wgtCallMethod

bool wgtGetProp(WidgetT *w, const char *propName, WgtValueT *out);
bool wgtSetProp(WidgetT *w, const char *propName, const WgtValueT *value);
bool wgtCallMethod(WidgetT *w, const char *methodName, const WgtValueT *args, int32_t argc, WgtValueT *ret);

By name on any widget, through its interface (wgtGetIfaceOf). False when the widget has no interface or no such property or method, otherwise as wgtIfaceGetProp / wgtIfaceSetProp / wgtIfaceCallMethod.

WgtValueT v = wgtValueStr("Right");
WgtValueT item = wgtValueStr("one");
WgtValueT count;

wgtSetProp(label, "Alignment", &v);
wgtCallMethod(list, "AddItem", &item, 1, NULL);
wgtCallMethod(list, "ListCount", NULL, 0, &count);

wgtIfaceGetProp / wgtIfaceSetProp

bool wgtIfaceGetProp(const WgtIfaceT *iface, WidgetT *w, const WgtPropDescT *p, WgtValueT *out);
bool wgtIfaceSetProp(const WgtIfaceT *iface, WidgetT *w, const WgtPropDescT *p, const WgtValueT *value);

Read or write property p (from iface's props[]) of w. A read is false when p is write-only or the dynamic hook failed (out is then the zero value of p's type); a STRING read is never NULL. A write converts value to p's type (wgtValueTo*; a STRING for an ENUM property is looked up in enumNames, case-insensitive) and is false when p is read-only, an enum name is unknown, or the dynamic hook refused.

wgtIfaceGetPropText / wgtIfaceSetPropText

bool wgtIfaceGetPropText(const WgtIfaceT *iface, WidgetT *w, const WgtPropDescT *p, char *out, int32_t outSize);
bool wgtIfaceSetPropText(const WgtIfaceT *iface, WidgetT *w, const WgtPropDescT *p, const char *text);

The same through the .frm / property-sheet text form: ENUM by value name, INT decimal, BOOL wgtParseBool (written "True" / "False"), FLOAT strtod / %g, STRING as it is. A read fails for an enum value that has no name.

wgtIfaceCallMethod

bool wgtIfaceCallMethod(const WgtIfaceT *iface, WidgetT *w, const WgtMethodDescT *m, const WgtValueT *args, int32_t argc, WgtValueT *ret);

Call method m with argc arguments, converted to the signature's types (wgtSigInfo); extra arguments are ignored. ret (may be NULL) receives the result, type WGT_IFACE_NONE for a void method. Returns false without calling when fewer arguments than the signature needs were given (ret is then the zero value of the result type), and when a dynamic method failed.

Custom Events

wgtFireEvent

bool wgtFireEvent(WidgetT *w, const char *name, WgtValueT *args, int32_t argc);

Fire event name on w. The universal events go to their WidgetT callback with their arguments taken from args (a missing one is 0): Click -> onClick, DblClick -> onDblClick, Change -> onChange, GotFocus -> onFocus, LostFocus -> onBlur, KeyPress (KeyAscii) -> onKeyPress, KeyDown / KeyUp (KeyCode, Shift) -> onKeyDown / onKeyUp, MouseDown / MouseUp / MouseMove (Button, X, Y, or the VB form Button, Shift, X, Y) -> onMouseDown / onMouseUp / onMouseMove (which take no Shift), Scroll (Delta) -> onScroll (names case-insensitive). Every other name goes to w->onEvent; the universal names never do, so a host that sets both sees each event once. Validate is not mapped (onValidate answers a question; fire it directly). onEvent may write back into args (ByRef parameters; the form runtime writes every ByRef parameter back, a String as text that stays valid until the next event). Returns false when a handler destroyed widgets -- do not touch w then.

ListView (ColumnClick), TextArea (GutterClick), and HelpView (LinkClick) fire their own events this way after their C callbacks (wgtListViewSetHeaderClickCallback, wgtTextAreaSetGutterClick, wgtHelpViewSetLinkCallback), which stay for C programs; the event is skipped when the C callback destroyed widgets. A TextArea's gutter takes clicks when either is set.

Property Data Type Constants

  Define              Value   Description
  ------              -----   -----------
  WGT_IFACE_STRING    0       const char *
  WGT_IFACE_INT       1       int32_t
  WGT_IFACE_BOOL      2       bool
  WGT_IFACE_FLOAT     3       float (WgtValueT: double)
  WGT_IFACE_ENUM      4       int32_t with named values
  WGT_IFACE_NONE      5       WgtValueT only: no value
  WGT_IFACE_DOUBLE    6       double (BASIC Double)

Create Function Signature Constants

  Define                              Value   Form
  ------                              -----   ----
  WGT_CREATE_PARENT                   0       fn(parent)
  WGT_CREATE_PARENT_TEXT              1       fn(parent, const char *text)
  WGT_CREATE_PARENT_INT               2       fn(parent, int32_t)
  WGT_CREATE_PARENT_INT_INT           3       fn(parent, int32_t, int32_t)
  WGT_CREATE_PARENT_INT_INT_INT       4       fn(parent, int32_t, int32_t, int32_t)
  WGT_CREATE_PARENT_INT_BOOL          5       fn(parent, int32_t, bool)
  WGT_CREATE_PARENT_BOOL              6       fn(parent, bool)
  WGT_CREATE_PARENT_DATA              7       fn(parent, data, w, h, pitch) -- not auto-creatable

WGT_MAX_CREATE_ARGS is 3.

dvxDlg.h -- Modal Dialogs

Pre-built modal dialog boxes that block the caller and run their own event loop via dvxUpdate() until the user dismisses them. Modal dialogs set ctx->modalWindow to prevent input from reaching other windows for the dialog's lifetime.

Message Box Flags

Button configurations (low nibble):

  Define             Value    Description
  ------             -----    -----------
  MB_OK              0x0000   OK button only (default)
  MB_OKCANCEL        0x0001   OK and Cancel
  MB_YESNO           0x0002   Yes and No
  MB_YESNOCANCEL     0x0003   Yes, No, and Cancel
  MB_RETRYCANCEL     0x0004   Retry and Cancel

Icon types (high nibble, OR with button flags):

  Define             Value    Description
  ------             -----    -----------
  MB_ICONINFO        0x0010   Information icon
  MB_ICONWARNING     0x0020   Warning icon
  MB_ICONERROR       0x0030   Error icon
  MB_ICONQUESTION    0x0040   Question mark icon

Return values:

  Define             Value   Button pressed
  ------             -----   --------------
  ID_OK              1       OK
  ID_CANCEL          2       Cancel or dialog closed
  ID_YES             3       Yes
  ID_NO              4       No
  ID_RETRY           5       Retry

dvxMessageBox

int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags);

Display a modal message box. Blocks the caller by running dvxUpdate() in a loop until a button is pressed or the dialog is closed. The window is automatically destroyed on return.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Window title (NULL falls back to a generic default)
  message     Body text
  flags       MB_xxx button + icon flags, OR'd together

Returns: ID_xxx of the button that was pressed.

// Example: yes/no confirmation
int32_t r = dvxMessageBox(ctx, "Save", "Save changes before closing?", MB_YESNOCANCEL | MB_ICONQUESTION);
if (r == ID_YES) { saveFile(); }

dvxButtonBox

int32_t dvxButtonBox(AppContextT *ctx, const char *title, const char *message, int32_t iconFlags, const char * const *labels, int32_t count);

A message box with the caller's own buttons, for a question the MB_xxx sets do not cover (for example Yes / No / Yes to All / Cancel). It looks and behaves like dvxMessageBox: Enter presses the first button, and Escape or closing the box chooses the last one, so make the last button the one that changes nothing.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Window title
  message     Body text (word-wrapped; may contain '\n')
  iconFlags   One MB_ICONxxx value, or 0 for none
  labels      Button labels; '&' marks the accelerator
  count       Number of buttons, 1 to DVX_BUTTON_BOX_MAX (6)

Returns: the index in labels of the button chosen, or -1 for bad arguments.

static const char *const labels[] = { "&Retry", "&Skip", "&Cancel" };
int32_t pick = dvxButtonBox(ctx, "Copy", "The disk is full.", MB_ICONERROR, labels, 3);

dvxErrorBox

int32_t dvxErrorBox(AppContextT *ctx, const char *title, const char *message);

Convenience wrapper for MB_OK | MB_ICONERROR. Title defaults to "Error" when NULL.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Dialog title, or NULL for "Error"
  message     Error message

Returns: ID_OK.

dvxInfoBox

int32_t dvxInfoBox(AppContextT *ctx, const char *title, const char *message);

Convenience wrapper for MB_OK | MB_ICONINFO.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Dialog title
  message     Informational message

Returns: ID_OK.

File Dialog

FileFilterT

Describes a filter entry in the file dialog's format dropdown. The glob pattern is extracted from inside the parentheses. Patterns are case-insensitive, may be a semicolon-separated list, and support '*' and '?' anywhere ("readme*", "a*.txt", "*.*" matches every file); blank entries in a list are ignored. A label without parentheses is itself the pattern when it contains a wildcard; otherwise it matches every file.

  Field   Description
  -----   -----------
  label   e.g. "Text Files (*.txt)" or "Images (*.bmp;*.png;*.jpg;*.gif)"

File Dialog Flags

  Define      Value    Description
  ------      -----    -----------
  FD_OPEN     0x0000   File open dialog (default)
  FD_SAVE     0x0001   File save dialog (with overwrite prompt)
  FD_FOLDER   0x0002   Choose a folder, new or existing

dvxFileDialog

bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);

Display a modal file open/save dialog. Shows a directory listing with parent/drive-letter navigation, a drive dropdown, a filename input, and an optional filter dropdown. The path box always shows the current folder, and the drive dropdown (platformDriveList, so listing the drives never touches empty floppy or CD-ROM drives) its drive; choosing a drive goes to its root, and a drive that cannot be read shows "Drive X: is not ready." and leaves the dialog where it was. Typing "D:" or "D:folder" in the path box changes drive/folder; the file name box also accepts a relative or absolute path ("sub\x.txt", "C:\X\A.TXT") and navigates to its folder. A folder that does not exist shows an "Invalid Path" error. The dialog closes at once if the app that owns it is ended.

With FD_FOLDER the dialog chooses a folder: the list shows folders only (filters are not used), the name box is "Folder name", and OK returns the folder shown, or the folder of the typed name in it (or the typed path itself when it is absolute). That folder need not exist yet; the caller makes it (platformMkdirRecursive). A name that is a file shows an "Invalid Folder" error, and so does a new name the disk cannot hold. An initialDir that does not exist yet opens at its nearest existing folder with the rest already in the name box, so OK alone returns initialDir unchanged. The default title is "Choose a Folder".

  Parameter     Description
  ---------     -----------
  ctx           Application context
  title         Window title (NULL = "Open", "Save As", or "Choose a Folder")
  flags         FD_OPEN, FD_SAVE, or FD_FOLDER
  initialDir    Starting directory (NULL = current working directory)
  filters       Array of FileFilterT, or NULL
  filterCount   Number of filter entries
  outPath       Output buffer for selected path
  outPathSize   Capacity of outPath buffer

Returns: true if the user selected a file (or folder), false if cancelled or closed.

Input Boxes

dvxInputBox

bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize);

Display a modal input box with a prompt label, text field, and OK/Cancel buttons. The prompt is word-wrapped to the dialog width and may contain '\n'. The field accepts at most outBufSize - 1 characters (capped at 4096), so the caller receives exactly what was typed. An outBufSize below 2 (no room for any input) returns false without showing the dialog.

  Parameter     Description
  ---------     -----------
  ctx           Application context
  title         Window title
  prompt        Prompt label above the input field
  defaultText   Initial text (NULL = empty)
  outBuf        Output buffer
  outBufSize    Capacity of outBuf

Returns: true if OK was pressed, false if cancelled.

dvxIntInputBox

bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal);

Display a modal integer input box with a spinner (up/down arrows) for clamped numeric input.

  Parameter     Description
  ---------     -----------
  ctx           Application context
  title         Window title
  prompt        Prompt label
  defaultVal    Initial value
  minVal        Minimum allowed value
  maxVal        Maximum allowed value
  step          Increment per arrow click
  outVal        Output: entered value

Returns: true if OK pressed, false if cancelled.

Choice Dialog

dvxChoiceDialog

bool dvxChoiceDialog(AppContextT *ctx, const char *title, const char *prompt, const char **items, int32_t itemCount, int32_t defaultIdx, int32_t *outIdx);

Display a modal dialog with a listbox of choices. The user picks one item and clicks OK, or cancels.

  Parameter     Description
  ---------     -----------
  ctx           Application context
  title         Window title
  prompt        Prompt label above the list
  items         Array of choice strings
  itemCount     Number of items
  defaultIdx    Initially highlighted item (-1 = none)
  outIdx        Output: selected index

Returns: true if a choice was made, false if cancelled.

Color Dialog

dvxColorDialog

bool dvxColorDialog(AppContextT *ctx, const char *title, uint32_t *rgbInOut);

Display a modal color picker: a palette of 48 basic colors, Red, Green, and Blue sliders each paired with a number box (0-255), and a preview swatch. Clicking a basic color sets all three channels; the basic color matching the current color is framed. From the keyboard, Tab moves between the sliders and number boxes (the Red slider has the focus first), the arrow keys, Page Up/Down, Home, and End move a slider, and Enter chooses OK. Works at every color depth; on 8-bit desktops the swatches show the nearest palette colors, but the chosen value is exact.

  Parameter     Description
  ---------     -----------
  ctx           Application context
  title         Window title (NULL: "Color")
  rgbInOut      In: the starting color; out: the chosen color. Both 0x00RRGGBB (DVX BASIC's colours are &HBBGGRR; wgtBasicColor converts between the two)

Returns: true if OK was chosen, false if cancelled or closed (then *rgbInOut is unchanged).

Macros for the 0x00RRGGBB layout: DVX_RGB(r, g, b) builds a color from three 0-255 channels, and DVX_RGB_RED(rgb), DVX_RGB_GREEN(rgb), and DVX_RGB_BLUE(rgb) take one apart.

uint32_t rgb = DVX_RGB(r, g, b);

if (dvxColorDialog(ctx, "Title Bar", &rgb)) {
    dvxSetColor(ctx, ColorActiveTitleBgE, DVX_RGB_RED(rgb), DVX_RGB_GREEN(rgb), DVX_RGB_BLUE(rgb));
}

Custom Dialogs

Any dialog built from widgets, with the behaviour of the boxes above: modal, Enter chooses OK (unless a button has the focus), Escape or closing the window cancels, a nested dialog (a Browse button, an error box) works, and the dialog ends cleanly if its program is ended meanwhile.

typedef bool (*DvxDialogOkFnT)(WindowT *dlg, void *userCtx);

WindowT *dvxDialogCreate(AppContextT *ctx, const char *title, int32_t contentW);
WidgetT *dvxDialogAddField(WindowT *dlg, const char *label, int32_t labelW, int32_t maxLen, const char *value);
void     dvxDialogAddOkCancel(WindowT *dlg, const char *okLabel, DvxDialogOkFnT onOk, void *userCtx);
void     dvxDialogEnd(WindowT *dlg, bool accepted);
bool     dvxDialogRun(AppContextT *ctx, WindowT *dlg);

dvxDialogCreate makes an empty dialog contentW pixels wide (NULL on failure); add widgets under dlg->widgetRoot, or labelled text boxes with dvxDialogAddField. dvxDialogAddOkCancel adds the right-aligned OK (labelled okLabel, "&OK" when NULL) and Cancel buttons after them. dvxDialogRun fits the height to the widgets, centers the dialog, runs it, and destroys it; it returns true when it ended with OK. Every created dialog must be run.

dvxDialogAddField adds a row: a label labelW pixels wide and a text box of up to maxLen characters holding value, filling the rest of the row. It returns the text box (NULL on failure); the box's parent is the row, so a Browse... button can be added after it with wgtButton(box->parent, ...). Give every field's label the same labelW so the boxes line up.

onOk (may be NULL) runs when OK is chosen, while the widgets still exist: copy their values out and return true to close the dialog, or tell the user what is wrong and return false to keep it open. It is never called for Cancel.

dvxDialogEnd ends a running dialog from any of the caller's own widget callbacks, such as a double-click in a list or an extra button: accepted true acts like OK (onOk runs first and can keep the dialog open), false like Cancel. Use it only on a dialog made by dvxDialogCreate.

static bool onOk(WindowT *dlg, void *userCtx) {
    (void)dlg;
    return dvxCopyTrimmed((char *)userCtx, NAME_MAX, wgtGetText(sName)) > 0;
}

WindowT *dlg = dvxDialogCreate(ctx, "Name", 240);
sName = dvxDialogAddField(dlg, "&Name:", 60, NAME_MAX - 1, "");
dvxDialogAddOkCancel(dlg, NULL, onOk, name);
if (dvxDialogRun(ctx, dlg)) {
    // name holds the text
}

Save Prompt

Return codes for dvxPromptSave:

  Define            Value   Action
  ------            -----   ------
  DVX_SAVE_YES      1       Save, then proceed
  DVX_SAVE_NO       2       Discard, then proceed
  DVX_SAVE_CANCEL   3       Abort the operation

dvxFontDialog

bool dvxFontDialog(AppContextT *ctx, const char *title, uint32_t flags, DvxFontChoiceT *choiceInOut);

Display a modal font picker: lists of the font families (dvxText.h), the four styles (Regular, Bold, Italic, and Bold Italic), and the sizes the chosen family comes in for the chosen style; Underline and Strikeout boxes; and a sample drawn in the chosen font as it changes. Choosing another family or style keeps the size nearest the one chosen. The labels' underlined letters move to the lists from the keyboard; Enter chooses OK.

  Parameter     Description
  ---------     -----------
  ctx           Application context
  title         Window title (NULL: "Font")
  flags         DVX_FONT_DLG_FIXED: offer only fixed-pitch families (for a text editor or a terminal)
  choiceInOut   In: the starting font (a family there is none of starts on "System"); out: the chosen one

DvxFontChoiceT holds family (FONT_FAMILY_MAX characters with the NUL), size (pixels), and style (FONT_STYLE_* bits, the lines included) -- what wgtSetFont and dvxFontOpen take.

Returns: true if OK was chosen, false if cancelled or closed (then *choiceInOut is unchanged).

DvxFontChoiceT choice = { "DejaVu Sans", 14, FONT_STYLE_BOLD };

if (dvxFontDialog(ctx, NULL, 0, &choice)) {
    wgtSetFont(label, choice.family, choice.size, choice.style);
}

dvxPromptSave

int32_t dvxPromptSave(AppContextT *ctx, const char *title);

Display a canonical "Save changes?" dialog with Yes/No/Cancel buttons and an appropriate question icon.

  Parameter   Description
  ---------   -----------
  ctx         Application context
  title       Document name for message context

Returns: DVX_SAVE_YES, DVX_SAVE_NO, or DVX_SAVE_CANCEL.

dvxRes.h -- DXE-Embedded Resource System

Resources are appended to DXE3 files (.app, .wgt, .lib) after the normal DXE content. The DXE loader never reads past its own header-specified sections, so the appended resource block is invisible to dlopen. This lets each DXE carry its own icons, localized text, help content, and binary data without separate resource files.

File Layout

[DXE3 content]           -- untouched, loaded by dlopen
[resource data entries]  -- sequential, variable length
[resource directory]     -- fixed-size entries (48 bytes each)
[footer]                 -- magic + directory offset + entry count (16 bytes)

Reading starts from the end: seek to EOF - sizeof(footer), verify the magic, then seek to the directory.

Constants

  Define              Value          Description
  ------              -----          -----------
  DVX_RES_ICON        1              Image data (BMP, PNG, etc.)
  DVX_RES_TEXT        2              Null-terminated string
  DVX_RES_BINARY      3              Arbitrary binary data (app-specific)
  DVX_RES_FONT        4              A .DFN font the program carries (added for it when it loads; dvxText.h)
  DVX_RES_MAGIC       0x52585644     Footer signature ("DVXR" little-endian)
  DVX_RES_NAME_MAX    32             Max resource name length including null
  DVX_RES_ONLY_MAGIC  "DVXRESONLY"   First bytes (NUL included, DVX_RES_ONLY_MAGIC_LEN) of a file that holds only resources

A file that starts with DVX_RES_ONLY_MAGIC has no code before its resources -- a control written in DVX BASIC, installed as a .wgt (see Using BASIC Controls from C). The loader reads such a widget file through the BASIC run-time library instead of loading it as a module.

Structures

DvxResDirEntryT

Directory entry describing one resource (48 bytes on disk).

  Field        Description
  -----        -----------
  name         Resource name (up to 32 chars, null-terminated)
  type         DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY
  offset       Absolute file offset of the data
  size         Data size in bytes
  reserved     Padding (must be zero)

DvxResFooterT

Footer at the very end of a DXE file with appended resources (16 bytes).

  Field         Description
  -----         -----------
  magic         DVX_RES_MAGIC
  dirOffset     Absolute file offset of the resource directory
  entryCount    Number of directory entries
  reserved      Padding (must be zero)

DvxResHandleT

Opaque runtime handle representing an open resource block.

  Field         Description
  -----         -----------
  path          Source file path
  entries       Directory entries (sorted by name for bsearch)
  entryCount    Number of entries

Runtime API

dvxResOpen

DvxResHandleT *dvxResOpen(const char *path);

Open a resource handle by reading the footer and directory. Returns NULL if the file has no resource block or cannot be read. Entries are sorted on open for O(log n) name lookup via bsearch.

  Parameter   Description
  ---------   -----------
  path        Path to the DXE (or any file) with appended resources

Returns: Handle pointer, or NULL.

dvxResRead

void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize);

Find a resource by name and read its data into a freshly malloc'd buffer. Caller must free the returned buffer.

  Parameter   Description
  ---------   -----------
  h           Resource handle
  name        Resource name
  outSize     Output: data size in bytes

Returns: Data buffer, or NULL if not found or I/O failed.

dvxResFind

const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name);

Look up a resource's directory entry by name. The returned pointer is valid until dvxResClose. A name of DVX_RES_NAME_MAX (32) or more characters never matches (names are not truncated to a prefix).

  Parameter   Description
  ---------   -----------
  h           Resource handle
  name        Resource name

Returns: Pointer to directory entry, or NULL if not found.

dvxResClose

void dvxResClose(DvxResHandleT *h);

Close a resource handle and free its memory.

  Parameter   Description
  ---------   -----------
  h           Handle to close

dvxResAppend

int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize);

Append a resource to an existing DXE file. Preserves existing resources and adds the new one. Thin wrapper over the resource writer in tools/dvxResWrite.h.

  Parameter   Description
  ---------   -----------
  path        Target DXE path
  name        Resource name (up to DVX_RES_NAME_MAX - 1 chars)
  type        DVX_RES_ICON, DVX_RES_TEXT, or DVX_RES_BINARY
  data        Resource payload
  dataSize    Payload size in bytes

Returns: 0 on success, -1 on failure.

dvxResRemove

int32_t dvxResRemove(const char *path, const char *name);

Remove a resource from a DXE file by name. The resource block is rewritten through a temporary file that is swapped in only when every write succeeded, so a failed write leaves the original file intact. If this was the last resource, the result is the bare DXE content with no resource block.

  Parameter   Description
  ---------   -----------
  path        Target DXE path
  name        Resource name to remove (32 or more characters never matches)

Returns: 0 on success, -1 if not found, the name is too long, or I/O failed.

See Also

dvxResLoadIcon / dvxResLoadText / dvxResLoadData -- higher-level helpers on top of this API

dvxPrefs.h -- INI Preferences System

Handle-based API over classic INI files. Each prefsLoad/prefsCreate returns a PrefsHandleT that must be passed to all subsequent calls and freed with prefsClose when done. Multiple INI files can be open simultaneously.

BASIC programs reach the same functions through prefs.bas (dvxPrefsBas.c in libdvx): PrefsLoad, PrefsCreate, PrefsGet$, PrefsGetInt, PrefsGetBool, PrefsSet, PrefsSetInt, PrefsSetBool, PrefsGetSecret$, PrefsSetSecret, PrefsRemove, PrefsKeyName$ (prefsKeyName, "" past the last key), PrefsSave, PrefsSaveAs, and PrefsClose, on handles (1, 2, ...; 0 = none) that belong to the program that made them and are closed, unsaved, when it ends. True is -1, False 0. PrefsDvxIni$ is platformDvxIniPath() (the user folder's DVX.INI), PrefsSetVideoMode is prefsSetVideoMode, and PrefsVideoWidth / PrefsVideoHeight / PrefsVideoBpp are the three values of prefsGetVideoMode (DVX_DEFAULT_VIDEO_* for a missing key or a bad handle). The choice tables below are PREFS_CHOICE_ACCEL (0), PREFS_CHOICE_WALLPAPER (1), and PREFS_CHOICE_WHEEL (2) there: PrefsChoiceCount, PrefsChoiceLabel$, PrefsChoiceName$ (iniName), PrefsChoiceValue, PrefsChoiceIndex, PrefsChoiceIndexOfValue, PrefsChoiceDefault (the index of the choice MOUSE_ACCEL_DEFAULT / WALLPAPER_MODE_DEFAULT / MOUSE_WHEEL_DIR_DEFAULT names; -1 for an unknown table), and PrefsGetChoice(handle, section, key, table) with the table's default. PrefsGetChoiceFrom(handle, section, key, choiceNames, defaultName) is prefsGetChoice on a table the program gives as "|"-separated names (each choice's iniName and label is its name, its value its index).

PrefsHandleT *prefsBasHandle(int32_t handle);

The settings behind a prefs.bas handle, NULL for 0 or a handle that is not open. Other libraries' BASIC entry points that take a prefs.bas handle (system.bas, joystick.bas, net.bas, print.bas, sound.bas) resolve it with this; libdvx is loaded first, so the symbol is always there.

The system INI

DVX's own settings are in DVX.INI in the user folder: prefsLoad(platformDvxIniPath()) (dvxPlat.h, DVX folders). The first start copies the shipped SYSTEM\DVX.INI there, with its comments and defaults; keys it does not have keep the defaults compiled into the code.

Types

PrefsHandleT

Opaque handle representing an in-memory INI file. Fields are not exposed.

Lifecycle

prefsCreate

PrefsHandleT *prefsCreate(void);

Create an empty preferences handle with no associated file. Use this when building an INI from scratch before calling prefsSaveAs.

Returns: New handle, or NULL on allocation failure.

prefsLoad

PrefsHandleT *prefsLoad(const char *filename);

Load an INI file into a new handle. If the file does not exist but a backup copy left by an interrupted save does (same name with the extension replaced by ~ and its first two characters, e.g. DVX.~IN), the backup is read instead, so the next save restores the file. Otherwise a missing file gives a valid empty handle. Either way the path is stored so prefsSave can write the file later.

File format rules:

  Parameter   Description
  ---------   -----------
  filename    Path to INI file

Returns: Handle, or NULL for a NULL filename or on allocation failure.

prefsSave

bool prefsSave(PrefsHandleT *h);

Save the in-memory state back to the file that was loaded (or captured by prefsLoad for a non-existent file). Returns false and writes nothing when the file existed but could not be read by prefsLoad, so an unreadable file is never replaced by defaults. Uses the same crash-safe write as prefsSaveAs.

  Parameter   Description
  ---------   -----------
  h           Preferences handle

Returns: true on success.

prefsSaveAs

bool prefsSaveAs(PrefsHandleT *h, const char *filename);

Save to a specific file, overriding the handle's stored path. The data is written to a sibling file whose extension is replaced by $ and the first two characters of the original extension (8.3-safe, e.g. DVX.INI -> DVX.$IN) and swapped in only after every write succeeded; the previous file is parked as DVX.~IN during the swap and then removed. A failed save (disk full, write error) returns false and leaves the previous file intact. When DVX runs from read-only media (platformIsReadOnly), a file that cannot be written is kept in memory instead (the save returns true) and prefsLoad of the same path reads that copy until DVX exits.

  Parameter   Description
  ---------   -----------
  h           Preferences handle
  filename    Output file path

Returns: true on success.

prefsClose

void prefsClose(PrefsHandleT *h);

Release all memory held by the handle. Does not save -- call prefsSave first if needed.

  Parameter   Description
  ---------   -----------
  h           Handle to close

Getters

prefsGetString

const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal);

Retrieve a string value from [section]/key. The returned pointer is valid until the key is modified or the handle is closed.

  Parameter    Description
  ---------    -----------
  h            Preferences handle
  section      Section name (without brackets)
  key          Key name
  defaultVal   Returned if the key is not present

Returns: Key value or defaultVal.

prefsGetInt

int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal);

Retrieve an integer value. Supports decimal and negative numbers.

  Parameter    Description
  ---------    -----------
  h            Preferences handle
  section      Section name
  key          Key name
  defaultVal   Returned if the key is absent or non-numeric

Returns: Parsed int, or defaultVal.

prefsGetBool

bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal);

Retrieve a boolean value. The whole value must be "true", "yes", or "1" (true) or "false", "no", or "0" (false), in any case; anything else (e.g. "t", "10", "normal") returns defaultVal.

  Parameter    Description
  ---------    -----------
  h            Preferences handle
  section      Section name
  key          Key name
  defaultVal   Returned if the key is absent or unrecognised

Returns: Parsed bool, or defaultVal.

prefsGetSecret

void prefsGetSecret(PrefsHandleT *h, const char *section, const char *key, char *out, int32_t outSize);

Read back a secret prefsSetSecret stored, into out (cut to outSize - 1 characters and always terminated; nothing is written when outSize is 0 or less). A missing key, or a NULL handle, gives "". A value prefsSetSecret did not write (one typed into the file by hand: no leading "*", or not hex digits after it) is copied as it is, so a password can be put in the file by hand and is scrambled the next time the program saves it.

  Parameter    Description
  ---------    -----------
  h            Preferences handle
  section      Section name
  key          Key name
  out          Receives the text
  outSize      Size of out in bytes

prefsGetVideoMode / prefsSetVideoMode

void prefsGetVideoMode(PrefsHandleT *h, int32_t *width, int32_t *height, int32_t *bpp);
void prefsSetVideoMode(PrefsHandleT *h, int32_t width, int32_t height, int32_t bpp);

Read or store the [video] display mode DVX starts in (keys width, height, and bpp). Missing keys read as DVX_DEFAULT_VIDEO_W/H/BPP (640x480, 16 bpp). The one place those key names live: the shell (start-up and safe video mode), the Control Panel, and Setup all go through these two functions.

Setters

prefsSetString

void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value);

Set a string value. Creates the section and key if they do not already exist; a new key is placed after the section's last key (not after trailing comments that belong to the next section). A NULL value is stored as an empty string.

prefsSetInt

void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value);

Set an integer value (stored in decimal).

prefsSetBool

void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value);

Set a boolean value (stored as "true"/"false").

prefsSetSecret

void prefsSetSecret(PrefsHandleT *h, const char *section, const char *key, const char *value);

Store a secret, such as a password, so it cannot be read at a glance: "*", a 32-bit salt as 8 hex digits, then each byte of the text XORed with an xorshift32 keystream started from the salt, as 2 hex digits. Each call takes a new salt (clock(), time(), and a call count), so the same text is stored differently each time. This is obfuscation, not encryption: anyone with this code can read the value back, so the file still needs the care of any file holding a password. An empty or NULL value is stored as "". prefsGetSecret reads it back.

prefsRemove

void prefsRemove(PrefsHandleT *h, const char *section, const char *key);

Remove a key from a section. No-op if the key does not exist.

prefsKeyName

const char *prefsKeyName(PrefsHandleT *h, const char *section, int32_t index);

Name of the index-th distinct key (0-based, file order) in a section, or NULL past the last one (and for a NULL handle or section or a negative index), so a section with keys not known in advance can be walked. A key repeated in the section counts once: the first, which the getters read. The pointer is valid until the key is removed or prefsClose.

for (int32_t i = 0; (key = prefsKeyName(h, "associations", i)) != NULL; i++) {
    printf("%s = %s\n", key, prefsGetString(h, "associations", key, ""));
}

Shared Preference Vocabularies

Enumerated preferences are stored in DVX.INI by name. One table per preference is the single source of truth for the label the Control Panel shows, the spelling written to the INI, and the value applied.

typedef struct {
    const char *label;    // user-facing text (dropdown item)
    const char *iniName;  // spelling stored in the INI
    int32_t     value;    // value applied
} PrefsChoiceT;

extern const PrefsChoiceT prefsAccelChoices[PREFS_ACCEL_CHOICE_COUNT];
extern const PrefsChoiceT prefsWallpaperChoices[PREFS_WALLPAPER_CHOICE_COUNT];
extern const PrefsChoiceT prefsWheelDirChoices[PREFS_WHEEL_DIR_CHOICE_COUNT];
  Table                   INI key                 Entries (iniName: value)                              Default
  -----                   -------                 ------------------------                              -------
  prefsAccelChoices       [mouse] acceleration    off, low, medium, high: MOUSE_ACCEL_* threshold       MOUSE_ACCEL_DEFAULT ("medium")
  prefsWallpaperChoices   [desktop] mode          stretch, tile, center: WallpaperModeE (indexed by it) WALLPAPER_MODE_DEFAULT ("stretch")
  prefsWheelDirChoices    [mouse] wheel           normal: 1, reversed: -1 (MOUSE_WHEEL_DIR_*)           MOUSE_WHEEL_DIR_DEFAULT ("normal")

The entry counts are PREFS_ACCEL_CHOICE_COUNT (4), PREFS_WALLPAPER_CHOICE_COUNT (3), and PREFS_WHEEL_DIR_CHOICE_COUNT (2). MOUSE_WHEEL_DIR_NORMAL (1) and MOUSE_WHEEL_DIR_REVERSED (-1) in dvxTypes.h are also the wheelDir values for dvxSetMouseConfig.

Labels are the capitalized names ("Off", "Stretch", "Reversed", ...). The accel table is in dropdown order. dvxMouseFromPrefs and dvxWallpaperFromPrefs (dvxApp.h) read [mouse] wheel, [mouse] acceleration, and [desktop] mode through prefsGetChoice, and the Control Panel builds its dropdowns from the same tables.

prefsChoiceIndex

int32_t prefsChoiceIndex(const PrefsChoiceT *choices, int32_t count, const char *name);

Return the index of the choice whose iniName equals name (case-insensitive), or -1 when there is none (also for a NULL name).

prefsChoiceIndexOfValue

int32_t prefsChoiceIndexOfValue(const PrefsChoiceT *choices, int32_t count, int32_t value);

Return the index of the choice whose value equals value, or -1 when there is none. A dropdown built from a choice table selects this index for the current setting.

prefsChoiceLabels

void prefsChoiceLabels(const PrefsChoiceT *choices, int32_t count, const char **labels);

Fill labels[0..count-1] with the choices' labels, for wgtDropdownSetItems. No strings are copied: the pointers are the table's own.

prefsGetChoice

int32_t prefsGetChoice(PrefsHandleT *h, const char *section, const char *key, const PrefsChoiceT *choices, int32_t count, const char *defaultName);

Return the index of the choice named by the key's value; the index of defaultName when the key is missing or holds an unknown name. Use choices[index].value for the value to apply.

dvxMem.h -- Per-App Memory Tracking

Wraps malloc/free/calloc/realloc/strdup with a 16-byte header that records the owning app ID and allocation size. The Task Manager displays per-app memory usage, and leaks are detected when an app is terminated.

DXE code does NOT need to include this header for tracking to work. The DXE export table maps malloc/free/calloc/realloc/strdup to these wrappers transparently. Call sites that need the explicit dvxXxx names (e.g. dvxMemGetAppUsage) or the dvxMemAppIdPtr declaration should include dvxMem.h directly.

Globals

dvxMemAppIdPtr

extern int32_t *dvxMemAppIdPtr;

Pointer to the current owning app ID. The shell points this at its currentAppId field during init so every allocation is charged to the running app. When dvxMemAppIdPtr is NULL or points to zero, allocations are charged to the shell.

Allocation Wrappers

dvxMalloc

void *dvxMalloc(size_t size);

Tracked malloc. Returns a pointer to user memory (header is hidden in front of it).

dvxCalloc

void *dvxCalloc(size_t nmemb, size_t size);

Tracked calloc.

dvxRealloc

void *dvxRealloc(void *ptr, size_t size);

Tracked realloc. Works correctly with tracked pointers only -- the magic header identifies them.

dvxFree

void dvxFree(void *ptr);

Tracked free. Falls through to the real free() if the pointer's header magic does not match, so mixing tracked and untracked pointers is safe.

dvxStrdup

char *dvxStrdup(const char *s);

Tracked strdup.

Accounting

dvxMemGetAppUsage

uint32_t dvxMemGetAppUsage(int32_t appId);

Return the total bytes currently charged to the given app ID.

  Parameter   Description
  ---------   -----------
  appId       App ID to query

Returns: Bytes allocated for this app.

dvxMemResetApp

void dvxMemResetApp(int32_t appId);

Free every tracked allocation charged to the given app. Called by the shell after an app exits or crashes to reclaim its memory.

  Parameter   Description
  ---------   -----------
  appId       App ID to flush

dvxWgtP.h -- Widget Plugin API

Included by widget .c files (DXE modules) to access core infrastructure for tree manipulation, focus management, scrollbar drawing, layout helpers, and shared interaction state. This header is NOT intended for application code -- use dvxWgt.h for the public widget API.

Validation Macros

VALIDATE_WIDGET / VALIDATE_WIDGET_VOID

VALIDATE_WIDGET(w, wtype, retval);     // returns retval on mismatch
VALIDATE_WIDGET_VOID(w, wtype);        // returns void on mismatch

Bail out of a widget method if the widget pointer is NULL or its type does not match the expected wtype. Use at the top of every widget-specific API function.

Core Constants

  Define             Value   Description
  ------             -----   -----------
  DEFAULT_SPACING    4       Default pixel spacing between VBox/HBox children
  DEFAULT_PADDING    4       Default internal padding for containers
  SB_MIN_THUMB       14      Minimum scrollbar thumb size in pixels
  WGT_SB_W           14      Widget-internal scrollbar width

The KEY_MOD_* modifier bits are defined in dvxTypes.h (see Accelerator Modifier Flags).

Inline Helpers

clampInt

static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi);

Clamp val into the [lo, hi] range.

drawTextEmbossed

static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors);

Draw text twice -- once offset by (1,1) in highlight color, then overlaid in shadow color at (x,y) -- for a classic Motif-style embossed look used on disabled labels.

drawTextAccelEmbossed

void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors);

Embossed text with & accelerator markers (declared in dvxDraw.h). Same offset logic as drawTextEmbossed, but processes & markers. drawWidgetTextAccel uses it for disabled widgets.

Shared Interaction State

Extern variables defined in widgetCore.c (sClosedPopup in widgetEvent.c, sKeyPressedBtn and sCursorBlinkFn in dvxApp.c). These hold system-wide widget state: only one focused widget, one open popup, one pressed button at a time.

  Variable              Type       Purpose
  --------              ----       -------
  sCursorBlinkOn        bool       Current state of the text cursor blink
  sDblClickTicks        clock_t    Double-click interval in clock() ticks
  sDebugLayout          bool       When true, draw colored debug borders around containers
  sClosedPopup          WidgetT*   Widget whose popup just closed (suppresses re-open)
  sFocusedWidget        WidgetT*   Currently focused widget
  sKeyPressedBtn        WidgetT*   Button being pressed via keyboard (for release tracking)
  sOpenPopup            WidgetT*   Currently open popup (dropdown/combo list)
  sDragWidget           WidgetT*   Widget being dragged (any drag type)
  sPollWidgets          WidgetT**  Dynamic array of poll widgets (stb_ds)
  sCursorBlinkFn        fn ptr     Callback invoked when the blink flips (repaints focused text)

Core Widget Functions

widgetAlloc

WidgetT *widgetAlloc(WidgetT *parent, int32_t type);

Allocate a new WidgetT, initialize its fields to defaults, link it as the last child of parent (if non-NULL), and assign its class pointer from widgetClassTable[type].

widgetAddChild

void widgetAddChild(WidgetT *parent, WidgetT *child);

Append child to parent's child list. Updates parent's firstChild/lastChild and child's nextSibling pointers.

widgetRemoveChild

void widgetRemoveChild(WidgetT *parent, WidgetT *child);

Unlink child from parent's child list. Does not destroy the child.

widgetDestroyChildren

void widgetDestroyChildren(WidgetT *w);

Recursively destroy all descendants of w, calling each one's wclsDestroy handler.

Focus Management

widgetFindNextFocusable

WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);

Find the next focusable widget in tab order after the given widget. Wraps to the beginning at the end of the tree.

widgetFindPrevFocusable

WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);

Find the previous focusable widget in tab order. Wraps to the end at the beginning of the tree.

widgetFindByAccel

WidgetT *widgetFindByAccel(WidgetT *root, char key);

Find a focusable widget whose accelKey (lowercase) matches the given character. Used for Alt+letter hotkeys on buttons and labels.

Hit Testing

widgetHitTest

WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);

Find the deepest widget at (x, y) within w's subtree. Respects WCLASS_NO_HIT_RECURSE: the search stops at such a container, whose class ON_MOUSE handler receives the press. Hit testing ignores the enabled flag; event dispatch checks widgetIsEnabledChain on the result.

widgetCallbackTarget

WidgetT *widgetCallbackTarget(WidgetT *root, int32_t x, int32_t y);

Find the widget the universal callbacks (onClick, onDblClick, onMouseDown/Up/Move), context menus, tooltips, and the cursor shape go to. Starts from widgetHitTest; while the result is a non-focusable WCLASS_NO_HIT_RECURSE container (Splitter, ScrollPane), it continues into the child whose paintClip (the part actually visible when last painted) contains the point. Returns the container itself when the point is on its own chrome (divider, scrollbars) or no child is visible there.

widgetClosePopup

void widgetClosePopup(void);

Close the open dropdown/combobox popup, if any: dirty its overlay rect and fully invalidate its window so the overlay pixels are erased. The single close path for click-outside, clicks in another window, Escape, and focus changes.

widgetIsEnabledChain

bool widgetIsEnabledChain(const WidgetT *w);

True when w and every ancestor are enabled. Disabling a container disables input to its whole subtree: mouse and keyboard dispatch use this test.

Utility Queries

widgetCountVisibleChildren

int32_t widgetCountVisibleChildren(const WidgetT *w);

Count children whose visible flag is true.

widgetFrameBorderWidth

int32_t widgetFrameBorderWidth(const WidgetT *w);

Return the effective border width for a container widget (used to inset child layout).

widgetIsFocusable

bool widgetIsFocusable(int32_t type);

True if the widget class with this type ID has the WCLASS_FOCUSABLE flag.

widgetIsHorizContainer

bool widgetIsHorizContainer(int32_t type);

True if the widget class lays out children horizontally (WCLASS_HORIZ_CONTAINER).

multiClickDetect

int32_t multiClickDetect(int32_t vx, int32_t vy);

Track consecutive clicks at roughly the same position within sDblClickTicks. Returns the current click count (1 for single, 2 for double, etc.).

Clipboard

clipboardCopy

void clipboardCopy(const char *text, int32_t len);

Copy text to the process-wide clipboard buffer. Same underlying storage as dvxClipboardCopy.

clipboardGet

const char *clipboardGet(int32_t *outLen);

Retrieve clipboard text.

Scrollbar Helpers

ScrollHitE

Scrollbar hit-test result.

  Value                Description
  -----                -----------
  ScrollHitNoneE       Click fell outside the scrollbar
  ScrollHitArrowDecE   Up/left arrow
  ScrollHitArrowIncE   Down/right arrow
  ScrollHitPageDecE    Trough above/before the thumb
  ScrollHitPageIncE    Trough below/after the thumb
  ScrollHitThumbE      The draggable thumb

widgetScrollbarThumb

void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);

Compute thumb position and size for a scrollbar with the given geometry. Enforces SB_MIN_THUMB.

widgetDrawScrollbarV

void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);

Draw a vertical scrollbar (WGT_SB_W wide) inside a widget.

widgetDrawScrollbarH

void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);

Draw a horizontal scrollbar (WGT_SB_W high) inside a widget.

widgetScrollbarHitTest

ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);

Classify a click position along a scrollbar into ScrollHitE. sbLen is the scrollbar length, relPos is the click position along the bar.

widgetScrollbarApplyHit

bool widgetScrollbarApplyHit(int32_t sbLen, int32_t relPos, int32_t total, int32_t visible, int32_t lineStep, int32_t pageStep, int32_t *pPos, int32_t *outDragOff);

Apply a click on a widget scrollbar to the scroll position *pPos: an arrow moves it by lineStep, the trough by pageStep, and the result is clamped to [0, total - visible]. A click on the thumb leaves *pPos unchanged and returns true with *outDragOff set to the grab offset inside the thumb; the caller then starts its thumb drag (see widgetScrollbarThumbDragScroll). The one arrow/page/thumb dispatch shared by every scrollbar-owning widget.

widgetScrollbarThumbDragScroll

int32_t widgetScrollbarThumbDragScroll(int32_t trackLen, int32_t total, int32_t visible, int32_t relMouse, int32_t maxScroll);

Map a dragged thumb position to a scroll position in [0, maxScroll]. relMouse is where the thumb's leading edge should be, relative to the start of a track of trackLen pixels (the mouse position minus the grab offset). Returns 0 when maxScroll <= 0.

widgetScrollbarNeeds

void widgetScrollbarNeeds(int32_t totalW, int32_t totalH, int32_t *innerW, int32_t *innerH, bool *needV, bool *needH);

Which scrollbars content totalW x totalH pixels needs in a viewport of *innerW x *innerH (the area inside the widget's border). A vertical bar narrows the viewport, which may make the content too wide; a horizontal bar shortens it, which may make the content too tall; both cases are followed through. *innerW and *innerH come back less the bars (WGT_SB_W each). For rows of a fixed height pass rows * height: the content is too tall exactly when there are more rows than whole rows fit. ListBox, ListView, TreeView, ScrollPane, and DBGrid decide their bars with it; BASIC's ScrollBarsNeeded is the same rule.

Layout Functions

widgetCalcMinSizeBox / widgetCalcMinSizeTree

void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font);
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font);

Generic box-container min-size calculation (bottom-up layout pass). widgetCalcMinSizeBox handles a single VBox/HBox; widgetCalcMinSizeTree recurses through the whole subtree.

widgetLayoutBox / widgetLayoutChildren

void widgetLayoutBox(WidgetT *w, const BitmapFontT *font);
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);

Generic box-container layout (top-down pass). widgetLayoutBox lays out a single container; widgetLayoutChildren processes each child's layout handler.

Event Dispatch

widgetOnPaint / widgetOnMouse / widgetOnKey / etc.

void widgetOnPaint(WindowT *win, RectT *dirtyArea);
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod);
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
void widgetOnFocus(WindowT *win);
void widgetOnBlur(WindowT *win);

Window-level event handlers that dispatch to individual widgets in the tree. wgtInitWindow installs these as the window's onPaint/onMouse/onKey/etc. callbacks.

widgetManageScrollbars

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

Add, remove, or resize window scrollbars based on the widget tree's current min-size vs. the window content area.

Paint Helpers

widgetPaintOne

void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);

Paint a single widget (sets clip rect, calls the widget's PAINT handler). Does not recurse.

widgetPaintOverlays

void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);

Paint the overlay pass: dropdown popups and tooltips drawn on top of the main tree.

widgetQuickPaintBegin

bool widgetQuickPaintBegin(WidgetT *w, DisplayT *cd);

A display onto w's window content buffer, clipped to w and every ancestor, for a widget's quick repaint (WGT_METHOD_QUICK_REPAINT) outside the paint walk: the widget draws the rows that changed and reports them, and only those are flushed. Returns false when w cannot be painted that way now -- hidden, clipped away entirely, or inside a scroll container, whose viewport is not its rect (w is then invalidated for the normal paint) -- and the caller drops what it meant to draw. The Terminal and RichEdit widgets use it.

Pressable Button Helpers

Shared state machine for WCLASS_PRESS_RELEASE widgets (Button, ImageButton). The w->pressed flag tracks the visual state; these helpers fire the widget's onClick callback on release-within-bounds.

widgetPressableOnMouse / widgetPressableOnKey / widgetPressableOnDragUpdate / widgetPressableOnDragEnd / widgetPressableOnAccelActivate

void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root);

Register these directly as the widget class's handlers for WGT_METHOD_ON_MOUSE, WGT_METHOD_ON_KEY, WGT_METHOD_ON_DRAG_UPDATE, WGT_METHOD_ON_DRAG_END, and WGT_METHOD_ON_ACCEL_ACTIVATE.

Generic Text Handlers

For widgets whose data struct has a (const char *) as its first field and the WCLASS_HAS_TEXT flag set, these implement GET_TEXT / SET_TEXT directly.

widgetTextGet / widgetTextSet

const char *widgetTextGet(const WidgetT *w);
void        widgetTextSet(WidgetT *w, const char *text);

Register these directly as the widget class's handlers for WGT_METHOD_GET_TEXT and WGT_METHOD_SET_TEXT. The text is strdup'd on set and freed automatically on destroy.

Class Table

widgetClassTable

extern const WidgetClassT **widgetClassTable;

stb_ds dynamic array. Each widget DXE appends its class pointer via wgtRegisterClass() during wgtRegister(). Indexed by the runtime type ID assigned at registration.

platform/dvxPlat.h -- Platform Abstraction Layer

All OS-specific and CPU-specific code is isolated behind this interface. Port DVX to a new platform by implementing a new dvxPlatformXxx.c against this header. The target implementation is dvxPlatformDos.c for DJGPP/DPMI; dvxPlatformHost.c is a Linux backend used only by the host test suites (fake clock, in-memory framebuffer, scripted input). Code shared by both lives in dvxPlatformUtil.c.

Platform functions must be stateless (or manage their own internal state) and must not reference AppContextT or any layer above dvxTypes.h.

Constants

  Define                   Value   Description
  ------                   -----   -----------
  DVX_MAX_PATH             260     Maximum file path length
  PLATFORM_SYSINFO_MAX     4096    Maximum size of formatted system info text
  DVX_PATH_SEP             "\\"    Native directory separator on DOS

Types

PlatformKeyEventT

Keyboard event with ASCII and scancode separated. Extended keys (arrows, F-keys) have ascii == 0 and a non-zero scancode.

  Field       Description
  -----       -----------
  ascii       ASCII value, 0 for extended/function keys
  scancode    PC scancode (e.g. 0x48 = Up, 0x50 = Down)

PlatformLogFnT

typedef void (*PlatformLogFnT)(const char *fmt, ...);

Log function signature used by the crash handler.

PlatformSymOverrideT

Name/function-pointer pair for registering symbol overrides with the DXE loader.

  Field   Description
  -----   -----------
  name    Symbol name as seen by DXEs
  func    Function pointer to use instead of the default

Lifecycle

dvxLog

void dvxLog(const char *fmt, ...);

Append a formatted line to dvx.log. Implemented in dvx.exe and exported to every DXE via the loader.

platformInit

void platformInit(void);

One-time platform initialization (a second call does nothing new; the loader and dvxInit both call it). On DOS this disables Ctrl+C and Ctrl+Break at every level (the DJGPP runtime check, SIGINT, and DOS break checking via INT 21h AH=33h) so the user cannot kill the GUI from inside a graphics mode, and points the real-mode INT 24h (critical error) vector at a stub in DOS memory that answers Fail. From then on a drive that is not ready, a write-protected disk, or a removed disk makes the DOS call return an error (errno) instead of waiting at "Abort, Retry, Fail" on the hidden text screen, so no DVX code needs a guard of its own. The handler is never removed: DOS restores the vector from the PSP when DVX exits. It does not install crash handlers (see platformInstallCrashHandler).

platformYield

void platformYield(void);

Cooperative CPU yield, used between frames when the event loop has nothing to do. On DOS this calls __dpmi_yield() to be friendly to multitaskers.

Timing

platformClock / platformClockHz

typedef uint32_t PlatformTicksT;

PlatformTicksT platformClock(void);
PlatformTicksT platformClockHz(void);
#define PLATFORM_MS_TO_TICKS(ms)
#define PLATFORM_TICKS_TO_MS(ticks)

Monotonic tick counter for UI timing (double-click, tooltip delay, cursor blink, timers), advancing platformClockHz() ticks per second. PlatformTicksT is unsigned so "now - then" is wrap-safe: always compare intervals by subtraction, never raw tick values. PLATFORM_MS_TO_TICKS converts milliseconds to ticks at the platform rate, PLATFORM_TICKS_TO_MS ticks to milliseconds (the millisecond clock the conn and xfer engines take is PLATFORM_TICKS_TO_MS(platformClock())).

platformEntropyStamp

uint64_t platformEntropyStamp(void);

Fine-grained, free-running timestamp for input-timing entropy. It is not a clock: units and wrap point are backend-specific. On DOS it returns the CPU time-stamp counter (RDTSC) when CPUID reports one, otherwise the BIOS tick count shifted left 16 bits with the live PIT channel 0 counter (about 0.84 us resolution) in the low 16 bits. Used by dvxSetInputEntropyHook.

platformMicros

uint64_t platformMicros(void);

Monotonic microseconds, the high-resolution clock for games and measurements. On DOS it counts from the start of the timer service (dvxInit): the PIT input clocks the timer interrupt has counted plus the part of the running period, read from the latched channel 0 count (about 0.84 us steps), or on a CPU with a time-stamp counter the TSC cycles since the last interrupt at the rate measured over the interrupt period before it, so the clock never drifts from the PIT and a TSC whose rate changes (power saving, an emulator) does no harm. The host backend returns its fake clock. Not for use inside a timer callback.

Timer Service

#define PLATFORM_PIT_HZ            1193182
#define PLATFORM_PIT_BIOS_DIVISOR  65536
#define PLATFORM_TIMER_MAX_HZ      1000
#define PLATFORM_TIMER_FAST_MAX_HZ 30000
#define PLATFORM_TIMER_SLOTS       16

typedef void (*PlatformTimerFnT)(void *userData);

void    platformTimerInit(void);
void    platformTimerShutdown(void);
int32_t platformTimerAdd(uint32_t hz, PlatformTimerFnT fn, void *userData, int32_t ownerAppId);
int32_t platformTimerAddFast(uint32_t hz, PlatformTimerFnT fn, void *userData, int32_t ownerAppId);
void    platformTimerRemove(int32_t timerId);
void    platformTimerRemoveOwner(int32_t ownerAppId);
void    platformTimerStats(PlatformTimerStatsT *out);

One timer interrupt for everything that must run at a steady rate however busy the programs are (audio refill, joystick polling). dvxInit starts the service; dvxShutdown stops it. On DOS it owns PIT channel 0 (rate generator mode) and INT 8: the interrupt runs as fast as the fastest registered timer needs (at most PLATFORM_TIMER_MAX_HZ; the BIOS 18.2 Hz when there is none) and exactly one interrupt per 65536 PIT clocks is passed on to the BIOS handler, so the BIOS tick count, the DOS time of day, clock(), and the floppy motor timeout keep their normal rate. A rate change takes effect at once (the PIT is restarted and the part of the period already run is counted). Shutdown restores the BIOS square-wave mode and the old vector; it also runs at exit() and from the crash handler when a crash is not recovered.

platformTimerAdd calls fn(userData) hz times a second, 1 to PLATFORM_TIMER_MAX_HZ; exact on average, while single intervals vary by up to one interrupt period. It returns a timer id, or -1 (bad arguments, all PLATFORM_TIMER_SLOTS in use, service not running). ownerAppId is recorded for platformTimerRemoveOwner, which the shell calls when a program ends; use 0 for a library that removes its own timers. gameTimerAdd (game.lib) registers one for the calling program. After platformTimerRemove returns, the callback is not running and will not run again.

platformTimerAddFast is the same for a device that must be fed at an audio rate (the PC speaker's digital sound): hz may be up to PLATFORM_TIMER_FAST_MAX_HZ. The whole timer interrupt then runs that often, so the callback must be very short (a few port writes); ask for PLATFORM_PIT_HZ / divisor to be called on every interrupt.

Warning: The callback runs INSIDE THE TIMER INTERRUPT, with interrupts disabled, on a small stack. Keep it to a few dozen microseconds; do not call the C library, DVX, or DOS; do not use floating point; and pin its code and every byte it touches with platformLockRegion (a page fault there is fatal on DOS). Set flags and counters, fill buffers, write I/O ports.

PlatformTimerStatsT reports rateHz (the interrupt rate, rounded), divisor (the PIT reload value), interrupts (since the service started), and biosTicks (of those, passed on to the BIOS).

Interrupt-Safe Updates and Shutdown Hooks

uint32_t platformIrqSave(void);
void     platformIrqRestore(uint32_t state);

#define PLATFORM_SHUTDOWN_HOOKS 8
typedef void (*PlatformShutdownFnT)(void);

bool platformShutdownHookAdd(PlatformShutdownFnT fn);
void platformShutdownHookRemove(PlatformShutdownFnT fn);

#define PLATFORM_SUSPEND_HOOKS 8
typedef void (*PlatformSuspendFnT)(bool suspending);

bool platformSuspendHookAdd(PlatformSuspendFnT fn);
void platformSuspendHookRemove(PlatformSuspendFnT fn);

platformIrqSave disables interrupts and returns the previous state for platformIrqRestore; bracket the few instructions that change data an interrupt handler reads (nothing happens on the host).

A shutdown hook runs when DVX ends without its normal shutdown (exit() or a crash with no recovery point), before the timer service and the keyboard hook are given back, so a module that drives hardware from an interrupt (a sound card playing by DMA) can stop it. Each hook runs once. A module removes its hook before it is unloaded; at most PLATFORM_SHUTDOWN_HOOKS exist at once (Add returns false when full; adding one already present does nothing).

A suspend hook runs as fn(true) before DVX steps aside for a DOS program (platformDosRun, dvxRunDos) and as fn(false) once DVX is back, so a module that drives hardware itself lets it go (and takes it again then, or at its next use): audio.lib stops every sound, closes the output and the synthesizers, and opens the synthesizers again; music.lib stops every music and lets go of where it plays, closing its context (and the SoundFont kept on the wavetable synthesizer) when none is left; dvxnet stops the packet driver (it starts again at the next use). Suspending runs the newest hook first (a library built on another, loaded after it, lets go before it), resuming the oldest first; a hook may remove itself as they run. At most PLATFORM_SUSPEND_HOOKS; the same rules as the shutdown hooks otherwise.

Video

platformVideoInit / platformVideoShutdown

int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
void    platformVideoShutdown(DisplayT *d);

Low-level video mode setup and teardown. Called by videoInit / videoShutdown. On DOS the mode is used through its linear framebuffer when the card offers one (VBE 2.0+) and the DPMI host maps it; otherwise through its 64 KB bank window (any VBE version), which only platformFlushRect sees. The log says which ("Video: 640x480 at 16 bpp, linear frame buffer" or "..., banked: window A, 64 KB in 64 KB steps"). It fails only when there is no VBE ("VBE: No VESA BIOS extension (VBE) found") or no usable mode; the shell then tells the user to load a VESA driver such as UniVBE.

platformVideoEnumModes

void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);

Enumerate the graphics modes platformVideoInit can actually set (on DOS: VBE modes at 8, 15, 16, or 32 bpp, linear or banked; 24 bpp is not offered). cb is invoked once per distinct width/height/depth. Feeds the video-mode list offered to the user; platformVideoInit selects its own best match independently.

platformVideoSetPalette

void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);

Program the VGA/VESA DAC palette registers (8-bit mode only). pal is the full 256-entry RGB table (3 bytes per entry); entries firstEntry..firstEntry+count-1 are written, clamped to the 256-entry table.

platformVideoFreeBuffers

void platformVideoFreeBuffers(DisplayT *d);

Free the backbuffer and palette without restoring text mode. Used when switching between graphics modes live.

platformVideoReadBack

bool platformVideoReadBack(const DisplayT *d, uint8_t *out);

Copy what video memory holds for d (the display on show: the desktop, or a full-screen program's) into out, pitch * height bytes, from the linear framebuffer or through a banked mode's window that can be read (which may be the other window). False where it cannot: Mode X (platformVideoExclusiveCapture reads that), or a banked mode with no readable window. The DOS test harness's VIDEOCHECK step compares it with the backbuffer, since screenshots are taken from the backbuffer and could not show a flush that wrote to the wrong place.

Framebuffer Flush

platformFlushRect

void platformFlushRect(const DisplayT *d, const RectT *r);

Copy a rectangle from the system RAM backbuffer to video memory. The single critical path where bus write speed matters. On DOS, each scanline (or the whole rectangle, when it spans whole scanlines) is one rep movsd run of aligned 32-bit writes to the linear framebuffer. A mode with no linear framebuffer (a VBE 1.x card, or a DPMI host that will not map it) is written through its bank window instead: a run is cut where the window ends and the window moved on (VBE function 4F05h) only when the next byte is outside it. System Information says which ("Video memory access").

platformBankChunk

#define PLATFORM_BANK_NONE 0xFFFFFFFFu

uint32_t platformBankChunk(uint32_t offset, uint32_t length, uint32_t granBytes, uint32_t winBytes, uint32_t *winStart);

The window arithmetic of the banked flush, shared so the host tests check it. The window shows winBytes of video memory from *winStart and moves in steps of granBytes (granBytes <= winBytes). For a copy of length bytes to offset, it moves *winStart to the step at or below offset when offset is not in the window (or *winStart is PLATFORM_BANK_NONE, not known yet, as after a mode set), and returns how many bytes fit before the window ends. The caller switches the bank when *winStart changed, copies that many bytes, and goes on with the rest.

Span Operations

platformSpanFill8/16/32

void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count);
void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count);
void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count);

Fill count pixels at dst with color. Three depth variants because the fill instruction differs by size. On DOS these use inline rep stosl / rep stosw / rep stosb asm.

platformSpanCopy8/16/32

void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);

Copy count pixels from src to dst. On DOS these use rep movsd.

Mouse Input

platformMouseInit

void platformMouseInit(int32_t screenW, int32_t screenH);

Initialize the mouse driver and constrain movement to the screen bounds. On DOS this resets the INT 33h driver, sets the X/Y range, and centers the cursor. Safe to call again after a video-mode change: the speed, acceleration, and wheel settings applied earlier are re-applied after the driver reset.

platformMousePoll

void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons);

Poll current mouse state. Buttons is a bitmask: bit 0 = left, bit 1 = right, bit 2 = middle. MOUSE_BUTTON_MASK (MOUSE_LEFT | MOUSE_RIGHT | MOUSE_MIDDLE, in dvxPlat.h) covers every bit it can report.

platformMouseWheelInit / platformMouseWheelPoll

bool    platformMouseWheelInit(void);
int32_t platformMouseWheelPoll(void);

Detect and activate the CuteMouse Wheel API. platformMouseWheelPoll returns the accumulated delta (positive = scroll down) since the last poll.

platformMouseSetAccel / platformMouseSetMickeys

void platformMouseSetAccel(int32_t threshold);
void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys);

Set the double-speed threshold and mickey-to-pixel ratio. See dvxSetMouseConfig for a higher-level interface.

platformMouseWarp

void platformMouseWarp(int32_t x, int32_t y);

Move the mouse cursor to an absolute screen position. Used to clamp the cursor to window edges during resize.

Keyboard Input

platformKeyboardGetModifiers

int32_t platformKeyboardGetModifiers(void);

Return the current modifier key state in BIOS shift-state format.

platformKeyboardRead

bool platformKeyboardRead(PlatformKeyEventT *evt);

Non-blocking read of the next key from the keyboard buffer. Returns true if a key was available. On DOS this uses INT 16h AH=11h/10h; extended keys have their ASCII byte zeroed, and Shift/Ctrl/Alt+F1..F12 are reported as the plain F-key scancode (see platformNormalizeFKeyScan) with the modifier available from platformKeyboardGetModifiers.

platformNormalizeFKeyScan

int32_t platformNormalizeFKeyScan(int32_t scancode);

Map the BIOS scancodes for Shift/Ctrl/Alt+F1..F12 (Shift F1-F10 0x54-0x5D, Ctrl 0x5E-0x67, Alt 0x68-0x71, Shift/Ctrl/Alt F11-F12 0x87-0x8C) onto the plain F-key scancodes (F1-F10 0x3B-0x44, F11 0x85, F12 0x86). Any other scancode is returned unchanged. Applied by every backend's platformKeyboardRead.

Raw Keys and the Held-Key Table

#define PLATFORM_KEY_EVENT_QUEUE 64

void platformKeyHookInit(void);
void platformKeyHookShutdown(void);
bool platformKeyEventRead(RawKeyEventT *evt);
bool platformKeyIsDown(int32_t rawCode);
void platformKeyStateGet(uint8_t out[KEY_RAW_COUNT]);
void platformKeyStateClear(void);
void platformInjectScan(uint8_t scan);

The keyboard hook (on DOS a chain on INT 9 that reads port 60h before the BIOS handler stores the key for platformKeyboardRead) decodes every set-1 byte: 0xE0 prefixes, the fake Shift codes the keyboard wraps around grey keys (dropped), the Pause burst (one press and one release), and keyboard replies such as ACK and overrun (dropped). Each key going down or up updates the held-key table and is queued as a RawKeyEventT (at most PLATFORM_KEY_EVENT_QUEUE; newer events are dropped when the queue is full, the table still changes). Keys are named by raw code, KEY_RAW_CODE(scancode, extended) (see dvxTypes.h): Left and Right Shift, Ctrl, and Alt are keys of their own, and the code does not change with modifiers. dvxInit installs the hook and dvxShutdown removes it; the event loop reads the queue (onKeyUp, onRawKey) and clears the table whenever the keyboard focus moves. platformKeyStateGet copies the table (1 = down). platformInjectScan feeds one byte to the decoder as if the keyboard had sent it (the test script's KEYDOWN / KEYUP use it).

platformAltScanToChar

char platformAltScanToChar(int32_t scancode);

Translate an Alt+key scancode to its corresponding ASCII character. Returns 0 for non-printable scancodes.

System Information

platformGetSystemInfo

const char *platformGetSystemInfo(const DisplayT *display);

Return a pre-formatted text string with hardware/OS information (CPU, clock, memory, video, mouse, disks). Valid for the lifetime of the process.

platformGetMemoryInfo

bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);

Query total and free physical memory.

File System

platformValidateFilename

const char *platformValidateFilename(const char *dir, const char *name);

Validate a name for a new file or folder in folder dir (NULL or "" for the current one) against the rules of its disk. On DOS: platformValidateLongName where platformLongNames(dir), else platformValidateShortName, and dir with the name must stay under DVX_MAX_PATH. The host backend refuses an empty name, . and .., separators, and control characters, and ignores dir. Returns NULL if valid, or a human-readable error string. The file dialog, the File Manager (fileOpsValidateName), and the IDE's project rename all use it.

Long file names: platformLongNames / platformValidateLongName / platformValidateShortName / platformLongNameChar / platformShortPath

#define PLATFORM_LONG_NAME_MAX 255

bool        platformLongNames(const char *path);
const char *platformValidateLongName(const char *name);
const char *platformValidateShortName(const char *name);
bool        platformLongNameChar(char c);
bool        platformShortPath(const char *path, char *out, int32_t outSize);

The DOS C library uses the long-name calls (INT 21h 71xxh) by itself whenever a driver provides them (Windows, DOSLFN), so open, fopen, rename, mkdir, stat, and the directory readers all take and return long names; these functions cover what the C library does not.

platformLongNames is true when the drive holding path (NULL or "": the current drive) takes long file names. On DOS that is a loaded long-name driver covering that drive; elsewhere always.

platformValidateLongName and platformValidateShortName are the two DOS name rules by themselves, whatever the disk takes; each returns NULL or why the name is not valid. A long name has 1 to PLATFORM_LONG_NAME_MAX characters and may hold spaces, several dots, and + , ; = [ ]; it may not hold a control character or \ / : * ? " < > |, and may not end with a dot or a space (DOS drops them). An 8.3 name has 1 to 8 printable ASCII characters, then at most one dot and up to 3 more, none of them a space or " * + , / : ; < = > ? [ \ ] |. Neither may be . or .., or a device name (CON, PRN, AUX, NUL, CLOCK$, COM1 to COM9, LPT1 to LPT9) before its first dot. They are shared code, so the host tests check the DOS rules too.

platformLongNameChar is true when a long name may hold character c; a file transfer uses it to turn the characters a sender's name cannot keep into _.

platformShortPath writes the short (8.3) form of an existing path into out, for what reads names before a long-name driver loads (AUTOEXEC.BAT) or cannot take blanks (an old program's command line). On a drive with long names DOS gives every part's short name (INT 21h AX=7160h CL=1); elsewhere, and when DOS cannot, path itself is copied (platformPathTidy: "C:/DVX" gives "C:\\DVX"). False when out is too small.

platformStatSize

uint64_t platformStatSize(const struct stat *st);

The size stat reported for a file. A FAT32 file reaches 4 GB - 1 byte, but the DOS C library's st_size is a signed 32-bit off_t, so a file past 2 GB reads as negative there; platformStatSize reads it as the unsigned 32-bit size DOS keeps. Use it wherever a file size comes from stat or fstat.

platformMkdirRecursive

int32_t platformMkdirRecursive(const char *path);

Create a directory and all parent directories (like mkdir -p). Existing directories are not an error. Returns 0 on success, -1 on failure (including a path of DVX_MAX_PATH characters or more).

platformChdir

int32_t platformChdir(const char *path);

Change the working directory, including drive letter on DOS (switches drive first when the path starts with "X:"). If the chdir fails the previous drive is restored. A drive prefix that is not a letter is rejected. Returns 0 on success, -1 on failure.

Path separators: platformPathIsSep / platformPathNative / platformPathSame / platformPathIsUnder / platformPathJoin / platformPathDirName / platformPathTidy

bool        platformPathIsSep(char c);
char       *platformPathNative(char *path);
bool        platformPathSame(const char *a, const char *b);
const char *platformPathIsUnder(const char *path, const char *dir);
bool        platformPathJoin(char *out, int32_t outSize, const char *dir, const char *name);
bool        platformPathDirName(const char *path, char *out, int32_t outSize);
char       *platformPathTidy(char *path);

DVX paths follow DOS rules on every platform: '/' and '\\' are the same separator, so a path a program writes with '/' works on DOS exactly as with '\\', and names compare without case (DOS and the host build also share INI files, projects, and program paths). Every path DVX shows uses the native separator, DVX_PATH_SEP ('\\' on DOS). DVX's own path code goes through these functions; a program that splits, compares, or joins paths should too, never testing for one separator by hand.

platformPathDirEnd

char *platformPathDirEnd(const char *path);

Return a pointer to the last directory separator in path (platformPathIsSep), or NULL.

platformPathIsAbsolute

bool platformPathIsAbsolute(const char *path);

True for a path rooted at a separator ('/' or '\\') or qualified with a drive letter ("C:..."). A NULL path is not absolute.

platformPathBaseName

const char *platformPathBaseName(const char *path);

Return a pointer to the leaf (basename) portion of path: after the last separator, or after a drive ("C:A.TXT" gives "A.TXT").

platformCopyFile

bool platformCopyFile(const char *srcPath, const char *dstPath);

Byte-for-byte file copy. Returns false on any open, read, or write failure; a partially written destination is removed so no truncated copy is left behind. A copy onto itself (platformPathSame) does nothing and returns true: opening the copy would otherwise empty the original first.

platformPathSibling

#define PLATFORM_TMP_EXT         ".$"
#define PLATFORM_BAK_EXT         ".~"
#define PLATFORM_SIBLING_EXT_MAX 3

bool platformPathSibling(char *out, int32_t outSize, const char *path, const char *ext);

Write path with its file-name extension replaced by ext into out, followed by as many leading characters of the original extension as fit in PLATFORM_SIBLING_EXT_MAX characters (a full-length ext such as ".BAK" replaces the extension outright; a name with no extension gets ext appended). Replacing rather than appending keeps an 8.3 name valid on DOS without long file names, and keeping part of the original extension gives files that differ only in extension distinct scratch names: USER\DVX.INI becomes USER\DVX.$IN (temp) and USER\DVX.~IN (backup), FOO.APP and FOO.HLP become FOO.$AP and FOO.$HL. Returns false when the result does not fit in outSize or would name path itself (compared case-insensitively).

platformReplaceFile

bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);

Crash-safe swap of a fully written tmpPath in for path. The original is renamed aside to bakPath first (DOS rename cannot overwrite) and removed only once the replacement is in place; if the swap fails the original is restored and tmpPath removed. A directory at path is never moved aside: the call fails and removes tmpPath. A leftover bakPath is removed only while path exists; with path missing it may be the only copy left by a crash between the two renames, so it is kept (prefsLoad reads it back). The resource writer and the help compiler save this way, with the PLATFORM_TMP_EXT and PLATFORM_BAK_EXT siblings from platformPathSibling.

platformWriteFile

bool platformWriteFile(const char *path, const void *data, int32_t len);

Write len bytes of data as the whole of path, all or nothing: into the PLATFORM_TMP_EXT sibling, then swapped in with platformReplaceFile, so a full disk or an I/O error leaves the file as it was. False when it was not done. The preferences system, the IDE's saves, Setup, and the package records use it.

platformReadFile

char *platformReadFile(const char *path, int32_t *outLen);

Slurp a whole file into a freshly malloc'd, NUL-terminated buffer. Caller frees. Binary-safe: the NUL is past the end of the reported length.

platformGlobMatch

bool platformGlobMatch(const char *pattern, const char *name);

Simple glob pattern match. Case-insensitive. Supports * (zero or more chars) and ? (single char). Iterative (a mismatch lets the last * take one more character), so patterns with many stars stay fast.

platformFilterMatch

#define PLATFORM_FILTER_ALL "*.*"
bool platformFilterMatch(const char *name, const char *filter);

True when name matches filter: platformGlobMatch patterns separated by ';', ',', or blanks ("*.txt;*.bat", "*.bmp, *.jpg"). A pattern equal to PLATFORM_FILTER_ALL matches every file, with or without an extension, as on DOS; a NULL or blank filter matches everything too. The file dialog (dvxFileDialog) and the file operations library (fileOpsListDir) both use it, so a filter behaves the same everywhere.

platformDriveList / platformDiskSpace / platformBootDrive

#define PLATFORM_DRIVE_MAX      26
#define PLATFORM_DRIVE_ROOT_MAX 4

typedef enum {
    PlatformDriveFixedE,
    PlatformDriveRemovableE,
    PlatformDriveRemoteE        // network drive or CD-ROM
} PlatformDriveTypeE;

typedef struct {
    char               root[PLATFORM_DRIVE_ROOT_MAX];   // "C:\"
    PlatformDriveTypeE type;
} PlatformDriveT;

int32_t platformDriveList(PlatformDriveT *out, int32_t max);
bool    platformDiskSpace(const char *path, uint64_t *totalBytes, uint64_t *freeBytes);
char    platformBootDrive(void);

platformDriveList fills out[] with the drives that exist, in letter order, and returns the count. On DOS it uses IOCTL calls only (INT 21h AX=4409h remote, 440Eh logical drive map, 4408h removable), never the media, so empty floppy and CD-ROM drives are listed at once; the second letter of a shared floppy drive (B: on a one-floppy system) is left out because using it makes DOS ask for a disk swap. The host backend reports one drive, "/". System Information and the file operations library both use it.

platformDiskSpace reports the size and free space of the disk holding path (DOS: INT 21h AX=7303h, the FAT32 call that counts past 2 GB, on the root of the path's drive letter or the current drive, falling back to AH=36h, which tops out near 2 GB, where DOS does not have it; host: statvfs of the nearest existing folder of path, so a folder about to be created reports its disk). False when unknown.

platformBootDrive returns the letter of the drive DOS started from (INT 21h AX=3305h, DOS 4.0+), whose root holds AUTOEXEC.BAT, or 0 where there is none (older DOS, and every platform but DOS).

DVX folders: platformUserInit / platformUserDir / platformUserPath / platformDvxIniPath / platformAppUserDir / platformKindFind

#define DVX_SYSTEM_DIR "SYSTEM"
#define DVX_APPS_DIR   "APPS"
#define DVX_SHARED_DIR "SHARED"
#define DVX_USER_DIR   "USER"
#define DVX_DATA_DIR   "DATA"
#define DVX_USER_ENV   "DVXUSER"
#define DVX_INI_NAME   "DVX.INI"
#define DVX_ASSOC_NAME "ASSOC.INI"
#define DVX_FONTS_DIR  "FONTS"
#define DVX_SOUNDS_DIR "SOUNDS"
#define DVX_THEMES_DIR "THEMES"
#define DVX_WPAPER_DIR "WPAPER"
#define DVX_BANKS_DIR  "BANKS"
#define DVX_SAVERS_DIR "SAVERS"
#define DVX_VENDOR_DIR "KPUNCH"

typedef enum {
    DvxKindFontsE,
    DvxKindSoundsE,
    DvxKindThemesE,
    DvxKindWpaperE,
    DvxKindBanksE,
    DvxKindSaversE,
    DvxKindCountE
} DvxKindE;

void        platformUserInit(void);
const char *platformUserDir(void);
bool        platformUserDirOf(char *out, int32_t outSize, const char *dvxDir);
bool        platformUserInTemp(void);
bool        platformUserPath(char *out, int32_t outSize, const char *rel);
const char *platformDvxIniPath(void);
bool        platformAppUserDir(char *out, int32_t outSize, const char *appPath);
const char *platformKindName(DvxKindE kind);
bool        platformKindDir(char *out, int32_t outSize, DvxKindE kind, bool user);
bool        platformKindBrowseDir(char *out, int32_t outSize, DvxKindE kind);
bool        platformKindFind(char *out, int32_t outSize, DvxKindE kind, const char *name);
bool        platformKindNameOf(DvxKindE kind, const char *path, char *out, int32_t outSize);
char      **platformKindReadDir(DvxKindE kind);

The DVX folder (the working directory) holds the program, which Setup installs, an upgrade replaces, and DVX only reads; everything written while DVX runs goes in the user folder, so it is the one folder to back up.

  Folder                    Holds
  ------                    -----
  SYSTEM                    DVX's own tools and files, and the shipped DVX.INI and ASSOC.INI
  LIBS, WIDGETS             The libraries and widgets
  APPS\<vendor>\<program>   A program, its help, and DATA (the files it ships; read-only)
  SHARED                    FONTS, SOUNDS, THEMES, WPAPER, BANKS, and SAVERS: what DVX and add-on packages ship for every program (an add-on's in a folder of its vendor's, such as FONTS\ACME)
  USER (the user folder)    DVX.INI, ASSOC.INI (the user's changes to SYSTEM's), DVX.LOG, the user's own FONTS, SOUNDS, THEMES, WPAPER, BANKS, and SAVERS, KEYS (publishers' keys the user trusts), APPS\<program path> (each program's settings and saved files), and DOWNLOAD

Only packages put files in SHARED: DVX's own, and add-on packages (pkg.lib), each in its vendor's folders. A program reads it but never writes or installs into it; a program's own files live with it (its DATA folder, or resources inside the .app), under its own vendor and program folder, so two programs' files never collide.

platformUserInit, called once by the loader, decides the user folder: the folder the DVXUSER environment variable names, else USER in the DVX folder, made when missing. When it cannot be written (DVX on a CD-ROM without DVXUSER), DVXUSER in the TEMP folder is used instead and platformUserInTemp is true; without a TEMP folder nothing can be saved and platformIsReadOnly is true. The first start copies SYSTEM\DVX.INI to the user folder. platformUserDir is the folder (DVXUSER or USER before platformUserInit has decided); platformUserPath joins a name to it; platformDvxIniPath is its DVX.INI. platformUserDirOf is the user folder of a DVX installed in another folder (DVXUSER, else that folder's USER), for Setup.

platformAppUserDir is a program's folder in the user folder: APPS\KPUNCH\TERMINAL\TERMINAL.APP gives USER\APPS\KPUNCH\TERMINAL; a program outside the DVX folder gives USER\APPS\<its file name without the extension>. It is not made here (the shell's userDir and BASIC's App.User are made on first use).

SHARED and the user folder have the same six kinds of folder (DvxKindE): fonts, sounds, themes, desktop pictures, MIDI instrument banks, and screen savers. Each may hold folders of add-on vendors (SHARED\FONTS\ACME), one level deep, whose files are named VENDOR\NAME. platformKindName is a kind's folder name; platformKindDir is SHARED's (user false) or the user's (user true); platformKindBrowseDir is where a file dialog for the kind starts (the user's when it exists, else SHARED's). platformKindFind finds a file of a kind by name (NAME or VENDOR\NAME, either separator), the user's first, so a user's file of the same name wins; any other name with a folder in it, or an absolute one, is taken as the path it is (a VENDOR\NAME not in the kind's folders is tried that way too). platformKindNameOf is the other way: a file in one of the kind's folders gives its name alone, or VENDOR\NAME in a vendor's folder, which is what settings store (DVX.INI [sounds], [desktop] wallpaper and saver, and [audio] midiBank), so moving the user folder or DVX keeps them working. platformKindReadDir lists both folders: the user's names (sorted, case ignored), then SHARED's not among them, with each vendor folder's files as VENDOR\NAME (the folders themselves are not listed); free it with dvxReadDirFree. BASIC: FileOpsUserDir$, FileOpsUserPath$, FileOpsUserInTemp, FileOpsKindDir$, FileOpsKindBrowseDir$, FileOpsKindFind$, FileOpsKindName$, and FileOpsKindList (fileops.bas, with FILEOPS_KIND_FONTS to FILEOPS_KIND_SAVERS), and App.User for a program's own folder.

Read-only media: platformSetReadOnly / platformIsReadOnly / platformDirIsWritable / platformTempDir / platformSetLogMemory / platformLogMemory

#define PLATFORM_LOG_MEM_MAX 16384

void        platformSetReadOnly(bool readOnly);
bool        platformIsReadOnly(void);
bool        platformDirIsWritable(const char *dir);
const char *platformTempDir(void);
void        platformSetLogMemory(void);
const char *platformLogMemory(void);

platformUserInit records with platformSetReadOnly whether anything can be saved: platformIsReadOnly is true only when the user folder cannot be written and there is no TEMP folder to put it in (platformTempDir: the TEMP or else TMP variable in native separators, NULL when neither is set; platformDirIsWritable creates and removes a scratch file in dir). Then: the log is kept in memory; prefsSave and prefsSaveAs keep a file they cannot write in memory, and prefsLoad reads it back until DVX exits; the shell says so once at startup (and, when the user folder went in TEMP, says that instead); and a program that allows several copies runs only once when there is no TEMP folder (a second copy is a temporary copy of the module). The boot-time help rebuild is skipped when nothing can be saved or the APPS folder cannot be written (its output and counts could not be kept). platformSetLogMemory sends dvxLog to a buffer holding the latest PLATFORM_LOG_MEM_MAX bytes (the oldest whole lines are dropped), which platformLogMemory returns; platformSetLogPath goes back to a file. SETUP.EXE logs to memory unless /LOG= is given, so it never writes to its own disk.

platformLineEnding / platformStripLineEndings

const char *platformLineEnding(void);
int32_t     platformStripLineEndings(char *buf, int32_t len);

platformLineEnding returns the native line ending string ("\r\n" on DOS). platformStripLineEndings removes every '\r' from buf[0..len) in place and NUL-terminates the result, so buf must hold at least len + 1 bytes. Returns the new length.

String Helpers

dvxSkipWs

const char *dvxSkipWs(const char *s);

Advance past leading spaces and tabs. Returns s unchanged if NULL or already non-whitespace.

dvxTrimRight

int32_t dvxTrimRight(char *buf);

Strip trailing spaces, tabs, carriage returns, and newlines from buf in place. Returns the new length.

dvxCopyTrimmed

int32_t dvxCopyTrimmed(char *dst, int32_t dstSize, const char *src);

Copy src into dst without its leading spaces and tabs or its trailing whitespace, truncated to fit dstSize. A NULL src gives an empty string. Returns the length of the result. The usual way to read a trimmed value out of a text box: dvxCopyTrimmed(name, sizeof(name), wgtGetText(input)).

dvxNextArg

#define DVX_ARG_QUOTE '"'

const char *dvxNextArg(const char *p, char *out, int32_t outSize, bool *quoted);

Copy the next argument of the command line p into out and return the text after it, or NULL when only blanks are left (or p is NULL). Arguments are separated by blanks and tabs; one that starts with a double quote runs to the closing quote (or the end), keeps its blanks, and loses the quotes, and *quoted is set. The copy is truncated to fit outSize. This is the one rule for splitting command lines: the shell's Run command, program launch arguments, and the BASIC COMMAND$ function all use it. Walk every argument with:

const char *p = args;
bool        quoted;
char        arg[DVX_MAX_PATH];

while ((p = dvxNextArg(p, arg, sizeof(arg), &quoted)) != NULL) {
    // use arg
}

dvxArgAppend

int32_t dvxArgAppend(char *buf, int32_t bufSize, int32_t pos, const char *arg);

Append arg to the command line in buf at pos as one argument that dvxNextArg reads back whole: in double quotes when it holds a blank or a tab (a long name such as C:\My Programs\Thing.app), as it is otherwise. Returns the new length, or -1 when it does not fit (as dvxStrAppendf). The shell's file-type associations and F1 help build their command lines with it; BASIC programs have ShellArgQuote$.

DXE Module Support

platformRegisterDxeExports

void platformRegisterDxeExports(void);

Register platform and C runtime symbols with the dynamic module loader so DXE modules can resolve them at load time.

platformRegisterSymOverrides

void platformRegisterSymOverrides(const PlatformSymOverrideT *entries);

Register function pointer overrides for subsequently loaded modules. entries is terminated by {NULL, NULL}. On DOS this wraps dlregsym(). Nothing in DVX itself calls it: it is SDK API for embedders and test harnesses that need to substitute a symbol (e.g. a stub) before loading a module.

Interrupt-Safe Memory

platformLockRegion / platformUnlockRegion

int32_t platformLockRegion(const void *addr, uint32_t size, bool isCode);
int32_t platformUnlockRegion(const void *addr, uint32_t size, bool isCode);

Pin (or unpin) a code (isCode = true) or data range into physical memory so an interrupt handler can touch it without faulting under a paging DPMI host. On DOS this converts the address to linear using the code or data segment base and calls the DPMI lock/unlock linear region services. Shared by the key-up INT 9 hook and the serial driver's ISR. Returns 0 on success, -1 on failure; a no-op returning 0 on hosts without paging concerns.

Crash Recovery

platformInstallCrashHandler

void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn);

Install signal handlers for SIGSEGV, SIGFPE, SIGILL. On crash, logs platform-specific diagnostics via logFn, stores the signal in *crashSignal, then longjmps to recoveryBuf. Without a recovery buffer the timer service and the keyboard hook are removed and the signal's default action runs.

platformAppExit / platformAppAbort / platformSetAppExitHook

#define PLATFORM_ABORT_STATUS 3
typedef void (*PlatformAppExitFnT)(int status);

void platformSetAppExitHook(PlatformAppExitFnT fn);
void platformAppExit(int status);
void platformAppAbort(void);

What exit() and abort() mean to loaded modules. The DXE export table maps them to platformAppExit and platformAppAbort (the host build links every app with the same substitution, HOST_APP_LDFLAGS in common.mk). Both call the hook first; the shell's hook ends only the program that is current, the way End Task does (its task, or its callback running on the shell task, is abandoned through the crash-recovery path and the program is removed without its appShutdown and without a fault message; the log says "'name' called exit (status n); ending it"). abort() reports status PLATFORM_ABORT_STATUS. With no program current, or no hook, they end the process like the C library functions. Handlers a program registered with atexit are not run.

platformLogCrashDetail

void platformLogCrashDetail(int sig, PlatformLogFnT logFn);

Log platform-specific crash diagnostics. Called internally from the crash handler and exposed for manual invocation.

platformOutOfMemory

void platformOutOfMemory(size_t bytes);

Out-of-memory hook for the stb_ds dynamic arrays and hash maps (arrput, hmput, shput, and friends have no error return). stb_ds_wrap.h points STBDS_OUT_OF_MEMORY at it wherever the stb_ds implementation is compiled. It logs "out of memory (N bytes) in dynamic array" via dvxLog and raises SIGSEGV, so under platformInstallCrashHandler a failed growth takes the normal crash-recovery path: the shell terminates the app that was current (its task, or its callback running on the shell task), and a failure with no app current is logged and the shell returns to its main loop. With no handler installed (the loader, the tools, the BASIC compiler) the process terminates. It never returns. The stb_ds patch that adds the hook is recorded in thirdparty/stb_ds.PATCHES.md.

VGA Splash Screen

platformSplashInit / platformSplashShutdown

void platformSplashInit(void);
void platformSplashShutdown(void);

Enter/leave VGA mode 13h (SPLASH_W x SPLASH_H x 256) used for the boot splash.

  Define                Value   Description
  ------                -----   -----------
  SPLASH_W, SPLASH_H    320, 200  Splash screen size
  SPLASH_BPP            8       Bits per pixel
  SPLASH_PAL_ENTRIES    256     Palette entries
  SPLASH_PAL_CHANNELS   3       Bytes per palette entry (R, G, B)
  SPLASH_PAL_BYTES      768     Palette size in the file
  SPLASH_PIXEL_BYTES    64000   Pixel data size in the file
  SPLASH_DAC_SHIFT      2       8-bit RGB to 6-bit DAC value shift

platformSplashLoadRaw

bool platformSplashLoadRaw(const char *path);

Load and display a raw splash file (SPLASH.RAW, written by the bmp2raw tool): SPLASH_PAL_BYTES of palette (6-bit DAC values, R, G, B per entry) followed by SPLASH_PIXEL_BYTES of 8-bit pixels, top-to-bottom scanlines.

platformSplashFillRect

void platformSplashFillRect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t color);

Fill a rectangle on the mode 13h screen with a palette index. Used to draw the progress bar during module loading.

platformSplashNearestColor

uint8_t platformSplashNearestColor(uint8_t r, uint8_t g, uint8_t b);

Palette index of the loaded splash image closest to an 8-bit RGB color, so overlays drawn on the splash (the progress bar) do not depend on the image's palette layout. Returns 0 when no splash palette is loaded (always 0 on the host test backend).

Sound Library

Sound for DVX programs through whatever sound hardware the computer has: sampled sound from WAV and VOC files, tones and noise, and streams a program feeds itself (music, speech). Every program opens its own audio context; any number of programs play at the same time through one mixer, and everything a program opened is stopped and released when it ends, normally, killed, or crashed.

Header: audio/audio.h (programs), audio/audioDrv.h (sound drivers)

Loaded as: bin/libs/kpunch/audio/audio.lib, drivers in bin/libs/kpunch/audio/drivers/*.drv

Contexts and the Device

AudioCtxT *audio = audioOpen();                 // NULL: no driver could start

int32_t voice = audioPlayFile(audio, "BOOM.WAV", AUDIO_VOLUME_MAX, AUDIO_PAN_CENTER, false);

if (voice < 0) {
    dvxLog("no boom: %s", audioErrorText(voice));
}
...
audioClose(audio);                              // or let the program end

The first audioOpen starts the sound device and the last audioClose stops it. In between, after AUDIO_IDLE_CLOSE_SEC (3) seconds with nothing playing, the device's output is closed so it costs no CPU time (desktop sounds would otherwise keep it mixing silence), and the next sound opens it again with the same driver before it starts, so nothing of it is lost; audioGetInfo still reports the device. Functions returning int32_t give AUDIO_OK or a voice id (above 0) on success and a negative AUDIO_ERR_* code on failure; audioErrorText says what a code means. audioGetInfo describes the device: the driver's name and description, the mixing rate it achieved, its output bits and channels, the voice limit, and whether it plays sampled sound (hasDac), has a hardware synthesizer (hasSynth), or plays tones on a hardware tone generator (hasTone, the PC speaker). audioGetStats counts mixed blocks and frames, stream underruns, clipped samples, dropped voices, and the voices playing.

None of the functions may be called from an interrupt (a timer-service callback).

Samples and Voices

audioSampleLoad (file), audioSampleLoadMem (memory), and audioSampleCreate (raw PCM in an AudioFormatT: rate, 8 bits unsigned or 16 bits signed, 1 or 2 channels) make an AudioSampleT; audioSampleFree frees it and stops the voices playing it. audioPlay(sample, volume, pan, loop) plays it on a new voice; audioPlayFile loads a file and plays it, freeing the sample when the voice ends.

  Setting              Range
  -------              -----
  volume               0 .. AUDIO_VOLUME_MAX (256)
  pan                  AUDIO_PAN_LEFT (-256) .. AUDIO_PAN_RIGHT (256); centre plays at full volume on both sides and panning turns the other side down
  pitch                AUDIO_PITCH_MIN (64) .. AUDIO_PITCH_MAX (1024), AUDIO_PITCH_NORMAL (256) = recorded speed

audioVoiceSetVolume, audioVoiceSetPan, audioVoiceSetPitch, audioVoicePause, audioVoiceStop, and audioVoiceIsPlaying take the context and the voice id; a voice id of another context, or of a voice that has ended, gives AUDIO_ERR_VOICE. audioVoicePause(ctx, voice, paused) holds a sample's or a stream's voice where it is (it stays playing, silent, and takes nothing from its stream) or lets it go on; a tone or a note queue gives AUDIO_ERR_ARG. audioStopAll stops every sample and tone of a context.

audioSetEndFn(ctx, fn, userData) asks for a call when a voice of ctx ends, with the reason: AUDIO_END_FINISHED (played out), AUDIO_END_STOPPED (stopped, or its sample or stream went away), or AUDIO_END_DROPPED (the voice limit made room for a louder sound). The call comes from audioPump or the shell's idle time, as the program that owns the context, never from an interrupt. The mixer runs up to one buffer (about 50 ms) ahead of what is heard, so a notification can come that much before the sound has finished playing.

Tones

audioTone(ctx, wave, hz, volume, ms) plays a square (AUDIO_WAVE_SQUARE), triangle (AUDIO_WAVE_TRIANGLE), or noise (AUDIO_WAVE_NOISE) tone of hz (AUDIO_TONE_MIN_HZ 20 to AUDIO_TONE_MAX_HZ 20000) for ms milliseconds, or until stopped when ms is 0. For noise, hz is how fast the noise changes: higher sounds brighter. audioVoiceSetFrequency changes a playing tone. When the computer has an FM chip (and DVX.INI [audio] fmTones is not false), a square or triangle tone plays on a free FM voice (a square as a carrier modulated at twice its pitch, which gives odd harmonics like a square wave; a triangle as a sine) at up to the chip's highest note (about 6,200 Hz); when every FM voice is taken it is mixed like any sound. Its voice id, volume, pan, pitch, and end notification work the same either way. On a device with a DAC tones are mixed like any sound; on the PC speaker in its tones mode the loudest square or triangle tone playing is sounded by the timer chip.

Note Queues

AudioNotesT *tune = NULL;

audioNotesOpen(audio, AUDIO_WAVE_SQUARE, AUDIO_VOLUME_MAX, &tune);
audioNotesAdd(tune, 440 * AUDIO_MILLIHZ_PER_HZ, 250000);   // A for 1/4 s
audioNotesAdd(tune, 0, 50000);                              // a rest

A note queue plays tones and rests one after the other, each lasting exactly as long as asked (its length is counted in output frames from the time queued since the queue last went quiet, so many short notes do not drift). audioNotesAdd(notes, milliHz, us) appends a tone of milliHz thousandths of a hertz (AUDIO_TONE_MIN_HZ to AUDIO_TONE_MAX_HZ, or 0 for a rest) lasting us microseconds; up to AUDIO_NOTES_MAX entries may wait (AUDIO_ERR_BUSY beyond). audioNotesQueued says how many entries have not finished (and how many of them are tones), audioNotesSpace how many more fit, audioNotesClear silences the queue at once. The queue plays on its own voice (audioNotesVoice, for volume and pan) that the voice limit never drops, in the mixer or on the PC speaker's tone generator (never on the FM chip); it is silent while empty. music.lib plays QBasic's PLAY and SOUND through note queues.

Streams

AudioFormatT  fmt    = { 22050, 16, 2 };
AudioStreamT *stream = NULL;

audioStreamOpen(audio, &fmt, 250, fillMusic, &player, &stream);

audioStreamOpen(ctx, fmt, bufferMs, fill, userData, &stream) opens a stream of any rate from AUDIO_RATE_MIN (4000) to AUDIO_RATE_MAX (48000), 8 or 16 bits, mono or stereo, with a buffer of bufferMs milliseconds (0: 250). It plays at once on its own voice (audioStreamVoice), silence until samples arrive. Without fill the program writes samples with audioStreamWrite, which takes as many frames as fit and returns how many; audioStreamSpace and audioStreamQueued tell how much room and how much sound are left. With fill, the library asks for samples whenever the buffer has room: fill(stream, buf, frames, userData) writes up to frames frames in the stream's format and returns how many. Fill functions run from audioPump and the shell's idle time; a program that keeps the desktop busy every frame should call audioPump once a frame. A stream that runs dry plays silence and counts underruns. audioStreamFlush drops what the stream holds and has not played yet (a seek or a stop is heard at once; the two frames the mixer has read ahead still play). audioStreamClose stops and frees it.

audioDrv.h also declares, for audio.lib and the libraries built on it (music.lib), audioDecode (a WAV or VOC file in memory as 16-bit frames), audioConvert (8- or 16-bit frames as 16-bit ones), and audioWavParse(head, headBytes, fileBytes, &fmt, &dataAt, &dataBytes) (a WAV file's format and where its frames are, from its first bytes: the frames are clamped to the file and whole; AUDIO_ERR_FORMAT for anything but a RIFF WAVE file, AUDIO_ERR_UNSUPPORTED, AUDIO_ERR_CORRUPT).

Recording

AudioFormatT fmt = { 22050, 16, 1 };
AudioRecT   *rec = NULL;

if (audioRecFile(audio, "TALK.WAV", &fmt, AUDIO_REC_MIC, &rec) == AUDIO_OK) {
    // ... later
    audioRecClose(rec);
}

audioRecOpen(ctx, fmt, source, bufferMs, &rec) records from the card's microphone, line, or CD input (AUDIO_REC_MIC, AUDIO_REC_LINE, AUDIO_REC_CD; AudioInfoT.canRecord, and recSources with bit 1 << source for each the card has) into a buffer of bufferMs (0: AUDIO_REC_MS_DEFAULT, 2 seconds; AUDIO_REC_MS_MIN to AUDIO_REC_MS_MAX). fmt asks for a rate, 8 or 16 bits, and mono or stereo (0: AUDIO_RATE_DEFAULT, AUDIO_REC_BITS_DEFAULT, mono) and comes back as what the card records: its nearest rate, and its own bits and channels when it cannot do the ones asked (a card before the SB Pro records 8-bit mono). The program takes the frames with audioRecRead(rec, pcm, frames) before the buffer fills; audioRecQueued says how many wait, audioRecLost how many were dropped because the buffer was full (whole blocks, so the frames stay in order), audioRecFrames how many were taken, and audioRecPeak the loudest sample since it was last asked, in percent (a level meter). audioRecFile(ctx, path, fmt, source, &rec) records into a WAV file instead: the library writes the frames as they come (audioPump and the shell's idle time) and the file's sizes at audioRecClose; the file starts with an empty header, so one cut short still reads. Everything that can be checked (the arguments, another recording, the card and its input) is checked before the file is made, so a recording that cannot start leaves a file of that name alone. audioRecClose stops and returns AUDIO_ERR_WRITE when the file could not all be written. audioWavHeader(out, fmt, dataBytes) writes the AUDIO_WAV_HEADER_BYTES of a WAV header for programs writing their own files.

The Sound Blasters (sb), Windows Sound System boards (wss), and the Gravis UltraSound (gus) record; the DACs on the printer port, the PC speaker, and null do not. Most cards record or play, not both, so recording takes the card: its output closes (as after a silence), voices already playing hold where they are, and a new voice gets AUDIO_ERR_BUSY (the desktop's sounds are skipped) until the recording ends and the held voices play on. One recording at a time for all of DVX (another gets AUDIO_ERR_BUSY). Closing the context (the program's end) and a DOS program run with DVX stepped aside (audioSuspend) end the recording.

FM Synthesizer

int32_t v = audioFmAlloc(audio);                // AUDIO_ERR_NO_VOICE: all taken

audioFmWrite(audio, v, AUDIO_FM_CAR_LEVEL, 0);
audioFmWrite(audio, v, AUDIO_FM_FNUM_LOW, fnum & 0xFF);
audioFmWrite(audio, v, AUDIO_FM_KEY_BLOCK, AUDIO_FM_KEY_ON | (block << AUDIO_FM_BLOCK_SHIFT) | (fnum >> AUDIO_FM_FNUM_HI_SHIFT));
audioFmFree(audio, v);                          // key off, back to everyone

An OPL2 (9 voices) or OPL3 (18 voices, each playing left, right, or both) found by the opl driver is shared by every program: audioFmAlloc(ctx) takes a two-operator voice (the least recently given back first, so a released note's tail is cut last) and returns its id, AUDIO_ERR_NO_VOICE when all are taken, AUDIO_ERR_BUSY while a program has the whole chip, AUDIO_ERR_NO_SYNTH without a chip. audioFmWrite(ctx, voice, reg, value) sets a register of the voice's channel, numbered as for the first channel of an OPL2 (AUDIO_FM_MOD_CHAR 20h, AUDIO_FM_CAR_CHAR 23h, levels 40h/43h, attack 60h/63h, sustain 80h/83h, waveforms E0h/E3h, AUDIO_FM_FNUM_LOW A0h, AUDIO_FM_KEY_BLOCK B0h, AUDIO_FM_FEEDBACK C0h); the library adds the channel's offsets and bank. On an OPL3 a feedback value without an output bit (AUDIO_FM_OUT_LEFT, AUDIO_FM_OUT_RIGHT) plays on both sides. Frequencies: hz = fnum x AUDIO_FM_CHIP_HZ (49,716) x 2^block / 2^20. audioFmFree keys the voice off and gives it back; closing the context gives back all of its voices. audioFmGetInfo tells the chip, its voices, and how many are free.

audioFmRawAcquire(ctx) gives one program the whole chip: every voice the others hold is keyed off and taken back (their tones go on in the mixer), new voices are refused, and audioFmRawWrite(ctx, reg, value) sets any register (bank x AUDIO_FM_BANK_REGS + index). A second program asking gets AUDIO_ERR_BUSY. audioFmRawRelease (or closing the context) silences the chip, sets it up again, and gives its voices back to everyone.

audioFmAlloc, audioFmFree, audioFmWrite, and audioFmRawWrite may also be called from a timer callback (platformTimerAdd); music.lib plays MIDI that way.

Wavetable Synthesizer

uint32_t      addr = 0;
int32_t       v    = audioWtAlloc(audio);       // AUDIO_ERR_NO_VOICE: all taken
AudioWtVoiceT note;

audioWtMemAlloc(audio, frames * 2, 16, &addr);
audioWtUpload(audio, addr, pcm, frames, 16);
audioWtNoteInit(&note);                         // a plain note: no envelope, the filter open
note.addr = addr;
note.end  = frames;
note.bits = 16;
audioWtStart(audio, v, &note);                  // pitch 0: AUDIO_WT_PITCH_RATE (44,100) frames a second

A wavetable synthesizer (a Gravis UltraSound's GF1, a Sound Blaster AWE32's or AWE64's EMU8000) plays samples from its own memory, and its voices and memory are shared by every program. audioWtGetInfo tells the chip (description, voices, free voices, memory, the largest free block, what it does itself: AUDIO_WT_ENVELOPES, AUDIO_WT_LFOS, AUDIO_WT_FILTER, AUDIO_WT_EFFECTS, AUDIO_WT_8BIT for 8-bit samples, AUDIO_WT_BANK_256K when a 16-bit sample must not cross a 256 KB boundary, AUDIO_WT_ROM for a General MIDI sample ROM; and its level, the centibels a voice at full volume is louder than full-scale digital sound, measured: 0 on a GUS, whose digital sound plays on its own voices, 76 on an AWE). audioWtMemAlloc(ctx, bytes, bits, &addr) takes memory for samples of bits bits (AUDIO_ERR_NO_ROOM when no free block is large enough), audioWtUpload(ctx, addr, frames, count, bits) copies 16-bit frames into it at bits, audioWtMemFree gives it back. audioWtAlloc(ctx) takes a voice (the least recently given back first), audioWtStart(ctx, voice, note) starts it on a note (AudioWtVoiceT: the sample's address, or AUDIO_WT_ADDR_ROM with an address in the ROM, and in frames from there the one it starts from (offset: before the loop or inside it), its loop, and its end; pitch in cents, 0 for AUDIO_WT_PITCH_RATE (44,100) frames a second; attenuation in centibels; pan; the filter, reverb and chorus; both envelopes in timecents; both LFOs; audioWtNoteInit makes a plain one, with no envelope or LFO shaping it, the filter open, no effects, centred, at full volume, for the caller to set the sample on), audioWtUpdate changes what its what bits say (AUDIO_WT_SET_PITCH, _VOLUME, _PAN, _FILTER, _EFFECTS, _LFO), audioWtRelease lets the key up, audioWtStop stops it at once, audioWtActive is false once it is silent, audioWtFree gives it back. The chip does what its flags say; the program does the rest by updating the voice as the note goes on. Closing the context gives back its voices and its memory. The voice calls may be made from a timer callback (music.lib plays SoundFont MIDI and modules that way); the memory calls may not.

MIDI Port

With DVX.INI [audio] midi = mpu401 the mpu401 driver opens an MPU-401 (or compatible, such as a Sound Blaster 16's) in UART mode, for an external synthesizer such as a Roland MT-32 or SC-55. The port belongs to one program at a time: audioMidiAcquire(ctx) (AUDIO_ERR_BUSY when another has it, AUDIO_ERR_NO_SYNTH without a port), audioMidiPut(ctx, byte) sends a byte and returns AUDIO_ERR_BUSY when the port cannot take one yet (send it again later; it may be called from a timer callback), audioMidiRelease gives it back (closing the context does too). audioMidiGetInfo tells whether there is a port and whether it is in use. music.lib uses the port for MIDI music.

MIDI Input

With DVX.INI [audio] midiIn = mpu401 the same port also brings in what a MIDI keyboard or another instrument plays (the port is shared with MIDI music when midi = mpu401 too). One program reads it at a time: audioMidiInOpen(ctx) (AUDIO_ERR_NO_SYNTH when there is no MIDI input, AUDIO_ERR_BUSY when another program reads it), audioMidiInClose(ctx) (closing the context does it too, and a DOS program run with DVX stepped aside closes it). A timer reads the port AUDIO_MIDI_IN_HZ (1,000) times a second into a buffer of AUDIO_MIDI_IN_BYTES with the time each byte came; audioMidiInRead(ctx, &msg) gives the next whole message, oldest first (false when none waits): AudioMidiMsgT has its status (a channel message 80h..EFh, the kind and the channel, or system common F1h..F3h), data1, data2, length (1 to 3 bytes), and ms, when its first byte came in milliseconds since audioMidiInOpen. Running status is filled in; clock and active-sensing bytes are dropped and system exclusive messages skipped. audioMidiInClock(ctx) is the time now on that clock, and audioMidiInLost(ctx) the bytes dropped because the buffer was full (the program read too late). audioMidiGetInfo says whether there is MIDI input (inPresent) and whether a program reads it (inOwned). music.lib plays what comes in (musicMidiSend) and records it into MIDI files (musicMidiRecordNew); the Sound Recorder does both.

Volume and the Voice Limit

audioSetMasterVolume sets the volume of all sound (DVX.INI [audio] volume, in percent, sets it when the device starts); audioSetVolume sets the volume of everything one context plays. At most the voice limit (DVX.INI [audio] voices, 1 to AUDIO_VOICES_MAX, default 16) plays at once. Starting another sample or tone then stops the quietest one playing (it ends with AUDIO_END_DROPPED); when the new sound would itself be the quietest it is not started (AUDIO_ERR_NO_VOICE). Streams are never dropped.

Files

WAV: uncompressed PCM, 8-bit unsigned or 16-bit signed, mono or stereo, 4000 to 48000 Hz; WAVE_FORMAT_EXTENSIBLE with the PCM sub-format is accepted, other chunks are skipped, and a data chunk that claims more bytes than the file holds plays what is there. VOC (Creative Voice File, header checksum checked): sound blocks with 8-bit unsigned samples, continuation, silence, extended (rate and stereo for the next sound block), and new-format blocks with 8-bit or 16-bit PCM; markers and text are skipped, and a repeated section plays once. Compressed sound (ADPCM, 24-bit, floating point) gives AUDIO_ERR_UNSUPPORTED, a damaged file AUDIO_ERR_CORRUPT, a file that is neither AUDIO_ERR_FORMAT.

The Mixer

All sound is mixed in 16-bit integers at the rate the device achieved (the mixer never assumes the rate it asked for: a Sound Blaster Pro asked for 22,050 Hz plays at 22,222 Hz), and every source is resampled to it with linear interpolation. Gains combine volume, pan, context volume, and master volume; the sum is clipped to 16 bits. The mixer runs from the sound card's own interrupt (at the end of each half buffer of about 23 ms), or from the timer service for devices without one, so sound keeps playing however busy the programs are. It uses no floating point and touches only memory pinned for interrupts (sample and stream buffers are pinned when they are made). A 486's multiplies are slow, so the mixer does none whose result is known: a sound at the device's rate is copied frame for frame without interpolating, a mono one is worked out once for both sides, and a centred mono one scaled once. Measured on an emulated 486DX2-66 at 22,050 Hz, eight mono sounds at the device's rate take about 19 percent of the CPU, eight at half of it (interpolated) about 30 percent.

Settings

DVX.INI section [audio]:

  Key          Meaning
  ---          -------
  driver       auto (default: find the hardware), or a driver name: wss, sb, gus, lpt, speaker, null
  rate         mixing rate asked for, 4000 to 48000 (default 22050)
  bits         0 (the device's best), 8, or 16
  channels     0 (the device's best), 1, or 2
  voices       voices mixed at once, 1 to 32 (default 16)
  volume       master volume in percent (default 100)
  speakerPcm   true: sampled sound on the PC speaker (see below); default false
  fmTones      false: tones always in the mixer, never on FM voices; default true
  oplPort      the FM chip's port, hex (default 388)
  midi         where MIDI music plays (music.lib): auto (default: a wavetable synthesizer with its SoundFont, else the FM chip, else the software synthesizer), mpu401, fm, software, softwave (the SoundFont in software)
  soundFont    the SoundFont for wavetable MIDI (music.lib): a name in the banks folders or a path, or an UltraSound's ULTRASND.INI; not set, the standard one, KPUNCH\DVXGM.SF2 (Control Panel's Sounds page picks it)
  wssPort      the Windows Sound System board's port, hex (530, 604, E80, or F40; not set: each is tried)
  wssIrq       its IRQ: 7 (default), 9, 10, or 11
  wssDma       its DMA channel: 0, 1 (default), or 3
  gusVoices    voices the Gravis UltraSound runs, 14 to 32 (default 28: 22,050 Hz output; fewer, a higher rate)
  lptDac       the parallel port DAC: auto (default: a Disney Sound Source, else a Stereo-on-1), covox, disney, stereo
  lptPort      its port, hex (not set: LPT1)
  midiIn       where MIDI input comes from: none (default) or mpu401 (the MPU-401 port)
  mpuPort      the MPU-401's port, hex (default 330); the port is only looked for with midi or midiIn = mpu401
  mpuMode      what is on the MPU-401 (music.lib): gm (default), mt32, or raw
  midiBank     the OP2 instrument bank for FM MIDI (music.lib): its name in SHARED\BANKS or the user's BANKS folder (an add-on's as VENDOR\NAME), or a path from the DVX folder; not set, the bundled one (Control Panel's Sounds page picks it)

A settings screen reads and writes the keys it offers through the library, so the key names live in one place (audio.h, Settings; prefs is a copy of DVX.INI the caller loads and saves): audioSettingDriver(prefs) (AUDIO_DRIVER_AUTO when missing), audioSettingMidi(prefs) (an index in audioMidiChoices, auto when missing or unknown), audioSettingMidiIn(prefs) (an index in audioMidiInChoices, none when missing or unknown), audioSettingModules(prefs) (an index in audioModulesChoices, wavetable when missing or unknown), audioSettingVolume(prefs) (percent, clamped to 0..100, 100 when missing), and audioSettingsStore(prefs, driver, midi, modules, volume) writes all four (an empty driver stores auto, a midi index out of range stores auto, a modules index out of range wavetable); audioSettingMidiInStore(prefs, midiIn) writes midiIn (an index out of range stores none), which takes effect when the device next starts. audioMidiChoices[AUDIO_MIDI_CHOICE_COUNT] holds the midi choices in list order, each with its label for people and the name stored (a PrefsChoiceT, dvxPrefs.h); audioMidiInChoices[AUDIO_MIDI_IN_CHOICE_COUNT] the midiIn choices, audioModulesChoices[AUDIO_MODULES_CHOICE_COUNT] the modules choices (wavetable: on a wavetable synthesizer's voices when it is free and the module fits there; software: always mixed in software). audioPercentToVolume(percent) turns a percent into 0..AUDIO_VOLUME_MAX for audioSetMasterVolume. The device reads the driver and volume through the same functions when it starts. The Control Panel's Sounds page uses all of these.

Desktop Sounds

The library plays the sounds DVX makes for its events (dvxPlaySound in dvxApp.h): when it loads it sets DVX's sound hook to audioDeskPlay(event, wait). The file of each event is read from DVX.INI [sounds] at the first sound: the key is audioDeskKey(event) and the value a WAV or VOC file, empty or missing for none: a name alone is found in the sound folders (the user folder's SOUNDS first, then SHARED\SOUNDS: platformKindFind), anything else is a path; audioDeskReload makes the next sound read the section again (the Control Panel calls it after saving). The sounds play on a context of the library's own, so no program's end cuts one short, at full volume under the master volume. When no sound device starts, the sounds stay silent without trying again until the next reload. With wait the call returns once the sound has played (at most 5 seconds); after the exit sound further events are quiet (DVX is ending). audioDeskLabel(event) names an event for people, audioDeskPlayFile(path) plays any file the same way (to try one out). audioDeskFile(prefs, event) reads an event's file from a copy of DVX.INI ("" for none) and audioDeskSetFile(prefs, event, path) sets it; the library's own reading of [sounds] goes through audioDeskFile too. The default set, made by DVX's own sound generator at build time, is in SHARED\SOUNDS.

  Key           Event
  ---           -----
  default       the default beep (dvxBeep; a message box without an icon)
  startup       DVX started
  exit          DVX is ending
  error         a message box with MB_ICONERROR
  warning       MB_ICONWARNING
  question      MB_ICONQUESTION
  information   MB_ICONINFO
  windowOpen    a window opened (not a dialog)
  windowClose   a window closed (not a dialog)

Drivers

  Driver       Device
  ------       ------
  sb           Sound Blaster family by DSP version: 1.x (8-bit single-cycle), 1.5 / 2.0 (8-bit auto-init, high-speed above 23,000 Hz on 2.0), Pro and Pro 2 (8-bit stereo at 11,025 or 22,050 Hz), 16 (8- or 16-bit, mono or stereo, any rate from 5,000 to 44,100 Hz). It records with the same modes' input commands (sbRecordPlan): before the Pro the microphone only, 8-bit mono up to 13,000 Hz (15,000 on DSP 2.01, high-speed above 13,000); the Pro and the 16 also the line and CD inputs (mixer 0Ch; 3Dh / 3Eh with the microphone at full on the 16), recording what they play; the mixer registers it changes are put back when it stops. The port, IRQ, and DMA channels come from the BLASTER variable (e.g. SET BLASTER=A220 I5 D1 H5); without it an SB16 is found at 220h or 240h and asked for its IRQ and DMA. If no interrupt arrives from the card within half a second (a wrong IRQ), the next driver is tried.
  speaker      The PC speaker. By default tones only: the timer chip plays the loudest square or triangle tone as a square wave, with no interrupts and no CPU cost; sampled sound and noise are not heard. With speakerPcm = true it plays everything by pulse-width modulation: the timer interrupt runs at 22,096 Hz and each interrupt sets the next pulse (about 5.7 bits). That costs a lot of CPU time: 22,096 interrupts a second plus the mixing. A 486DX2-66 spends a large share of its time on it (estimated 10 to 15 percent for the interrupts alone on plain DOS, much more under a memory manager or Windows) and can lose interrupts, which slows the sound and the DVX clock down. Meant for Pentium-class machines.
  wss          A Windows Sound System board (an AD1848 codec or its kin: Microsoft's, Crystal CS4231 / CS4248, Aztech, OPTi in WSS mode) at wssPort: 8- or 16-bit, mono or stereo, the codec's nearest rate, double-buffered by auto-init DMA on wssIrq and wssDma (set through the board's configuration register; a Plug and Play board set up by its own utility must be on the ones DVX.INI names). It records the same way (the codec's capture on the same DMA channel): the line input, AUX1 (the CD on WSS boards), or the microphone with its 20 dB boost, putting back the inputs it found. Tried before the Sound Blaster, which such a board often also has.
  gus          A Gravis UltraSound, found through the ULTRASND variable (port, DMA channels, IRQs: SET ULTRASND=220,1,1,11,5): digital sound on two of its voices from a ring at the top of its memory, refilled by DMA at each half, and its wavetable synthesizer on the rest (gusVoices active in all). It records from the microphone or the line input on ULTRASND's second DMA channel (the first when the second is not given): an UltraSound MAX (board version 10 or 11) through its CS4231 codec, as a Windows Sound System board records (8- or 16-bit, mono or stereo, the codec's rates), any other board with the GF1's own sampling (8-bit, mono or stereo, 5,510 to 44,100 Hz, each half started from the terminal count of the one before, since the GF1 does not run on by itself); no emulator DVX is tested on records on a GUS, so this follows Linux's drivers. Tried after a Sound Blaster beside it, which then plays the digital sound while the GUS plays music (guswt).
  lpt          A DAC on the parallel port (lptDac, lptPort): a Covox Speech Thing (used only when lptDac = covox: it cannot be found), a Disney Sound Source (a FIFO at a fixed 7 kHz, topped up by the timer), or a Stereo-on-1. 8-bit at most 11,048 Hz; the Covox and the Stereo-on-1 take a timer interrupt for every frame.
  null         No sound device: plays nothing, but voices still run their course and end, so programs behave the same.
  opl          (synthesizer) The OPL2 or OPL3 FM chip of an AdLib or Sound Blaster at 388h (oplPort), found by the AdLib timer test (timer 1 must overflow at its fastest count and not at its slowest, which also tells the AdLib-compatible timers of a Gravis UltraSound, which has no FM, from a chip); the status register tells an OPL2 from an OPL3. Register writes are timed by status reads (3.3 and 23 us on an OPL2) with interrupts off around each index/data pair. Closing zeroes every register.
  awe          (synthesizer) The EMU8000 of a Sound Blaster AWE32 or AWE64 (BLASTER's E, else A + 400h), set up as Creative's programmer's guide gives: 30 voices (two carry the FM chip's sound), its memory in 512 KB steps, envelopes, LFOs, filter, reverb and chorus in hardware, and its 1 MB General MIDI sample ROM.
  gus, guswt   (synthesizer) The Gravis UltraSound's voices playing samples from its memory, with the library running the envelopes and LFOs: gus.drv's own when it plays the digital sound too, else guswt.drv (the GUS beside another sound device, all its voices for music).
  mpu401       (synthesizer) An MPU-401 MIDI port in UART mode at mpuPort, only with midi or midiIn = mpu401: reset (FFh, answered FEh; tried twice, since a port left in UART mode takes the first FFh as data), then UART mode (3Fh). Bytes are sent when the port says it is ready (a short wait, else the caller sends later); input the port has waiting then is kept for get, the MIDI input's timer (64 bytes; more is dropped while nobody reads it). Closing sends All Notes Off on every channel and resets the port.

audioDriverNames(names, max) lists the short names of the sound drivers installed, the one tried first first (for a settings screen; AUDIO_DRIVERS_MAX is the most DVX's own callers ask for).

Sound drivers are tried in this order: the one DVX.INI names, then from the highest priority down (Windows Sound System, Sound Blaster, Gravis UltraSound, parallel port DAC, PC speaker, null). The first that starts is used. Synthesizer drivers are opened with the sound device, beside it: the sound device's own first (the GUS's), then of each kind (FM chip, MIDI port, wavetable) the first that finds its hardware, and they close with the device. The sound device's description and hasSynth in AudioInfoT tell whether there is a synthesizer.

Writing a Driver

A driver is a driver module (see dvxDrv.h in the API reference): <name>.drv in bin/libs/kpunch/audio/drivers, built by the audio Makefile from each drivers/<name>/ folder, that exports one AudioDriverT named audioDriver, written with AUDIO_DRIVER(name, AUDIO_PRIORITY_*, .open = ..., .play = ..., .close = ...). The descriptor carries DVX_ABI_VERSION; a driver built for another version is refused. audio.lib loads the drivers when the first program opens sound, keeps the one that starts (and the synthesizers that find their hardware) and unloads the rest. open detects the hardware and fills AudioDrvInfoT (description, achieved rate, bits, channels, hasDac, hasSynth, a hardware tone function) without making a sound; play starts output; close stops it and releases everything, after which the mixer is not called again. After a few seconds of silence audio.lib calls close and later open and play again on the same driver, so open must work more than once and must not make a sound (the driver's module stays loaded in between). A driver whose card records also sets AudioDrvInfoT.recSources and gives recordStart(want, source, got) and recordStop: recordStart is called with the output closed and takes the card for recording (its own DMA buffer and interrupt; the format the card records in got), its interrupt then calls audioRecPush(src, bytes) with each block recorded (whole frames; a block that does not fit is dropped and counted), and recordStop gives everything back as close does (sb.drv, wss.drv, and gus.drv use their close for both; the AD1848 codec's routines a Windows Sound System board and an UltraSound MAX share are codec/codecHw.c). While playing, the driver calls audioMixRender(buffer, frames) for each block of output from its own interrupt, or calls audioClockStart to have the timer service drive the mixer (the output is thrown away) when it only plays tones or nothing. Code and data an interrupt touches must be pinned: audioLockSpan pins a module's listed functions and data (a module's code comes before its data, so one span covers them), and the library is built without hot/cold function splitting and without loops turned into library calls. Interrupt code must not call the C library, DVX, or DOS and must not use floating point. audio.lib stops the device from a shutdown hook if DVX ends without closing it.

A synthesizer driver exports an AudioSynthDriverT named audioSynth instead, written with AUDIO_SYNTH_DRIVER(name, priority, .kind = AUDIO_SYNTH_FM, AUDIO_SYNTH_MIDI, or AUDIO_SYNTH_WAVETABLE, .open = ..., .close = ..., then .write, .put, or the wavetable functions). open detects the hardware, fills AudioSynthDrvInfoT (description; for FM the voices and whether it is stereo; for a wavetable synthesizer its voices, memory, AUDIO_WT_* flags, and level, 0 when it is as loud as the digital sound or not known) and leaves it silent; close silences it. write(reg, value) sets an FM register (bank x AUDIO_FM_BANK_REGS + index) and must keep the index/data pair together (audioIrqOff / audioIrqRestore); put(byte) sends a MIDI byte, returning false when the port is not ready; a MIDI driver whose port receives also has get(&byte), the next byte the port has (false when none), which the MIDI input's timer calls (mpu401.drv keeps what put finds waiting for get). A wavetable driver has upload(addr, frames, count, bits) (program time; addresses as audioWtMemAlloc gives them) and start, update, release, stop, and active for its voices (numbered from 0, as audio.lib hands them out); it plays a note as AudioWtVoiceT gives it, in its own units. write, put, and the voice functions run at interrupt time too (timer callbacks), so they follow the rules above and are pinned by the driver. A sound driver whose card also has a synthesizer (the GUS) points AudioDrvInfoT.synth at its own AudioSynthDriverT, which is opened before the others.

BASIC

src/include/basic/sound.bas (DECLARE LIBRARY "audio"): PlayWave(fileName), PlayWaveLoop(fileName), PlayTone(frequency, milliseconds, volume), PlayNoise(frequency, milliseconds, volume) return a voice number (0 on failure: SoundError$ says why); StopWave(voice), StopAllSounds, SoundPlaying(voice), SoundVolume(percent), SoundInfo$ (the device, e.g. "Sound Blaster 16 (DSP 4.05) at 220h, 22050 Hz, 16-bit stereo"). Volumes are percent. Each BASIC program plays through its own context, closed when it ends.

For a settings program (the Control Panel's Sounds page): SoundEventCount, SoundEventLabel$(eventIdx), SoundEventKey$(eventIdx) (audioDeskLabel / audioDeskKey; events numbered from 0 in DvxSoundE order); SoundEventFile$(prefs, eventIdx) and SoundSetEventFile(prefs, eventIdx, fileName) (audioDeskFile / audioDeskSetFile); SoundEventsReload (audioDeskReload); SoundTestFile(fileName) (audioDeskPlayFile, a name alone found in the sound folders as the events' files are: a voice number, or 0 with SoundError$); SoundFolder$ (SHARED\SOUNDS, the sounds DVX ships; FileOpsKindDir$ and FileOpsKindList give the user's too); SoundMasterVolume and SoundSetMasterVolume(percent) (audioGetMasterVolume / audioSetMasterVolume in percent); SoundDriverCount (reads audioDriverNames, at most AUDIO_DRIVERS_MAX) and SoundDriverName$(index); SoundDriverAuto$ (AUDIO_DRIVER_AUTO); SoundMidiCount and SoundMidiLabel$(index) (audioMidiChoices); SoundMidiInCount and SoundMidiInLabel$(index) (audioMidiInChoices); SoundModulesCount and SoundModulesLabel$(index) (audioModulesChoices); SoundSettingDriver$(prefs), SoundSettingMidi(prefs), SoundSettingMidiIn(prefs), SoundSettingModules(prefs), SoundSettingVolume(prefs), SoundSettingsStore(prefs, driver, midi, modules, volume), and SoundSettingMidiInStore(prefs, midiIn) (the audioSetting functions). prefs is a prefs.bas handle (prefsBasHandle, dvxPrefs.h); a handle that is not open reads the defaults and stores nothing.

The rest of audio.h, in percent: PlayTriangle (audioTone with AUDIO_WAVE_TRIANGLE); SoundGetVolume (audioGetVolume); SoundCaps(field) (audioGetInfo: "dac", "synth", "tone", "running", "record" True / False; "voices" = voiceLimit, "rate", "bits", "channels", "recordsources"; any case, 0 for another field); SoundPlayEvent(eventIdx, wait) (dvxPlaySound / dvxPlaySoundWait, so the hook audioDeskPlay plays it; CONSTs SOUND_EVENT_* in DvxSoundE order). Samples: SoundLoad(fileName) (audioSampleLoad) and SoundCreate(pcm AS BINARY STRING, rate, bits, channels) (audioSampleCreate; frames = bytes / frame size, a trailing partial frame ignored) return a sound number; SoundFree (audioSampleFree), SoundLength (audioSampleInfo, in milliseconds), SoundPlay(sound, volume, pan, loopIt) (audioPlay; pan -100..100 scaled to AUDIO_PAN_LEFT..AUDIO_PAN_RIGHT). Voices: SoundSetVolume, SoundSetPan, SoundSetPitch(voice, percent) (25..400 percent of AUDIO_PITCH_NORMAL, clamped), and SoundSetFrequency(voice, hz) (the audioVoiceSet functions), SoundPause(voice) and SoundResume(voice) (audioVoicePause; a sound's or a stream's voice); the result is kept for SoundError$. Streams (push only): SoundStreamOpen(rate, bits, channels, bufferMs) (audioStreamOpen without a fill function), SoundStreamWrite(stream, pcm AS BINARY STRING) (audioStreamWrite of the whole frames; returns the bytes taken), SoundStreamSpace / SoundStreamQueued (frames), SoundStreamVoice, SoundStreamFlush (audioStreamFlush), SoundStreamClose. Recording, one per program (audioRecFile): RecordStart(fileName, rate, bits, channels, inputSource) (True / False with SoundError$; 0 for the defaults), RecordStop (audioRecClose; True when the file was all written), Recording, RecordLength (milliseconds recorded, written or waiting), RecordLevel (audioRecPeak), RecordLost, RecordRate, RecordBits, RecordChannels (audioRecFormat), with CONSTs RECORD_MIC, RECORD_LINE, RECORD_CD (AUDIO_REC_*) and SoundCaps "record" and "recordsources". FM: FmInfo$(field) (audioFmGetInfo: "description", "voices", "free", "stereo", "owned" as text), FmAlloc, FmWrite(voice, reg, value) (True / False), FmFree, FmRawAcquire, FmRawWrite(reg, value), FmRawRelease, with CONSTs FM_* for the AUDIO_FM_* values and FM_BANK_REGS (AUDIO_FM_BANK_REGS). Wavetable: WtInfo$(field) (audioWtGetInfo: "description", "voices", "free", "memory", "freememory", "level", and the flags "envelopes", "lfos", "filter", "effects", "8bit", "rom" as text), WtMemAlloc(bytes, bits) (an address, -1 for no room), WtUpload(address, pcm AS BINARY STRING, bits) (16-bit mono samples), WtMemFree, WtAlloc, WtStart(voice, address, startFrame, endFrame, loopStart, loopEnd, loopMode, bits, pitch, attenuation, pan) (a plain note: no envelope or LFO, the filter open; pitch in cents, attenuation in centibels, pan -100..100 scaled to AUDIO_WT_PAN_MAX), WtSetPitch, WtSetAttenuation, WtSetPan (audioWtUpdate of the note kept for the voice; True / False), WtRelease, WtStop, WtActive, WtFree. The MIDI port, declared in music.bas: MidiPortInfo$(field) (audioMidiGetInfo: "description", "present", "owned", "input", "inputowned"), MidiAcquire, MidiPut(byteValue) (False on AUDIO_ERR_BUSY: send it again), MidiRelease; its input: MidiInOpen (True / False with SoundError$), MidiInClose, MidiInMessage (audioMidiInRead: the message packed as status + data1 * 256 + data2 * 65536, AUDIO_MIDI_PACKED_*, or MIDI_NONE, -1), MidiInTime (the ms of the message MidiInMessage gave last), MidiInClock (audioMidiInClock), MidiInLost, with CONSTs MIDI_NOTE_OFF to MIDI_PITCH_BEND, MIDI_KIND_MASK, and MIDI_CHANNEL_MASK. CONSTs SOUND_PAN_*, SOUND_PITCH_* (percent), SOUND_RATE_* (AUDIO_RATE_*), and SOUND_STREAM_MS_* (AUDIO_STREAM_MS_*).

Sound and stream numbers are 1..64 per kind, valid only for the program that got them; audioClose of a BASIC context forgets its numbers (audioBasForget) as it frees the samples and streams. BINARY STRING parameters arrive as a pointer and a byte count (two C parameters; see the DECLARE LIBRARY rules in the BASIC reference).

BASIC Development Library

basdev.lib is the DVX BASIC compiler and what a development tool builds on it: the build pipeline (basBuild.h), the compile-time Control.Member validator (basValidate.h), the debugger's view of a paused program (basDebug.h), and the SUB / FUNCTION scanner (basProcs.h). The DVX BASIC IDE uses it, and BASIC programs reach it through compiler.bas and debug.bas (basDevBas.c holds their entry points). It also carries the STUB and noicon resources every build inside DVX uses (BAS_BUILD_STUB_CARRIER). It needs basrt.lib, which holds the run host (basRunHost.h) the IDE, the standalone stub, and debug.bas share.

Headers: apps/dvxbasic/basBuild.h, apps/dvxbasic/basdev/basValidate.h, apps/dvxbasic/basdev/basDebug.h, apps/dvxbasic/basdev/basProcs.h, apps/dvxbasic/basdev/basSource.h, apps/dvxbasic/basdev/basHelpMap.h, apps/dvxbasic/basdev/basProject.h, apps/dvxbasic/formrt/basRunHost.h

Compiling (basBuild.h)

  Function                Description
  --------                -----------
  basBuildAddSource       Append one file to a BasProjectSourceT the way a project is built: a module in BEGINMODULE, a class module's code in BEGINCLASS, a UserControl's code in BEGINCLASS ... USERCONTROL (its header is kept in ctlTexts), a form's code section in BEGINFORM with its controls (the .frm text is kept in frmTexts), a lone program unwrapped. Records the file in the source map.
  basBuildConcatProject   Read every file an open .dbp names (modules, class modules, UserControls, forms) into a BasProjectSourceT through basBuildAddSource.
  basBuildCompile         Compile source into a BasModuleT with OPTION EXPLICIT and an optional BasCtrlValidatorT. A BasCompileErrorT says why it failed: BAS_COMPILE_ERROR (line, column, message), BAS_COMPILE_NO_MEMORY, or BAS_COMPILE_NO_MODULE.
  basBuildErrorText       A compiler message without its "Line N, Col M: " prefix.
  basBuildMapLine         Line of the concatenated source to file and file line.
  basBuildMapFileLine     File and file line to the line of the concatenated source (names compared as DOS does: any case, / and \ alike).
  basBuildSpecFromProject Fill a BasBuildSpecT's project metadata from an open .dbp.
  basBuildApp             Build an .app from a module, the .frm texts, and the .ctl headers (ctlSources). A NULL selfPath takes the stub from BAS_BUILD_STUB_CARRIER (this library).
  basBuildControl         Build a control file (.wgt, resources only) from a control project's module and .ctl headers: the module, each header as CTLn with its toolbox icon24 and name resources, the project's metadata, and help file. The IDE's Make Control and CompBuildApp of a control project.
  basBuildIsControlProject  True when an open .dbp is a control project ([Project] Type = Control).
  basBuildWrapForPath     The kind of a project file by its extension.

Validating Control Members (basValidate.h)

A BasValidatorT (zeroed) collects the forms, controls, and menu items of the .frm texts basValidateScanFrm parses; its onCode hook also receives each form's code section, so one parse serves both. basValidateCallbacks fills the BasCtrlValidatorT the compiler takes; pass &validator.compiler as basBuildCompile's liveCompiler so a control name resolves on the form whose code is compiled. Member names are checked against the loaded widget interfaces (WgtIfaceT) and the form runtime's own property tables. badTypes lists controls whose type no loaded widget library registers (BAS_VALIDATE_BAD_TYPE_FMT formats one). The command-line compiler does not validate: no widget library is loaded there.

Debugging (basDebug.h)

Every function takes the paused program's VM (or NULL when there is none).

  Function                Description
  --------                -----------
  basDbgCurProcIdx        The compiled procedure the paused VM is in (-1: module level).
  basDbgProcNameForPc     Procedure name holding a code address (BAS_DBG_MODULE_FRAME before every procedure).
  basDbgVarVisible        Whether a debug variable is in scope where the VM is paused (QBasic rules: SHARED, STATIC, form, and member variables).
  basDbgFindVar           The visible debug variable of a name.
  basDbgVarSlot           The live value slot of a debug variable.
  basDbgResolvePath       The live slot of var, var(i, j), var.field, or var(i).field (a TYPE field or a class member).
  basDbgFormatValue       Display text of a value (strings quoted, #dates#, array bounds, class names, Collection item counts).
  basDbgLocals            The visible variables as rows (name, type, value) through a BasDbgRowFnT.
  basDbgCallStack         The call stack as rows (procedure, line).
  basDbgEval              A watch: a variable path read directly, any other expression compiled and run as a snippet.
  basDbgExec              An Immediate-window line: assignment to the paused program's variable, a statement, or an expression to print.
  basDbgSetVar            Set a variable path to the value of an expression (scalars).
  basDbgSnippetVm         Compile a snippet whose globals are the paused program's visible variables, with their values copied in.
  basDbgRunSnippet        Run a snippet in bounded slices (an endless loop is abandoned).
  basDbgAppendDropping    Append to a fixed output buffer, dropping the oldest lines.

Procedures (basProcs.h)

basProcDeclAt, basProcSkipToEnd, and basProcNameFromDecl find the SUB / FUNCTION blocks of source text without compiling it (BAS_PROC_KW_END_SUB / BAS_PROC_KW_END_FUNCTION end them). The IDE's one-procedure-at-a-time editor and CompProcList$ use them.

Source Text (basSource.h)

Source text read with the compiler's lexer, line by line, on text that need not compile: basSourceTokens reports every token with its kind (BasSrcKindE: keyword, identifier, string, number, date, symbol, comment, error), line, column, and source span; basSourceInCode tells whether an offset is code (not in a string literal or a comment); basSourceRename renames a form or control (oldName followed by '.' or '_' as a whole word), optionally only in code; basSourceWithEvents and basSourceClassEvents report the WithEvents variables and the Events of a class module. The IDE's rename (ideRenameInCode, also after the Menu Editor renames items) and its Object / Event lists use them; compiler.bas has CompTokens$, CompInCode, CompRenameInCode$, CompWithEvents$, and CompClassEvents$.

Help Topics (basHelpMap.h)

basHelpKeywordTopic gives the DVX BASIC help topic of a keyword or built-in function (NULL for none) and basHelpCtrlTopic the topic of a control type (BAS_HELP_CTRL_PREFIX and the type name in lower case): what F1 opens in the IDE, and CompHelpTopic$ / CompHelpCtrlTopic$.

Projects (basProject.h)

The project (.dbp) model: PrjStateT with its file list (PrjFileT: path relative to the project's folder, kind -- PrjFileKindE: module, class module, UserControl ([Controls], .ctl), or form, in compile order -- edit buffer) and settings, prjLoad / prjSave (one table of [Project] / [Settings] keys drives load, save, prjFieldGet, and prjFieldSet; [Project] Type = Control makes a control project, prjIsControlProject), prjNew, prjAddFile, prjRemoveFile, prjRenameFile (disk and project together), prjFileIndexByPath, prjKindForPath (the build's rule, basBuildWrapForPath), prjKindWrap, prjClassName (a class module's or UserControl's class name), prjReadFileText, the fonts the program carries ([Fonts]: prjAddFont, prjRemoveFont, prjFontCount, prjFontPath; the build makes them FONTn font resources, basBuildSpecT fonts), and the source map of the last compile (prjMapLine, prjMapFileLine). The IDE keeps only its Project window and Properties dialog (ideProject.c); compiler.bas has ProjOpen, ProjNew, ProjSave, ProjClose, ProjPath$, ProjFileCount, ProjFile$, ProjFilePath$, ProjFileKind, ProjFileFind, ProjAddFile, ProjRemoveFile, ProjRenameFile, ProjFontCount, ProjFont$, ProjAddFont, ProjRemoveFont, ProjGet$, and ProjSet; CompKeywordClass and CompKeywordReserved expose the keyword table's flags.

A UserControl's header (the Begin UserControl block of a .ctl) is read and changed key by key, every other line of the text kept: prjCtlKeyAt / prjCtlKeyFind give the keys (PrjCtlKeyT: name and PrjCtlValueE kind -- Name, the control's name on the Begin line, then Width, Height, CanGetFocus, BackColor, ToolboxIcon, Description, DefaultEvent), prjCtlHeaderGet the value the control takes (its default when the header leaves the key out), prjCtlHeaderSet a malloc'd copy of the text with one key changed, added before End, or removed (an empty value), written as its kind is (a text in quotes, a color &HBBGGRR&, True or False), and prjCtlNew the text of a new UserControl (its header with Width and Height, Option Explicit, an empty UserControl_Paint written with PRJ_HANDLER_FMT and PRJ_PRIVATE, the form the IDE writes event handlers in). The IDE's File > New UserControl and Properties window use them; compiler.bas has ProjCtlNew$, ProjCtlHeader$, ProjCtlSetHeader$, and ProjCtlKeys$ (the keys and their kinds, prjCtlKeyAt order).

Running a Program (basRunHost.h, in basrt.lib)

basRunHostCreate makes the VM and form runtime for a module from a BasRunSpecT: App.Path, App.User (from the program's file: its folder in the user folder), App.Data (the DATA folder beside it), COMMAND$, the print, input, and DoEvents callbacks, the screen window's name, the help file, whether runtime errors show their own box, and whether it is a standalone program (the stub's own: the runtime owns the module, frees it with itself, and is ended through the shell's kill hook, shellRegisterAppKill, when the program is ended by force; the IDE and basdev end their runtimes themselves). basRunHostRegisterFrm registers a .frm text, basRunHostLoadForms loads the forms and shows the startup form, basRunHostSlice runs module code once under the step limit (an error's message and line are always set afterwards), basRunHostAlive says whether the event-loop phase goes on, basRunHostTagBreak puts BAS_RUN_BREAK_TAG on the captions while paused, and basRunHostDestroy ends it. The runtime made inside another program's code does not become the current runtime (basFormRtCurrent / basFormRtSetCurrent); each of these calls makes it current while its code runs.

VM accessors a debugger host uses (vm.h): basVmSetAppPaths, basVmSetBreakpointCallback, basVmSetDebugPaused, basVmResume, basVmStop, basVmClearStepState, basVmGetCurrentLine, basVmGetErrorLine.

From BASIC

compiler.bas and debug.bas declare the entry points (see Include Libraries in the BASIC language reference). Source sets, modules, and runs are handles freed with their program. A run is polled: RunPoll runs the child for a bounded time and never waits. A pause in the child's module code returns RUN_PAUSED; a pause inside one of its event handlers (or its forms' Load code, inside RunStart) waits in place while the child pumps DVX events, so the caller's Timer and menus can step, continue, or stop it; RunPoll called from there returns at once. RunFree while the child's code is on the stack stops it at once and releases it at the next library call after that code has unwound.

CompDesignControls registers a source set's UserControls for the calling program's design forms (basCtlLoadDesign with its runtime, the current one: a child run's own when the child calls it) and holds the module -- one of the caller's own handles; another program's is refused -- until CompDesignRelease, the next CompDesignControls, or the end of that runtime (appEnded for a program, runDestroyHost for a child run), CompModuleFree meanwhile only marking it.

BASIC Runtime Library

Stack-based p-code virtual machine and value system for DVX BASIC. Embeddable: the host provides I/O and UI callbacks. No DVX GUI dependencies in the core runtime.

Headers: apps/dvxbasic/runtime/vm.h, apps/dvxbasic/runtime/values.h

Value System (values.h)

Virtual Machine (vm.h)

The .frm Model (frmModel.h)

Value System

Tagged union value type for the VM evaluation stack, variables, and array elements. Strings, arrays, and UDT instances are reference-counted for automatic memory management without a garbage collector.

Header: apps/dvxbasic/runtime/values.h

Type Tags

  Constant         Value   C Union Field   Description
  --------         -----   -------------   -----------
  BAS_TYPE_INTEGER 0       intVal          16-bit signed integer.
  BAS_TYPE_LONG    1       longVal         32-bit signed integer.
  BAS_TYPE_SINGLE  2       sngVal          32-bit float.
  BAS_TYPE_DOUBLE  3       dblVal          64-bit float.
  BAS_TYPE_STRING  4       strVal          Reference-counted dynamic string.
  BAS_TYPE_BOOLEAN 5       boolVal         True (-1) or False (0).
  BAS_TYPE_ARRAY   6       arrVal          Reference-counted array.
  BAS_TYPE_UDT     7       udtVal          Reference-counted user-defined type.
  BAS_TYPE_OBJECT  8       objVal          Opaque host pointer (form, control).
  BAS_TYPE_REF     9       ref             Reference (ByRef argument): ref.target is the variable, element, or field it names; ref.owner keeps that storage alive.

A reference only ever lives in a call's parameters (a ByRef parameter, a ParamArray element). Read through it with basValDeref, which returns the value a reference names (or the value itself). refKind says what ref.owner is: BAS_REF_SLOT (none: a global, local, or form variable), BAS_REF_ARRAY (the array, counted and locked so REDIM / ERASE raise error 10), BAS_REF_UDT (the TYPE instance holding the field), BAS_REF_OBJECT (the class instance holding the member variable), or BAS_REF_CELL (a BasRefCellT holding both the locked array and the TYPE instance, for a field of an array element). refType is the target's declared type (stores convert to it) and refLen the length of a STRING * n target. basValCopy and basValRelease count the owner.

BasValueT

Tagged union holding any BASIC value.

struct BasValueTag {
    uint8_t  type;         // BAS_TYPE_*
    uint8_t  refType : 5;  // BAS_TYPE_REF: the target's declared type
    uint8_t  refKind : 3;  // BAS_TYPE_REF: BasRefKindE
    uint16_t refLen;       // BAS_TYPE_REF: STRING * n length (0 = none)
    union {
        int16_t     intVal;
        int32_t     longVal;
        float       sngVal;
        double      dblVal;
        BasStringT *strVal;
        int16_t     boolVal;
        BasArrayT  *arrVal;
        BasUdtT    *udtVal;
        void       *objVal;
        BasObjectT *instVal;
        struct {
            void      *owner;
            BasValueT *target;
        } ref;
    };
};

BasValueT *basValDeref(const BasValueT *v);   // inline: what a reference names, else v

Value Constructors

BasValueT basValInteger(int16_t v);
BasValueT basValLong(int32_t v);
BasValueT basValSingle(float v);
BasValueT basValDouble(double v);
BasValueT basValString(BasStringT *s);
BasValueT basValStringFromC(const char *text);
BasValueT basValBool(bool v);
BasValueT basValObject(void *obj);

Each returns a BasValueT with the appropriate type tag set. basValString increments the string's reference count.

Value Lifetime

BasValueT basValCopy(BasValueT v);
void      basValRelease(BasValueT *v);
  Function       Description
  --------       -----------
  basValCopy     Copy a value. Increments reference count for strings, arrays, and UDTs.
  basValRelease  Release a value. Decrements reference count and frees if it reaches zero.

Type Conversion

BasValueT basValToInteger(BasValueT v);
BasValueT basValToLong(BasValueT v);
BasValueT basValToSingle(BasValueT v);
BasValueT basValToDouble(BasValueT v);
BasValueT basValToString(BasValueT v);
BasValueT basValToBool(BasValueT v);

Each returns a new value of the target type. The original is not released; the caller manages both lifetimes.

Value Utilities

  Function                          Description
  --------                          -----------
  basValToNumber(v)                 Convert any numeric value to double.
  basValFormatString(v)             Return a new ref-counted string representation of v.
  basValIsTruthy(v)                 True if non-zero number or non-empty string.
  basValCompare(a, b)               Compare two values. Returns -1, 0, or 1.
  basValCompareCI(a, b)             Case-insensitive comparison (OPTION COMPARE TEXT).
  basValPromoteType(a, b)           Determine common type for binary ops (e.g. Integer + Single -> Single).

BasStringT

Reference-counted string with flexible array member for inline storage.

typedef struct {
    int32_t refCount;
    int32_t len;
    int32_t cap;
    char    data[];
} BasStringT;

String Functions

  Function                            Description
  --------                            -----------
  basStringNew(text, len)             Allocate from a C string. refCount starts at 1.
  basStringAlloc(cap)                 Allocate an empty string with given capacity.
  basStringRef(s)                     Increment reference count. Returns s.
  basStringUnref(s)                   Decrement reference count. Frees when it reaches zero.
  basStringConcat(a, b)               Concatenate two strings. Returns a new string (refCount 1).
  basStringSub(s, start, len)         Extract a substring. Returns a new string (refCount 1).
  basStringCompare(a, b)              Compare. Returns <0, 0, >0 (like strcmp).
  basStringCompareCI(a, b)            Case-insensitive compare.

The global basEmptyString is a singleton that is never freed.

BasArrayT

Reference-counted multi-dimensional array (up to BAS_ARRAY_MAX_DIMS = 8 dimensions).

typedef struct {
    int32_t    refCount;
    uint8_t    elementType;
    int32_t    dims;
    int32_t    lbound[BAS_ARRAY_MAX_DIMS];
    int32_t    ubound[BAS_ARRAY_MAX_DIMS];
    int32_t    totalElements;
    BasValueT *elements;
} BasArrayT;

Array Functions

  Function                                  Description
  --------                                  -----------
  basArrayNew(dims, lbounds, ubounds, type) Allocate an array. refCount starts at 1.
  basArrayFree(arr)                         Free all elements and release the array.
  basArrayRef(arr)                          Increment reference count.
  basArrayUnref(arr)                        Decrement reference count. Frees at zero.
  basArrayIndex(arr, indices, ndims)        Compute flat index from multi-dimensional indices. Returns -1 if out of bounds.

BasUdtT

Reference-counted user-defined type instance.

typedef struct {
    int32_t    refCount;
    int32_t    typeId;
    int32_t    fieldCount;
    BasValueT *fields;
} BasUdtT;

UDT Functions

  Function                          Description
  --------                          -----------
  basUdtNew(typeId, fieldCount)     Allocate a UDT instance. refCount starts at 1.
  basUdtFree(udt)                   Free all fields and release.
  basUdtRef(udt)                    Increment reference count.
  basUdtUnref(udt)                  Decrement reference count. Frees at zero.

Virtual Machine

Stack-based p-code interpreter. Executes compiled BASIC bytecode modules. The host provides I/O, UI, SQL, and external library callbacks. The VM has no DVX dependencies; it can be embedded in any C program.

Header: apps/dvxbasic/runtime/vm.h

VM Limits

  Constant                Value   Description
  --------                -----   -----------
  BAS_VM_STACK_SIZE       256     Evaluation stack depth.
  BAS_VM_CALL_STACK_SIZE  64      Maximum call nesting depth.
  BAS_VM_MAX_GLOBALS      512     Global variable slots.
  BAS_VM_MAX_LOCALS       64      Local variables per stack frame.
  BAS_VM_MAX_MEMBER_VARS  65536   Member variables per class object; variables per form (their opcodes' 16-bit index).
  BAS_VM_MAX_FOR_DEPTH    32      Maximum nested FOR loop depth.
  BAS_VM_MAX_FILES        16      Open file channels (1-based).

BasVmResultE

Result codes returned by basVmRun and basVmStep.

  Code                    Value   Description
  ----                    -----   -----------
  BAS_VM_OK               0       Program completed normally.
  BAS_VM_HALTED           1       HALT instruction reached.
  BAS_VM_ERROR            2       Runtime error.
  BAS_VM_STACK_OVERFLOW   3       Evaluation stack overflow.
  BAS_VM_STACK_UNDERFLOW  4       Evaluation stack underflow.
  BAS_VM_CALL_OVERFLOW    5       Call stack overflow.
  BAS_VM_DIV_BY_ZERO      6       Division by zero.
  BAS_VM_TYPE_MISMATCH    7       Type mismatch in operation.
  BAS_VM_OUT_OF_MEMORY    8       Memory allocation failed.
  BAS_VM_BAD_OPCODE       9       Unknown opcode encountered.
  BAS_VM_FILE_ERROR       10      File I/O error.
  BAS_VM_SUBSCRIPT_RANGE  11      Array subscript out of range.
  BAS_VM_STEP_LIMIT       12      Step limit reached (not an error).
  BAS_VM_BREAKPOINT       13      Breakpoint or step completed (not an error).

Lifecycle

BasVmT *basVmCreate(void);
void    basVmDestroy(BasVmT *vm);
void    basVmLoadModule(BasVmT *vm, BasModuleT *module);
void    basVmReset(BasVmT *vm);
  Function         Description
  --------         -----------
  basVmCreate      Allocate and initialize a new VM instance.
  basVmDestroy     Destroy the VM and free all resources.
  basVmLoadModule  Load a compiled module (BasModuleT) into the VM.
  basVmReset       Reset to initial state (clear stack, globals, PC).

Execution

BasVmResultE basVmRun(BasVmT *vm);
BasVmResultE basVmStep(BasVmT *vm);
void         basVmSetStepLimit(BasVmT *vm, int32_t limit);
  Function           Description
  --------           -----------
  basVmRun           Execute the loaded module until it ends, halts, yields, errors, or hits a breakpoint/step limit.
  basVmStep          Execute a single instruction and return. Useful for debugger stepping.
  basVmSetStepLimit  Set maximum instructions per basVmRun call. 0 = unlimited (default). Returns BAS_VM_STEP_LIMIT when reached.

I/O Callbacks

The host provides these callbacks for PRINT, INPUT, and DoEvents statements.

void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);

Callback Types

typedef void (*BasPrintFnT)(void *ctx, const char *text, bool newline);
typedef bool (*BasInputFnT)(void *ctx, const char *prompt,
    char *buf, int32_t bufSize);
typedef bool (*BasDoEventsFnT)(void *ctx);
  Type             Description
  ----             -----------
  BasPrintFnT      Called for PRINT output. text is null-terminated. newline indicates line advance.
  BasInputFnT      Called for INPUT. Fill buf (up to bufSize-1 chars). Return true on success, false on cancel.
  BasDoEventsFnT   Called for DoEvents. Process pending events and return. Return false to stop the program.

UI Callbacks

For form and control integration. The VM resolves all UI operations through these callbacks, keeping it independent of any specific GUI toolkit.

void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);

BasUiCallbacksT

  Field         Signature                                            Description
  -----         ---------                                            -----------
  getProp       BasValueT (*)(ctx, ctrlRef, propName)                Get a control property value.
  setProp       void (*)(ctx, ctrlRef, propName, value)              Set a control property.
  callMethod    BasValueT (*)(ctx, ctrlRef, methodName, args, argc)  Call a method on a control.
  createCtrl    void *(*)(ctx, formRef, typeName, ctrlName)          Create a control on a form.
  findCtrl      void *(*)(ctx, formRef, ctrlName)                    Find a control by name.
  findCtrlIdx   void *(*)(ctx, formRef, ctrlName, index)             Find a control array element.
  loadForm      void *(*)(ctx, formName)                             Load a form by name.
  unloadForm    void (*)(ctx, formRef)                               Unload a form.
  showForm      void (*)(ctx, formRef, modal)                        Show a form (modal or modeless).
  hideForm      void (*)(ctx, formRef)                               Hide a form (keep in memory).
  msgBox        int32_t (*)(ctx, message, flags)                     Display a message box.
  inputBox      BasStringT *(*)(ctx, prompt, title, defaultText)     Display an input box.
  loadFormText  void *(*)(ctx, text)                                 LoadFormText: a form from .frm text (its own copy when the name is loaded), NULL when it does not load.
  ctx           void *                                               User pointer passed to all callbacks.

SQL Callbacks

For database integration.

void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql);

BasSqlCallbacksT

  Field           Signature                                  Description
  -----           ---------                                  -----------
  sqlOpen          int32_t (*)(path)                          Open a database. Returns handle.
  sqlClose         void (*)(db)                               Close a database.
  sqlExec          bool (*)(db, sql)                          Execute a non-query SQL statement.
  sqlError         const char *(*)(db)                        Get last error message.
  sqlQuery         int32_t (*)(db, sql)                       Execute a query. Returns result set handle.
  sqlNext          bool (*)(rs)                               Advance to next row.
  sqlEof           bool (*)(rs)                               Check if at end of result set.
  sqlFieldCount    int32_t (*)(rs)                            Get number of columns.
  sqlFieldName     const char *(*)(rs, col)                   Get column name by index.
  sqlFieldText     const char *(*)(rs, col)                   Get column value as text by index.
  sqlFieldByName   const char *(*)(rs, name)                  Get column value as text by name.
  sqlFieldInt      int32_t (*)(rs, col)                       Get column value as integer.
  sqlFieldDbl      double (*)(rs, col)                        Get column value as double.
  sqlFreeResult    void (*)(rs)                               Free a result set.
  sqlAffectedRows  int32_t (*)(db)                            Get number of rows affected by last statement.

External Library Callbacks

For DECLARE LIBRARY support. The VM resolves external functions at runtime via the host.

void basVmSetExternCallbacks(BasVmT *vm,
    const BasExternCallbacksT *ext);

BasExternCallbacksT

The compiler converts every argument to its declared parameter type, so callExtern receives values whose type is the declared type. The stock basExternCall pushes cdecl stack words by that type: STRING as a char pointer, a BINARY STRING (binaryMask bit i set for argument i) as the char pointer followed by an int32_t length, SINGLE as one 4-byte float, DOUBLE as 8 bytes, everything else as one int32_t. When binaryMask has BAS_EXTERN_BINARY_RET the result is a BINARY STRING: an int32_t * is pushed last and the result is that many bytes at the returned pointer. DOUBLE and SINGLE returns are read from the x87 ST(0) register; INTEGER returns keep only the low 16 bits.

  Field           Signature                                                                        Description
  -----           ---------                                                                        -----------
  resolveExtern   void *(*)(ctx, libName, funcName)                                                Resolve a native function by library and symbol name. Cached after first call.
  callExtern      BasValueT (*)(ctx, funcPtr, libName, funcName, args, argc, retType, binaryMask)  Call a resolved native function, marshalling arguments and return value.
  ctx             void *                                                                           User pointer passed to both callbacks.

A native SUB is called with OP_CALL_EXTERN like a FUNCTION and leaves a dummy result on the stack; a call written as a statement drops it, as it drops a FUNCTION's result.

Terminal Controls for Libraries

A library's BASIC functions can show a byte stream on a Terminal control the way SerAttach and CommAttach do (the connection library's ConnAttach is one). The form runtime (formrt.h, exported from basrt) keeps the binding safe:

typedef int32_t (*BasTermWriteFnT)(void *ctx, const uint8_t *data, int32_t len);
typedef void    (*BasTermUnbindFnT)(void *ctx);

WidgetT *basFormRtTermBind(const char *ctrlName, void *ctx, BasTermWriteFnT writeFn, BasTermUnbindFnT onUnbind);
void     basFormRtTermUnbind(void *ctx);
void     basFormRtTermForget(void *ctx);

basFormRtTermBind finds the Terminal control ctrlName on the running program's forms, ends whatever fed it before (a serial port, a packet link, another binding) and ctx's own earlier binding, sends what is typed there to writeFn, and returns the widget for the library to write what arrives to (NULL when there is no Terminal of that name). When the control goes away -- its form unloads, it is removed, another binding takes it -- the runtime ends the binding and calls onUnbind(ctx): the library must forget the widget. basFormRtTermUnbind ends a binding the library no longer wants; basFormRtTermForget drops it without touching the widget, for a library whose program was ended (its windows are already gone). A library outside basrt looks these up with dlsym when it first needs them.

Form Context

Set the active form context during event dispatch.

void basVmSetCurrentForm(BasVmT *vm, void *formRef);
void basVmSetCurrentFormVars(BasVmT *vm,
    BasValueT *vars, int32_t count);

Collections for the Host

A host hands BASIC a Collection of its own objects (the form runtime's Form.Controls) with these; basValInstance of the new Collection takes its one reference.

BasObjectT *basVmCollectionNew(BasVmT *vm);
int32_t     basVmCollectionAdd(BasObjectT *coll, BasValueT item, const char *key);   // key NULL: none; 0 or the BASIC error number

Control Types and the Design Surface

formrt/formDesign.c holds what a form designer needs, shared by the DVX BASIC IDE's designer and by designer.bas (DECLARE LIBRARY "basrt": CtlTypeCount, CtlTypeName, CtlTypeFlags, CtlTypeNamePrefix, CtlTypeDefaultEvent, CtlTypeTooltip, CtlTypeIconFile, CtlPropCount, CtlPropName, CtlPropType, CtlPropEnum, CtlMethodList, CtlEventList, CtlEventParams, CtlTypeEventParams, and FrmFormName, FrmControlNames, FrmCode, ClsClassName over the basRes.h scanners). CtlTypeTooltip and CtlTypeIconFile fall back to a UserControl's header (its Description; its ToolboxIcon file, basUcIconPath) when the type has no widget file:

  Function                                                  Description
  --------                                                  -----------
  basCtlIface(typeName)                                     The widget interface of a BASIC type name, or NULL.
  basCtlPropAt(typeName, idx, out) / basCtlPropFind(...)    A type's properties as BasCtlPropT (name, type, readable, writable, enum names): the runtime table's ("Form", "Menu", or the common one), the widget's own winning on a name, then the widget's others. The IDE's properties window uses basCtlPropFind.
  basCtlMethodAt / basCtlEventAt(typeName, idx)             A type's methods (the widget's, then the common ones) and events (the master BAS_EVENT_LIST's for that kind of object, then the widget's); the IDE's event list uses basCtlEventAt.
  basCtlEventParams(typeName, eventName)                    The parameter list of a handler of a type's event: the widget's own (WgtEventDescT params), else the master list's; typeName NULL searches every widget after the master list, BAS_UC_OBJECT ("UserControl", the object in a UserControl's own code) has BAS_UC_EVENT_LIST's alone. The IDE's handler skeletons, CtlEventParams, and CtlTypeEventParams.
  basCtlDefaultEvent, basCtlNamePrefix, basCtlIsContainer   As the toolbox and designer need them.
  basCtlResPath(typeName, resBase, resName, size)           The widget file and the (suffixed) resource name of its toolbox icon (BAS_CTL_RES_ICON) or tooltip (BAS_CTL_RES_NAME).
  basCtlTypeAt(rt, idx)                                     The idx-th control type rt sees (basUcIfaceVisible; rt NULL: a C host) in alphabetical order of BASIC name, NULL past the end. The IDE's toolbox and CtlTypeName list the types in this order; the registry's own (wgtIfaceAt) is the order the widget files loaded in.
  basDsgnHandleOrigin / basDsgnHandleAt / basDsgnCursorAt   Selection handle geometry: where handle h of a widget is, the resize handle under a point, the mouse pointer there.
  basDsgnPaintHandles(ctx, win, w)                          Paints a widget's eight selection handles over a window's painted content.
  basDsgnPropertyChanged(w, propName)                       The code of UserControl widget w called PropertyChanged: a form in DesignMode holding it raises DesignPropertyChanged (CtrlName, PropertyName). formCtl.c calls it beside the C hook (basUcSetPropChangedHook).
  basDsgnWidgetHit(w, x, y)                                 True when a point is on a visible widget.

Form.DesignMode replaces the form window's mouse, key, paint, and cursor handlers while it is True (the controls get no input, and fireCtrlEvent drops their events), raises DesignMouse, DesignKey, and DesignPropertyChanged, and paints DesignSelection's handles after the widgets. Form.Controls, Control.Parent / Container, and MoveBefore (basFormRtMoveControl) are dispatched there too.

UserControls for a Host

formrt/formCtl.c registers UserControls (widget classes written in BASIC) with the widget system. A program's own controls (basFormRtRegisterCtl: the stub's and the IDE's runs) and a designer's (basCtlLoadDesign) get private registry keys, the class name plus a serial, so the same project running and being designed, or two programs, never share one; a control file's (basCtlLoadFile) keeps its name. Resolve names through these instead of wgtGetIface:

  Function                                                  Description
  --------                                                  -----------
  basCtlLoadDesign(rt, module, headers, count, path)        Designer rt's controls (rt: a BASIC program designing forms, compiler.bas CompDesignControls; NULL: a C host, the IDE): the .ctl headers' classes of module (the caller keeps it until basCtlUnloadDesign or rt's end), run without the module-level code, App.Path the folder of path. Returns how many were registered (a name a native or installed control has, or rt's own control has, is not). The set rt registered before is let go first.
  basCtlUnloadDesign(rt)                                    Lets designer rt's controls go (their widgets keep their last picture and run no more code). False while their code runs. basUcRuntimeEnd lets a program's go when it ends.
  basUcIconPath(iface, out, outSize)                        The ToolboxIcon picture file of a control file's or a designer's UserControl: the header's name in the folder of the path it was registered with (the project's, for a designer's). False when it names none or iface is no such control.
  basUcResolveType(rt, typeName)                            The registry key of a BASIC type name in rt's program: its own UserControl first (its program's or the ones it registered for design; rt NULL: the C host's design controls), else wgtResolveTypeName -- never another owner's private control (NULL then).
  basUcOwnIface(rt, typeName)                               The interface of rt's own UserControl (its program's or a design one; rt NULL: the C host's design controls), NULL for none. basCtlIface looks here first, for the current program (basFormRtCurrent).
  basUcIfaceVisible(rt, iface)                              False when iface is a private UserControl of another owner; a list of control types (basCtlTypeAt) leaves those out.
  basUcIfaceHeader(iface)                                   The parsed header (BasUcHeaderT: name, size, BackColor, ToolboxIcon, Description, DefaultEvent) of a UserControl's interface, NULL for any other.
  basUcSetPropChangedHook(fn)                               fn(widget, propName) runs when a control's code calls PropertyChanged: a C designer reads its property sheet again (a BASIC one gets DesignPropertyChanged, basDsgnPropertyChanged).
  basDsgnSetDesignWindow(win, on) / basDsgnIsDesignWindow   Marks a window as a designer's (unmark it when it is destroyed): UserControls on it see UserMode False and get no user input.

Stack Access

Push and pop values on the evaluation stack for host integration.

bool basVmPush(BasVmT *vm, BasValueT val);
bool basVmPop(BasVmT *vm, BasValueT *val);

Both return true on success, false on stack overflow/underflow.

Error Reporting

const char *basVmGetError(const BasVmT *vm);

Returns the current error message string. Valid after basVmRun returns BAS_VM_ERROR, and after a call of a SUB from the host (an event handler) that an untrapped error stopped.

The form runtime ends the program on every untrapped error, in module-level code and in event handlers alike (basFormRtRuntimeError for errors the runtime finds, the same path for the VM's own): the error is logged to DVX.LOG (summary line, details indented), the VM halts (ended), the runtime is marked terminated so its event loop ends, and the BASIC Runtime Error box shows it -- unless the host set suppressErrorDialog (the IDE), which reports it itself. The VM's message ("Runtime error 11 on line 8: Division by zero") stays readable with basVmGetError.

Sub/Function Calls from Host

Call a SUB or FUNCTION by its code address from host code.

bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr,
    const BasValueT *args, int32_t argCount);
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr,
    const BasValueT *args, int32_t argCount,
    BasValueT *outArgs, int32_t outCount);
  Function                Description
  --------                -----------
  basVmCallSub            Call a SUB with no arguments.
  basVmCallSubWithArgs    Call a SUB with arguments pushed onto the stack frame.
  basVmCallSubWithArgsOut Call a SUB whose first outCount parameters are references to outArgs (set to the arguments first; each must hold a valid value): what the SUB stores in them is there when it returns, also after an error.

All three push a call frame, execute until the SUB returns, then restore the previous execution state. Return true on normal completion, false on error or if the VM is not idle.

Debugger API

void    basVmSetBreakpoints(BasVmT *vm,
            int32_t *lines, int32_t count);
void    basVmStepInto(BasVmT *vm);
void    basVmStepOver(BasVmT *vm);
void    basVmStepOut(BasVmT *vm);
void    basVmRunToCursor(BasVmT *vm, int32_t line);
void    basVmClearStepState(BasVmT *vm);
void    basVmSetBreakpointCallback(BasVmT *vm,
            BasBreakpointFnT fn, void *ctx);
void    basVmSetDebugPaused(BasVmT *vm, bool paused);
void    basVmResume(BasVmT *vm);
void    basVmStop(BasVmT *vm);
int32_t basVmGetCurrentLine(const BasVmT *vm);
int32_t basVmGetErrorLine(const BasVmT *vm);
void    basVmSetAppPaths(BasVmT *vm, const char *path, const char *user, const char *data);
  Function                    Description
  --------                    -----------
  basVmSetBreakpoints         Set the breakpoint list (sorted array of source line numbers, host-owned memory).
  basVmStepInto               Break at the next OP_LINE instruction.
  basVmStepOver               Break when call depth returns to the current level.
  basVmStepOut                Break when call depth drops below the current level.
  basVmRunToCursor            Break when execution reaches the specified source line.
  basVmClearStepState         Cancel a pending step or run-to-cursor request.
  basVmSetBreakpointCallback  The pause notification for SUBs the host calls (see below).
  basVmSetDebugPaused         Break mode on or off: while on, basVmCallSub refuses to run event handlers.
  basVmResume                 Leave break mode; a handler paused at a breakpoint carries on.
  basVmStop                   Stop a debugged program: step state cleared, breakpoints detached, break mode ended, the running code unwinds.
  basVmGetCurrentLine         Get the current source line number (from the last OP_LINE instruction).
  basVmGetErrorLine           The source line where the current runtime error was raised (0: none).
  basVmSetAppPaths            Set App.Path, App.User (the program's user folder, made on first use), and App.Data.

The breakpoint callback notifies the host when a breakpoint fires during nested sub calls:

typedef void (*BasBreakpointFnT)(void *ctx, int32_t line);

BasModuleT

Compiled module produced by the BASIC compiler and loaded into the VM.

typedef struct {
    uint8_t          *code;
    int32_t           codeLen;
    BasStringT      **constants;
    int32_t           constCount;
    int32_t           globalCount;
    int32_t           entryPoint;
    BasValueT        *dataPool;
    int32_t           dataCount;
    BasProcEntryT    *procs;
    int32_t           procCount;
    BasFormVarInfoT  *formVarInfo;
    int32_t           formVarInfoCount;
    BasSlotInitT     *globalInits;
    int32_t           globalInitCount;
    BasSlotInitT     *localInits;
    int32_t           localInitCount;
    BasLineNumberT   *lineNumbers;
    int32_t           lineNumberCount;
    BasClassInfoT    *classes;
    int32_t           classCount;
    BasEnumInfoT     *enums;
    int32_t           enumCount;
    BasWithEventsT   *withEvents;
    int32_t           withEventsCount;
    BasDebugVarT     *debugVars;
    int32_t           debugVarCount;
    BasDebugUdtDefT  *debugUdtDefs;
    int32_t           debugUdtDefCount;
} BasModuleT;
  Field              Description
  -----              -----------
  code               P-code bytecode array.
  codeLen            Length of bytecode in bytes.
  constants          String constant pool.
  constCount         Number of string constants.
  globalCount        Number of global variable slots needed.
  entryPoint         PC of the first instruction (module-level code).
  dataPool           DATA statement value pool (for READ).
  dataCount          Number of values in the data pool.
  procs              Procedure table (SUBs and FUNCTIONs).
  procCount          Number of procedures.
  formVarInfo        Per-form variable counts and init code addresses.
  formVarInfoCount   Number of forms with form-scoped variables.
  globalInits        The starting type of each typed global slot (kept in a release build).
  globalInitCount    Number of global slot entries.
  localInits         The starting type of each typed local slot, per procedure (BasProcEntryT initFirst / initCount).
  localInitCount     Number of local slot entries.
  lineNumbers        The program's QBasic line numbers in source order (ERL reports them).
  lineNumberCount    Number of line numbers.
  classes            Class modules and UserControls (BasClassInfoT): members, events, Implements maps.
  classCount         Number of classes.
  enums              The Enums UserControl properties are declared As (BasEnumInfoT: member names and values in declaration order; a class member's enumIdx names one), so a host shows and stores such a property by member name. Kept in a release build.
  enumCount          Number of Enums.
  withEvents         WithEvents variables and their handlers.
  withEventsCount    Number of WithEvents variables.
  debugVars          Variable names and metadata for the debugger.
  debugVarCount      Number of debug variable entries.
  debugUdtDefs       UDT type definitions for the debugger.
  debugUdtDefCount   Number of debug UDT definitions.

The .frm Model

A form file as data, with no widgets and no windows: the form's properties, its controls (type, name, control array index, parent, the built-in size and state fields, and every other "Key = Value" pair as text), its menu items, and its code section. frmModelLoad reads .frm text (through frmParse) and frmModelWrite writes the canonical text the DVX BASIC IDE saves. The IDE's designer, properties window, menu editor, and copy and paste all work on this model and keep only their live widgets on the side (each control's userData); designer.bas binds it for BASIC programs (the Frm* functions, formrt/frmModelBas.c).

Header: apps/dvxbasic/formrt/frmModel.h (part of basrt.lib)

Types

  Type            Description
  ----            -----------
  FrmModelT       A form: name, caption, width / height / left / top, padding, spacing, windowState, layout, helpTopic, contextMenu, centered / autoSize / resizable, controls (stb_ds array of FrmCtrlT pointers in tree order: each control directly followed by its children), menuItems (stb_ds array), props (other runtime form properties that were set: Icon, Tag, MousePointer, the Font properties ...), code (malloc'd, NULL = none), dirty (changed by an edit since it was made or loaded).
  FrmCtrlT        A control: name, typeName, parentName + parentIndex (the parent's control array index, so a child of one member of a container array belongs to that member), index (FRM_NO_INDEX: not in an array), left, top, width (MinWidth), height (MinHeight), maxWidth, maxHeight, weight, dragMode, helpTopic, visible, enabled, props (stb_ds array of FrmPropT: every other key, unquoted), userData (the caller's; the model never reads it and loads, copies, and pastes start it at NULL).
  FrmMenuItemT    A menu item: caption ("-" for a separator), name, shortcut, shortcutText, level (0: a menu-bar menu or a popup; 1: its items; 2+: submenus), id (an editor identity, see frmModelMenuReplace), checked, checkable (Checked was written), radioCheck, enabled, visible (False on a level-0 item: a popup menu).
  FrmPropT        One stored "Key = Value": name, value.
  FrmIntPropT     One built-in integer property (Left, Top, MinWidth alias Width, MinHeight alias Height, MaxWidth, MaxHeight, Weight, DragMode): name, alias, offset into FrmCtrlT, saveRule (FRM_SAVE_ALWAYS / IF_NONZERO / IF_POSITIVE).

Only values somebody set are stored and written: a property a control has by default (a PictureBox's BorderStyle, a TextBox's MaxLength) is not in the model and not in the file. Values are written in the canonical form for their widget interface type, or for a key the widget does not know, for their type in the runtime's common property table (STRING quoted, BOOL True / False, INT a number, FLOAT and ENUM unquoted); other keys are written as quoted strings. The limits come from the parser's: FRM_MAX_NEST_DEPTH control levels, FRM_MAX_MENU_LEVEL menu levels, and no property line longer than FRM_MAX_LINE_LEN - 1 characters (the writer fails with FRM_WRITE_LINE_TOO_LONG rather than write one the parser would cut).

Loading and Writing

  Function                                      Description
  --------                                      -----------
  frmModelNew(name)                             A blank form with the runtime form's defaults.
  frmModelLoad(text, len)                       The model of .frm text; NULL when it is not a form (no Begin Form, frmParse refuses it, two controls share a name and index) or out of memory. A Key = Value line is the innermost open block's, also after the blocks inside it, as the runtime reads it; a child names its container by the container's final Index. Of several OptionButtons of one container saved with Value True, the last keeps it.
  frmModelFree(m)                               Frees the model, its controls, and menus.
  frmModelWrite(m, buf, bufSize)                The .frm text into buf: its length, or FRM_WRITE_TOO_LARGE / FRM_WRITE_LINE_TOO_LONG (never a partial text).
  frmModelWriteSize(m)                          A buffer size that always holds the text.
  frmModelText(m, &len)                         The text in a new malloc'd string; NULL with len set to the FRM_WRITE_* code.
  frmModelSetCode(m, code)                      Replaces the code section.
  frmCodeSection(trailing, len, &outLen)        The code section of the text after a form's End (blank lines skipped, the first code line keeps its indent).
  frmModelFromForm(rt, form)                    A model of a loaded form as it is now: its properties, its controls in their containers with each property that differs from a new control of the type (compared with one made in a hidden window; the six Font properties of the form and of each control that has a font of its own), its menus; no code. What FormText$ writes.

Properties

  Function                                              Description
  --------                                              -----------
  frmModelFormProp(m, name, out, size)                  A form property as text (an unset design property gives "", "0", or "False"); false for run-time-only ones (Visible, DesignMode, DesignSelection, object properties).
  frmModelSetFormProp(m, name, value)                   Sets one from text; Name renames the form; an empty value takes an optional one out.
  frmCtrlPropText(ctrl, name, out, size)                A control property as the control holds it (Name, Index, the built-in integers, HelpTopic, Visible / Enabled unless the widget defines them, the stored ones).
  frmCtrlSetPropText(ctrl, key, value)                  Applies one the way the loader does (a VB TabIndex is dropped unless the widget defines it).
  frmModelSetCtrlProp(m, ctrl, name, value)             The same with an editor's rules: Name renames the control array, Index makes an array member, then the OptionButton group rule.
  frmCtrlGetProp / frmCtrlSetProp / frmCtrlRemoveProp   The stored properties.
  frmIntPropAt / frmIntPropFind / frmIntPropGet / Set   The built-in integer table.
  frmIfaceProp(typeName, propName)                      The widget interface property of a type, NULL when none.

Font Properties

FontName, FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru (BAS_PROP_FONT*) are in both the form table and the common control table. They are one font, a DvxFontChoiceT (dvxDlg.h: family, size in pixels, FONT_STYLE_* flags), and the widget's font (wgtFont, wgtSetFont) is the only place they live: a read gives the font the object draws with (a form's is its root widget's), a write gives it that font with the one part changed. The .frm loader collects a block's Font properties and opens one font when the block's properties are read, so their order in the file does not matter. Helpers in formrt.h, for loaders and designers that hold them as .frm text:

  Function                                        Description
  --------                                        -----------
  basFontPropAt(idx)                              The Font property names in .frm order (FontName, FontSize, then the flags), NULL past the end.
  basFontPropIs(name)                             True for a Font property name (any case).
  basFontChoiceOf(font, out)                      A BitmapFontT as the properties name it (dvxFontFamilyOf, pixelSize, style).
  basFontPropText(choice, name, out, size)        One property of a choice as .frm text (the family, the size, True / False).
  basFontPropSetText(choice, name, text)          Sets one property of a choice from .frm text.

Editing Controls

  Function                                                  Description
  --------                                                  -----------
  frmModelAddCtrl(m, typeName, name, parent)                Adds a control as the last child of parent (NULL: the form); name "" or NULL gives a free one (frmModelAutoName); Caption = its name. NULL when the name is taken or parent cannot hold it.
  frmModelRemoveCtrl(m, ctrl)                               Removes ctrl and its subtree.
  frmModelRenameCtrl(m, oldName, newName)                   Renames a whole control array; children and DataSource / MasterSource of a renamed Data control follow. Identifier rules are the caller's.
  frmModelMakeArrayMember(m, ctrl, index)                   Gives a control array index; its children follow.
  frmModelMoveCtrl(m, ctrl, before)                         Moves ctrl with its subtree before a sibling (NULL: after the last).
  frmModelReparent(m, ctrl, parent)                         Makes ctrl the last child of another container (refuses cycles, non-containers, and too deep a nesting).
  frmModelSibling(m, idx, dir)                              The sibling next to a control (+1 after its subtree, -1 before).
  frmModelFindCtrl / CtrlPos / FindParent / CtrlDepth       Lookups.
  frmModelIsDescendant / SubtreeEnd / NormalizeOrder        The tree order.
  frmModelNameInUse / AutoName / OptionSelected             Names and the OptionButton group rule.
  frmModelCtrlText(m, ctrl, &len)                           ctrl and its children as .frm control blocks (a copy for the Clipboard).
  frmModelCtrlsFromText(text, len)                          Copied blocks read into a scratch model.
  frmModelInsertTree(m, src, srcRoot, parent, name, index)  Moves a subtree out of the scratch model into m: the root takes name / index (NULL: its own name when free, else a free one); a child whose name is taken gets a free one, the members of one array together; children follow their renamed parents.

Menus

The frmMenu* functions work on any stb_ds array of FrmMenuItemT (an editor's working copy as well as m->menuItems) and keep the levels a tree.

  Function                                                  Description
  --------                                                  -----------
  frmMenuItemInit(mi)                                       A blank item (enabled, visible, level 0).
  frmMenuInsert(&items, at, level)                          Inserts a blank item; the level is clamped so the menus stay a tree.
  frmMenuRemove(&items, idx)                                Removes an item with its subtree.
  frmMenuMove(&items, idx, dir)                             Moves an item with its subtree past its previous / next sibling; its new position or FRM_NO_INDEX.
  frmMenuIndent(items, idx, delta)                          Indents / outdents an item with its subtree (at most one level below the item above, not past FRM_MAX_MENU_LEVEL).
  frmMenuSubtreeEnd / CheckLevels / StripBlank / AutoName   Tree helpers; AutoName makes mnuSep1 ... or "mnu" plus the caption's letters and digits.
  frmMenuPropText / frmMenuSetPropText                      Menu properties as text (Checked makes a check item).
  frmModelMenuValidate(m, items, isIdent, reason, size)     The checks before items replace m's menus (levels; every name set, an identifier by isIdent, not the form's or a control's, unique; every Shortcut a key): the first bad item and the reason.
  frmModelMenuReplace(m, items)                             Replaces m's menus by a copy; ContextMenu bindings follow renamed items, matched by id (give every item id = position + 1 before editing).
  frmModelSetMenuProp(m, idx, key, value)                   Sets an item's property; a Name rename refuses a taken name and ContextMenu bindings follow.
// Load a form, rename a control, add a label inside a frame, save.
FrmModelT *m = frmModelLoad(text, len);

if (m) {
    int32_t   at     = frmModelFindCtrl(m, "Frame1", FRM_NO_INDEX);
    FrmCtrlT *frame  = (at >= 0) ? m->controls[at] : NULL;
    FrmCtrlT *lbl    = frmModelAddCtrl(m, "Label", "", frame);
    int32_t   outLen = 0;

    frmModelRenameCtrl(m, "Command1", "cmdOk");
    frmModelSetCtrlProp(m, lbl, "Caption", "Name:");

    char *out = frmModelText(m, &outLen);

    // write out[0 .. outLen) to the file
    free(out);
    frmModelFree(m);
}

Connection Library

One interface for byte-stream connections: a serial port, a modem with an AT command dialer, the secure link to the DVX host proxy (over a COM port or over the network), and Telnet. Every call except the host name lookup is non-blocking: connOpen starts the connection (dialing, TCP connect, key exchange) and connPoll moves it along, fills the receive buffer, and sends what connWrite queued. Call connPoll as often as you can (every frame). The lookup of a host name waits for the answer, but other programs keep running meanwhile; a dotted address needs no lookup.

Loaded as: bin/libs/kpunch/conn/conn.lib (needs the serial, networking, and transfer libraries; the transfer library for its BASIC functions)

Header: conn/conn.h

Connection Types

  Type         Behaviour
  ----         ---------
  ConnSerialE  A COM port with the given speed, data bits, parity, stop bits, and flow control (none, XON/XOFF, RTS/CTS). Open at once.
  ConnModemE   The COM port, then the init string (waits for OK), then dial prefix + phone number. CONNECT opens the connection; BUSY, NO ANSWER, and NO CARRIER dial again after redialDelay seconds, up to redialCount more times; NO DIAL TONE and ERROR end it. An empty phone number opens the port for typing AT commands. When the carrier (DCD) has been seen and drops, the connection ends. Closing drops DTR, which makes the modem hang up. Everything the modem says is also delivered as received data.
  ConnSecureE  The secure link (secLink): the key exchange runs in the background, authenticated when a passphrase is set (both sides must use the same one, or none). Only encrypted, authenticated data on channel 0 is delivered. With host empty it runs on the COM port; with host set it runs over a TCP connection to host:tcpPort (the proxy's listening port, 2323 by default), through secLinkOpenStream.
  ConnTelnetE  TCP to host:tcpPort with Telnet (RFC 854). Negotiated: the host may ECHO and SUPPRESS-GO-AHEAD; BINARY both ways; TERMINAL-TYPE answered "ANSI"; NAWS sends the size given to connSetWindowSize (80 x 25 until then, sent again on every change). Other options are refused, and an option is only answered when its state changes. 0xFF is doubled on the way out and undoubled on the way in; outside binary mode a lone CR goes out as CR NUL and CR NUL comes in as CR.

The Telnet protocol itself is a separate filter (connTelnet.c) that only moves bytes, so it is tested byte for byte without a network.

ConnConfigT

  Field                     Meaning
  -----                     -------
  type                      ConnTypeE.
  comPort                   RS232_COM1 .. RS232_COM4.
  bps, dataBits, parity     Line settings (parity 'N', 'E', 'O', 'M', or 'S').
  stopBits, flow            1 or 2 stop bits; RS232_HANDSHAKE_* flow control.
  initString, dialPrefix    Modem: sent before dialing (empty = none); dial command such as ATDT.
  phone                     Modem: the number (empty = no dialing).
  redialCount, redialDelay  Modem: extra attempts and the seconds between them.
  passphrase                Secure link: the shared secret (empty = none).
  host, tcpPort             Telnet; secure link over the network (host set) or over the COM port (host empty). A name or a dotted address.

Functions

  Function                         Description
  --------                         -----------
  ConnT *connCreate(void)          Allocate a closed connection.
  void connDestroy(c)              Close and free.
  void connConfigDefaults(cfg)     COM1, 9600 8N1, no flow control, ATZ, ATDT.
  int32_t connOpen(c, cfg, nowMs)  Start connecting (cfg is copied). CONN_OK, CONN_ERR_PARAM, CONN_ERR_PORT, or CONN_ERR_HOST (the host was not found or refused at once); connStatus explains. Later failures (refused, no answer within 30 seconds, connection lost, host closed it) end the connection with the reason in connStatus.
  void connPoll(c, nowMs)          Dial, key exchange, receive, send, timers.
  int32_t connRead(c, buf, max)    Take received bytes (0 when none).
  int32_t connWrite(c, data, len)  Queue bytes to send; returns the count accepted (0 while not open or when the 8 KB send buffer is full).
  int32_t connPending(c)           Bytes queued (here and in the layer below) not yet sent.
  bool connSendBreak(c, nowMs)     Hold a break for 300 ms (serial and modem).
  void connClose(c)                Close; a modem hangs up.
  ConnStateE connState(c)          ConnClosedE, ConnOpeningE (dialing, key exchange), or ConnOpenE.
  const char *connStatus(c)        What is happening, or why the connection ended, in plain words.
  uint32_t connStatusSerial(c)     Changes whenever the status text changes.
  void connSetWindowSize(c, cols, rows)  The terminal size for Telnet's NAWS (sent now if the host asked for it).
  bool connTypeAvailable(type)     True for every ConnTypeE value.
  bool connCanBreak(type)          True for serial and modem.
  const char *connErrorText(err)   Text for a connOpen result.

Example

ConnT      *c = connCreate();
ConnConfigT cfg;

connConfigDefaults(&cfg);
cfg.type = ConnModemE;
snprintf(cfg.phone, sizeof(cfg.phone), "555-0100");
connOpen(c, &cfg, nowMs());

// every frame:
connPoll(c, nowMs());
n = connRead(c, buf, sizeof(buf));

A Telnet session is the same with cfg.type = ConnTelnetE, cfg.host = "bbs.example.org", and cfg.tcpPort = 23.

A file transfer runs over a connection by giving the transfer library connRead, connWrite, and connPending as its XferIoT callbacks (ZMODEM over Telnet works: 0xFF bytes are escaped by the Telnet filter).

Connections from BASIC

Add conn.bas to a BASIC project (and xfer.bas for transfers): connBas.c gives BASIC the connection library by handle, plus what a terminal program needs around it. The Terminal program is written this way.

  Function                                  Description
  --------                                  -----------
  ConnCreate()                              A closed connection with connConfigDefaults; a handle, or 0.
  ConnSetSerial h, com, bps, data, parity$, stop, flow  COM port 1-4 and its framing (flow: CONN_FLOW_NONE, _XONXOFF, _RTSCTS).
  ConnSetModem h, init$, prefix$, phone$, redials, delay  The modem's commands and redialing.
  ConnSetHost h, host$, port                Telnet; the secure link over the network ("" = over the COM port).
  ConnSetPassphrase h, pass$                The secure link's passphrase, forgotten once ConnOpen used it.
  ConnSetWindowSize h, cols, rows           connSetWindowSize.
  ConnSetXferWindow h, bytes                XferOptionsT.rxWindow of the transfers started later: 0 = no limit, up to XFER_WINDOW_MAX; -1 (the default) = by the line, see below.
  ConnOpen(h, type)                         connOpen with type and the settings so far.
  ConnErrorText$(code)                      connErrorText of a ConnOpen result.
  ConnPoll h                                One step: connPoll, then the running transfer (cancelled when the line drops), else what arrived to the attached Terminal (see below). Call it from a Timer.
  ConnState, ConnStatus$, ConnStatusSerial  connState, connStatus, connStatusSerial.
  ConnWrite(h, bytes$), ConnRead$(h), ConnPending(h)  Bytes out and in without a Terminal: connWrite / connRead with BINARY STRING data, so every byte value passes.
  ConnCanBreak(type), ConnSendBreak(h)      connCanBreak, connSendBreak.
  ConnClose h, ConnFree h                   Close (a modem hangs up); close and free the handle.
  ConnAttach(h, ctrl$), ConnDetach h        Show the connection on a Terminal control, and send what is typed there.
  ConnSetEcho h, on; ConnSetAnswerback h, text$  Local echo (Enter as CR LF); the reply to ENQ.
  ConnCapture(h, path$)                     Append what arrives to path ("" stops). True while capturing.
  ConnZmodemRequest(h)                      True once after a ZMODEM sender started (xferDetectFeed).
  ConnSendFiles(h, proto, files$, escapeCtl, resume)  Send files (one path per line). An xfer.bas handle, with XFER_FAILED and the reason when it could not start; 0 when the line is not open or busy.
  ConnReceiveFiles(h, proto, target$, exists, escapeCtl)  Receive (exists: XFER_EXIST_).

ConnPoll with a Terminal attached does what the C Terminal's heartbeat did: it reads up to 4 KB, appends them to the capture file, answers each ENQ with the answerback (ENQ itself is not shown), stops at a ZMODEM start frame (the rest of what was read is dropped, and ConnZmodemRequest becomes True), and writes the rest to the Terminal. While a transfer started on the connection runs (or waits for XferAnswer), ConnPoll moves it along instead, nothing reaches the Terminal, and typing is dropped; a transfer on a line that is no longer open is cancelled. The receive window of a ZMODEM download on a serial or modem line without flow control is 2 KB, so a slow frame never loses data, unless the program set one with ConnSetXferWindow.

The Terminal binding goes through the form runtime (basFormRtTermBind, basFormRtTermUnbind, and basFormRtTermForget in basrt, looked up when ConnAttach first needs them): the runtime unbinds a Terminal whose form unloads, and tells the library, so a connection never writes to a freed control. Every handle belongs to the program that made it; whatever it leaves open is closed (a modem hangs up, a capture file is closed, a transfer cancelled) when it ends or is ended.

Networking Library

TCP/IP for DVX programs with the standard BSD socket API: a C programmer who knows sockets writes DVX network code the usual way. IPv4 today; the types and constants for IPv6 (AF_INET6, sockaddr_in6, sockaddr_storage) are defined so code can be written for both.

Loaded as: bin/libs/kpunch/dvxnet/dvxnet.lib (needs the shell, the task switcher, and the core library)

Headers: dvxnet/dvxNet.h (settings and status), and the standard headers in dvxnet/include: sys/socket.h, netinet/in.h, netinet/tcp.h, arpa/inet.h, netdb.h, sys/select.h, poll.h, sys/ioctl.h, and errno.h (the C library's plus the network codes). Put the include directory on the compiler's include path ahead of the C library's. In the SDK they are in include/net, and a project Makefile that sets NET = 1 gets them (see SDK Quick Start).

The socket API

Waiting, polling, and cooperative multitasking

Settings and status (dvxNet.h)

How it works

Networking from BASIC

Hardware

The network card is reached through a DOS packet driver (FTP Software's specification) loaded before DVX, for example NE2000.COM 0x60 3 0x300 for an NE2000 at I/O 300h, IRQ 3. dvxnet finds it on software interrupts 60h to 80h (or the one set in DVX.INI) and registers for IPv4 and ARP frames. Any card with a packet driver works, and so do SLIP/PPP links through the usual packet-driver shims.

Without a packet driver the library still runs: 127.0.0.1 works (programs can talk to each other), and connections elsewhere fail with ENETUNREACH.

Example

#include <arpa/inet.h>
#include <netdb.h>
#include <sys/socket.h>
#include <unistd.h>

struct addrinfo  hints = { 0 };
struct addrinfo *res;
int              fd;

hints.ai_family   = AF_INET;
hints.ai_socktype = SOCK_STREAM;

if (getaddrinfo("bbs.example.org", "telnet", &hints, &res) == 0) {
    fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

    if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) {
        send(fd, "hello\r\n", 7, 0);
    }

    close(fd);
    freeaddrinfo(res);
}

Licence of the TCP/IP stack

The protocol engine is lwIP 2.2.1, unmodified, under the 3-clause BSD licence:

Copyright (c) 2001, 2002 Swedish Institute of Computer Science.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The Socket API

Standard names, arguments, and meanings. A socket is an ordinary file descriptor: read(), write(), and close() work on it, as do fcntl(F_GETFL/F_SETFL, O_NONBLOCK), ioctl(FIONBIO, FIONREAD), select(), and poll().

  Function                                   Notes
  --------                                   -----
  socket(AF_INET, SOCK_STREAM or SOCK_DGRAM, 0)  Starts the network with the saved settings the first time. EAFNOSUPPORT for other families (AF_INET6 included, for now).
  bind, listen, accept                       listen's backlog is capped at SOMAXCONN (16). An unbound socket listens on a free port. Accepted sockets are blocking (O_NONBLOCK is not inherited, as on Linux).
  connect                                    Blocking: waits for the connection (SO_SNDTIMEO limits the wait: EINPROGRESS). Non-blocking: EINPROGRESS, then poll/select for writable and read SO_ERROR (ECONNREFUSED, ETIMEDOUT, ...). UDP: sets the default peer; AF_UNSPEC removes it.
  send, recv, sendto, recvfrom               Flags MSG_PEEK, MSG_DONTWAIT, MSG_WAITALL (TCP), MSG_NOSIGNAL (accepted; there are no signals). MSG_OOB: EOPNOTSUPP. A blocking TCP send returns when everything is queued. recv returns 0 at the end of the stream. UDP messages longer than the buffer are cut; up to 8192 bytes are sent.
  shutdown(SHUT_RD, SHUT_WR, SHUT_RDWR)      As usual; the other side reads the end of the stream after SHUT_WR.
  close                                      An orderly close in the background; unread received data makes it a reset (as on BSD).
  setsockopt / getsockopt                    SO_REUSEADDR, SO_KEEPALIVE, SO_BROADCAST, SO_RCVTIMEO, SO_SNDTIMEO, TCP_NODELAY; SO_ERROR, SO_TYPE, SO_ACCEPTCONN, SO_RCVBUF, SO_SNDBUF (read only: 5840 for TCP, 8192 for UDP); SO_LINGER is accepted and reads as off. Others: ENOPROTOOPT.
  getsockname, getpeername                   IPv4 addresses.
  select, poll                               Sockets report real readiness (POLLIN, POLLOUT, POLLERR, POLLHUP); other descriptors always count as ready.
  getaddrinfo, freeaddrinfo, gai_strerror    AF_INET or AF_UNSPEC; names, dotted addresses, "localhost"; service numbers or the common names (telnet, http, ftp, smtp, domain, echo, ...). AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST, AI_NUMERICSERV.
  gethostbyname, gethostbyaddr, h_errno      gethostbyaddr does no reverse lookup: the name is the dotted address.
  getnameinfo                                Numeric host and service; NI_NAMEREQD gives EAI_NONAME.
  gethostname                                The computer name from the network settings.
  inet_pton, inet_ntop                       AF_INET (AF_INET6: EAFNOSUPPORT).
  inet_addr, inet_aton, inet_ntoa            BSD forms (a.b.c.d, a.b.c, a.b, a; decimal, octal, or hex).
  htonl, htons, ntohl, ntohs                 Inline in <netinet/in.h>; also real functions for code built without optimisation.

Error Codes

errno.h adds the network codes: EWOULDBLOCK (the same as EAGAIN), EINPROGRESS, EALREADY, ENOTSOCK, EDESTADDRREQ, EMSGSIZE, EPROTOTYPE, ENOPROTOOPT, EPROTONOSUPPORT, ESOCKTNOSUPPORT, EOPNOTSUPP (ENOTSUP), EPFNOSUPPORT, EAFNOSUPPORT, EADDRINUSE, EADDRNOTAVAIL, ENETDOWN, ENETUNREACH, ENETRESET, ECONNABORTED, ECONNRESET, ENOBUFS, EISCONN, ENOTCONN, ESHUTDOWN, ETIMEDOUT, ECONNREFUSED, EHOSTDOWN, EHOSTUNREACH. The C library's strerror() does not know them; netErrorText(errno) gives plain words for them.

Descriptors

Sockets are real descriptors below FD_SETSIZE (256), at most 32 at a time. dup() and dup2() refuse socket descriptors (EOPNOTSUPP), lseek gives ESPIPE.

Waiting, Polling, and Cooperative Multitasking

DVX runs programs cooperatively, so a call that has to wait never stops the system: blocking connect, accept, recv, send (with a full buffer), select, poll, and name lookups wait by running the network and then letting the other tasks run (tsYield), again and again, until they can return. Other programs, and other connections, keep working meanwhile.

The network also runs from the shell's idle time, so connections progress (and data arrives) while a program is busy with other things; every socket call runs it too. A program that polls non-blocking sockets in its own loop needs nothing else.

Right after the network starts, DHCP needs a moment to get an address. connect, sendto, and name lookups wait for it (up to 20 seconds) instead of failing, so a program started together with DVX can connect at once.

Sockets Belong to Programs

Each socket belongs to the program that created it (accept gives the new socket to the program that accepts). When a program ends, normally or because it was ended or crashed, the shell closes every socket it still had open, and drops its unfinished name lookups.

Settings and Status (dvxNet.h)

The settings live in DVX.INI, section [network]: dhcp (true/false), address, netmask, gateway, dns1, dns2, hostname, and vector (auto or 0x60-0x80). The Control Panel's Network page edits them.

  Function                                  Description
  --------                                  -----------
  int32_t netStart(void)                    Start with the saved settings unless running: NET_OK, NET_ERR_NO_DRIVER (the stack runs anyway, for 127.0.0.1), NET_ERR_CONFIG, or NET_ERR_NO_MEMORY. Socket calls do this themselves.
  void netStop(void)                        Close every socket and stop (the packet driver is released).
  int32_t netRestart(void)                  Stop, then start with the saved settings (after they changed).
  void netPoll(void)                        Take received frames, run the timers, send. Called from the shell's idle time and by every socket call.
  void netGetStatus(NetStatusT *status)     State (NetStoppedE, NetNoDriverE, NetConfiguringE, NetUpE), plain-words message, addresses, name servers, host name, packet driver name, interrupt and hardware address, packet and byte counters, frames lost while DVX was busy, open sockets.
  const char *netStateText(int32_t state)   Plain words for a state.
  const char *netErrorText(int32_t err)     Plain words for an errno value, network codes included.
  void netConfigDefaults(NetConfigT *cfg)   DHCP, find the driver, host name DVX.
  void netConfigLoad(NetConfigT *cfg)       Read DVX.INI [network].
  void netConfigFromPrefs(h, cfg)           Read [network] from an open preferences handle.
  void netConfigToPrefs(h, cfg)             Store cfg as [network] in an open handle (the caller saves).
  bool netConfigEqual(a, b)                 True when two configurations are the same settings (the Control Panel restarts the network only when they differ).
  const char *netConfigProblem(cfg)         NULL when usable, else why not in plain words (bad address or mask, name, interrupt).
  bool netParseAddress(text, &addr)         Strict dotted IPv4 (four decimal parts 0-255) to network byte order.
  void netCloseAppSockets(int32_t appId)    Close every socket of that app and free the net.bas settings handles it left (the shell calls this when an app ends).

Name servers set in the settings are used even with DHCP; left empty, the DHCP server's are used.

How It Works

  Part           Source                   Job
  ----           ------                   ---
  BSD layer      netSock.c, netResolve.c  sockaddr and errno handling, descriptors, blocking and timeouts, select/poll, lookups.
  Protocol glue  netProto.c               One object per socket over lwIP's raw TCP/UDP API: received data stays queued until read (the TCP window reopens only for bytes read), sends copy into lwIP, errors are kept for SO_ERROR.
  Stack          netStack.c               lwIP start/stop, the Ethernet interface, DHCP, DNS, polling, counters.
  Driver         pktDrvDos.c, pktRing.c   Packet driver calls through DPMI; a real-mode callback receives frames at interrupt time into a 16-slot ring in conventional memory; the stack takes them out later.
  DOS glue       netDos.c                 Descriptors through DJGPP File System Extensions, fcntl for sockets, the shell hooks (idle poll, app cleanup), orderly shutdown.
  Settings       dvxNet.c                 DVX.INI, status, errors in plain words.

lwIP runs without threads (NO_SYS), configured only by port/lwipopts.h: TCP MSS 1460 with a 4-segment window, 16 TCP connections, 48 KB of stack memory, DHCP, DNS (2 servers), ARP, ICMP (ping answers), IP fragments, loopback. Nothing runs lwIP at interrupt time.

The receive ring holds 16 frames. A burst larger than that while DVX is busy loses frames (counted as "lost" in the status); TCP sends them again.

On the host the same sources are built into the "net" test suite: a fake packet driver passes frames through the real ring to a second copy of the stack in another process, which plays DHCP, DNS, and echo server.

Networking from BASIC

net.bas declares simple functions over the socket API (netBas.c): HostLookup$, HostName$, NetAddress$, NetError$, TcpConnect, TcpListen, TcpAccept, TcpSend, TcpPending, TcpRecv$, TcpConnected, TcpShutdown, TcpPeer$, TcpLocalPort, TcpSetNoDelay, TcpSetKeepAlive, TcpClose, UdpOpen, UdpSend, UdpRecv$, UdpFrom$, UdpClose. A handle is the socket's descriptor + 1, so 0 means failure. Only TcpConnect (up to 20 seconds) and TcpSend wait; everything else returns at once, so a program polls from a Timer. TcpSend, TcpRecv$, UdpSend, and UdpRecv$ pass their data as BINARY STRING (pointer and length), so every byte value goes through. See the BASIC language reference, Include Libraries.

  BASIC                                   C it calls
  -----                                   ----------
  HostName$(address)                      getnameinfo: the address itself, as this stack does not look names of addresses up; "" when it is not an address.
  TcpShutdown(handle)                     shutdown(SHUT_WR); True when done.
  TcpPeer$(handle)                        getpeername, as "address:port" ("" when not connected).
  TcpLocalPort(handle)                    getsockname: the local port (-1 for a bad handle).
  TcpSetNoDelay(handle, enable)           setsockopt(IPPROTO_TCP, TCP_NODELAY); True when done.
  TcpSetKeepAlive(handle, enable)         setsockopt(SOL_SOCKET, SO_KEEPALIVE); True when done.
  NetStop                                 netStop (every program's sockets are closed).

The network settings and status (what the Control Panel's Network page shows) are reachable too, over the functions of dvxNet.h:

  BASIC                                   C it calls
  -----                                   ----------
  NetConfigLoad(prefs)                    netConfigFromPrefs on a prefs.bas handle (prefsBasHandle; 0 = the defaults) into a new settings handle, which also keeps the settings as read.
  NetConfigGet$ / NetConfigSet            One field by name: dhcp, vector, address, netmask, gateway, dns1, dns2, hostname (text; yes / no as "-1" / "0").
  NetConfigProblem$(cfg)                  netConfigProblem ("" when usable).
  NetConfigChanged(cfg)                   netConfigEqual against the settings as read.
  NetConfigStore(cfg, prefs)              netConfigToPrefs into a prefs.bas handle.
  NetConfigFree(cfg)                      Free the handle.
  NetVectorCount / NetVector(i)           The packet driver interrupt choices: 0 (find it), then NET_VECTOR_FIRST..NET_VECTOR_LAST.
  NetRestart()                            netRestart; True for NET_OK.
  NetStatus$(field)                       netGetStatus, one field as text: message, up, link, dhcp, address, netmask, gateway, dns1, dns2, hostname, driver, mac, vector, sockets, rxpackets, rxbytes, rxdropped, txpackets, txbytes.

Settings handles (1 to 16, 0 = none) belong to the program that made them; netCloseAppSockets frees what a program left.

DVX Shell Library

The DVX shell library manages the lifecycle of DXE applications: loading, launching, tracking, and reaping. It provides the bridge between the DVX GUI compositor and dynamically loaded DXE3 application modules.

Header: dvxshell/shellApp.h

Loaded as: bin/libs/dvxshell.lib

App Model

The shell supports two kinds of DXE apps:

Both types use the same DXE interface: an exported appDescriptor and appMain function.

DXE Interface

Every .app DXE module must export these symbols (COFF convention uses leading underscore):

  Symbol                Type                            Required
  ------                ----                            --------
  _appDescriptor        AppDescriptorT                  Yes
  _appMain              int32_t (*)(DxeAppContextT *)   Yes
  _appShutdown          void (*)(void)                  No

The shell resolves appDescriptor at dlopen time; if missing, the load fails with an error dialog. If its abiVersion is not the shell's DVX_ABI_VERSION the app was built against other headers: the load fails with "This program was built for a different version of DVX and cannot run." and both numbers go to DVX.LOG. appMain is called immediately for callback-only apps or from the new task for main-loop apps. appShutdown, if present, is called during graceful reap (but not during force kill from crashed apps).

Callback-Only vs Main-Loop Apps

The app descriptor's hasMainLoop flag selects between two very different lifecycles:

Both app types use the same export interface; only the descriptor's flags differ. Apps cannot switch modes at runtime.

Icon Conventions

Shell-level UI (Program Manager, Task Manager) displays app icons at 16x16 and 32x32. Icons are not handled by the shell itself; each app embeds its own icons via the DVX resource system (DVX_RES_ICON):

The Program Manager reads the app's 32x32 icon resource when building shortcut tiles. Apps without an icon resource fall back to a default shell-provided bitmap.

State Machine

App slots progress through four states:

Free -> Loaded -> Running -> Terminating -> Free

LoadedE is transient (only during shellLoadApp before the entry point is called). TerminatingE means the app's task has exited, or a force kill was deferred (killPending) because the app's code was still running, but cleanup has not yet occurred. The shell's main loop reaps terminating apps each frame via shellReapApps().

Contents

Types and Constants

Lifecycle API

Query API

Starting Up

Program Folders API

Desktop Callbacks

System Information

Installed Programs

Running Programs and Documents

The Screen Saver

File Associations

BASIC Entry Points

BASIC System Settings

Types and Constants

Constants

  Constant              Value                           Description
  --------              -----                           -----------
  SHELL_APP_NAME_MAX    64                              Maximum length of an app name string.
  SHELL_STACK_DEFAULT   0                               Use in AppDescriptorT.stackSize to get the default task stack size.
  SHELL_DESKTOP_APP     "apps/kpunch/progman/progman.app"  Default desktop application path.
  SHELL_INI_SECTION     "shell"                         DVX.INI section of the shell's own keys.
  SHELL_INI_DESKTOP     "desktop"                       [shell] key: the desktop app (SHELL_DESKTOP_APP when missing).
  SHELL_INI_WELCOME     "welcome"                       [shell] key: a program started after the desktop while the key exists (see Starting Up).
  SHELL_HELP_TOPIC_MAX  128                             Capacity of DxeAppContextT.helpTopic.
  DVX_HELP_VIEWER_APP_LITERAL   "APPS\KPUNCH\DVXHELP\DVXHELP.APP"  Path of the help viewer app.
  DVX_SYSTEM_HELP_FILE_LITERAL  "APPS\KPUNCH\PROGMAN\DVXHELP.HLP"  Path of the system help file (F1 fallback, Program Manager's Help menu).

SHELL_APP_CALL

SHELL_APP_CALL(app, call);

Brackets a direct shell call into an app's code (appMain of a callback-only app, appShutdown, onHelpQuery, onNewArgs). It counts the call on the calling task (shellAppCallEnter / shellAppCallLeave), so a force kill issued from inside that call is deferred until the call has returned.

AppDescriptorT

Exported by every DXE app as a global named appDescriptor. The shell reads it at load time to determine how to launch the app. Declare it with SHELL_APP_DESCRIPTOR, which names the global and fills abiVersion:

SHELL_APP_DESCRIPTOR(
    .name          = "Clock",
    .hasMainLoop   = true,
    .multiInstance = true,
    .stackSize     = SHELL_STACK_DEFAULT,
    .priority      = TS_PRIORITY_LOW
);
  Field                          Type      Description
  -----                          ----      -----------
  abiVersion                     uint32_t  DVX_ABI_VERSION the app was built with (always the first field). The shell refuses the app when it differs from its own.
  name[SHELL_APP_NAME_MAX]       char[]    Display name of the application.
  hasMainLoop                    bool      false = callback-only, true = dedicated task.
  multiInstance                  bool      true = allow multiple simultaneous instances via temp copy.
  stackSize                      int32_t   SHELL_STACK_DEFAULT or explicit byte count for the task stack.
  priority                       int32_t   Task priority, TS_PRIORITY_LOW..TS_PRIORITY_HIGH (0..10). Any other value makes the launch fail.

DxeAppContextT

Passed as the sole argument to appMain(). Gives the app access to the shell's GUI context and its own identity. Heap-allocated by shellLoadApp so its address is stable across reallocations of the internal app slot table (apps may safely cache the pointer in their static globals).

  Field                          Type                    Description
  -----                          ----                    -----------
  shellCtx                       AppContextT *           The shell's GUI context (for creating windows, drawing, etc.).
  appId                          int32_t                 This app's unique ID (slot index, 1-based).
  appPath[DVX_MAX_PATH]          char[]                  Full path to the .app DXE file.
  appDir[DVX_MAX_PATH]           char[]                  Directory containing the .app file (for loading resources).
  userDir[DVX_MAX_PATH]          char[]                  The program's folder in the user folder, for its settings and the files it saves (e.g. USER\APPS\KPUNCH\DVXBASIC). Made on first use (shellEnsureUserDir).
  args[1024]                     char[]                  Launch arguments (empty string if none): blank-separated, one with blanks in double quotes. Walk them with dvxNextArg. A document opened through its file type arrives here (see File Associations); BASIC programs read these arguments with COMMAND$.
  helpFile[DVX_MAX_PATH]         char[]                  Help file path (for F1 context help). Before appMain the shell sets it to <app>.hlp in the .app's directory when that file exists (else empty); appMain may change it.
  helpTopic[128]                 char[]                  Current help topic ID (updated by the app at runtime). Size is SHELL_HELP_TOPIC_MAX.
  onHelpQuery                    void (*)(void *ctx)     Optional callback the shell fires on F1 so the app can refresh helpTopic from context.
  helpQueryCtx                   void *                  Opaque context pointer passed to onHelpQuery.
  onNewArgs                      void (*)(void *ctx, const char *args)  Optional. Set by an app that can take new launch arguments while running. Loading a single-instance app that is already running and sets this hands it the new arguments and raises it (the load returns its app id) instead of saying it is already running; F1 hands its "helpfile [topic]" arguments to a running help viewer that sets this, and raises it, instead of loading another copy.
  newArgsCtx                     void *                  Opaque context pointer passed to onNewArgs.

The appDir field is derived from the .app file path at load time so apps can find their own resources via relative paths. This is necessary because the working directory is shared by all apps in DOS. The user directory is mirrored from the app path in the user folder (platformAppUserDir): an app at APPS\KPUNCH\DVXBASIC\dvxbasic.app gets USER\APPS\KPUNCH\DVXBASIC. A full path inside the DVX directory (for example one picked with the Task Manager's Run... dialog) is mirrored the same way. An app outside the DVX directory, or loaded by a bare file name, gets USER\APPS\<file name without extension>. The user folder is USER in the DVX directory unless the DVXUSER environment variable names another (see DVX Folders in the architecture guide).

AppStateE

  Value                 Description
  -----                 -----------
  AppStateFreeE         Slot is available for reuse.
  AppStateLoadedE       DXE loaded, entry point not yet called (transient).
  AppStateRunningE      Entry point called, app is active.
  AppStateTerminatingE  Shutdown in progress, awaiting reap.

ShellAppT

Per-app slot in the shell's app table. Slot 0 is reserved for the shell itself; apps use slots 1 and above.

  Field                          Type                            Description
  -----                          ----                            -----------
  appId                          int32_t                         Unique ID (slot index, 1-based; 0 = shell).
  name[SHELL_APP_NAME_MAX]       char[]                          Display name from AppDescriptorT.
  path[DVX_MAX_PATH]             char[]                          Original DXE file path.
  tempPath[DVX_MAX_PATH]         char[]                          Temp copy path for multi-instance (empty if not a copy).
  dxeHandle                      void *                          dlopen() handle for the DXE module.
  state                          AppStateE                       Current lifecycle state.
  hasMainLoop                    bool                            Whether this app has a dedicated task.
  mainTaskId                     uint32_t                        Task ID if hasMainLoop, else 0.
  entryFn                        int32_t (*)(DxeAppContextT *)   Pointer to appMain.
  shutdownFn                     void (*)(void)                  Pointer to appShutdown (may be NULL).
  dxeCtx                         DxeAppContextT *                Heap-allocated context (address stable across realloc).
  taskCodeDepth                  int32_t *                       Per task id: SHELL_APP_CALL calls into this app still open on that task's stack.
  killPending                    bool                            A force kill was deferred; shellReapApps finishes it.
  faulted                        bool                            It crashed: its force kill runs no kill hooks (shellRegisterAppKill).

App Lifecycle API

shellAppInit

void shellAppInit(void);

Initialize the app slot table. Seeds slot 0 (reserved for the shell). Must be called once at startup before any other shell API function.

shellLoadApp

int32_t shellLoadApp(AppContextT *ctx, const char *path);

Load and start an app from a DXE file. Returns the app ID (>= 1) on success, or -1 on error.

For callback-only apps, appMain() runs synchronously and returns before shellLoadApp returns. For main-loop apps, a cooperative task is created and the app begins running on the next tsYield().

If multiInstance is false in the app's descriptor and the same DXE is already loaded, the running app is handed args through its onNewArgs and raised, and its app id is returned, when it sets onNewArgs; otherwise the call fails with an error dialog. If multiInstance is true, the DXE is copied to a temp file so dlopen gets an independent code and data image.

shellLoadAppWithArgs

int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, const char *args);

Load and run an app with arguments. The args string is copied into DxeAppContextT.args before appMain() is called. Otherwise identical to shellLoadApp().

shellReapApps

bool shellReapApps(AppContextT *ctx);

Scan for and reap finished apps. Call once per frame from the main loop.

Returns true if any apps were reaped, so the caller can trigger a desktop refresh. For main-loop apps, termination is detected by the AppStateTerminatingE state (set when appMain returns). For callback-only apps, termination is detected when no windows remain for that app.

shellReapApp

void shellReapApp(AppContextT *ctx, ShellAppT *app);

Gracefully shut down a single app. Calls the app's shutdownFn (if present), destroys all windows belonging to the app, kills its task (if any), releases any widget classes and system-wide hotkeys the app registered and did not unregister (wgtUnregisterAppClasses, dvxUnregisterAppHotkeys), closes the DXE handle, and frees the context. The slot returns to AppStateFreeE.

shellForceKillApp

void shellForceKillApp(AppContextT *ctx, ShellAppT *app);

Forcibly kill an app. Used by the Task Manager "End Task" or as part of crash recovery (which marks the app faulted first). The app's shutdownFn is NOT called: a force kill targets a hung or faulting app whose own code cannot be trusted to run. Instead the libraries' kill hooks (shellRegisterAppKill) end what they run for the app, unless it faulted, then the shell drops every idle and desktop-update callback the app registered, destroys all of the app's windows, kills its task (for main-loop apps), and finally closes the DXE handle.

Cleanup order: callbacks and windows first (removes them from the compositor), then the task (frees its stack; any dispatch frames still open on it are dropped from the context's dispatch depth), then the widget classes and system-wide hotkeys the app registered (its shutdownFn never got the chance to unregister them), then the DXE handle (unmaps the code). Closing the DXE before destroying windows would cause callbacks into unmapped code.

If the app's code may still be running underneath the call -- End Task clicked in a dialog the app has open, a kill issued from the app's own task, or from inside a shell call into the app -- the unload is deferred: the app's windows are hidden and queued for destruction, the slot is marked Terminating, and the shell finishes the kill (still without shutdownFn) at the next safe point of its main loop, once that code has returned. Event-handler frames on the app's own task do not hold the kill back, since they end with the task: an app stuck in an endless event handler is killed at once (or at the next main-loop pass).

shellOpenHelp

void shellOpenHelp(AppContextT *ctx, const char *args);

Show help. args is "helpfile [topicId]"; empty or NULL args open the system help. A running help viewer that accepts new arguments (onNewArgs) is given them and raised; otherwise a new viewer is loaded. F1 and application Help menus both use it, so a Help > Contents command can pass ctx->helpFile as is.

shellTaskDepthForget / shellTaskDepthShare / shellAppCodeForgetTask

void    shellTaskDepthForget(uint32_t taskId);
int32_t shellTaskDepthShare(uint32_t taskId);
void    shellAppCodeForgetTask(uint32_t taskId);

The context's dispatch depth counts event-dispatch frames on every task's stack. shellTaskDepthShare returns one task's share. When a task is killed its frames can never unwind: shellTaskDepthForget removes its share, and (through shellAppCodeForgetTask) the SHELL_APP_CALL calls it held. Crash recovery does the same for the crashed task and the shell's own task.

shellTerminateAllApps

void shellTerminateAllApps(AppContextT *ctx);

Force-kill all running apps. Called during shell shutdown, after the main loop has exited, so every active app is released immediately (never deferred) and no shutdownFn runs. By then Exit DVX (below) has already given every app except the desktop the chance to close itself; what is left is the desktop app and apps that had no visible window to close.

Exit DVX: shellRequestExit / shellRunExitRequest / shellCloseAllApps

void shellRequestExit(void);
void shellRunExitRequest(AppContextT *ctx, int32_t keepAppId);
bool shellCloseAllApps(AppContextT *ctx, int32_t keepAppId);

Exiting DVX never closes an app without first asking it to close, the way the user would. The desktop app's Exit command (Program Manager's File > Exit DVX and its window's close box, after its own "Are you sure?" confirmation) calls shellRequestExit, which only records the request. The shell's main loop calls shellRunExitRequest after every frame; when a request is pending it clears it, runs shellCloseAllApps for every app except the desktop, and calls dvxQuit only if that returns true. Otherwise DVX keeps running.

shellCloseAllApps asks the running apps to close one at a time, in app-slot order, exactly as if the user had closed their windows: for each app, its oldest visible window is raised and dvxRequestWindowClose is called on it inside a dispatch frame, so the window's onClose runs just as it does for the close box or Alt+F4. A C app's onClose shows its own save prompt; a BASIC app's form runs QueryUnload and Unload. This repeats with the app's next visible window until it has none left or the app has ended. Windows that hide on close (tool palettes) count as closed, and hidden windows are left alone. Apps that close are reaped normally (their shutdownFn runs). Between requests the shell keeps pumping frames (dvxUpdate, a task yield, shellReapApps), so main-loop apps can react.

A window that is still open after a request is judged as follows:

shellCloseAllApps also returns false, without asking any app, when a dialog box is already open (ctx->modalWindow): the dialog is raised so it can be answered first. It pumps the event loop, so call it only from the shell's top level, never from inside a callback. An app ended from the Task Manager while the sequence runs simply counts as closed.

Closing one app: shellRequestAppClose / shellRunCloseRequests / shellCloseApp

void shellRequestAppClose(int32_t appId);
void shellRunCloseRequests(AppContextT *ctx);
bool shellCloseApp(AppContextT *ctx, int32_t appId);

shellCloseApp asks one app to close exactly as shellCloseAllApps asks each (the same routine): its visible windows are closed the way the user would close them, so its save prompt or QueryUnload runs, and one that does not respond is offered to be ended ("'name' is not responding to the request to close. End it?", titled Close Program). True when the app is gone or was not running; false when it kept a window or a dialog box is open. It pumps the event loop, so it belongs to the shell's top level. shellRequestAppClose only records the request (a second request for the same app is not added; an app that ends, or is ended, before it runs has its requests dropped by shellPurgeAppCallbacks, since its slot may be reused); the main loop calls shellRunCloseRequests right after each frame, before shellRunExitRequest, and it asks the waiting apps oldest first. BASIC's ShellRunningClose is shellRequestAppClose.

The desktop ending

The shell itself exits DVX when the desktop app ends in any other way -- its last window closed, ended from the Task Manager, or crashed: an app-cleanup hook (shellRegisterAppCleanup) notices the desktop's id going away, logs "Shell: the desktop ended; exiting DVX" and calls shellRequestExit, so every other app is asked to close as above. If one of them refuses, DVX keeps running and ends as soon as the last app has ended. A desktop app (C or BASIC) therefore never calls dvxQuit itself.

Query API

shellGetApp

ShellAppT *shellGetApp(int32_t appId);

Look up an app slot by ID. Returns a pointer to the ShellAppT, or NULL if the ID is out of range or the slot is free. Valid app IDs are 1 through shellAppSlotCount() - 1.

shellAppSlotCount

int32_t shellAppSlotCount(void);

Return the total number of app slots (including slot 0). Use as the iteration bound when scanning all slots. Slot 0 is the shell itself; app slots start at 1.

shellRunningAppCount

int32_t shellRunningAppCount(void);

Count running apps, not counting the shell itself. Includes apps in both AppStateLoadedE and AppStateRunningE states.

shellRunningAppId

int32_t shellRunningAppId(int32_t index);

The id of running app index, 0 .. shellRunningAppCount() - 1 in slot order (the same apps shellRunningAppCount counts); 0 out of range. BASIC: ShellRunningId.

shellActivateApp

bool shellActivateApp(AppContextT *ctx, int32_t appId);

Bring an app to the front, as the Task Manager's Switch To does (it calls this): its topmost visible window that is not being destroyed is restored when minimized (dvxRestoreWindow; a maximized window stays maximized), else raised (dvxRaiseWindow), and gets the focus. False when the app has no visible window. BASIC: ShellRunningActivate.

shellAppFindByPath

ShellAppT *shellAppFindByPath(const char *path);

Return an active app slot loaded from path, or NULL. Paths are compared with platformPathSame (/ and \ the same separator, a trailing one ignored, case ignored on DOS).

Starting Up

shellMain brings DVX up in this order: preferences (DVX.INI), the task system, the GUI in the saved display mode, the desktop app, then the start-up programs.

Safe Video Mode

The GUI starts in the [video] mode of DVX.INI (prefsGetVideoMode). If that mode cannot be set (dvxInit fails), or a Shift key is held down while DVX starts (read with platformKeyboardGetModifiers just before the mode is set), or DVX was started with /SAFE, the shell starts in the built-in default mode instead (DVX_DEFAULT_VIDEO_W/H/BPP: 640x480, 16 bpp or the closest mode the card has). Before the desktop loads, a message box says so and asks whether to use that mode from now on: Yes writes it to DVX.INI (prefsSetVideoMode, prefsSave); No leaves DVX.INI alone, so the saved mode is tried again next time. If the default mode fails too, DVX does not start.

Start-up Programs

After the desktop app has loaded:

Both are started with shellRunCommand, so a missing program is reported to the user.

Program Folders API

Each program has two folders of its own. Its user folder (ctx->userDir) is in DVX's user folder, mirrored from its path: an app at APPS\KPUNCH\DVXBASIC\dvxbasic.app gets USER\APPS\KPUNCH\DVXBASIC. Settings and every file the program saves go there, apart from the program files, so an upgrade or a reinstall never overwrites them and backing up the user folder keeps them. Its DATA folder, beside its .app, holds the files it ships; DVX never writes there, and on a CD-ROM it cannot be written at all.

A program never writes or installs into SHARED (DVX's own fonts, sounds, themes, and wallpapers): its own files live in its DATA folder or as resources in its .app.

shellEnsureUserDir

int32_t shellEnsureUserDir(const DxeAppContextT *ctx);

Make the program's user folder, with all parent folders as needed. Returns 0 on success, -1 on failure. Call it before writing the first file there.

shellUserPath

void shellUserPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);

Build the full path of a file in the program's user folder by joining ctx->userDir with filename.

// Example:
char path[DVX_MAX_PATH];
shellEnsureUserDir(ctx);
shellUserPath(ctx, "PROGMAN.INI", path, sizeof(path));
// -> "USER\APPS\KPUNCH\PROGMAN\PROGMAN.INI"

shellDataPath

void shellDataPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);

Build the full path of a file the program ships in the DATA folder beside its .app ("" when it does not fit), e.g. "APPS\ACME\MYGAME\DATA\LEVEL1.DAT". Read-only.

Random Seed File

When the serial library is loaded, the shell feeds keyboard and mouse event timings to its random-number generator (dvxSetInputEntropyHook) and carries generator state across runs in SECLINK.RND in the user folder: the file is read and immediately rewritten at startup, and rewritten again at shutdown. The library is found at run time; without it no seed file is written.

Desktop Callbacks

The shell wires several AppContextT event hooks (onCtrlEsc, onF1, onTitleChange, idleCallback) during startup and exposes them to other DXEs via function-pointer extern and registration API. Desktop managers register a callback to refresh their display when apps come and go. The Task Manager DXE registers its Ctrl+Esc handler via the shellCtrlEscFn extern pointer (see Task Manager documentation); shellOpenTaskManager is the one way to open it.

Shell Event Hooks

  AppContextT field    Handler                 Purpose
  -----------------    -------                 -------
  idleCallback         shellIdleDispatch       Runs one frame of Run Minimized (shellMinimizeIdle) and every registered idle handler (see shellRegisterIdle), then yields to cooperative tasks, when dvxUpdate has no input events and no dirty rects.
  onCtrlEsc            ctrlEscHandler          shellOpenTaskManager.
  onF1                 f1HelpHandler           Opens the focused app's helpFile/helpTopic (or the system help) in the help viewer, reusing a running viewer that sets onNewArgs.
  onTitleChange        titleChangeHandler      Runs shellDesktopUpdate so Program Manager window lists refresh.

shellRegisterDesktopUpdate

void shellRegisterDesktopUpdate(void (*updateFn)(void));

Register a callback for app state change notifications. Multiple callbacks are supported; registering the same callback again has no effect. Apps typically call this during appMain() to receive notifications. A callback may register or unregister callbacks (including itself) while notifications are being delivered.

shellUnregisterDesktopUpdate

void shellUnregisterDesktopUpdate(void (*updateFn)(void));

Remove a previously registered callback. Call this before app shutdown to avoid dangling function pointers.

shellRegisterIdle

void shellRegisterIdle(void (*fn)(void *ctx), void *ctx);

Register an idle handler invoked every idle frame by the shell main loop, regardless of which app is foreground. Background pollers (serial and secLink) register here so their connections keep pumping while another app is in front. Registration is idempotent on the (fn, ctx) pair. The registering module MUST unregister before it is unloaded -- app DXEs do this from their _appShutdown hook on a graceful exit. On a force kill (which does not run _appShutdown) the shell drops the app's registrations itself.

shellUnregisterIdle

void shellUnregisterIdle(void (*fn)(void *ctx), void *ctx);

Remove a previously registered idle handler. No-op if the (fn, ctx) pair is absent, so a double-unregister is harmless.

shellRegisterAppCleanup

typedef void (*ShellAppCleanupFnT)(int32_t appId);

void shellRegisterAppCleanup(ShellAppCleanupFnT fn);
void shellUnregisterAppCleanup(ShellAppCleanupFnT fn);

Register a function the shell calls with an app's id whenever that app ends -- normally, force-killed, or after a crash -- once its windows are gone, next to the shell's own release of the app's widget classes and hotkeys. A library uses it to release what the app left open with it: the networking library closes the app's sockets this way. For library code that stays loaded; registering the same function again has no effect, and unregistering one that is not registered does nothing.

shellRegisterAppKill

void shellRegisterAppKill(ShellAppCleanupFnT fn);
void shellUnregisterAppKill(ShellAppCleanupFnT fn);

Register a function the shell calls with an app's id when the app is ended by force (End Task, a screen saver that would not close, DVX exiting) while none of the app's code is running and its windows are still there -- before the windows go and before the cleanup hooks. A library ends what it runs for the app the way the app's own end would: the BASIC runtime (basrt) frees a standalone program's VM, forms, screen, and module this way, closing its serial ports and its forms' windows, so End Task on a BASIC program leaks nothing. Not called for an app that faulted (its state cannot be trusted), nor for one whose kill had to wait until its code returned (End Task from inside the app's own dialog: its windows went at once). Registration rules as for cleanup hooks.

shellDesktopUpdate

void shellDesktopUpdate(void);

Notify all registered desktop callbacks that app state has changed. Called internally by the shell after loading or reaping an app. Can also be called by apps that need to trigger a desktop refresh. Each call also adds one to the change counter (shellDesktopChangeCount).

shellDesktopChangeCount

int32_t shellDesktopChangeCount(void);

How many times shellDesktopUpdate has run (it wraps around). For a desktop that cannot take a callback -- a BASIC program polls it from a Timer (ShellChangeCount) and refreshes its running-program count when the value differs from the one it saw last.

shellCtrlEscFn / shellOpenTaskManager

extern void (*shellCtrlEscFn)(AppContextT *ctx);
void shellOpenTaskManager(AppContextT *ctx);

shellCtrlEscFn is set by the taskmgr DXE's constructor; NULL if the task manager is not loaded. shellOpenTaskManager calls it when it is set and does nothing otherwise; Ctrl+Esc, Program Manager's Help > Task Manager, and BASIC's ShellTaskManager all open Task Manager through it.

System Information

Header: dvxshell/shellInf.h

Thin wrapper around the platform layer's hardware detection. Gathers system information at startup, logs it, and caches the result for display in dialogs.

shellInfoInit

void shellInfoInit(AppContextT *ctx);

Gather all hardware information via the platform layer, log each line to DVX.LOG, and store the result for later retrieval. Call once after dvxInit().

shellGetSystemInfo

const char *shellGetSystemInfo(void);

Return the formatted system information text. The returned pointer is valid for the lifetime of the process (static buffer in the platform layer).

Installed Programs

Header: dvxshell/shellProg.h

Finds the installed programs (every .app file under APPS, SHELL_PROG_DIR) and reads what they are called and what they look like, for launchers, file managers, and setup programs. A program's display name and description are its "name" and "description" text resources (the capitalized file name when it has no name resource); its picture is its "icon32" resource.

  Constant                 Value      Meaning
  --------                 -----      -------
  SHELL_PROG_DIR           "apps"     Folder, relative to the DVX root, holding every program.
  SHELL_PROG_EXT           ".app"     A program's file extension.
  SHELL_PROG_DESC_MAX      128        ShellProgramT.description capacity.
  SHELL_PROG_ICON          32         Large icon edge in pixels.
  SHELL_PROG_ICON_SMALL    16         Small icon edge in pixels.
typedef struct {
    char path[DVX_MAX_PATH];
    char name[SHELL_APP_NAME_MAX];
    char description[SHELL_PROG_DESC_MAX];
} ShellProgramT;

shellProgramScan

int32_t shellProgramScan(const char *dir, const char *skipPath, bool readInfo, ShellProgressFnT progress, void *progressCtx, ShellProgramT **outList);

Every program under dir (NULL for SHELL_PROG_DIR) and its subfolders, except skipPath (compared with platformPathSame; NULL for none), in the order found. With readInfo each entry is filled by shellProgramInfo; without it only path is set, which is fast (no program file is opened) and enough to notice programs being added or removed. progress, when not NULL, is called with progressCtx after each program (for example to keep the screen responsive). Returns the count; *outList is an array of that many entries, NULL when there are none. Free it with shellProgramListFree.

shellProgramInfo

void shellProgramInfo(const char *path, ShellProgramT *out);

Fill out with the program's path, display name, and description. Always succeeds: without resources (or for a file that is not a program) the name is the file name without its extension, first letter capitalized, and the description is empty.

shellProgramIcon

uint8_t *shellProgramIcon(AppContextT *ctx, const char *path, int32_t edge, int32_t *outPitch);

A picture edge x edge pixels in display format, for drawing with rectCopy: a program's icon32 resource, or any image file dvxLoadImage reads (BMP, PNG, JPEG, GIF). Other sizes are scaled (nearest pixel). Returns NULL when there is no picture. Free with dvxFreeImage.

shellItemIcon

uint8_t *shellItemIcon(AppContextT *ctx, const char *iconPath, const char *path, int32_t edge, int32_t *outPitch);

The icon a launcher shows for path (a program or a document), edge x edge pixels as shellProgramIcon gives them: the picture file iconPath when it is given (NULL or "" for none) and can be read; else path's own picture -- a program's icon32, or path itself when it is a picture file; else, for a document, the icon of the program its file type opens in (shellAssocCommand). NULL when there is none; free with dvxFreeImage. Program Manager's items (both sizes) and its Properties preview use it, so an item and its preview always show the same icon. (A BASIC program's LOADPICTURE of a .app reads the program's icon itself, with dvxResLoadIconData.)

shellItemIconFile

bool shellItemIconFile(AppContextT *ctx, const char *iconPath, const char *path, char *out, int32_t outSize);

The same rule as shellItemIcon (one implementation), naming the file the icon comes from in out: iconPath, path, or the program path's file type opens in; false with "" when none gives a picture. dvxLoadImage of that file (BASIC LOADPICTURE) gives the icon. BASIC: ShellItemIcon$ in shell.bas.

shellProgramListFree / shellIsProgram

void shellProgramListFree(ShellProgramT *list);
bool shellIsProgram(const char *path);

shellProgramListFree frees a list from shellProgramScan (NULL is fine). shellIsProgram is true when path has the .app extension (any case).

Running Programs and Documents

Header: dvxshell/shellRun.h

One way for every program to start another (Program Manager's items and Run..., the Task Manager's Run..., File Manager, setup programs). A command line is a program, in quotes if it contains blanks, followed by its arguments; it is split with dvxNextArg, the same rule a program uses to read its own arguments. A document in place of a program opens in the program associated with its file type (see File Associations).

shellRunProgram

int32_t shellRunProgram(AppContextT *ctx, const char *program, const char *args, const char *folder);

Start program with args. A .app is loaded with shellLoadAppWithArgs, its arguments first passed through shellResolveArgs(folder, ...). A DOS program (shellIsDosProgram: .EXE, .COM, or .BAT, any case) is set to run with shellRunDos (folder is where it runs; looked for in folder first when relative). Anything else is a document: it is looked for in folder first (when relative), must exist, and is opened with shellAssocCommand's program and arguments; when that program is a DOS program, the document goes to it by its full short name (it runs in its own folder) through shellRunDos. Problems are shown to the user in an error box (a missing document, a document type with no association; load errors come from shellLoadApp as usual). Returns the new application id, SHELL_RUN_DOS (0) for a DOS program set to run, or -1.

shellRunDos / shellRunDosRequest

void shellRunDos(const char *program, const char *args, const char *folder, bool pause);
void shellRunDosRequest(AppContextT *ctx, int32_t keepAppId);
bool shellIsDosProgram(const char *path);

Run a DOS program with DVX stepped aside (libdvx's dvxRunDos, which takes the same program, args, folder, and pause). shellRunDos only records the request (a newer one replaces one still waiting): closing the other programs pumps the event loop, which is not safe from an event handler. The main loop calls shellRunDosRequest after shellRunExitRequest; keepAppId is the desktop, which stays. A program named with a folder must exist ("Cannot find X.", titled Run DOS Program); a bare name is looked for on the PATH by DOS. When programs other than the desktop are running, a question names them (by their program names, shellProgramInfo) and says each asks about unsaved work as it closes; No, or a program that keeps a window (shellCloseAllApps), cancels the run. A program that cannot be started is reported after DVX is back ("DVX could not run X." with the reason). The Task Manager is the shell's own window and is not asked. Program Manager's DOS items call shellRunDos with their "Wait for a key" setting (PmItemT.pause, PROGMAN.INI itemN.pause); shellRunProgram uses pause false.

SHELL_DOS_FILTER ("DOS programs (*.exe;*.com;*.bat)") is the file dialogs' filter for them (Run..., Program Manager's Properties, Control Panel's File Types). A Program Manager item for a DOS program with no icon file of its own shows SHELL_DOS_ICON (SYSTEM\DOSPROG.BMP, from config/DOSPROG.BMP).

shellRunCommand

int32_t shellRunCommand(AppContextT *ctx, const char *command);

shellSplitCommand, then shellRunProgram with no working folder. Returns the application id or -1 (also for an empty command).

shellRunDialog

bool shellRunDialog(AppContextT *ctx, const char *const *history, int32_t historyCount, char *outCommand, int32_t outSize);

The standard Run dialog: an Open box listing history (newest first, the first one preselected) with a Browse... button. Returns true with the trimmed, non-empty command in outCommand when the user chose OK; OK with an empty box does nothing. The caller runs the command (shellRunCommand) and keeps its own history. SHELL_RUN_CMD_MAX (512) is a suitable outCommand size.

shellSplitCommand

bool shellSplitCommand(const char *command, char *program, int32_t programSize, char *args, int32_t argsSize);

Split a command line into the program (quotes removed) and the rest, trimmed. False, with both outputs empty, when there is no program.

shellResolveArgs

void shellResolveArgs(const char *folder, const char *args, char *out, int32_t outSize);

Copy args to out with blanks normalized, replacing every relative argument that names an existing file inside folder by folder\name, so a program finds the files an item's arguments name no matter where it runs. Quoted arguments keep their quotes. With an empty or NULL folder the arguments are only normalized. DVX programs share one current folder, so this is how a program item's working folder takes effect.

shellMinimizeApp

void shellMinimizeApp(AppContextT *ctx, int32_t appId);
void shellMinimizeIdle(AppContextT *ctx);

Run Minimized. shellMinimizeApp minimizes every visible window of appId (an id from shellRunProgram) at once and again on each of the next SHELL_MINIMIZE_FRAMES (60) idle frames, because a program with a main loop opens its windows from its own task after it has started. Asking again for the same program starts its frames over; nothing happens for an id that is not running, and a program that ends is forgotten (an app-cleanup hook). shellMinimizeIdle is the per-frame step, called by the shell's idle dispatch. Program Manager's Run Minimized items and BASIC's ShellMinimizeApp use it.

The Screen Saver

Header: dvxshell/shellSaver.h

A screen saver is a program (.app, C or BASIC) in a SAVERS folder (DvxKindSaversE: SHARED\SAVERS, which DVX and add-ons fill, and the user's SAVERS); Program Manager does not list it. After the wait with no key or mouse input (dvxIdleMs) the shell starts the chosen one with SHELL_SAVER_ARGS ("/S"). It takes the whole screen (dvxExclusiveBegin; BASIC SCREEN and FULLSCREEN ON) and draws until its window is closed; it needs no input handling of its own, since the input that wakes the screen goes to it (full-screen mode), never to the program underneath. Input SHELL_SAVER_GRACE_MS (1,000 ms) or more after it started asks each of its windows to close (dvxRequestWindowClose, as Alt+F4 does); one still running SHELL_SAVER_END_MS (3,000 ms) later is ended (shellForceKillApp). The log says "Shell: screen saver X started" and "... ended".

The saver does not start while a dialog box is open (ctx->modalWindow) or a program has the full screen (ctx->exclusive.owner: a game played with a joystick gives no key or mouse input). After a saver ends (or fails to start: missing, or not a program, logged and never shown in a message box) the next wait counts from then.

DVX.INI [desktop] saver is the saver's name in SAVERS ("" or missing: none) and saverWait the minutes (SHELL_SAVER_WAIT_MIN to SHELL_SAVER_WAIT_MAX, 1 to 60; SHELL_SAVER_WAIT_DEFAULT, 10, when missing). The shell reads them at start; Control Panel's Desktop page sets them live and stores them with OK.

void        shellSaverSet(const char *name, int32_t minutes);
const char *shellSaverName(void);
int32_t     shellSaverWait(void);
bool        shellSaverStart(void);
bool        shellSaverRunning(void);
void        shellSaverStep(AppContextT *ctx);
void        shellSaverFromPrefs(PrefsHandleT *h);
void        shellSaverToPrefs(PrefsHandleT *h);
  Function              What it does
  --------              ------------
  shellSaverSet         Choose the saver (a file in SAVERS is kept by its name there, platformKindNameOf; any other path as it is; "" or NULL: none) and the wait, clamped to the range; at once
  shellSaverName / Wait The settings in force
  shellSaverStart       Start the saver now (Control Panel's Test); only recorded, the next shellSaverStep starts it.  False with none chosen or one running
  shellSaverRunning     True while a saver runs
  shellSaverStep        The main loop's step (shell top level): starts the saver when due, ends a running one at input
  shellSaverFromPrefs   Read [desktop] saver and saverWait (start, Control Panel's Cancel)
  shellSaverToPrefs     Write them (Control Panel's OK and its Cancel snapshot)

BASIC: SysScreenSaver$, SysScreenSaverWait, SysSetScreenSaver, SysScreenSaverStart, SysScreenSaverRunning, SysScreenSaverStore, SysScreenSaverLoad, and SysIdleSeconds (system.bas; see BASIC System Settings).

Writing a screen saver

A C saver is a main-loop program that opens a window, takes the full screen with dvxExclusiveBegin, and draws (dvxExclusivePresent8, paced with gameFrameWait) until the window's onClose sets a flag; then it destroys the window and returns (src/savers/kpunch/starfld). A BASIC saver is a program with no form: SCREEN 13, FULLSCREEN ON, and a loop drawing with FRAMEWAIT (src/savers/kpunch/blank, marquee, mystify); closing its screen window ends it. Put the .app in SHARED\SAVERS (an add-on package's kind saver) or the user's SAVERS; the name Control Panel lists is the program's (its "name" resource, a BASIC project's Name).

File Associations

Header: dvxshell/shellAssoc.h

Which program opens each kind of document. One store serves every program, in two layers: the shipped list, SYSTEM\ASSOC.INI (SHELL_ASSOC_SHIPPED_PATH), and the user's changes, ASSOC.INI in the user folder, both with the section [associations]. A type in the user's file replaces the shipped one; an empty value there removes a shipped type. Each key is an extension without the dot, upper case; each value is a program path relative to the DVX root, optionally followed by ,arguments where %1 stands for the document:

[associations]
TXT = apps/kpunch/notepad/notepad.app
FRM = apps/kpunch/dvxbasic/dvxbasic.app,%1

Users change the types on the Control Panel's File Types page, and a setup program registers its own types with shellAssocSet. Every change goes to the user's file, so an upgrade's new shipped types appear while the user's choices stay. Both files are read once through dvxPrefs and kept; every change is saved at once with the crash-safe rewrite, and a failed save drops the cached copies so the next call rereads the files. Extension arguments may have a leading dot and any case.

Looking Up

bool    shellAssocLookup(const char *extension, char *programPath, int32_t programPathSize);
int32_t shellAssocCount(void);
bool    shellAssocGet(int32_t index, char *extension, int32_t extensionSize, char *programPath, int32_t programPathSize);

shellAssocLookup gives the stored value (program plus any ,arguments) for an extension; false when there is none (or its value is empty). shellAssocCount and shellAssocGet walk every association in file order (index 0 to count-1); the extension comes back as written in the file.

Opening a Document

bool shellAssocCommand(const char *documentPath, char *program, int32_t programSize, char *args, int32_t argsSize);
bool shellAssocOpenDocument(AppContextT *ctx, const char *documentPath);

shellAssocCommand builds the program and argument string that open a document: the value is split at the first comma, every %1 in the arguments becomes documentPath, and without arguments documentPath alone is passed. The path goes in double quotes when it contains a blank (dvxArgAppend), so dvxNextArg and COMMAND$(1) still return a long name such as C:\My Documents\Letter.txt whole; a template that already quotes the token ("%1") gets the path as it is. A program that opens documents takes the first argument of its DxeAppContextT.args (dvxNextArg) as the file to open when it starts; a BASIC program uses COMMAND$(1). shellAssocOpenDocument does that and starts the program; false when there is no association or the program does not start. It shows nothing to the user (shellRunProgram does, for a document it cannot open).

Changing

bool shellAssocSet(const char *extension, const char *programPath);
bool shellAssocRemove(const char *extension);
bool shellAssocNormalize(const char *extension, char *out);

shellAssocSet adds or replaces an association (programPath may carry ,arguments) and saves; false for a bad extension, an empty program, or a failed save. shellAssocRemove removes one and saves; false when it did not exist or the save failed. shellAssocNormalize writes the extension as the store keys it (SHELL_ASSOC_EXT_MAX bytes: no dot, upper case) and is false for an empty extension, one longer than eight characters, or anything but letters and digits.

BASIC Entry Points

Source: dvxshell/shellBas.c; BASIC declarations: src/include/basic/shell.bas (DECLARE LIBRARY "dvxshell").

The shell's services for DVX BASIC programs -- everything Program Manager does, so a desktop can be written in BASIC. Only BASIC-marshalable types: strings and LONG in, LONG or STRING out, True (-1) / False (0) for success. "This program" is the app whose code is running (ctx->currentAppId, which the shell keeps for every app's task and window callback). A returned string stays valid until the next call of the same function. Program lists (1, 2, ...; 0 means none) and hotkeys belong to the program that made them and are released by an app-cleanup hook (shellRegisterAppCleanup) when it ends.

Starting programs and documents

int32_t     ShellRun(const char *program, const char *args);
int32_t     ShellRunProgram(const char *program, const char *args, const char *folder);
int32_t     ShellRunCommand(const char *command);
void        ShellRunDos(const char *program, const char *args, const char *folder, int32_t pause);
const char *ShellRunDialog(const char *history);
void        ShellMinimizeApp(int32_t appId);
int32_t     ShellOpenDocument(const char *path);

ShellRunProgram is shellRunProgram (a document opens through its association; file names in args, and a relative document, are looked for in folder; "" for none), returning the new app id, -1 (shell.bas SHELL_RUN_DOS) for a DOS program set to run, or 0. ShellRun is ShellRunProgram without a folder, True when it started or a DOS program will run; the two are one launch path. ShellRunCommand is shellRunCommand (id, -1, or 0). ShellRunDos is shellRunDos (pause True or False): the DOS program runs once the calling procedure has returned and the programs besides the desktop, the caller too, have closed. ShellRunDialog splits history at line ends (CHR$(10), a CR before it is dropped, empty lines skipped) into the list shellRunDialog shows, and returns the command chosen or "" when cancelled. ShellMinimizeApp is shellMinimizeApp. ShellOpenDocument is shellAssocOpenDocument (it shows nothing when there is no association; ShellRunProgram does).

Installed programs

int32_t     ShellProgramScan(const char *folder, const char *skipPath);
int32_t     ShellProgramCount(int32_t list);
const char *ShellProgramPath(int32_t list, int32_t index);
void        ShellProgramFree(int32_t list);
const char *ShellProgramName(const char *path);
const char *ShellProgramDescription(const char *path);
int32_t     ShellIsProgram(const char *path);
int32_t     ShellIsDosProgram(const char *path);

ShellProgramScan is shellProgramScan of folder ("" for SHELL_PROG_DIR) without skipPath ("" for none), paths only (readInfo false), so it never opens a program; it returns a list handle, 0 when nothing was found. ShellProgramCount and ShellProgramPath read it (0 / "" for a bad list or index); ShellProgramFree frees it. ShellProgramName and ShellProgramDescription are shellProgramInfo's name and description, read one program at a time so a long list can be filled with DoEvents in between (the C progress callback). ShellIsProgram is shellIsProgram, ShellIsDosProgram shellIsDosProgram.

File types

const char *ShellAssocProgram(const char *extension);
const char *ShellAssocCommand(const char *document);
int32_t     ShellAssocCount(void);
const char *ShellAssocExt(int32_t index);
const char *ShellAssocValue(int32_t index);
int32_t     ShellAssocSet(const char *extension, const char *value);
int32_t     ShellAssocRemove(const char *extension);
const char *ShellAssocNormalize(const char *extension);

ShellAssocProgram is shellAssocLookup: the stored value, the program followed by ",args" when there is a template. ShellAssocCommand is the program part of shellAssocCommand for a document. ShellAssocCount, ShellAssocExt, and ShellAssocValue enumerate shellAssocGet (0-based; "" past the end). ShellAssocSet, ShellAssocRemove, and ShellAssocNormalize are shellAssocSet, shellAssocRemove, and shellAssocNormalize ("" when the extension cannot be one). Every change is saved at once.

Running programs and DVX

int32_t     ShellRunningCount(void);
int32_t     ShellChangeCount(void);
void        ShellTaskManager(void);
const char *ShellSystemInfo(void);
void        ShellExitDvx(void);

ShellRunningCount is shellRunningAppCount. ShellChangeCount is shellDesktopChangeCount: a BASIC desktop polls it from a Timer in place of shellRegisterDesktopUpdate's callback. ShellTaskManager is shellOpenTaskManager, ShellSystemInfo is shellGetSystemInfo ("" before shellInfoInit), and ShellExitDvx is shellRequestExit (every app is asked to close, then DVX quits; a desktop asks the user first).

int32_t     ShellRunningId(int32_t index);
const char *ShellRunningName(int32_t appId);
const char *ShellRunningPath(int32_t appId);
int32_t     ShellRunningFind(const char *program);
int32_t     ShellRunningActivate(int32_t appId);
int32_t     ShellRunningClose(int32_t appId);
int32_t     ShellRunningKill(int32_t appId);
int32_t     ShellAppIsHung(int32_t appId);
int32_t     ShellAppMemory(int32_t appId);

The Task Manager's operations, one routine each with it. ShellRunningId is shellRunningAppId. ShellRunningName and ShellRunningPath are the ShellAppT name and path of an app in AppStateRunningE ("" otherwise; the same state every call below requires). ShellRunningFind is shellAppFindByPath (platformPathSame rules), 0 when that app is not running. ShellRunningActivate is shellActivateApp. ShellRunningClose is shellRequestAppClose: the app is asked at the main loop's top level, after the BASIC code has returned; True when the request was recorded. ShellRunningKill is shellForceKillApp followed by shellDesktopUpdate (End Task: no shutdownFn, callbacks purged, the unload deferred while the app's code is on a stack); the running program itself is refused (BASIC's END ends it). ShellAppIsHung is dvxAppIsHung and ShellAppMemory dvxMemGetAppUsage (clamped to INT32_MAX; 0 for an app that is not running).

Command lines and priority

const char *ShellCommandProgram(const char *command);
const char *ShellCommandArgs(const char *command);
int32_t     ShellArgCount(const char *command);
const char *ShellArg(const char *command, int32_t index);
const char *ShellArgQuote(const char *arg);
int32_t     ShellAppId(void);
int32_t     ShellPriority(void);
int32_t     ShellSetPriority(int32_t priority);

ShellCommandProgram and ShellCommandArgs are the two parts of shellSplitCommand (what shellRunCommand runs). ShellArgCount and ShellArg walk a command line with dvxNextArg -- the tokenizer shellSplitCommand and COMMAND$(n) use -- counting the program as argument 0; ShellArg drops the quotes and gives "" past the last. ShellArgQuote is the other direction (dvxArgAppend): arg as one word of a command line, in double quotes when it holds blanks, "" when it is too long for one (SHELL_ARGS_MAX). ShellAppId is ctx->currentAppId. ShellPriority and ShellSetPriority are tsGetPriority / tsSetPriority on the running program's mainTaskId (TS_PRIORITY_LOW .. TS_PRIORITY_HIGH, shell.bas SHELL_PRIORITY_*); -1 / False for a program without a task of its own (every BASIC program has one) or a priority out of range.

Hotkeys

int32_t     HotkeyAdd(const char *keyText);
int32_t     HotkeyNext(void);
void        HotkeyRemove(int32_t id);
const char *HotkeyText(const char *keyText);

HotkeyAdd parses keyText with dvxAccelParse and registers it with dvxRegisterHotkey for the running program; it returns an id of its own (> 0), or 0 when keyText is not a key or dvxRegisterHotkey refuses it (it needs Ctrl or Alt unless it is a function or navigation key, and a key already registered is refused). A BASIC program cannot be called back, so the hotkey's callback queues the id for its program (up to 16 presses not yet collected); HotkeyNext returns and removes the oldest one, 0 when there is none. HotkeyRemove drops one of the running program's hotkeys (other ids are ignored). HotkeyText is dvxAccelParse followed by dvxAccelFormat ("" when keyText is not a key): the spelling menus show. dvxUnregisterAppHotkeys removes a program's hotkeys when it ends; the app-cleanup hook drops their records and queued presses.

This program and DVX settings

const char *ShellAppPath(void);
int32_t     ShellInstanceCount(void);
void        ShellLog(const char *text);
int32_t     ShellSetDesktop(const char *program);
int32_t     ShellSetWelcome(const char *program);

ShellAppPath is the running program's .app path as started ("" when unknown). A BASIC program's user folder is App.User (the folder a C program's userDir names; every copy of a program gets the same one). ShellInstanceCount counts the running apps with the same path, this one included (a second copy of a multiInstance program loads from a temporary copy but keeps the original path). ShellLog writes one line with dvxLog. ShellSetDesktop and ShellSetWelcome set, or with "" remove, DVX.INI [shell] desktop (the desktop app; SHELL_DESKTOP_APP when missing) and [shell] welcome (the program started after the desktop); both share one writer and return False (logged) when DVX.INI cannot be saved.

Programs folder, item icons, program groups

const char *ShellProgramsFolder(void);
const char *ShellItemIcon(const char *iconPath, const char *path);

ShellProgramsFolder is SHELL_PROG_DIR (where ShellProgramScan("") looks and a Browse for a program starts). ShellItemIcon is shellItemIconFile ("" for none). The program-group bindings (ShellGroups*, ShellGroup*, ShellItem*) are in pmModelBas.c; see Program Groups.

A desktop in BASIC

A BASIC program can be the desktop ([shell] desktop, ShellSetDesktop): the shell exits DVX when it ends, so it needs no code for that; its Exit command confirms and calls ShellExitDvx. Its groups use the shell's program-group model (Program Groups: the same PROGMAN.INI format, rules, and limits as Program Manager) through shell.bas, in its App.User folder. An icon grid is drawn on a PictureBox with PaintPicture and pictures from LOADPICTURE of ShellItemIcon$ (a .app gives its icon), with MouseDown/MouseUp for selecting and dragging and KeyDown for the keyboard. The test fixture src/test/dos/fixtures/parpm does all of this.

Program Groups

Header: dvxshell/pmModel.h; source pmModel.c (model) and pmModelBas.c (BASIC entry points, shell.bas).

Program Manager's groups, items, and PROGMAN.INI, as plain data with no windows, part of the shell so the C Program Manager and a desktop written in BASIC share one model (the pm prefix names it). groups[PM_INSTALLED_IDX] is the automatic Installed Programs group (pmInstalledScan: every program under SHELL_PROG_DIR, sorted by name, never saved; pmInstalledChanged tells cheaply whether a program was added or removed); the others are the user's, saved by pmModelSave with a crash-safe rewrite in this layout:

[options]  minimizeOnRun, restoreAlone, autoArrange, view, group
[groups]   count
[groupN]   name, count, itemM.name, itemM.program, itemM.args,
           itemM.folder, itemM.icon, itemM.key, itemM.minimized,
           itemM.col, itemM.row   (N and M count from 1)
[run]      historyN (newest first)
  Function                                     Description
  --------                                     -----------
  pmModelInit / pmModelLoad / pmModelSave / pmModelFree   An empty model; load a file (one never saved gives Main and Startup; damaged entries are skipped); save; free.
  pmGroupAdd / pmGroupDelete / pmGroupFind / pmGroupRename / pmGroupNameValid   Groups (names trimmed, unique in any case, PM_GROUP_MAX of them; the automatic group cannot change).
  pmItemAdd / pmItemCopy / pmItemMove / pmItemDelete / pmItemReorder / pmItemPlace   Items (a name and a program each, PM_ITEM_MAX per group).
  pmItemGetField / pmItemSetField              An item's field by its PROGMAN.INI name (PM_FIELD_*); a key must pass pmHotkeyParse, a name or program cannot be emptied.
  pmOptionGet / pmOptionSet                    An [options] value by name (flags, view, group).
  pmArrangeGroup / pmPlaceUnplaced / pmCellItem   Grid cells for an icon view.
  pmItemMatches / pmContainsNoCase             Program Manager's search.
  pmHistoryAdd                                 The Run history (PM_HISTORY_MAX, newest first).
  pmHotkeyParse                                An item shortcut: dvxAccelParse, and Ctrl plus Alt or Shift so it never takes a key a program uses itself; "" is no shortcut.
  pmInstalledScan / pmInstalledChanged / pmInstalledReplace   The automatic group.

BASIC (shell.bas, DECLARE LIBRARY "dvxshell"): ShellGroupsLoad / Save / Free / Scan / Changed / Get / Set / History$ / HistoryAdd, ShellGroupCount / Name$ / Find / Add / Delete / Rename / NameValid / Arrange / PlaceUnplaced / CellItem, ShellItemCount / Add / Get$ / Set / Delete / Copy / Move / Reorder / Place / Matches / KeyValid. A groups file is a handle of the program that opened it, freed when it ends (shellRegisterAppCleanup).

BASIC System Settings

Source: dvxshell/shellSysBas.c; BASIC declarations: src/include/basic/system.bas (DECLARE LIBRARY "dvxshell"); prototypes in shellApp.h.

What Control Panel changes, for DVX BASIC programs: the video mode, the system colors and themes, the wallpaper, the screen saver, text smoothing, and the mouse, each applied at once to shellContext(). Only BASIC-marshalable types; True (-1) / False (0); colors are BASIC RGB() values (&HBBGGRR, converted with wgtBasicColor); lists are 0-based. Arguments named prefs are prefs.bas handles, resolved with prefsBasHandle (dvxPrefs.h); 0 or a closed handle makes a Store or Load do nothing (a Load returns False).

int32_t     SysVideoModeCount(void);
int32_t     SysVideoModeWidth(int32_t index);
int32_t     SysVideoModeHeight(int32_t index);
int32_t     SysVideoModeBpp(int32_t index);
int32_t     SysSetVideoMode(int32_t width, int32_t height, int32_t bpp);
int32_t     SysColorCount(void);
const char *SysColorLabel(int32_t id);
const char *SysColorName(int32_t id);
int32_t     SysColorId(const char *name);
int32_t     SysGetColor(int32_t id);
void        SysSetColor(int32_t id, int32_t rgb);
void        SysResetColors(void);
int32_t     SysLoadTheme(const char *path);
int32_t     SysSaveTheme(const char *path);
void        SysColorsStore(int32_t prefs);
int32_t     SysColorsLoad(int32_t prefs);
int32_t     SysSetWallpaper(const char *path);
const char *SysWallpaper(void);
void        SysSetWallpaperMode(int32_t mode);
int32_t     SysWallpaperMode(void);
void        SysWallpaperStore(int32_t prefs);
int32_t     SysWallpaperLoad(int32_t prefs);
const char *SysScreenSaver(void);
int32_t     SysScreenSaverWait(void);
void        SysSetScreenSaver(const char *fileName, int32_t minutes);
int32_t     SysScreenSaverStart(void);
int32_t     SysScreenSaverRunning(void);
void        SysScreenSaverStore(int32_t prefs);
void        SysScreenSaverLoad(int32_t prefs);
int32_t     SysIdleSeconds(void);
void        SysSetTextSmoothing(int32_t on);
int32_t     SysTextSmoothing(void);
void        SysFontsStore(int32_t prefs);
void        SysFontsLoad(int32_t prefs);
void        SysSetMouse(int32_t wheelDir, int32_t dblClickMs, int32_t accel, int32_t mickeys, int32_t wheelStep);
int32_t     SysMouseWheelDir(void);
int32_t     SysMouseDoubleClick(void);
int32_t     SysMouseAccel(void);
int32_t     SysMouseMickeys(void);
int32_t     SysMouseWheelStep(void);
void        SysMouseStore(int32_t prefs);
void        SysMouseLoad(int32_t prefs);
int32_t     SysFontWidth(void);
int32_t     SysFontHeight(void);
int32_t     SysFontFirstChar(void);
int32_t     SysFontCharCount(void);
int32_t     SysFontGlyphRow(int32_t code, int32_t row);
int32_t     SysMouseX(void);
int32_t     SysMouseY(void);
int32_t     SysMouseButtons(void);
void        SysSetMousePos(int32_t x, int32_t y);
int32_t     SysShiftState(void);
int32_t     SysSendKey(int32_t keyCode, int32_t shift);
int32_t     SysScreenshot(const char *path);
int32_t     SysMemoryTotalKb(void);
int32_t     SysMemoryFreeKb(void);
int32_t     SysLibraryLoaded(const char *name);
  Function                     Calls
  --------                     -----
  SysVideoMode*                dvxGetVideoModes (0 out of range)
  SysSetVideoMode              dvxChangeVideoMode; False on -1 (also while a program is in exclusive full-screen mode)
  SysColorLabel / SysColorName dvxColorLabel / dvxColorName ("" for an id outside 0 .. ColorCountE - 1)
  SysColorId                   the id whose dvxColorName matches, any case; -1 for none
  SysGetColor / SysSetColor    dvxGetColor / dvxSetColor (an unknown id: 0 / nothing)
  SysResetColors               dvxResetColorScheme
  SysLoadTheme / SysSaveTheme  dvxLoadTheme / dvxSaveTheme
  SysColorsStore / Load        dvxColorsToPrefs / dvxColorsFromPrefs (Load: True when the handle had a [colors] key)
  SysSetWallpaper              dvxSetWallpaper ("" clears)
  SysSetWallpaperMode          dvxSetWallpaperMode with the value of prefsWallpaperChoices[mode]; other modes are ignored
  SysWallpaper / Mode          ctx->wallpaperPath; the prefsWallpaperChoices index of ctx->wallpaperMode
  SysWallpaperStore / Load     dvxWallpaperToPrefs / dvxWallpaperFromPrefs (Load: False when the image could not be loaded)
  SysScreenSaver / Wait        shellSaverName / shellSaverWait
  SysSetScreenSaver            shellSaverSet
  SysScreenSaverStart          shellSaverStart (True when it will start)
  SysScreenSaverRunning        shellSaverRunning
  SysScreenSaverStore / Load   shellSaverToPrefs / shellSaverFromPrefs
  SysIdleSeconds               dvxIdleMs / 1000
  SysSetTextSmoothing          dvxSetFontSmoothing (nonzero is on)
  SysTextSmoothing             ctx->fontSmoothing (the setting; text is smoothed only above 8 bits per pixel)
  SysFontsStore / Load         dvxFontsToPrefs / dvxFontsFromPrefs
  SysSetMouse                  dvxSetMouseConfig
  SysMouse*                    ctx->wheelDirection, dblClickMs, mouseAccel, mouseMickeys, wheelStep
  SysMouseStore / Load         dvxMouseToPrefs / dvxMouseFromPrefs
  SysFont*                     dvxGetFont: charWidth, charHeight, firstChar, numChars; SysFontGlyphRow is glyphData[(code - firstChar) * charHeight + row] (0 outside the font)
  SysMouseX / Y / Buttons      ctx->mouseX, mouseY, mouseButtons (MOUSE_LEFT / RIGHT / MIDDLE = vbLeftButton ...)
  SysSetMousePos               platformMouseWarp, clamped to the screen; the next poll reports the move like the user's
  SysShiftState                ctx->keyModifiers & KEY_MOD_ALL (KEY_MOD_SHIFT / CTRL / ALT = vbShiftMask / vbCtrlMask / vbAltMask)
  SysSendKey                   queues (up to 64) keyCode + shift as the BIOS stores the keystroke (platformBiosScanCode for combinations with codes of their own, KEY_EXT_FLAG keys by scan code, Ctrl+letter as its control character); a shell idle handler (shellRegisterIdle) injects one per idle frame with platformInjectKey, so each is read with its own modifiers, and releases them (platformInjectModifiers(0)) after the last
  SysScreenshot                dvxScreenshot (BMP for .bmp, else PNG)
  SysMemoryTotalKb / FreeKb    platformGetMemoryInfo; -1 when unknown
  SysLibraryLoaded             dvxLibraryLoaded (the module's ABI stamp, by its base name in any case)

There is no binding for platformInjectMouse: on DOS it replaces the real mouse until the next call and has no way back to it, so a synthesized click would leave the pointer stuck. A program moves the pointer with SysSetMousePos and "clicks" a control by calling its event procedure or setting its value.

system.bas also holds MOUSE_WHEEL_STEP_MIN / MAX / DEFAULT, MOUSE_DBLCLICK_DEFAULT_MS, and MOUSE_SPEED_DEFAULT, and SYS_VIDEO_DEFAULT_WIDTH / HEIGHT / BPP (DVX_DEFAULT_VIDEO_W / H / BPP, the mode prefsGetVideoMode and safe mode fall back to), as CONSTs; the basdev test suite (caseIncludeConsts) checks each against dvxTypes.h.

The Store / Load routines are the ones the shell uses at start (dvxMouseFromPrefs, dvxColorsFromPrefs, dvxWallpaperFromPrefs on DVX.INI) and Control Panel uses for OK and for its Cancel snapshot (an in-memory prefsCreate handle), so the INI keys are written in one place (dvxApp.c).

dvxSql -- SQL Database Interface

High-level wrapper around SQLite3 for DVX applications. Manages database connections and result set cursors via integer handles so BASIC code never touches raw pointers. All handles are 1-based; 0 indicates an error or invalid handle.

Header: sql/dvxSql.h

SQLite Wrapping

dvxSql wraps the bundled SQLite 3.53.4 amalgamation (sql/thirdparty/sqlite3/sqlite3.c and sqlite3.h; origin, checksum, and build options in sql/thirdparty/sqlite3/DVX-VERSION.md). It manages two internal dynamic tables keyed by 1-based handles:

Growing either table doubles its capacity. Closed slots are recycled by subsequent dvxSqlOpen or dvxSqlQuery calls, keeping handle values stable for the caller.

SQLite Build and File Layer

SQLite is compiled with the options listed in sql/thirdparty/sqlite3/sqliteOpts.mk (the host test build uses the same list). The ones that change what programs see:

The DOS port is SQLite's SQLITE_OS_OTHER build with a small DVX file layer, the "dvx" VFS in sql/dvxSqlVfs.c (the stock unix layer does not build under DJGPP). It uses the POSIX file calls, so the host tests run the same code:

Databases written by the SQLite 3.5.6 that earlier DVX releases bundled open, read, and write unchanged. Files created now use the current file format, which SQLite 3.5.6 cannot read.

Handle Model

Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it, so callers do not need to track cursor lifetimes per-database.

Connection Lifecycle

Common Patterns

Database Operations

Cursor Operations

Utility Functions

Example

Database Operations

dvxSqlOpen

int32_t dvxSqlOpen(const char *path);

Open a SQLite database file. Creates the file if it does not exist.

  Parameter    Description
  ---------    -----------
  path         Path to the database file.

Returns a database handle greater than 0 on success, or 0 on error (null path, open failure, or no free slots). After a failed open, dvxSqlError(0) returns the reason (for example "unable to open database file (No such file or directory)").

dvxSqlClose

void dvxSqlClose(int32_t db);

Close a database and free all associated resources. Any open cursors belonging to this database are automatically finalized.

  Parameter    Description
  ---------    -----------
  db           Database handle returned by dvxSqlOpen.

Safe to call with an invalid handle (no-op).

dvxSqlExec

bool dvxSqlExec(int32_t db, const char *sql);

Execute one or more SQL statements that return no result rows. Suitable for DDL (CREATE TABLE, DROP TABLE, etc.) and DML (INSERT, UPDATE, DELETE).

  Parameter    Description
  ---------    -----------
  db           Database handle.
  sql          SQL statement(s) to execute.

Returns true on success, false on error. On failure, the error message is available via dvxSqlError. On success, the affected row count is available via dvxSqlAffectedRows.

dvxSqlError

const char *dvxSqlError(int32_t db);

Return the last error message for a database handle. The returned string is stored internally and valid until the next operation on the same handle.

  Parameter    Description
  ---------    -----------
  db           Database handle.

Returns the error string, or "Invalid database handle" if the handle is invalid. Messages are SQLite's own (for example "no such table: x" or "database is locked"); when a file could not be opened, read, or written, the operating system's reason follows in parentheses. Handle 0 (what a failed dvxSqlOpen returns) reports why the most recent open failed; after a successful open it is invalid again.

dvxSqlAffectedRows

int32_t dvxSqlAffectedRows(int32_t db);

Return the number of rows inserted, updated, or deleted by the last dvxSqlExec call on this handle.

  Parameter    Description
  ---------    -----------
  db           Database handle.

Returns the row count, or 0 if the handle is invalid.

Cursor Operations

Result set cursors are created by dvxSqlQuery and must be freed with dvxSqlFreeResult when no longer needed. A new cursor is positioned before the first row; call dvxSqlNext to advance to the first row before reading field values.

dvxSqlQuery

int32_t dvxSqlQuery(int32_t db, const char *sql);

Execute a SELECT query and return a cursor handle for iterating the results. The cursor is positioned before the first row.

  Parameter    Description
  ---------    -----------
  db           Database handle.
  sql          SQL SELECT statement.

Returns a cursor handle greater than 0 on success, or 0 on error (invalid handle, null SQL, SQL that is empty or only a comment, SQL syntax error, or no free cursor slots). On failure, the error message is available via dvxSqlError on the database handle.

dvxSqlNext

bool dvxSqlNext(int32_t rs);

Advance the cursor to the next row.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle returned by dvxSqlQuery.

Returns true if a row is now available for reading, false if the cursor has reached the end or the handle is invalid.

dvxSqlEof

bool dvxSqlEof(int32_t rs);

Test whether the cursor is past the last row.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.

Returns true if the cursor is exhausted or the handle is invalid, false otherwise.

dvxSqlFieldCount

int32_t dvxSqlFieldCount(int32_t rs);

Return the number of columns in the result set.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.

Returns the column count, or 0 if the handle is invalid.

dvxSqlFieldName

const char *dvxSqlFieldName(int32_t rs, int32_t col);

Return the name of a column by index.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.
  col          Column index (0-based).

Returns the column name, or "" if the handle or index is invalid.

dvxSqlFieldText

const char *dvxSqlFieldText(int32_t rs, int32_t col);

Return the value of a column as a string.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.
  col          Column index (0-based).

Returns the text value, or "" if the handle or index is invalid or the value is NULL.

dvxSqlFieldByName

const char *dvxSqlFieldByName(int32_t rs, const char *name);

Return the value of a column identified by name as a string. The name match is case-insensitive.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.
  name         Column name (case-insensitive).

Returns the text value, or "" if the handle is invalid, the name is NULL, or no column with that name exists.

dvxSqlFieldInt

int32_t dvxSqlFieldInt(int32_t rs, int32_t col);

Return the value of a column as a 32-bit integer.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.
  col          Column index (0-based).

Returns the integer value, or 0 if the handle or index is invalid.

dvxSqlFieldDbl

double dvxSqlFieldDbl(int32_t rs, int32_t col);

Return the value of a column as a double-precision floating point number.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.
  col          Column index (0-based).

Returns the double value, or 0.0 if the handle or index is invalid.

dvxSqlFreeResult

void dvxSqlFreeResult(int32_t rs);

Close a result set cursor and free its resources. Must be called for every cursor returned by dvxSqlQuery.

  Parameter    Description
  ---------    -----------
  rs           Cursor handle.

Safe to call with an invalid handle (no-op).

Utility Functions

dvxSqlEscape

int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize);

Escape a string for safe inclusion in SQL string literals. Doubles single quotes so that O'Brien becomes O''Brien.

  Parameter    Description
  ---------    -----------
  src          Source string to escape.
  dst          Destination buffer for the escaped string.
  dstSize      Size of the destination buffer in bytes.

Returns the length of the escaped string (not including the null terminator), or -1 if the buffer was too small or any parameter is NULL/invalid.

Example Usage

Creating a Table and Inserting Data

int32_t db = dvxSqlOpen("mydata.db");
if (!db) {
    // handle error
}

dvxSqlExec(db, "CREATE TABLE IF NOT EXISTS contacts ("
               "id INTEGER PRIMARY KEY, "
               "name TEXT, "
               "phone TEXT)");

dvxSqlExec(db, "INSERT INTO contacts (name, phone) "
               "VALUES ('Alice', '555-0100')");

Querying and Iterating Results

int32_t rs = dvxSqlQuery(db, "SELECT id, name, phone FROM contacts");
if (!rs) {
    printf("Error: %s\n", dvxSqlError(db));
}

while (dvxSqlNext(rs)) {
    int32_t     id    = dvxSqlFieldInt(rs, 0);
    const char *name  = dvxSqlFieldText(rs, 1);
    const char *phone = dvxSqlFieldText(rs, 2);
    printf("%d: %s -- %s\n", id, name, phone);
}

dvxSqlFreeResult(rs);
dvxSqlClose(db);

Escaping User Input

char escaped[512];
dvxSqlEscape(userInput, escaped, sizeof(escaped));

char sql[1024];
snprintf(sql, sizeof(sql),
         "INSERT INTO notes (text) VALUES ('%s')", escaped);
dvxSqlExec(db, sql);

File Operations Library

Attributes, folder listing, path helpers, and recursive copy/move/delete jobs with progress, Cancel, and conflict handling. The File Manager and Setup are user interfaces over this library; any program can use it. Drives, free space, and wildcard filters come from the platform layer (dvxPlat.h): platformDriveList, platformDiskSpace, and platformFilterMatch.

Header: fileops/fileOps.h

Loaded as: bin/libs/kpunch/fileops/fileops.lib

DOS Disk Errors

On DOS, touching a drive with no disk (or a write-protected or removed one) raises a critical error, which would normally wait at an "Abort, Retry, Fail" prompt on the hidden text screen. DVX installs a critical-error handler at startup (platformInit) that answers Fail, so every call here, and your own disk code, simply gets an error (errno) instead. platformDriveList never touches the media at all (IOCTL calls only), so listing drives costs nothing even with empty drives.

Big Disks and Long File Names

Disks past 2 GB report their real size and free space: platformDiskSpace asks DOS with the FAT32 call first and falls back to the older one, which stops near 2 GB. File sizes are 64-bit (FileOpsEntryT.size, fileOpsTreeSize, the job byte counts); a FAT32 file reaches 4 GB - 1 byte, and platformStatSize reads it correctly where stat's st_size would be negative. Where a long-name driver is running (Windows, DOSLFN) and covers the drive (platformLongNames), listings show long names, jobs copy and move them, attributes and renames work on them, fileOpsValidateName accepts them, and fileOpsPathNormalize keeps their case; elsewhere the 8.3 rules apply.

Attributes and Dates

  Function                                     Description
  --------                                     -----------
  fileOpsGetAttr(path, &attr)                  FILEOPS_ATTR_READONLY/HIDDEN/SYSTEM/ARCHIVE bits. Elsewhere than DOS: read-only = no write permission, hidden = a dot-file.
  fileOpsSetAttr(path, attr)                   Set the FILEOPS_ATTR_EDITABLE bits (elsewhere than DOS only read-only applies).
  fileOpsSetTime(path, mtime)                  Set a file's modification time.

Listing, Names, and Paths

  Function                                              Description
  --------                                              -----------
  fileOpsListDir(dir, filter, &entries, &count)         Read a folder into an FileOpsEntryT array (name, size, mtime, isDir). Files not matching filter (platformFilterMatch: wildcards * and ?, several patterns separated by ';', ',', or blanks; "*.*" and an empty filter match everything) are left out; folders always stay. Free with fileOpsListFree.
  fileOpsListSort(entries, count, col, descending)      Folders first, then by FileOpsSortNameE/SizeE/TypeE/DateE; ties by name.
  fileOpsExtension(name)                                Extension without the dot, "" when none.
  fileOpsTypeName(entry, out, size)                     "Folder", "Program" (.APP), "TXT File", "File".
  fileOpsFormatSize(bytes, out, size)                   "1,234,567".
  fileOpsFormatDate(t, out, size)                       "YYYY-MM-DD HH:MM" in local time.
  fileOpsParseDate(text, &t)                            The time fileOpsFormatDate's text stands for ("YYYY-MM-DD HH:MM", optionally ":SS"), local time; false for anything else, a date that does not exist included.
  fileOpsTreeSize(path, &bytes, &files, &folders)       Total size of a file or of a folder and everything in it.
  fileOpsMakeDir(dir, name, err, errSize)               Create a folder; a reason in err on failure.
  fileOpsRename(dir, oldName, newName, err, errSize)    Rename within a folder; refuses a name already in use.
  fileOpsValidateName(dir, name, err, errSize)          Check a new name for folder dir by the rules of its disk (platformValidateFilename): on DOS long names where the drive takes them (platformLongNames), else 8.3; never a device name.
  fileOpsPathJoin(out, size, dir, name)                 dir + separator + name in native separators (platformPathJoin: no doubled separator; an absolute name stands alone); false when it does not fit.
  fileOpsPathParent(path, out, size)                    Parent folder, in native separators; false at a root.
  fileOpsPathIsRoot(path)                               True for "C:\", "C:", or "/".
  fileOpsPathIsInside(path, dir)                        True when path is dir or below it (platformPathIsUnder).
  fileOpsPathSame(a, b)                                 Same place (platformPathSame: separators equal, case ignored, a trailing separator ignored).
  fileOpsPathNormalize(path, out, size)                 Canonical absolute form of an existing folder (DOS: upper case, except names on a disk with long names, which keep their case).

Jobs

A job copies, moves, or deletes any number of files and folders (recursively):

FileOpsJobT *job = fileOpsJobCreate(FileOpsCopyE, "C:\\BACKUP");

fileOpsJobAddSource(job, "C:\\DOCS\\LETTER.TXT");
fileOpsJobAddSource(job, "C:\\DOCS\\PHOTOS");

for (;;) {
    FileOpsStepE step = fileOpsJobStep(job);

    if (step == FileOpsStepDoneE || step == FileOpsStepCancelledE) {
        break;
    }

    if (step == FileOpsStepAskE) {
        // fileOpsJobMessage(job): "... already exists" or "... is read-only"
        fileOpsJobAnswer(job, FileOpsAnswerAllE);
    } else if (step == FileOpsStepErrorE) {
        fileOpsJobAnswer(job, FileOpsAnswerSkipE);
    }

    // show progress, tsYield(), honour a Cancel button here
}

fileOpsJobFree(job);

Each fileOpsJobStep does one bounded piece of work: read one folder, copy one FILEOPS_COPY_CHUNK of one file, or delete one item. A cooperative program yields between steps.

  Step result              Meaning
  -----------              -------
  FileOpsStepBusyE         More work to do.
  FileOpsStepAskE          A destination file exists, or a file to delete is read-only. Answer Yes, No, All, or Cancel.
  FileOpsStepErrorE        Something failed; fileOpsJobMessage says why. Answer Retry, Skip, or Cancel.
  FileOpsStepDoneE         Finished.
  FileOpsStepCancelledE    Stopped by Cancel.

Until it is answered, the job keeps returning the same question. What the job does:

fileOpsJobClearAttr(job, attr), called before the first step, clears those attribute bits on every file a copy or move writes: Setup passes FILEOPS_ATTR_READONLY, because every file on a CD-ROM is read-only and the installed copies must not be.

fileOpsJobRun(job, callbacks) runs a job to the end instead: progress(job, ctx) is called after every step (return false to cancel) and ask(job, kind, message, ctx) answers questions. With no callbacks, existing files are kept (No) and failing items are skipped.

BASIC

src/include/basic/fileops.bas declares the BASIC functions (fileOpsBas.c): whole trees at once (FileOpsCopyTree, FileOpsMoveTree, FileOpsDeleteTree), drives and disks (FileOpsDriveList$, FileOpsDriveType (the PlatformDriveTypeE of platformDriveList, CONSTs FILEOPS_DRIVE_*; -1 for no such drive), FileOpsBootDrive$ (platformBootDrive, "" when none), FileOpsFreeKb, FileOpsFreeBytes, and FileOpsTotalBytes (platformDiskSpace), FileOpsIsWritable (platformDirIsWritable), FileOpsReadOnlySystem (platformIsReadOnly), FileOpsTempDir$ (platformTempDir, "" when none)), DVX's folders (FileOpsUserDir$ (platformUserDir), FileOpsUserPath$ (platformUserPath, "" when it does not fit), FileOpsUserInTemp (platformUserInTemp), and for the kinds of DvxKindE (CONSTs FILEOPS_KIND_*) FileOpsKindDir$ (platformKindDir), FileOpsKindBrowseDir$ (platformKindBrowseDir), FileOpsKindFind$ (platformKindFind), FileOpsKindName$ (platformKindNameOf), and FileOpsKindList (platformKindReadDir as a listing, sorted by name; "" or 0 for a kind out of range)), folder listings (FileOpsList, FileOpsListCount, FileOpsListSort, FileOpsListFind, FileOpsListName$, FileOpsListIsFolder, FileOpsListSize, FileOpsListRows$, FileOpsListFree), paths and names (FileOpsPathNormalize$, FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (platformPathIsAbsolute), FileOpsPathIsInside, FileOpsPathSep$ (DVX_PATH_SEP), FileOpsPathNative$ (platformPathNative), FileOpsFilterMatch (platformFilterMatch), FileOpsBaseName$, FileOpsExtension$), files and folders (FileOpsMakeDir, FileOpsMakePath (platformMkdirRecursive), FileOpsRename, FileOpsError$, FileOpsTempPath$ and FileOpsReplaceFile (an atomic save: platformPathSibling + platformReplaceFile), FileOpsValidName (platformValidateFilename for a name in a folder), FileOpsValidLongName and FileOpsValidShortName (platformValidateLongName / platformValidateShortName, the rules alone), FileOpsLongNames (platformLongNames), FileOpsShortPath$ (platformShortPath, "" when it does not fit), FileOpsIsFolder, FileOpsGetAttr, FileOpsSetAttr, FileOpsModified$, FileOpsSetModified (fileOpsParseDate + fileOpsSetTime), FileOpsTreeSize with FileOpsTreeBytes / FileOpsTreeFiles / FileOpsTreeFolders, FileOpsFormatSize$), and jobs (FileOpsJobCreate, FileOpsJobAddSource, FileOpsJobClearAttr (masked to FILEOPS_ATTR_EDITABLE), FileOpsJobRunFor, FileOpsJobMessage$, FileOpsJobAnswer, FileOpsJobCancel, FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, FileOpsJobBytesTotal (FileOpsProgressT; byte counts are DOUBLE), FileOpsJobFree). The fileops.bas CONSTs mirror fileOps.h and dvxPlat.h; the basdev test suite (caseIncludeConsts) checks each against its C value.

Listings and jobs are handles (small numbers, 0 = none) into a table owned by the library; each records the app that made it, and the shell's app-cleanup hook frees what an app leaves open when it ends. FileOpsJobRunFor(job, ms) steps the job until the time is used or it needs an answer, so a BASIC Timer can run it without blocking the program. A returned string stays valid until the next call returning one (the BASIC runtime copies it at once). Single files are handled by the built-in FILECOPY, KILL, NAME, MKDIR, RMDIR, GETATTR, and SETATTR statements.

Game Helpers Library

Frame pacing, held keys, program-owned timers, and nearest-colour lookup for programs that run like games in a DVX window. It builds on the core's game input windows, held-key table, timer service, and high-resolution clock (see Writing Games for DVX).

Header: game/game.h

Loaded as: bin/libs/kpunch/game/game.lib

Frame Pacing

GameFramePacerT pacer;

gameFrameInit(&pacer, 70);

while (running) {
    uint32_t missed = gameFrameWait(&pacer);   // 0 when on time

    updateGame(missed + 1);                   // catch the game logic up
    drawFrame();
}

gameFrameInit(p, hz) starts pacing at hz frames a second (1 to GAME_FRAME_MAX_HZ, 1000; out of range is clamped) with frame 0 beginning now. gameFrameWait waits until the next frame begins and returns the number of frames that were missed. While it waits it gives the time to the other programs (tsYield), so call it from a program with its own main loop (hasMainLoop), never from a window callback. Frame n begins at start + n / hz on platformMicros, so the pace does not drift however long it runs. When the frame after the next one has already begun (the caller was busy), the frames in between are skipped, not caught up: gameFrameWait returns at once with how many, and GameFramePacerT.missed keeps the total. A frame that starts late but within its own time counts as on time.

gameFrameNext(p, &missed) does the same without the waiting: it moves on to the next frame (skipping and counting missed ones the same way) and returns the platformMicros time that frame begins, for a caller that waits its own way. The BASIC runtime uses it for the FRAMEWAIT statement, keeping the desktop running while it waits.

Held Keys

gameKeyIsDown(win, rawCode) is true while the key is held down and win has the keyboard focus. Keys are named by raw code (KEY_RAW_CODE, KEY_RAW_* in dvxTypes.h). The held-key table starts over whenever the focus moves, so a key pressed while another window had the focus is not seen until it repeats.

Timers

gameTimerAdd(ctx, hz, fn, userData) is platformTimerAdd for the calling program (ctx->currentAppId): the timer is removed when the program ends, however it ends. fn runs inside the timer interrupt; read the rules at the Timer Service in the platform reference before writing one. Remove it with platformTimerRemove.

Colours on a 256-Colour Desktop

On an 8-bit desktop every window shares the desktop palette (dvxGetDisplay(ctx)->palette; NULL at 15 bits and more). A game with its own colours draws the nearest desktop entries.

  Function                                   Description
  --------                                   -----------
  gamePalNearest(pal, r, g, b)               The entry of the 256-entry palette pal (R, G, B bytes) closest to the colour: smallest squared RGB distance, lowest index on a tie. An exact search of all 256 entries.
  gamePalCacheInit(c, pal)                   Record pal in the cache c and empty it. GamePalCacheT is about 37 KB: allocate it.
  gamePalCacheLookup(c, r, g, b)             The entry nearest to the centre of the colour's 15-bit cell (5 bits per channel), searched once per cell and then remembered, so every colour in a cell gets the same answer.
  gamePalCacheSync(c, pal)                   Start over (and return true) when pal differs from the recorded palette; call it once a frame if the palette can change.

BASIC

src/include/basic/game.bas declares GameKeyDown(keyCode), True while the key is held and one of the calling program's windows has the focus, with GAMEKEY_* constants for common keys; GameKeyClear (platformKeyStateClear, only while the calling program's window has the focus); GameWaitRetrace (platformVideoWaitRetrace); and FrameMissed(), the missed count of the BASIC runtime's own FRAMEWAIT pacer (GameFramePacerT.missed since FRAMEWAIT last started at a new rate; 0 before the first FRAMEWAIT). The BASIC runtime gives the library the GUI context (gameBasSetContext) when a program starts, and runs FRAMEWAIT on gameFrameInit / gameFrameNext.

Help File Library

Reads compiled DVX help files (.hlp, written by the help compiler dvxhlpc or HelpCompile): topics, the table of contents, the keyword index, each topic's content records in order, pictures, and full-text search. The Help Viewer, the HelpView widget (BASIC HelpPane), and BASIC's hlpfile.bas all read help files through it, so they find, search, and show topics the same way.

Header: hlpfile/hlpFile.h (the file format: hlpfile/hlpFormat.h, shared with the help compiler)

Loaded as: bin/libs/kpunch/hlpfile/hlpfile.lib

Opening and Topics

A file is an opaque HlpFileT. Strings returned point into the file's string table and stay valid until hlpFileClose.

  Function                                      Description
  --------                                      -----------
  hlpFileOpen(path)                             Open a help file; NULL when it cannot be read or is not a help file. The header, topic directory, strings, table of contents, index, and search index are read at once; topic content and pictures are read when asked for.
  hlpFileClose(f)                               Close it (NULL is ignored).
  hlpFileTopicCount(f)                          Number of topics.
  hlpFileTopicId(f, topic) / Title(f, topic)    A topic's ID (what links and HelpTopic name) and title; "" out of range.
  hlpFileFindTopic(f, topicId)                  The topic with that ID, or -1.
  hlpFileStartTopic(f, topicId)                 The topic to open for "file [topic]": topicId (NULL or "" for none), else the file's .default topic, else the first; -1 for a file with no topics.
  hlpFileString(f, offset)                      A string table entry ("" out of range).
  hlpFileTocCount(f) / hlpFileToc(f, i)         Table of contents entries (HlpTocEntryT: title string, topic or HLP_NO_TOPIC for a section heading, depth, HLP_TOC_EXPANDED flag).
  hlpFileIndexCount(f) / hlpFileIndex(f, i)     Keyword index entries (HlpIndexEntryT: keyword string, topic), sorted.

Records and Pictures

hlpFileForEachRecord(f, topic, fn, userData) calls fn(hdr, payload, userData) for each record of a topic in order (HLP_REC_TEXT, HEADING1 .. 3, LIST_ITEM, NOTE with its HLP_NOTE_ kind in hdr->flags, CODE, TABLE, IMAGE with its HLP_IMG_ alignment, LINK, HRULE); the payload is NUL-terminated. fn returns false to stop. hlpRecordLink gives a link record's target topic ID and display text (the target when the text is empty); hlpRecordImage its picture's place in the file, and hlpFileReadImage the picture itself: the image file as the compiler took it in (BMP, PNG, JPEG, or GIF), malloc'd, for dvxLoadImageAlpha. Reading a picture inside the record walk is safe.

hlpWrapText(text, cols) wraps flowing text at cols columns (at least HLP_MIN_WRAP_COLS) as the viewer does, a malloc'd copy with '\n' at the wrap points.

Search

hlpFileSearch(f, query, matches) finds the topics whose title or text contains every word of query (any case, separated by blanks), plus those with an index keyword containing the whole query, setting matches[topic] (hlpFileTopicCount bytes) and returning the count (-1 on a failure). The query is not changed. The compiler's trigram index narrows the candidates first; the trigram characters (hlpTrigramSymbol) and the record types searched (hlpRecordSearchable) are defined once in hlpFormat.h for both. Words with no three letters or digits in a row cannot use the index, so then only titles and keywords are searched.

uint8_t *matches = malloc(hlpFileTopicCount(f));
int32_t  n       = hlpFileSearch(f, "serial port", matches);

for (int32_t t = 0; n > 0 && t < hlpFileTopicCount(f); t++) {
    if (matches[t]) {
        printf("%s\n", hlpFileTopicTitle(f, t));
    }
}

free(matches);

BASIC

src/include/basic/hlpfile.bas declares the BASIC functions (hlpFileBas.c): HlpOpen, HlpClose, HlpTopicCount, HlpTopicId$, HlpTopicTitle$, HlpFindTopic, HlpStartTopic, HlpTocCount, HlpTocTitle$, HlpTocTopic, HlpTocDepth, HlpTocExpanded, HlpIndexCount, HlpIndexKeyword$, HlpIndexTopic, HlpSearch, HlpSearchTopic, HlpLoadTopic, HlpRecordType, HlpRecordFlags, HlpRecordText$, HlpRecordLink$, HlpRecordImageSave, and HlpWrap$, with the HLP_REC_, HLP_NOTE_, and HLP_IMG_ constants of hlpFormat.h.

A handle (a small number, 0 = none) is an open file plus what the program last asked of it -- HlpSearch keeps the topics found for HlpSearchTopic, HlpLoadTopic copies a topic's records for the HlpRecord functions -- since BASIC cannot take a callback or a byte array. Each handle records the program that opened it; the shell's app-cleanup hook closes what a program leaves open when it ends. hlpFileBasHandle(handle) gives the HlpFileT behind a handle to C code (the HelpPane control's ShowTopic uses it). A returned string stays valid until the next call returning one (the BASIC runtime copies it at once).

HTTP Library

An HTTP/1.1 client: GET, POST, and other requests to a web server over the network library's sockets, run a step at a time like the transfer and package jobs, so a program draws its progress and stays responsive. Software talks to the package server with it.

Header: http/http.h

Loaded as: bin/libs/kpunch/http/http.lib (it needs the network library, dvxnet)

BASIC: http.bas (HttpOpen, HttpStart, HttpRunFor, HttpStatus, HttpBody$, ...).

A Request

HttpT *h = httpCreate("GET", "http://example.com/v1/catalogue");

if (h && httpStart(h, nowMs)) {
    while (httpPoll(h, nowMs) == HttpBusyE) {
        // draw progress (httpProgress), yield to other programs
    }
}

// HttpDoneE: httpStatus(h) is 200, 404, ...; httpBody(h, &len) the reply
// HttpFailedE: httpMessage(h) says why
httpFree(h);

nowMs is a millisecond clock (PLATFORM_TICKS_TO_MS(platformClock())); it may wrap. Each httpPoll does one bounded piece of work: start connecting, send, or read up to 4 KB; on DOS it also lets the network stack run. Every request says Connection: close.

  Function                                    Description
  --------                                    -----------
  httpCreate(method, url)                     A request; NULL when out of memory.
  httpHeaderAdd(h, name, value)               A header to send (before starting).
  httpBodySet(h, data, len, contentType)      A body to send (copied); Content-Length is sent for it, and for any POST or PUT.
  httpSaveTo(h, path, resume)                 The body to a file instead of memory; with resume, a part already there is kept and the rest asked for (a Range request).
  httpTimeoutSet(h, ms)                       Give up after ms without progress (HTTP_TIMEOUT_DEFAULT_MS, 30 seconds, to start with).
  httpStart(h, nowMs)                         False (httpMessage) for an address that is not http://.
  httpPoll(h, nowMs)                          HttpBusyE, HttpDoneE, or HttpFailedE.
  httpProgress(h, &done, &total)              Bytes of the body so far (counting a resumed file's first part), and the whole, HTTP_SIZE_UNKNOWN until the server says.
  httpStatus(h), httpHeader(h, name)          The reply's status and its headers (any case).
  httpBody(h, &len)                           The body in memory, NUL-terminated ("" when it went to a file).
  httpUrl(h)                                  Where the reply came from, after redirects.
  httpUrlParse(url, &parts, err, size)        Split an address into host, port, and path.

What Comes Back

Any status is an answer (HttpDoneE): the program decides what 404 or 500 means. HttpFailedE is for no answer: a bad address, a name that cannot be found, no connection, silence past the timeout, a reply that is not HTTP, or one cut short. Bodies may be sized (Content-Length), chunked, or end when the server closes; they go to memory up to HTTP_MEMORY_MAX (1 MB), past which the request fails -- save big ones to a file. Redirects (301, 302, 303, 307, 308) are followed, HTTP_REDIRECTS_MAX (5) at most; after a 303, or a 301 or 302 of a POST, the request becomes a GET without its body.

Saving to a file: only a 2xx body is written to it. A resumed download that the server answers with 206 is appended to the part there; one it answers with 200 (it will not resume) starts the file again; 416 (asked for past the end: the file was already whole) leaves it as it is. Any other status's body -- an error page -- goes to memory and the file stays as it was, so a download can be resumed later. Check the file afterwards (its size and SHA-256, as the package library does) before using it.

Only plain http:// works: there is no encryption, so send nothing secret, and check what comes back by its signature or hash.

Joystick Library

Game controllers for DVX programs behind one API, whatever the hardware: the devices the drivers found, axes calibrated to -32768..32767 with a dead zone round the centre, buttons, hats, read when the program wants (polled) or delivered as events. A program's handles close when it ends, however it ends. Calibration and the other settings live in DVX.INI [joystick]; the Control Panel's Game Controllers page sets them.

Header: joystick/joystick.h

Loaded as: bin/libs/kpunch/joystick/joystick.lib, drivers in bin/libs/kpunch/joystick/drivers/*.drv (joyport.drv, joykeys.drv), loaded only when a program first asks for devices.

Reading a Joystick

if (joyCount() > 0) {
    int32_t   stick = joyOpen(ctx, 0);    // device 0, owned by the calling program
    JoyStateT st;

    if (joyRead(stick, &st) && st.connected) {
        shipX += st.axes[0] / 4096;       // -8..7
        firing = (st.buttons & 1) != 0;
    }
}

joyCount() is the number of devices (the first call of any joystick function finds them); joyGetInfo(device, &info) describes one: name, id (the driver's name and unit number, "gameport0", which prefixes the device's DVX.INI keys), axisCount, buttonCount, hatCount, axisPresent (bit n set when axis n answers; a missing axis reads 0), and caps (JOY_CAP_CALIBRATE for analog axes, JOY_CAP_KEYBOARD for the keyboard joystick).

joyOpen(ctx, device) returns a handle owned by ctx->currentAppId, or -1. joyOpenShared(ctx, device) returns the calling program's shared handle on the device, opening it the first time (for code with no place to keep a handle; the BASIC functions use it). joyClose closes a handle; a program's handles are closed for it when it ends.

joyRead(handle, &st) fills a JoyStateT: axes[] (JOY_AXIS_MIN..JOY_AXIS_MAX, 0 inside the dead zone), hats[] (JOY_HAT_CENTERED or degrees clockwise from up), buttons (bit n = button n), and connected (false when the device stopped answering; everything then reads neutral). A button that went down and up again since the handle last looked reads as down once, so a quick tap between two reads is not lost. joyButtonPressed(handle, button) asks the same about one button. joyButtonWentDown(handle, button) only asks whether the button went down since the last time this function asked about it (a press counter of its own, apart from joyRead's).

The keyboard joystick (JOY_CAP_KEYBOARD) reads neutral for a handle whose program has no window with the focus, so keys typed into another program do not steer the game.

Events

joySetEventFn(handle, fn, userData) delivers the device's changes from now on: JoyEventAxisE when an axis moved by at least JOY_EVENT_AXIS_STEP or reached the centre or an end, JoyEventButtonDownE / JoyEventButtonUpE (a tap between two polls gives both), JoyEventHatE. fn runs in program context as the handle's program (ctx->currentAppId is its id), so DVX calls are fine. Events are delivered by joyPump, which the shell calls on every idle frame; a program with its own main loop that repaints every frame (so the shell is never idle) calls joyPump itself or reads with joyRead. joyPump is not reentrant: an event function that runs a modal dialog gets its remaining events after it returns. A NULL fn stops the events.

Polling and CPU Cost

While at least one handle is open, a timer-service callback marks a poll due [joystick] pollHz times a second (JOY_POLL_HZ_DEFAULT 60, JOY_POLL_HZ_MIN 10 to JOY_POLL_HZ_MAX 200) and runs the drivers' tick hooks (the game port latches button presses there). The poll itself runs in program context, the next time a program reads (joyRead, joyButtonPressed, joyGetRaw) or the shell is idle, so however often a program reads there are at most pollHz polls a second. With no handle open nothing is polled and the timer is removed.

A game port poll waits for the slowest connected axis to run out: about 0.6 ms with the stick centred, up to about 1.2 ms at an end. At 60 polls a second that is about 4 to 7 percent of the processor's time on any machine, whatever its speed, while a program has a joystick open; the timer interrupt's part (one port read) is negligible. The keyboard joystick costs nothing measurable.

Calibration and Settings

joyGetRaw(device, &raw) returns the last raw poll (driver units: microseconds for the game port). joyGetCalibration / joySetCalibration get and set an axis's JoyAxisCalT (min, center, max in raw units; min < center < max): min..center maps to -32768..0, center..max to 0..32767, clamped. A NULL calibration forgets it. An axis never calibrated ranges automatically: centred on the reading taken when the device was found, starting at center / 8 .. center * 2 and widening as the stick moves. joyGetDeadZone / joySetDeadZone get and set the device's dead zone in percent (0..JOY_DEADZONE_MAX, default JOY_DEADZONE_DEFAULT 8). joyAxisScale(cal, raw) and joyDeadZoneApply(value, percent) are the two steps as plain functions.

Settings changed through the library take effect at once and reach DVX.INI only through joySettingsStore(prefs) (the caller saves the file); joySettingsLoad(prefs) replaces them all, e.g. to undo. joySettingGet / joySettingSet read and write single values. The [joystick] keys:

  Key                          Value
  ---                          -----
  pollHz                       Polls a second (10 to 200, default 60).
  fallback                     auto (default): the keyboard joystick only when no other device is found; always; never.
  gameport.mode                twoSticks (default) or fourAxes: one joystick using all four axes and buttons (flight sticks with a throttle or rudder).
  keyboard.up ... keyboard.right, keyboard.button1 ... keyboard.button4   Key names (joyKeyName: Up, Down, Left, Right, LCtrl, RCtrl, LShift, RShift, LAlt, RAlt, Space, Enter, Tab, Backspace, Insert, Delete, Home, End, PgUp, PgDn, Keypad0 to Keypad9, A to Z, 0 to 9). Defaults: the arrows, Left Ctrl, Space, Left Shift, Z.
  <id>.axis<n>                 min,center,max: axis n of the device with that id, e.g. gameport0.axis0 = 40,560,1150.
  <id>.deadZone                The device's dead zone in percent.

Two settings are offered as lists: joyChoices(which, &count) returns the choices of JoyChoiceGamePortE (gameport.mode: "Two joysticks", "One joystick with 4 axes") or JoyChoiceFallbackE (fallback: "When no joystick is found", "Always", "Never") in list order, each a PrefsChoiceT (dvxPrefs.h) with its label for people and the name stored; joyChoiceGet(which) is the index of the current one (the default for a missing or unknown value) and joyChoiceSet(which, index) stores one. The keyboard joystick's keys go by slot (0 to JOY_KBD_SLOT_COUNT - 1 in JOY_KBD_SLOT_NAMES order: up, down, left, right, button1 to button4): joyKbdSlotGet(slot) is the joyKeyName index of the slot's key (its default key when the setting names no known key), joyKbdSlotSet(slot, keyIndex) stores one; the keyboard driver reads its keys through joyKbdSlotGet. joyKeyIndex(name) finds a key name's index (joyKeyFind its raw code). The Control Panel's Game Controllers page uses these, so the stored names and the defaults live only in the library.

joyRescan() looks for devices again (a joystick plugged in later, a changed gameport.mode); it works only while no handle is open and returns -1 otherwise. joyRescanApp(appId) closes every handle of that program first (joyAppCleanup) and then rescans, for a program that holds handles itself (the BASIC JoyRescan; the Control Panel closes its one handle and calls joyRescan).

The Game Port Driver

joyport.drv drives the standard PC game port at I/O port 201h. Writing to the port starts four one-shot timers, one per axis; each runs for a time proportional to its axis's resistance (about 24 us plus 11 us per kilohm). The driver times them against the platform's microsecond clock (platformMicros) with interrupts left enabled, so the port never holds up the timer, the sound card, or the mouse: each sample reads the port and the clock, and an axis whose fall can only be placed in a window wider than JOYPORT_GAP_US (40 us; an interrupt ran in between) keeps its previous value for that poll. A measurement gives up after JOYPORT_TIMEOUT_US (3 ms). At the probe an axis that does not run out, or runs out in under 10 us (some cards read 0 for an unconnected axis), is missing. At run time an axis that times out three polls in a row is left out of the measurement, so an unplugged stick does not cost the timeout every poll, and is tried again every 60 polls; a stick all of whose axes time out reads as not connected. Buttons A1, A2, B1, B2 are read at every poll and sampled by the timer interrupt, so short presses are latched.

In the default mode the port makes up to two joysticks (A with axes 0-1 and buttons 0-1, B with axes 2-3 and buttons 2-3; either may be missing); in the four-axis mode one joystick with four axes and four buttons.

The Keyboard Joystick

joykeys.drv is a fallback driver: a joystick made of keys (two axes: full deflection while a key is held, the centre with both or neither; four buttons), read from the held-key table, so games work without joystick hardware. It is listed only when no other device is found, unless [joystick] fallback says always or never.

Writing a Driver

A driver is a driver module (see dvxDrv.h in the API reference): a file <name>.drv in bin/libs/kpunch/joystick/drivers, built by the joystick Makefile from each drivers/<name>/ folder, that exports one JoyDriverT named joyDriver, written with JOY_DRIVER(name, priority, .flags = ..., .probe = ..., .info = ..., .poll = ..., .tick = ..., .settingsChanged = ...). Nothing lists drivers and nothing loads them at startup: the first time a program asks for devices (the GUI and the timer service are running by then), and at every joyRescan, the library loads them all, probes them from the highest priority down (JOY_PRIORITY_PORT, ..., JOY_PRIORITY_KEYBOARD), probes the fallback drivers only when the fallback setting asks for them, and unloads every driver that found no device. The descriptor carries DVX_ABI_VERSION; a driver built for another version, one without probe, info, or poll, or one whose name a higher priority driver already has is skipped and logged.

  Member            Meaning
  ------            -------
  hdr.name          Lower case, no blanks ("gameport"); the prefix of the device ids and of the driver's own [joystick] keys.
  hdr.priority      Probing order (JOY_PRIORITY_*); devices are numbered in that order.
  flags             JOY_DRIVER_FALLBACK for a stand-in like the keyboard joystick.
  probe()           Find the units; return how many. Called right after the driver is loaded.
  info(unit, out)   Describe one unit (name, counts, axisPresent, caps); the library fills in id.
  poll(raw, units)  Read every unit now, in program context: raw[0..units-1]. Set axisValid for each axis measured (the others keep their last value), buttons held now, pressed for buttons that went down since the last poll, hats (JOY_HAT_CENTERED when not pushed), and connected. Axes are raw units with JOY_CAP_CALIBRATE, else JOY_AXIS_MIN..JOY_AXIS_MAX.
  tick()            NULL, or called INSIDE THE TIMER INTERRUPT at the poll rate while a handle is open. The timer service's rules apply; the driver pins the code and data it touches with platformLockRegion and releases them with platformUnlockRegion when its module is unloaded (a destructor), so rescans do not pile up locked memory.
  settingsChanged() NULL, or re-read the driver's settings (joySettingGet) after they changed.

Up to JOY_MAX_DRIVERS drivers in use get their tick called. A driver is loaded again at every probe, so it must not rely on state kept from an earlier load; set its hardware up in probe (or a constructor).

BASIC

src/include/basic/joystick.bas declares JoyCount, JoyName$, JoyAxes, JoyButtons, JoyX, JoyY, JoyAxis, JoyButton, and JoyHat. Joystick, axis, button, and hat numbers start at 1; each function reads through the calling program's shared handle.

For a settings program (the Control Panel's Game Controllers page) it also declares: JoyHats, JoyAxisPresent(joy, axis), JoyCanCalibrate(joy) (JoyInfoT hatCount, axisPresent, JOY_CAP_CALIBRATE); JoyConnected(joy) (joyRead's connected); JoyRescan (joyRescanApp for the calling program: its own handles and STICK readings go first; -1 while another program has a joystick open); JoyRawAxis(joy, axis) and JoyRawButtons(joy) (joyGetRaw); JoySetCalibration(joy, axis, minV, centerV, maxV) and JoyClearCalibration(joy, axis) (joySetCalibration); JoyDeadZone / JoySetDeadZone (joyGetDeadZone / joySetDeadZone); JoyChoiceCount(which), JoyChoiceLabel$(which, index), JoyChoice(which), JoySetChoice(which, index) with JOY_CHOICE_GAMEPORT = 0 and JOY_CHOICE_FALLBACK = 1 (JoyChoiceE; joyChoices / joyChoiceGet / joyChoiceSet); JoyKeyCount and JoyKeyName$(index); JoyKbdSlotCount, JoyKbdSlot(slot), JoySetKbdSlot(slot, keyIndex) (joyKbdSlotGet / Set); JoySetting$(key, defaultValue) and JoySetSetting(key, value) ("" removes; joySettingGet / Set); JoySettingsLoad(prefs) and JoySettingsStore(prefs) with a prefs.bas handle (prefsBasHandle); JoyCalibration(joy, axis, part) (joyGetCalibration; part JOY_CAL_MIN / JOY_CAL_CENTER / JOY_CAL_MAX, joystick.h; 0 when joyGetCalibration fails); JoyIsKeyboard(joy) (JOY_CAP_KEYBOARD); JoyKeyIndex(keyName), JoyKeyCode(index), JoyKeyNameOf$(rawCode) (joyKeyIndex / joyKeyCode / joyKeyNameOf). CONSTs JOY_DEADZONE_DEFAULT / _MAX, JOY_POLL_HZ_DEFAULT / _MIN / _MAX, and JOY_KEY_POLL_HZ mirror joystick.h. Lists (choices, keys, slots) are numbered from 0; True is -1.

The BASIC runtime calls joyBasStick(n) and joyBasStrig(n) for QBasic's STICK and STRIG. Joystick A is device 0 (axes 0-1, buttons 0 and 1); joystick B is device 1, or axes 2-3 and buttons 2-3 of device 0 when it is the only device and has more than two axes. STICK(0) reads A's X and remembers A's Y and B's X and Y for STICK(1) to STICK(3) (per program, forgotten when it ends); positions are 1 to 200 ((value - JOY_AXIS_MIN) x 199 / 65535 + 1), 0 without that joystick. Even STRIG numbers use joyButtonWentDown (0 A button 1, 2 B button 1, 4 A button 2, 6 B button 2), odd ones the state joyRead reports; True is -1.

JSON Library

Reads JSON text -- a web service's reply, a settings file -- into a tree, finds values in it by path, and quotes text for JSON of your own. The package server's replies are read with it.

Header: json/json.h

Loaded as: bin/libs/kpunch/json/json.lib

BASIC: json.bas (JsonParse, JsonText$, JsonNumber, JsonCount, JsonKey$, JsonQuote$, ...).

Reading

jsonParse(text, len, err, errSize) reads a document and returns it (NULL when it is not JSON: err says why and on which line; jsonFree frees it). Its nodes are numbered from 0, the root. jsonFind(doc, node, path) finds a node from another: a path is member names and [index]es, such as packages[3].version, [0], or "" (the node itself); members match by exact name, the first when a name repeats. JSON_NO_NODE stands for a node that is not there, and every function accepts it.

  Function                          Description
  --------                          -----------
  jsonType(doc, node)               JsonNullE, JsonBoolE, JsonNumberE, JsonStringE, JsonArrayE, JsonObjectE; JsonNoneE for no node.
  jsonText(doc, node)               A string's text ("" for anything else).
  jsonNumber(doc, node)             A number's value, a double (0 for anything else).
  jsonBool(doc, node)               True only for true.
  jsonCount(doc, node)              An array's items or an object's members (0 for anything else).
  jsonChild(doc, node, index)       Item or member index, in order.
  jsonKey(doc, node)                A member's name ("" for anything else).

Strings are held decoded: the escapes \n, \t, \", \\, \/, \b, \f, and \r become their characters, \u escapes below 128 their character and others '?'; other bytes, UTF-8 included, pass through. Arrays and objects nest JSON_DEPTH_MAX (64) deep at most.

char      err[JSON_ERR_MAX];
JsonDocT *doc = jsonParse(text, len, err, sizeof(err));

for (int32_t i = 0; doc && i < jsonCount(doc, jsonFind(doc, 0, "packages")); i++) {
    int32_t pkg = jsonChild(doc, jsonFind(doc, 0, "packages"), i);

    printf("%s %s\n", jsonText(doc, jsonFind(doc, pkg, "id")), jsonText(doc, jsonFind(doc, pkg, "version")));
}

jsonFree(doc);

Writing

jsonQuote(text, out, outSize) writes text as a JSON string -- quotes, escapes, control characters as \u00XX -- and returns the length it needs (as snprintf does), so a NULL out with size 0 measures it.

Writing Games for DVX

A game in DVX is a program with its own main loop (hasMainLoop = true in its SHELL_APP_DESCRIPTOR). DVX gives it its own task: appMain runs the game loop itself, draws into the game's window, and waits for each frame on the high-resolution clock while the other programs keep running. Nothing in DVX has to stop for a game: the desktop, other programs, the Task Manager (Ctrl+Esc), and help all stay available.

Starting From a Template

  bin/sdk/newProject.sh game myGame        a C game
  bin/sdk/newProject.sh basicgame myGame   a DVX BASIC game

The C template is a small but complete game, and every part of this guide is shown in it: a game input window, a 320x200 screen of 8-bit colour numbers drawn scaled into the window on any desktop colour depth, frame pacing, the arrow keys and a joystick, a sound effect and music carried inside the .app as resources, tones for the end of a round, the best score saved in the program's settings folder, its own help file (F1) and icon. The BASIC template does the same in a few dozen lines with FRAMEWAIT, GameKeyDown, STICK, STRIG, and PLAY.

The Shape of a Game

int32_t appMain(DxeAppContextT *ctx) {
    GameFramePacerT pacer;

    createGameWindow(ctx);           // gameInput = true, onClose sets quit
    gameFrameInit(&pacer, 60);

    while (!quit) {
        uint32_t missed = gameFrameWait(&pacer);   // yields until the frame is due

        audioPump();                 // sound end calls and streams
        musicPump();
        update(missed + 1);          // move by the frames that passed
        draw();                      // into the game's own framebuffer
        present();                   // changed parts -> window, dvxInvalidateRect
    }

    cleanUp();                       // or just return: DVX releases the rest
    dvxDestroyWindow(ctx->shellCtx, win);
    return 0;
}

The sections that follow cover each part.

Keyboard, Mouse, and Joysticks

Timing and Frame Pacing

The Screen: Framebuffers and Palettes

Sound, Music, and MIDI

Data Files and Saved Games

Packaging and Ending

Games in DVX BASIC

Porting a Game Library to DVX

Keyboard, Mouse, and Joysticks

Game Input Windows

Set gameInput on the game's window and give it an onRawKey callback if it wants every key going down and up:

win->gameInput = true;
win->onKey     = gameKey;       // typed keys: every key but three
win->onRawKey  = gameRawKey;    // each key going down and up

While the window has the focus, DVX keeps only three keys, so the user can always switch away, open the Task Manager, or close the game:

  Key         Always does
  ---         -----------
  Alt+Tab     Switch to the next window
  Ctrl+Esc    Open the Task Manager
  Alt+F4      Close the window (its onClose)

Every other key, F1, F10, Alt+letter, and the accelerators included, goes to onKey instead of opening help or menus. The game decides what F1 does (the template opens its help with shellOpenHelp); its menus still work with the mouse. onRawKey gets a RawKeyEventT for every key going down or up: the set-1 scancode with an extended flag for the grey keys, down or up, whether it is a typematic repeat, and the modifiers. Shift, Ctrl, and Alt are keys of their own there, left and right apart.

The key that completes one of the three (Esc with Ctrl held, Tab or F4 with Alt held) never reaches the game: not in onRawKey, not in the held-key table, not its repeats, and not its release. The modifier itself does, because when Ctrl or Alt goes down nobody can tell yet what will follow. A game that treats any key as "go on" therefore still sees the Ctrl or Alt of Ctrl+Esc or Alt+Tab.

Held Keys

For "is it held right now" use gameKeyIsDown(win, rawCode) with a raw key code (KEY_RAW_UP, KEY_RAW_LCTRL, or KEY_RAW_CODE(scan, extended) for any key); it is true only while win has the focus. platformKeyIsDown and platformKeyStateGet read the table itself. When the focus moves away, the game gets a release for every key still held and the table starts over, so no key stays stuck down. A key already held when the window gets the focus is seen once it repeats.

Typed Characters and the Mouse

onKey gets typed characters (with Shift and the keyboard layout applied) as it does in any window; keep them in the game's own queue if the game reads text. onMouse gets the pointer in content-area pixels: divide by the scale the game draws at, and clamp.

Joysticks

joyCount says how many joysticks there are; joyOpen(ctx, device) gives the program a handle (closed for it when it ends) and joyRead fills a JoyStateT: axes calibrated to -32768..32767 with a dead zone, buttons (a press between two reads is not lost), hats. Read it once a frame. Programs that wait for input can take events instead (joySetEventFn). With no joystick connected the keyboard joystick stands in (the arrow keys and four buttons, chosen in the Control Panel), answering only while the program's window has the focus; so a game that reads both the keys and the joystick should let the keys win rather than add the two up. Polling a game port joystick costs about 4 to 7 percent of the processor while a program has one open. See the Joystick Library.

Timing and Frame Pacing

platformMicros() is a monotonic microsecond clock from the PIT, the timer chip every PC has (with the CPU's time-stamp counter between timer interrupts when there is one). Use it for any game time: elapsed milliseconds are platformMicros() / 1000.

Frame Pacing

gameFrameInit(&pacer, hz) starts a frame clock (1 to GAME_FRAME_MAX_HZ frames a second); gameFrameWait(&pacer) once a frame yields to the other programs until the next frame is due and returns how many frames were missed. Frame n starts at start + n / hz, so the rate never drifts; frames that already ended while the game was busy are skipped, not caught up (pacer.missed counts them). Move by missed + 1 frames' worth (with a limit, so a long stall does not teleport the player) and the game runs at the same speed on a slow machine. pacer.frame is a frame counter.

There is no vertical retrace wait: a game window is composited with the rest of the desktop, and the frame rate is a virtual one. 60 or 70 frames a second both work; pick what the game was designed for.

The Timer Service

For work that must happen at a steady rate however busy the programs are (a music player, a sample-accurate sequencer), the timer service calls a function up to 1000 times a second from the timer interrupt: gameTimerAdd(ctx, hz, fn, userData), removed when the program ends (or platformTimerAdd). DVX speeds the timer interrupt up only as far as the fastest timer needs and keeps the DOS clock at its normal rate. The callback runs at interrupt time: no C library, no DVX calls, no floating point, and its code and data pinned with platformLockRegion. See Timer Service in the platform reference.

The Screen: Framebuffers and Palettes

A window's content buffer (win->contentBuf, win->contentPitch bytes per row, contentW x contentH pixels) is in the desktop's own pixel format: 8, 15, 16, or 32 bits per pixel, as dvxGetDisplay(ctx)->format says. A game that must look the same everywhere draws into a framebuffer of its own (for example 320x200 bytes, each a colour number into a 256-colour game palette) and converts only what changed into the content buffer each frame.

The Colour Lookup Table

Keep one desktop pixel value per game colour, and rebuild it when the game palette changes or the desktop's pixel format or palette does (the user can change the video mode while the game runs):

const DisplayT *d = dvxGetDisplay(ctx);

for (int32_t i = 0; i < 256; i++) {
    const uint8_t *c = &gamePal[i * 3];

    lut[i] = d->palette ? gamePalNearest(d->palette, c[0], c[1], c[2])   // 8-bit desktop
                        : packColor(d, c[0], c[1], c[2]);                 // 15, 16, 32 bits
}
  Desktop     Pixel size    Game colours
  -------     ----------    ------------
  32-bit      4 bytes       Exact.
  16-bit      2 bytes       5-6-5 bits of red, green, blue: very close.
  15-bit      2 bytes       5 bits each: very close.
  8-bit       1 byte        The nearest of the desktop's 256 colours.

On an 8-bit desktop all windows share the one desktop palette: a 6x6x6 colour cube, a 16-step grey ramp, and the chrome colours (see dvxPal.h). A game cannot change it; gamePalNearest finds the closest entry, and a GamePalCacheT remembers the answer per 15-bit colour when there are many colours to map (gamePalCacheSync starts over if the desktop palette changed). Gradients come out banded; design for it, or ask players to use a 15-bit or better desktop for the best look.

Palette animation (colour cycling, fades) works at every depth: change the game palette entries, look up just those entries again, and redraw the pixels that use them. Games whose hardware picks a palette per scan line keep one small table per palette and pick the table per row.

Presenting a Frame

Size the window so its content area is exactly the framebuffer times a whole scale that fits the screen (the template computes the scale from the display size and the chrome constants, then corrects with dvxResizeWindow after adding the menu bar). Copying only what changed matters on slower machines: a full 320x200 frame at 2x on a 16-bit desktop is 256 KB of pixels to convert and composite.

The Whole Screen

A game may also take the whole screen in a video mode of its own, as the template does with Alt+Enter. Every other program keeps running while it does: their windows go on painting into their own buffers, their timers fire, their sound plays, and the desktop is drawn again, as it is now, when the game lets go.

static void fullChanged(WindowT *win, ExclusiveStateE state, void *userData) {
    // Active: draw the whole frame.  Suspended: draw in the window again.
    // Ended: the mode could not be set again; full screen is over.
}

if (dvxExclusiveBegin(ctx, win, 320, 200, 8, fullChanged, NULL) == DVX_EXCL_OK) {
    dvxExclusiveSetPalette(ctx, gamePal, 0, 256);
}

// every frame
if (!dvxExclusivePresent8(ctx, fb, 320, 200, 320)) {
    // not on the whole screen: present into the window as usual
}

Sound, Music, and MIDI

Games play sound on whatever the computer has: a Sound Blaster family card, a Windows Sound System card, a Gravis UltraSound, a DAC on the printer port, an FM synthesizer chip, a wavetable synthesizer (a Gravis UltraSound's or a Sound Blaster AWE's), a MIDI port, the PC speaker, or nothing. Nothing fails because of missing hardware: audioOpen returns NULL (or the device plays tones only), and the game carries on silently. Several programs can play at once, and everything a program opened is stopped and released when it ends.

Sound Effects and Tones (audio.h)

Music (music.h)

musicLoad or musicLoadMem load a tracker module (MOD, S3M, XM, IT) or a Standard MIDI File; musicPlay(music, loop) plays it, with pause, resume, stop, volume, position, and an end notification. MIDI plays on the best synthesizer there is: an external one on the MPU-401 port when the user chose it, else a wavetable card's recorded instruments (a Gravis UltraSound or a Sound Blaster AWE with a SoundFont), else the FM chip with a General MIDI bank, else DVX's own FM software synthesizer; modules play on a wavetable card's own voices (taking little of the CPU), else on any card that plays sampled sound (mixed in software). musicMidiOpen gives live MIDI (note on and off, program change, controllers, pitch bend), and musicMmlPlay plays tunes written in QBasic's music macro language.

The FM Chip and the MIDI Port

For music drivers of its own, a game can allocate two-operator FM voices shared with the other programs (audioFmAlloc, audioFmWrite with the AUDIO_FM_* register numbers), take the whole FM chip for itself (audioFmRawAcquire; refused with AUDIO_ERR_BUSY while another program has it), or send bytes to the MIDI port (audioMidiAcquire, audioMidiPut). Give them back as soon as the game does not need them.

Keeping Sound Fed

Sampled sound is mixed from the sound card's own interrupt, so it keeps playing however busy the game is. End notifications, pull streams, modules, and the software synthesizer are made in the desktop's idle time; a game that redraws every frame should call audioPump() and musicPump() once a frame so they keep up. Modules use floating point: on a computer without a maths coprocessor they are slow.

See the Sound Library, the Music Library, and the Joystick Library for every function.

Data Files and Saved Games

DOS has one current directory for everything, so a program never relies on it. appMain gets a DxeAppContextT with the paths it needs:

  Field        What it is                                Use it for
  -----        ----------                                ----------
  appPath      the program's .app file                   dvxResOpen: resources inside the .app
  appDir       the folder the .app is in                 its DATA folder: read-only game data
  userDir      the program's own folder in USER          saved games, high scores, options
  helpFile     the program's .hlp file                   shellOpenHelp (F1)

userDir is the user folder plus the folder the .app is in: a game in APPS\ACME\MYGAME keeps its files in USER\APPS\ACME\MYGAME.

Packaging and Ending

Packaging

make package lays out pkg/APPS/<VENDOR>/<NAME>/ with NAME.app, NAME.hlp, and the files listed in DATA; zip the contents of pkg/ and the player unpacks it into the DVX directory (make install DVX_DIR=... does it for you). The .app carries its icon, name, author, and description for Program Manager (NAME.res). Every game should have its own help (NAME.dhs, compiled to NAME.hlp), since F1 in a game input window goes to the game: open the help from the game's F1 and from Help > Contents. A game built on a static library of its own links it in with the LIBS variable (LIBS = port/libgame.a).

Ending

Close the game's window and return from appMain. Every sound, music, joystick handle, and timer the program opened is released when it ends, however it ends, so a crash or End Task leaves nothing playing. A game (or any program) that calls exit() or abort() ends only itself, like End Task: its windows close, its timers are removed, and DVX keeps running. Handlers registered with atexit are not run; clean up before calling exit(), or return from appMain. malloc and free work as usual; DVX counts each program's memory (the Task Manager shows it).

Games in DVX BASIC

newProject.sh basicgame NAME starts a BASIC game. A Timer fires once and runs the game loop; FRAMEWAIT rate waits for the next frame while the desktop and the program's forms keep running, and the loop ends when the form closes:

Sub tmrStart_Timer ()
    tmrStart.Enabled = False
    Do
        MovePlayer          ' GameKeyDown, STICK(0), STICK(1), STRIG(1)
        DrawScreen          ' a PictureBox: Clear, FillRect, FillCircle
        FRAMEWAIT 30
    Loop Until quitting     ' set in the form's Unload event
End Sub

GameKeyDown (game.bas) tells whether a key is held, GameKeyClear forgets the held keys, FrameMissed() counts the frames FRAMEWAIT skipped, and GameWaitRetrace waits for the screen's retrace; QBasic's STICK and STRIG read joysticks (the keyboard joystick when there is none); BEEP, SOUND, and PLAY make sound, PLAY "MB ..." in the background so the game does not wait. sound.bas plays WAV files and tones, sounds loaded once and played with volume, pan, and pitch, sound the game makes as it goes (streams), and FM instruments; music.bas plays modules and MIDI files, and joystick.bas reads joysticks in full. See Sound, Joysticks, and Frames and Include Libraries in the BASIC reference.

Porting a Game Library to DVX

Portable game libraries for DOS-era machines usually keep everything machine-specific behind a small hardware layer: start and stop, show a frame, set the palette, read input, wait for the next frame, play sound, and find files. A DVX port replaces that layer. The rest of the library, including drawing code, sprite compilers that write x86 code into memory, and asset formats built for DOS, works unchanged, because a DVX program is 32-bit protected-mode x86 code in the same flat memory as a DOS program built with the same compiler.

The Model: a Window, a Task, a Virtual Frame Rate

The game becomes a DVX program with its own main loop: appMain calls the game's main() (compile the game with -Dmain=gameMain, say, and call gameMain from appMain). The library's start-up creates a game input window whose content area is the game's screen at a whole scale; its frame wait yields until the next frame on the high-resolution clock. In a window the game shares the screen with the desktop, so there is no mode change, no direct palette hardware, and no vertical retrace; the port converts the library's back buffer into the window each frame. On the whole screen (see The Whole Screen) the game has its own mode, its own palette, and the vertical retrace, while the other programs run on.

Mapping a Hardware Layer to DVX

  Hardware layer                   DVX
  --------------                   ---
  Start-up                         SHELL_APP_DESCRIPTOR (hasMainLoop), dvxCreateWindowCentered, gameInput
  Shut-down                        dvxDestroyWindow, return from appMain
  Error text, debug log            dvxLog (DVX.LOG)
  Present the back buffer          colour table -> win->contentBuf, dvxInvalidateRect; onPaint
  Present, whole screen            dvxExclusivePresent8, or dvxExclusiveDisplay + dvxExclusiveFlush
  Full screen on and off           dvxExclusiveBegin, dvxExclusiveEnd
  Palette                          packColor, gamePalNearest, GamePalCacheT; whole screen: dvxExclusiveSetPalette
  Palette per scan line            one colour table per palette, chosen per row
  Wait for vertical blank          gameFrameInit, gameFrameWait; whole screen: dvxExclusiveWaitRetrace
  Frame counter, frame rate        GameFramePacerT.frame, the rate given to gameFrameInit
  Milliseconds                     platformMicros() / 1000
  Held keys                        onRawKey, gameKeyIsDown, platformKeyStateGet
  Typed characters                 onKey
  Mouse                            onMouse
  Joystick                         joyCount, joyOpen, joyRead
  Periodic interrupt callback      gameTimerAdd, platformLockRegion
  Critical section                 platformIrqSave, platformIrqRestore
  Audio start and stop             audioOpen, audioClose
  Sound effect slots               audioSampleCreate, audioSampleLoadMem, audioPlay, audioVoiceSetPan, audioVoiceStop
  Streamed sound                   audioStreamOpen, audioStreamWrite
  Tracker modules                  musicLoadMem, musicPlay, musicStop, musicIsPlaying
  Tone and noise voices            audioTone, audioVoiceSetFrequency, audioVoiceSetVolume, audioNotesOpen, audioFmAlloc
  Per-frame audio work             audioPump, musicPump
  Read-only data folder            DxeAppContextT.appDir, dvxResOpen, dvxResRead
  Save folder                      DxeAppContextT.userDir, shellEnsureUserDir, shellUserPath
  Disk free                        platformDiskSpace
  Memory                           malloc, free
  Serial port                      the serial library (rs232Open)
  exit()                           ends only the game

Notes on the Mapping

Things to Decide

Checking a Port

The library's own drawing tests (hashes of the back buffer after each drawing call) should give the same answers as on DOS, since the drawing code is the same. Test in a 256-colour and a 16-bit desktop, with and without a sound card and a joystick, and with other programs running; End Task and exit() must leave DVX, the sound, and the timer as they were.

SDK Quick Start

The DVX SDK holds everything needed to build DVX modules outside the DVX source tree: the public headers, the host tools, a shared set of build rules, and a template for each kind of module. A DVX build writes it to bin/sdk.

  Path             Contents
  ----             --------
  newProject.sh    Creates a project from a template
  sdk.mk           Build rules every project Makefile includes
  bin/             dvxres (resources), dvxhlpc (help compiler), bascomp (BASIC compiler), mkfont (fonts: see Making Fonts), dxeGen.sh (runs dxe3gen)
  include/         Public C headers, one directory per library, plus include/basic for BASIC declaration files
  templates/       The project templates
  samples/basic/   Example BASIC projects

Requirements

Creating a Project

  bin/sdk/newProject.sh KIND NAME [DIR]
  KIND      Creates
  ----      -------
  app       A C program (NAME.app)
  widget    A C widget (NAME.wgt) usable from C programs and DVX BASIC
  library   A C library of shared functions (NAME.lib) with BASIC declarations
  basic     A DVX BASIC program (NAME.dbp, built to NAME.app)
  game      A C game (NAME.app): a 320x200 screen in a window, frame pacing, keyboard and joystick, sound effects and music
  basicgame A DVX BASIC game (NAME.dbp): a FRAMEWAIT loop, GameKeyDown, STICK, STRIG, and PLAY
  control   A control written in DVX BASIC (NAME.dbp with NAME.ctl, built to NAME.wgt) usable from C programs and DVX BASIC

The two game templates are explained in Writing Games for DVX.

NAME is a letter followed by letters and digits, 8 characters at most, because it becomes a DOS file name. Files are named NAME with its first letter in lower case (camelCase, such as myApp); DVX shows it in upper case. The script refuses DOS device names (CON, PRN, AUX, NUL, COM1, and so on), the name of a widget DVX already has, and an existing directory. The project goes in DIR, or a new directory of that name in the current one.

The template's names become yours everywhere: file names, identifiers in each case style (tplName, TplName, tplname, TPLNAME), help topic ids, and the ABI stamp. The Makefile refers to the SDK by a relative path, so a project and the SDK can move together.

Building

Every project Makefile only names the SDK, the kind, and the module; the rules come from sdk.mk:

  Command                            Does
  -------                            ----
  make                               Builds the module with its resources, and NAME.hlp from the help source
  make package                       Lays out pkg/ exactly as the files go into the DVX directory, and makes NAME.dpk, the add-on package Software installs
  make install DVX_DIR=/path/to/dvx  Packages, then copies pkg/ into a DVX installation
  make clean                         Removes everything the build made

Every .c file in the project directory is compiled with warnings as errors (-Wall -Wextra -Werror) and every SDK include directory on the path. Add your own flags with CFLAGS, for example CFLAGS=-I../myLib to use a library's header. VENDOR (default local) is the vendor directory the module installs under.

Programs That Use the Network

The dvxnet library provides TCP/IP through the standard socket calls (socket, connect, send, recv, getaddrinfo, select, and so on). Its headers are in include/net: dvxNet.h, plus sys/socket.h, netinet/in.h, netinet/tcp.h, arpa/inet.h, netdb.h, poll.h, sys/select.h, sys/ioctl.h, and an errno.h that adds the socket error codes to the C library's. Because they stand in for headers of the same name, include/net is not on every module's path; a module that uses sockets adds one line to its Makefile, before the include of sdk.mk:

  NET = 1

sdk.mk then puts include/net ahead of the C library's headers, and the program uses the ordinary names:

  #include <sys/socket.h>
  #include <netdb.h>

  struct addrinfo  hints = {0};
  struct addrinfo *res   = NULL;

  hints.ai_socktype = SOCK_STREAM;

  if (getaddrinfo("example.com", "80", &hints, &res) == 0) {
      int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
      ...
  }

Nothing else is needed: DVX loads dvxnet.lib when it starts and connects the calls when it loads the program. Close a socket with close(), and free the getaddrinfo result with freeaddrinfo.

The dvxnet library

The Guides

Your First DVX Program

Your First Widget

Your First Library

Your First BASIC Program

Your First BASIC Control

Using BASIC Controls from C

Debugging: the Log and Crash Reports

Binary Interface Rules

Packaging and Installing

Making Fonts

Writing Games for DVX

Your First DVX Program

Create and Build

  bin/sdk/newProject.sh app hello
  cd hello
  make

The build makes hello.app and hello.hlp. The project holds:

  File         Purpose
  ----         -------
  hello.c      The program
  hello.res    Resources appended to hello.app: its icon, name, author, description, and a greeting text
  icon32.bmp   The 32x32 icon Program Manager shows
  hello.dhs    The program's help, compiled to hello.hlp
  Makefile     Names the SDK, KIND = app, and NAME = hello

How the Program Works

SHELL_APP_DESCRIPTOR(...) declares the exported appDescriptor: the program's name, whether it has its own main loop, whether several copies may run, its task stack size, and priority. It also records the binary interface version the program was built for.

DVX calls appMain(DxeAppContextT *ctx) once. The template is a callback-only program (hasMainLoop = false): appMain creates a window with a File and Help menu, a label, and a button, and returns 0. From then on DVX calls the window's onClose and onMenu callbacks and the button's onClick. Destroying the program's last window ends it. A program that needs to keep working between events sets hasMainLoop = true, loops in appMain, and calls tsYield() often (see the Clock program's source for an example).

ctx gives the program its paths: appPath (the .app file, used to read its own resources with dvxResOpen and dvxResRead), appDir, userDir (its folder in the user folder, for its settings and the files it saves: shellEnsureUserDir makes it, shellUserPath names a file there), and helpFile (preset to hello.hlp beside hello.app). The files it ships are in the DATA folder beside it (shellDataPath), which DVX never writes.

A program writes only in its user folder, and never writes or installs anything into DVX's SHARED folder (DVX's own fonts, sounds, themes, and wallpapers): its own files stay with it, in its DATA folder or as resources in the .app, under its vendor and program folder, so two programs' files never collide.

A program can carry fonts of its own as font resources, so its text looks the same whatever fonts were installed: add a line such as "title font TITLE.DFN" to hello.res (dvxres checks that the file is a DVX font). DVX adds the program's fonts when it starts and forgets them when it ends; other programs never see them, and where a family is installed too, the program's sizes and styles come first (dvxText.h, Fonts a Program Carries). Make a .DFN with bin/mkfont from a TrueType, Type 1, or bitmap font (see Making Fonts; one size and only the characters the program shows keep the file small), or copy one from DVX's SHARED\FONTS folder. A .DFN carries the notice and licence of the font it was made from.

Run

  make install DVX_DIR=/path/to/dvx

This copies hello.app and hello.hlp to APPS/LOCAL/HELLO/ in the DVX directory. Start DVX and run the program from Program Manager (File > Run), or add an icon for it. After a change, rebuild and install again; close the running copy first.

Help

F1 and Help > Contents open hello.hlp. Edit hello.dhs and rebuild; the compiler stops on an error with the file and line. The format is described in the Help Viewer's own help (Help Source Format).

Next

Debugging: the Log and Crash Reports

Binary Interface Rules

Packaging and Installing

Your First Widget

Create and Build

  bin/sdk/newProject.sh widget badge
  cd badge
  make

The build makes badge.wgt and badge.hlp (a preview of both help sources compiled together). The project holds:

  File         Purpose
  ----         -------
  badge.c      The widget: class, API table, BASIC interface, wgtRegister
  badge.h      The public API: BadgeApiT, the dvxBadgeApi() accessor, and the wgtBadge... macros
  badge.res    Resources: icon24 (the DVX BASIC toolbox icon) and name (its tool tip)
  icon24.bmp   The 24x24 toolbox icon
  badge.dhs    Developer help, added to the system help
  badge.bhs    BASIC help, added to the DVX BASIC help
  Makefile     KIND = widget

How the Widget Works

DVX loads every widget when it starts and calls its wgtRegister once. The template registers three things:

DVX_ABI_STAMP(badge) at file scope records the binary interface version. Its argument must be the module's file name in lower case.

Run

Install it (make install DVX_DIR=/path/to/dvx puts badge.wgt and its help in WIDGETS/LOCAL/BADGE/) and restart DVX. The widget is then:

Warning: DVX stops at start-up with a message naming a widget or library it cannot load or that was built for a different version. Remove that module's directory to start DVX again.

Help

badge.dhs and badge.bhs are installed as BADGE.DHS and BADGE.BHS beside the widget. When DVX starts and finds a different set of widgets or libraries, it rebuilds the system help and the DVX BASIC help with them, so your pages appear there. The make build compiles both into badge.hlp only to check them.

Next

Debugging: the Log and Crash Reports

Binary Interface Rules

Packaging and Installing

Your First Library

Anything several programs could share belongs in a library: file operations, protocols, device access. Programs then stay thin user interfaces over it.

Create and Build

  bin/sdk/newProject.sh library mathx
  cd mathx
  make

The build makes mathx.lib and mathx.hlp (a preview of the help). The project holds:

  File         Purpose
  ----         -------
  mathx.c      The functions and DVX_ABI_STAMP(mathx)
  mathx.h      The public header programs include; MATHX_VERSION
  mathx.dep    Libraries that must load first, one per line (the template lists libdvx)
  mathx.bas    DECLARE LIBRARY declarations for BASIC programs
  mathx.res    Resources identifying the module (name, author, description, icon)
  icon32.bmp   The icon in the resources
  mathx.dhs    Developer help, added to the system help
  Makefile     KIND = library

How the Library Works

DVX loads every library when it starts, before any widget or program, in the order the .dep files require. Every function that is not static is exported: a program, widget, or library loaded later calls it directly, and DVX connects the call when it loads that module. A library that calls another library's functions lists it in its .dep file.

BASIC programs add mathx.bas to their project. DECLARE LIBRARY looks up each function by exactly the name it declares, without a trailing $ (a $ marks a function returning a string), so mathxGreet$ calls the C function mathxGreet. Keep the declarations and the C functions in step; strings are passed as const char * and returned as const char *.

A string declared AS STRING is text and stops at the first zero byte. For bytes (network data, samples, encrypted data) declare BINARY STRING: each BINARY STRING parameter becomes two C parameters at its place, the bytes (const uint8_t *, a private copy valid during the call, followed by a zero byte) and their count (int32_t); a BINARY STRING result adds one last C parameter, int32_t *outLen, where the function stores the length of the bytes it returns (the runtime copies them right after the call, so a static buffer is fine; NULL, a negative length, or a length never set give an empty string). The template's tplNameXor shows both:

  // tplNameXor$(BYVAL bytes AS BINARY STRING, BYVAL key AS LONG) AS BINARY STRING
  const uint8_t *tplNameXor(const uint8_t *data, int32_t len, int32_t key, int32_t *outLen);

Run

Install it (LIBS/LOCAL/MATHX/ gets mathx.lib, mathx.dep, and MATHX.DHS) and restart DVX. Build programs that use it with CFLAGS=-I../mathx.

Help

mathx.dhs goes into the system help, under Technical Reference > Libraries, when DVX next starts with the library installed.

Next

Debugging: the Log and Crash Reports

Binary Interface Rules

Packaging and Installing

Your First BASIC Program

Create and Build

  bin/sdk/newProject.sh basic greeter
  cd greeter
  make

The build compiles the project with bascomp into greeter.app and the help into greeter.hlp. The project holds:

  File          Purpose
  ----          -------
  greeter.dbp   The project: name, author, version, icon, modules, forms, startup form
  greeter.frm   The main form (controls and menus) and its code
  icon32.bmp    The program's icon (the project's Icon entry)
  greeter.dhs   The program's help, compiled to greeter.hlp
  Makefile      KIND = basic

The project lists the SDK's include/basic/help.bas, which declares HelpView; Help > Contents calls HelpView "greeter.hlp", which opens the file beside the program. The project settings in the .dbp take the place of a C program's resource file; fonts the program carries go in its [Fonts] section (Project > Properties in DVX BASIC), and the build makes them font resources.

Run and Debug

make install puts greeter.app and greeter.hlp in APPS/LOCAL/GREETER/. You can also copy the project directory to the DVX machine and open greeter.dbp in DVX BASIC (File > Open Project...): Run > Run (F5) runs it, Run > Debug sets it going under the debugger with breakpoints (F9), stepping, and the locals, watch, and call stack windows, and File > Make Executable... builds the .app there. The DVX BASIC help describes the language and the IDE.

Next

Packaging and Installing

Your First BASIC Control

Create and Build

  bin/sdk/newProject.sh control dial
  cd dial
  make

The build compiles the control project with bascomp into dial.wgt, a control file, and the help into dial.hlp. The project holds:

  File         Purpose
  ----         -------
  dial.dbp     The project: [Project] Type = Control, the [Controls] list (and any [Modules] the controls need)
  dial.ctl     The control: its Begin UserControl header (natural size, focus, toolbox picture and tooltip, default event) and its code, a class
  icon24.bmp   The toolbox picture (the header's ToolboxIcon)
  dial.bhs     The control's BASIC help, installed beside it and added to the DVX BASIC help
  Makefile     KIND = control

The control's Public properties, methods, and events are what programs use; UserControl is its drawing surface. The DVX BASIC help (UserControls: Your Own Controls) describes the language side.

Install

make install puts dial.wgt and DIAL.BHS in WIDGETS/LOCAL/DIAL/. When DVX starts it reads the control file through the BASIC run-time library and registers its controls; BASIC programs put a Dial on a form (Begin Dial Dial1 in a .frm, or from the designer's toolbox) and C programs create one by name (see Using BASIC Controls from C).

Next

Using BASIC Controls from C

Packaging and Installing

Using BASIC Controls from C

A control written in DVX BASIC is an ordinary widget type once it is registered: it has a widget class, an interface (WgtIfaceT) with its properties, methods, and events, and a type name equal to its BASIC name. A C program uses it through the generic widget access, without a header of its own:

  WidgetT  *dial = wgtCreateByName("Dial", parent);
  WgtValueT v    = wgtValueInt(40);
  WgtValueT ret;

  wgtSetProp(dial, "Level", &v);            // Public Property Let Level
  wgtGetProp(dial, "Level", &v);            // Property Get: v.i == 40
  wgtCallMethod(dial, "Reset", NULL, 0, &ret);  // Public Sub Reset (ret.type WGT_IFACE_NONE)
  dial->onEvent = onDialEvent;              // RaiseEvent in the control's code

  static void onDialEvent(WidgetT *w, const char *name, WgtValueT *args, int32_t argc) {
      if (strcmp(name, "Changed") == 0 && argc > 0) {
          ...                               // args[0]: the event's first argument
      }
  }

Property values follow the BASIC types: Integer, Long, and Byte are WGT_IFACE_INT, Boolean WGT_IFACE_BOOL, Single WGT_IFACE_FLOAT, Double and Currency WGT_IFACE_DOUBLE, String, Date, and Variant WGT_IFACE_STRING. A property declared As an Enum whose members are numbered 0, 1, 2 ... (each number once) is WGT_IFACE_ENUM with the member names in value order (WgtPropDescT.enumNames; wgtSetProp takes the number or a member name, wgtIfaceGetPropText gives the name); an Enum with other values is WGT_IFACE_INT. A method takes its arguments as they come (WGT_SIG_DYNAMIC) and returns its Function's value, or no value (WGT_IFACE_NONE) for a Sub. An event's numeric and Boolean arguments that onEvent changes go back to the control (ByRef parameters). Events the control names Click, DblClick, Change, KeyPress, KeyDown, KeyUp, MouseDown, MouseUp, MouseMove, or Scroll go to the matching universal callback (onClick ...) instead; the dispatcher calls those for a BASIC control only when its code raises them (WCLASS_RAISES_EVENTS). The interface's events list each event with its parameters as BASIC writes them (WgtEventDescT.params).

wgtCreateByName goes through the interface's dynamic create hook (WgtIfaceT.dyn); the class has no C API structure (wgtGetApi finds none), because one create function cannot tell which BASIC class to make.

The Control File

A control project builds NAME.wgt, a file that holds only resources:

  Part          Contents
  ----          --------
  Marker        DVX_RES_ONLY_MAGIC ("DVXRESONLY" and its NUL) at offset 0: no code
  MODULE        The compiled BASIC module (with DEBUG unless built for release)
  CTL0, CTL1 ...  Each control's Begin UserControl header text, in [Controls] order
  icon24, name  The first control's toolbox picture and tooltip; the second and later have "-2", "-3" ... suffixes, as for any widget file holding several widgets
  author, version ...  The project's metadata; helpfile its help file's name

The loader tells such a file from a module by its first bytes: instead of loading it, it hands it, once every library and native widget is loaded, to the BASIC run-time library's basCtlLoadFile (found with dlsym; without the library the file is skipped and logged). basCtlLoadFile registers each control in the file for every program and the loader records the file as theirs, so the toolbox finds their icon24 and name.

The Bridge

Each control is one object of its BASIC class plus a border-less canvas, the UserControl surface its code draws on (the same drawing code as a BASIC PictureBox). The widget class forwards to the class's code: layout sizes the canvas, and between frames a new size runs UserControl_Resize and UserControl_Paint; the mouse, key, wheel, and focus class methods run UserControl_MouseDown and the other handlers. A control file's code runs in one VM shared by every program that uses its controls; it never yields, and a call from another task while it runs code is refused (the paint keeps the old picture, the event is dropped). A BASIC program's own controls ([Controls] in its project) are registered by the program when it starts, owned by it, and run in its own VM.

Your First BASIC Control

Your First Widget

Debugging: the Log and Crash Reports

The Log

dvxLog(fmt, ...) (dvxPlat.h) appends one printf-style line to DVX.LOG in the user folder (USER in the DVX directory, or the folder DVXUSER names). The file is opened and closed for every line, so everything written before a crash is kept. DVX logs there too: each module it loads, why a module was refused, each program started (Shell: loaded 'Name' ... entry=0x...), and each help rebuild.

Crash Reports

When a program faults, DVX ends that program with the message "'Name' has caused a fault and will be terminated." and keeps running. DVX.LOG then holds the report:

  === CRASH ===
  Signal: 11 (SIGSEGV (page fault))
  EIP: 0x...  CS: 0x...
  EAX: ...  (all registers)
  App name: Hello
  App path: APPS\LOCAL\HELLO\hello.app

To find the faulting code of a program, take its entry= address from the Shell: loaded line: it is where appMain is. EIP minus that address is the fault's distance from appMain. Then:

  i586-pc-msdosdjgpp-nm -n hello.o         appMain's offset in .text
  i586-pc-msdosdjgpp-objdump -d hello.o    the code at appMain's offset plus the distance

This works directly when the program is one .c file (as the templates are). Build with CFLAGS=-g and use objdump -d -S to see the source lines.

Other Tips

Binary Interface Rules

DVX_ABI_VERSION (dvxTypes.h) is one number for the binary interface between DVX and every module. Each module records the number it was built with, and DVX refuses a module whose number differs, so a module built against another release's headers cannot call the wrong functions.

  Module      Records it with                                    If it differs
  ------      ---------------                                    -------------
  Program     SHELL_APP_DESCRIPTOR(...)                          "This program was built for a different version of DVX and cannot run."
  Widget      DVX_ABI_STAMP(name); WGT_API_HEADER in its API     DVX stops at start-up naming the module
  Library     DVX_ABI_STAMP(name)                                DVX stops at start-up naming the module

The DVX side of these rules (what bumps the number) is under Binary Interface Version in the API reference.

Packaging and Installing

make package lays out pkg/ exactly as the files go into the DVX directory and makes NAME.dpk from it, the add-on package anyone installs with DVX's Software program; make install DVX_DIR=... copies pkg/ into a DVX directory. VENDOR (default local) names the vendor directory; use a short name of your own (8 characters at most: a-z, 0-9, and _, since the package name is VENDOR.NAME in lower case).

  Kind       Directory                     Files
  ----       ---------                     -----
  Program    APPS/<VENDOR>/<NAME>/         NAME.app, NAME.hlp, DATA/ with the DATA files (if any)
  Widget     WIDGETS/<VENDOR>/<NAME>/      NAME.wgt, NAME.DHS, NAME.BHS, NAME.dep (if any)
  Library    LIBS/<VENDOR>/<NAME>/         NAME.lib, NAME.DHS, NAME.dep (if any)
  Any        SYSTEM/                       A program for the DOS prompt that goes with it (not in SYSTEM/KEYS or SYSTEM/PACKAGES)

The Add-on Package

The package's details come from sdk.mk settings, on the make command line or in the project Makefile:

  Setting         Meaning
  -------         -------
  PKG_TITLE       The name Software shows (default NAME)
  PKG_VERSION     major.minor.patch (default 1.0.0); raise it for every version you hand out, so Software installs it over the old one
  PKG_SUMMARY     A sentence about it
  PKG_PUBLISHER   Who publishes it; also PKG_AUTHOR, PKG_LICENCE, and PKG_HOMEPAGE
  PKG_NEEDS       The packages it needs, id=oldest-version each: PKG_NEEDS="acme.cards=1.0 acme.dice=2.1"
  PKG_KEY         The secret key file to sign it with

Programs, libraries, and widgets are marked with the binary interface version of the SDK that built them (see Binary Interface Version); Software will not install them on a DVX of another version. Sign packages so the people who install them can tell they are yours:

bin/dvxpkg keygen "Acme Games" acme              # once: acme.SEC and acme.KEY
make package PKG_KEY=acme.SEC PKG_VERSION=1.1.0
bin/dvxpkg info NAME.dpk                         # what the package says

Give acme.KEY to the people who install your packages (Software, File > Trust a Publisher's Key) and keep acme.SEC private and backed up: anyone who has it can sign as you, and an update must be signed with the key that signed the version installed. bin/dvxpkg also signs and checks packages and lists their contents; on DVX, SYSTEM/DVXPKG.EXE does the same, and installs and removes packages. The package library and its file format are described under Package Library.

DVX's Own Packages and Optional Parts

DVX is packages too: kpunch.dvx (the loader, SYSTEM, the shell, the core libraries and widgets, and the everyday programs), kpunch.basefont (the base fonts), and the optional parts Setup's "What to install" page offers. Each part is a bundle (kind bundle: a package with no files of its own that needs its members) and each member is one module in its own package: kpunch.basic (dvxbasic, basdev), kpunch.data (dvxsql, datactrl, dbgrid), kpunch.net (dvxnet, http, pkgnet), kpunch.comm (terminal, conn, xfer; it needs kpunch.net), kpunch.media (player, recorder), kpunch.devtools (iconed, resedit, helpedit, pkgbuild, widshow), and kpunch.demos (dvxdemo, basdemo). src/packages.ini lists them. The build (src/installer/mkPackages.py) works out what each member needs from the modules its code imports, the libraries its BASIC program declares, and the manifest's uses (a module reached by name), and stops when DVX itself, or a part, would need a part it does not name. A member's files outside its folder are its paths: the DOS tools in SYSTEM go with the part they serve (BASCOMP.EXE with kpunch.basdev, DVXRES.EXE with kpunch.resedit, and DVXHLPC.EXE with kpunch.helpedit, the Help Editor); DVXPKG.EXE stays in DVX itself, to install and remove packages at the DOS prompt.

A module of yours that cannot work without a part's library names the library's package in PKG_NEEDS (PKG_NEEDS="kpunch.dvxnet=1.0.0"): Software then installs it only where that part is. One that can do without it does not import it: it asks dvxLibraryLoaded (BASIC: SysLibraryLoaded, system.bas) and reaches the library by name only when it is there, as Software does with pkgnet. BASIC's SQL functions do nothing without the Databases part (SQLOpen returns 0).

Publishing on the Package Server

Software's Available and Updates pages list the packages on the DVX package server. To publish one there, make an account on the server (POST /v1/account/register, then the link in the mail), give its email and password in Software's File > Package Server, and choose File > Publish a Package; Software's Published page shows each version's review. From the build machine, sign in (POST /v1/account/login gives a session token) and upload the package with it:

curl -H "Authorization: Bearer TOKEN" -H "Content-Type: application/octet-stream" \
     --data-binary @NAME.dpk http://SERVER/v1/upload

The server checks the package as DVX does before installing it and holds it for review; GET /v1/mine shows its state, and the note when it is turned down. Once the server's operator approves it, it is signed with DVX's repository key and listed, and every DVX that has an older version is offered the update. An id belongs to the account that first uploaded it, so later versions must come from the same account; ids of the vendor kpunch are DVX's own. The server's API and its rules are in server/README.md in the DVX source tree.

Making Fonts

DVX never draws an outline: every size it draws or prints is made beforehand by mkfont, from a TrueType, OpenType, Type 1, BDF, or PCF font, into a .DFN file. One file holds one family in one style (regular, bold, italic, or bold italic) at every size it comes in: the sizes for the screen, their smoothed copies, and the sizes for printing, all made from the same source in the same run, so the screen and the paper never show two versions of a font. When DVX starts it reads only each file's header and size list; a size's glyphs load when something draws or prints at that size, so sizes cost disk space, not memory.

Three Kinds of Size

  Kind       Manifest key   What it is for
  ----       ------------   --------------
  Screen     sizes          The pixel sizes text is drawn at (8 to 24 for text faces); "native" for a bitmap face, which has its own
  Smoothed   smooth         Screen sizes also made anti-aliased (2 bits per pixel), drawn when text is smoothed (screens of more than 256 colors); about twice the room of the 1-bit size, and best from 12 pixels
  Print      print          Sizes made 1-bit for printing (25 and up); none of them a screen size, and never smoothed

A size is in pixels, which on paper are printer dots: a size in points is points x dpi / 72 dots, so 12 points is 50 dots at 300 dpi and 30 at 180. Printing picks the face's smallest size at least as large as the text in dots and scales it down a little; without one it scales the largest up, smoothed but softer. PostScript printers draw with their own faces. The base set's print sizes cover the usual point sizes at both resolutions:

  Pixels   Points at 300 dpi   Points at 180 dpi
  ------   -----------------   -----------------
  25       6                   10
  29       7                   11.6
  33       8                   13.2
  38       9                   15.2
  42       10                  16.8
  50       12                  20
  58       14                  23.2
  67       16                  26.8
  75       18                  30
  83       20                  33.2
  100      24                  40

A file holds at most 64 strikes: its screen sizes, smoothed sizes, and print sizes together (DejaVu Sans has 12, 8, and 11, so 31). A size is at most 127 pixels. Every size also shows in the Font dialog and can be drawn on the screen, so a program can use the large ones for headings.

The Manifest

mkfont works from a manifest, an INI file: [fonts] lists the files to make (8.3 base name = family name), [licences] names each licence and the text files it is made of, and each font has a section of its own. src/fonts/fonts.ini is DVX's; src/tools/mkfont.c's header documents every key.

[fonts]
ACMESANS = Acme Sans
ACMESANB = Acme Sans

[licences]
ACME     = acme/OFL.txt

[ACMESANS]
style    = regular
licence  = ACME
source   = acme/AcmeSans-Regular.ttf
sizes    = 8 9 10 11 12 13 14 15 16 18 20 24
smooth   = 12 13 14 15 16 18 20 24
print    = 25 29 33 38 42 50 58 67 75 83 100
hinting  = native

[ACMESANB]
style    = bold
licence  = ACME
source   = acme/AcmeSans-Bold.ttf
sizes    = 8 9 10 11 12 13 14 15 16 18 20 24
smooth   = 12 13 14 15 16 18 20 24
print    = 25 29 33 38 42 50 58 67 75 83 100
hinting  = native
  Key        Meaning
  ---        -------
  style      regular, bold, italic, or bolditalic
  licence    A key of [licences]: the text every file carries
  source     The font file, under the source folder; source1, source2 ... more files for other sizes, each with its own sizes1, smooth1 ...
  sizes      Screen sizes, or native
  smooth     Sizes (or all) also made smoothed
  print      Sizes made for printing
  hinting    native (the font's own), auto (FreeType's), or none; italics often look better with auto
  graycut    Sizes whose 1-bit glyphs are cut from the smoothed rendering, for a face whose 1-bit hinting draws it too light
  boxdraw    true: DVX draws the box, block, and shade characters itself
  chars      Only these characters (hex codes and ranges), the rest marked missing
  weight     The weight of a variable font to make
  encoding   cp437 for a font that numbers its glyphs in code page 437
  set        DVX's own fonts only: base or more (where the file ships)

Names and Licences

Building and Checking

In the SDK, bin/mkfont MANIFEST SOURCEDIR OUTDIR makes every font the manifest lists, and stops with a message naming the font for any mistake: a bad size, a size listed twice, more than 64 strikes, a missing licence text. It prints a line per font with the characters the font lacks (DVX fills those in from DejaVu Sans or the system font when it draws). bin/mkfont -p FONT.DFN SHEET.BMP draws every size of a font into a picture, with the characters it lacks marked in red: look at it before shipping a font.

In the DVX source tree, src/fonts/fonts.ini lists every font DVX ships, and the build makes them into bin/shared/fonts/kpunch (DVX's vendor folder) from sources src/fonts/installFonts.sh downloads (pinned versions). Each font names its set: base, the DVX Fonts package (kpunch.basefont, src/fonts/PACKAGE.INI), which Setup installs with DVX unless told to install no fonts and which the package server can update; or more, the More Fonts add-on. mkfont stops when a font names no set, and when a font of the base set has no print sizes, so neither a font nor its print sizes can be left out.

Where Fonts Go

Checklist

libtasks -- Cooperative Task Switching

Credit-based cooperative (non-preemptive) multitasking library for DOS protected mode (DJGPP/DPMI). Each task receives (priority + 1) credits per scheduling round. Tasks run round-robin, consuming one credit per turn. When all credits are exhausted, every ready task is refilled. Higher-priority tasks run proportionally more often but never starve lower ones.

Header: libtasks/taskSwch.h

Loaded as: bin/libs/libtasks.lib

Why Cooperative

DOS is single-threaded with no kernel scheduler. DPMI provides no preemption primitives. The DVX GUI event model is inherently single-threaded: one compositor, one input queue, one window stack. Cooperative switching lets each task yield at safe points, avoiding synchronization primitives entirely.

Scheduling Semantics

Task storage is a stb_ds dynamic array. Terminated slots are recycled by tsCreate so the array does not grow unboundedly. The scheduler performs a linear scan for the next Ready task whose credit count is positive; when no task has credits left, every Ready task is refilled with (priority + 1) credits and the scan restarts.

  Priority            Credits per round  Approximate share
  --------            -----------------  -----------------
  TS_PRIORITY_LOW     1                  ~4% (1 of 25 possible credits)
  TS_PRIORITY_NORMAL  6                  ~22%
  TS_PRIORITY_HIGH    11                 ~41%

Actual share depends on the mix of priorities currently running. What the algorithm guarantees is that no ready task ever goes unscheduled -- even a priority-0 task always earns at least one turn per round.

Stack Allocation

Task 0 uses the caller's stack (no separate allocation). Every other task gets a heap-allocated stack of the size passed to tsCreate (or TS_DEFAULT_STACK_SIZE if 0 is passed; sizes below TS_MIN_STACK_SIZE are raised to it). Stacks are freed at task termination: at once by tsKill, or on the next scheduler entry after tsExit.

Error Codes

  Constant         Value   Description
  --------         -----   -----------
  TS_OK            0       Success
  TS_ERR_INIT      -1      Task system not initialized
  TS_ERR_PARAM     -2      Invalid parameter (bad task ID, priority, etc.)
  TS_ERR_NOMEM     -4      Memory allocation failed
  TS_ERR_STATE     -5      Invalid state transition

Constants

  Constant                  Value   Description
  --------                  -----   -----------
  TS_DEFAULT_STACK_SIZE     32768   Default stack size per task (32 KB)
  TS_MIN_STACK_SIZE         4096    Smallest stack tsCreate allocates (4 KB)
  TS_NAME_MAX               32      Maximum task name length including NUL
  TS_PRIORITY_LOW           0       Low priority (1 credit per round)
  TS_PRIORITY_NORMAL        5       Normal priority (6 credits per round)
  TS_PRIORITY_HIGH          10      High priority (11 credits per round)

Types

TaskStateE

Task scheduling state. Only Ready tasks participate in scheduling.

  Value                  Description
  -----                  -----------
  TaskStateReady         Eligible for scheduling
  TaskStateRunning       Currently executing (cosmetic; marks active task)
  TaskStatePaused        Skipped by scheduler until explicitly resumed
  TaskStateTerminated    Slot available for reuse

TaskEntryT

typedef void (*TaskEntryT)(void *arg);

Task entry point function signature. The void* argument lets the caller pass arbitrary context (e.g. a shell app descriptor).

tsInit

int32_t tsInit(void);

Initialize the task system. The calling context becomes task 0 (the main task). Task 0 is special: it cannot be killed or paused, and tsRecoverToMain() always returns control here. No separate stack is allocated for task 0; it uses the process stack directly.

Returns: TS_OK on success, TS_ERR_STATE if already initialized.

tsShutdown

void tsShutdown(void);

Shut down the task system and free all task stacks and internal storage. Safe to call even if tsInit() was never called.

tsCreate

int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);

Create a new task. Terminated task slots are recycled to avoid unbounded array growth.

  Parameter    Description
  ---------    -----------
  name         Task name (truncated to TS_NAME_MAX - 1 characters)
  entry        Task entry point function
  arg          Opaque argument passed to entry
  stackSize    Stack size in bytes (pass 0 for TS_DEFAULT_STACK_SIZE; raised to TS_MIN_STACK_SIZE if smaller)
  priority     Scheduling priority (TS_PRIORITY_LOW..TS_PRIORITY_HIGH, i.e. 0..10)

Returns: Task ID (>= 0) on success, or a negative error code (TS_ERR_INIT if not initialized, TS_ERR_PARAM if entry is NULL or priority is out of range, TS_ERR_NOMEM if stack allocation failed).

tsYield

void tsYield(void);

Yield CPU to the next eligible ready task using credit-based round-robin. This is the sole mechanism for task switching. Every task must call this (or a GUI function that calls it) periodically, or it will monopolize the CPU.

tsPause

int32_t tsPause(uint32_t taskId);

Pause a task, removing it from scheduling. Cannot pause the main task (ID 0). If a task pauses itself, an implicit yield occurs.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to pause

Returns: TS_OK on success, TS_ERR_INIT if not initialized, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE if the task is the main task or not in a pausable state (not Running or Ready).

tsResume

int32_t tsResume(uint32_t taskId);

Resume a paused task. Credits are refilled so the task gets a fair share of CPU time immediately rather than waiting for the next scheduling round.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to resume

Returns: TS_OK on success, TS_ERR_INIT if not initialized, TS_ERR_PARAM on invalid ID, TS_ERR_STATE if the task is not paused.

tsSetPriority

int32_t tsSetPriority(uint32_t taskId, int32_t priority);

Set a task's scheduling priority. Also refills credits so the change takes effect immediately.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to modify
  priority    New priority level (TS_PRIORITY_LOW..TS_PRIORITY_HIGH, i.e. 0..10)

Returns: TS_OK on success, TS_ERR_INIT if not initialized, TS_ERR_PARAM on invalid ID or out-of-range priority, or TS_ERR_STATE if the task is terminated.

tsGetPriority

int32_t tsGetPriority(uint32_t taskId);

Get a task's current scheduling priority.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to query

Returns: Priority value (0..10) on success, TS_ERR_INIT if not initialized, TS_ERR_PARAM on invalid ID.

tsGetState

TaskStateE tsGetState(uint32_t taskId);

Get a task's current scheduling state.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to query

Returns: TaskStateE value. Returns TaskStateTerminated for invalid IDs.

tsCurrentId

uint32_t tsCurrentId(void);

Get the ID of the currently executing task. Always valid while the task system is initialized.

Returns: Current task ID.

tsGetName

const char *tsGetName(uint32_t taskId);

Get a task's name string.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to query

Returns: Pointer to the task's name, or NULL on invalid ID. The pointer remains valid until the task slot is reused.

tsExit

void tsExit(void);

Terminate the calling task. Must not be called from the main task (ID 0). The slot is marked for reuse immediately; the stack, which is still in use until the switch, is freed on the next entry into the scheduler. This function never returns; it performs an internal context switch to the next ready task.

tsKill

int32_t tsKill(uint32_t taskId);

Forcibly terminate another task. Cannot kill the main task (ID 0) or the currently running task (use tsExit() for self-termination). Safe because cooperative scheduling guarantees the target is suspended at a yield point.

  Parameter   Description
  ---------   -----------
  taskId      ID of the task to terminate

Returns: TS_OK on success, TS_ERR_INIT if not initialized, TS_ERR_PARAM on invalid ID, or TS_ERR_STATE on illegal target (main task, self, or already terminated).

tsRecoverToMain

void tsRecoverToMain(void);

Crash recovery: force the scheduler back to the 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 by this call; call tsKill() afterward to free its resources. This exists because longjmp unwinds the crashed task's stack but the scheduler state still points to it.

tsActiveCount

uint32_t tsActiveCount(void);

Get the number of non-terminated tasks currently in the system.

Returns: Count of tasks in Ready, Running, or Paused state.

List Helper Library

Shared helper routines for dropdown and list-based widget DXEs (ListBox, Dropdown, ComboBox, ListView). Provides the owned-string item list, the single/multi-select model, drag-reorder helpers, the dropdown popup (state, geometry, painting, hit testing, and keyboard handling), dropdown arrow rendering, text measurement in any font (fixed-pitch or proportional), keyboard navigation, type-ahead search, popup scrollbar hit testing (with the same thumb geometry the window manager paints), and the icon view rules of Program Manager's view, which BASIC icon views share through listhelp.bas.

Header: listhelp/listHelp.h

Loaded as: bin/libs/listhelp.lib

Constants

  Constant               Value               Description
  --------               -----               -----------
  DROPDOWN_BTN_WIDTH     16                  Width of the dropdown arrow button in pixels.
  DROPDOWN_MAX_VISIBLE   8                   Maximum number of items visible in a popup list before scrolling.
  DROPDOWN_MIN_CHARS     8                   Minimum text-area width of a dropdown/combobox, in characters.
  DROPDOWN_BEVEL_W       2                   Bevel width of the dropdown/combobox text area and drop button.
  DROPDOWN_ARROW_ROWS    4                   Rows in the drop-arrow triangle.
  POPUP_BORDER           2                   Popup list border width; items start this far inside the popup.
  POPUP_SCROLLBAR_W      SCROLLBAR_WIDTH     Popup vertical scrollbar width (mirrors the core window-manager scrollbar width).
  LIST_REORDER_LINE_H    2                   Thickness of the drag-reorder insertion line.

ListItemsT (owned-string item list)

Item list shared by ListBox, Dropdown, and ComboBox. items points either at a caller-owned array (isOwned false) or at owned, an stb_ds array of strdup'd strings (isOwned true). maxW caches the widest item's width in pixels of widthFont (NULL until it is first measured); listItemsMaxWidth returns it, measuring every item again when asked for another font, and the mutators keep it current incrementally. NULL entries in a caller's array read as empty strings.

typedef struct {
    const char        **items;
    char              **owned;
    const BitmapFontT  *widthFont;
    int32_t             count;
    int32_t             maxW;
    bool                isOwned;
} ListItemsT;

bool        listItemsAdd(ListItemsT *li, const BitmapFontT *font, const char *text);
void        listItemsAdopt(ListItemsT *li);
bool        listItemsClear(ListItemsT *li);
void        listItemsFree(ListItemsT *li);
const char *listItemsGet(const ListItemsT *li, int32_t idx);
int32_t     listItemsMaxWidth(ListItemsT *li, const BitmapFontT *font);
bool        listItemsRemove(ListItemsT *li, const BitmapFontT *font, int32_t idx);
bool        listItemsSetExternal(ListItemsT *li, const BitmapFontT *font, const char **items, int32_t count);

listItemsAdd and listItemsRemove first adopt (deep-copy) a caller's array, so they extend or shorten it without modifying it. listItemsSetExternal keeps the previously owned strings until the next adopt, clear, or free, because the new array may point into them, and never reads the caller's previous array (it may be freed already). The mutators measure in font, the widget's own (wgtFont), and return true when the widest item's width changed, i.e. when the widget's minimum width changed and it needs a relayout rather than just a repaint. listItemsGet returns "" for an index out of range.

widgetAdoptOwnedStrings(owned, src, count) builds the deep copy that adoption uses (complete before the old strings are freed, so src may alias them) and widgetFreeOwnedStrings(owned) frees every string of an owned array and empties it. ListView uses both for its row-major cell copy.

ListSelT (selection model)

Selection state shared by ListBox and ListView. cursor is the current (keyboard) item and anchor the start of Shift ranges; bits holds one byte per item in multi-select mode. Every index is a data index; functions that take a display index also take an optional display-to-data order map (NULL = identity) so a sorted view selects what the user sees as a contiguous range.

typedef struct {
    uint8_t *bits;
    int32_t  cursor;
    int32_t  anchor;
    bool     multi;
} ListSelT;
  Function                                          Description
  --------                                          -----------
  listSelInit(s) / listSelFree(s)                   Initialize (no cursor, no anchor) / free the bits.
  listSelReset(s, count)                            Rebuild the bits from scratch: all clear except the cursor item.
  listSelInsert(s, newCount, idx)                   An item was inserted at idx: existing selections are kept, cursor/anchor shift.
  listSelRemove(s, oldCount, idx)                   The item at idx was removed: bits close the gap, cursor/anchor shift; a removed cursor/anchor becomes -1.
  listSelReorder(s, from, dest)                     Drag-reorder moved an item: its bit moves with it and it becomes the cursor.
  listSelClick(s, count, order, disp, shift, ctrl)  Mouse selection: plain, Ctrl (toggle), or Shift (range from the anchor).
  listSelMove(s, count, order, disp, shift)         Keyboard cursor move; Shift selects the range from the anchor.
  listSelKey(s, count, key, mod)                    Multi-select Ctrl+A (ListSelKeyAllE) and Space (ListSelKeyToggleE); ListSelKeyNoneE otherwise.
  listSelSetCursor(s, count, idx)                   Programmatic selection: idx becomes the only selected item.
  listSelSetAll / listSelSetItem                    Set every bit / one bit; return false when there are no selection bits (not multi-select, or an empty list) or idx is out of range.
  listSelIsSelected(s, count, idx)                  Bit state in multi-select mode, idx == cursor otherwise.
  listDisplayRowOf(order, count, dataIdx)           Display row of a data index (-1 if none).
  listIndexAfterRemove(idx, removed)                Where an index ends up after a removal (-1 if it was removed).
  listIndexAfterMove(idx, from, dest)               Where an index ends up after an item moved.

Drag-Reorder Helpers

void    listReorderCancel(WidgetT *w, int32_t *pDragIdx, int32_t *pDropIdx);
int32_t listReorderDest(int32_t from, int32_t to, int32_t count);
void    listReorderMove(void *base, size_t elemSize, int32_t from, int32_t dest);
void    listReorderTrack(int32_t relY, int32_t rowH, int32_t visibleRows, int32_t count, int32_t *pScrollPos, int32_t *pDropIdx);
void    listReorderPaint(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t top, int32_t w, int32_t h, int32_t rowH, int32_t scrollPos, int32_t dropIdx, uint32_t color);

listReorderCancel abandons an item drag when the list changes under it (an item is removed or the items are replaced): it resets both indices and, when w is dragging an item, ends the drag so the rest of the mouse motion is ignored. listReorderDest maps a drag from item from to insertion point to (0..count) to the item's final index, or -1 when the drop is a no-op. listReorderMove moves one element of any size in place (a ListView row is colCount pointers). listReorderTrack updates the drop index from the pointer position and auto-scrolls one row when the pointer is on the first or last visible row. listReorderPaint draws the insertion line.

ListPopupT (dropdown popup)

Popup state shared by Dropdown and ComboBox: the item list plus the selected item, the hovered item, the popup scroll position, and whether it is open.

typedef struct {
    ListItemsT items;
    int32_t    selectedIdx;
    int32_t    hoverIdx;
    int32_t    scrollPos;
    bool       open;
} ListPopupT;
  Function                                          Description
  --------                                          -----------
  listPopupInit(p)                                  No selection, no hover, closed.
  listPopupAddItem / listPopupRemoveItem            Item list edits; the selection and hover stay on the same item (-1 when it is removed).
  listPopupClearItems / listPopupSetItems           Replace the items; an out-of-range selection becomes -1.
  listPopupOpen(w, p)                               Open with the selection hovered and scrolled into view (keyboard, mouse, and accelerator all use it).
  listPopupClose(w, p)                              WGT_METHOD_CLOSE_POPUP body.
  listPopupCommit(p)                                Close and make the hovered item the selection; false when nothing valid is hovered.
  listPopupNavKey(p, key)                           Up/Down move the hover item, a printable key jumps to the next matching item.
  listPopupOnMouse(w, p, vx, vy)                    Click while open: ListPopupHitScrollE (scrollbar), ListPopupHitItemE (item selected, closed), or ListPopupHitNoneE (closed).
  listPopupItemAt(p, font, popY, vy)                Item under vy, or -1 on the border or past the last item.
  listPopupPaint(w, p, ...)                         Overlay paint; uses the window content height, the same source as the hit test.
  listPopupDrawFrame(d, ops, colors, w, bg, down)   Sunken text area plus the drop button and arrow.
  listPopupCalcMinSize(w, font, p)                  Minimum size from the widest item in font.
  listPopupEnsureVisible(p)                         Scroll the hover item into view.
  listPopupVisibleCount(itemCount)                  Rows the popup shows (1..DROPDOWN_MAX_VISIBLE).

widgetDrawDropdownArrow

Draw the triangular dropdown arrow glyph centered at a given position.

void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
  Parameter   Description
  ---------   -----------
  d           Display context.
  ops         Blit operations vtable for the active pixel depth.
  centerX     Horizontal center of the arrow in backbuffer coordinates.
  centerY     Vertical center of the arrow in backbuffer coordinates.
  color       Pre-packed pixel color for the arrow.

widgetMaxItemWidth

Scan an array of strings and return the width in pixels of the widest item in a font.

int32_t widgetMaxItemWidth(const BitmapFontT *font, const char **items, int32_t count);
  Parameter   Description
  ---------   -----------
  font        Font the items are drawn in (fixed-pitch or proportional).
  items       Array of null-terminated string pointers (NULL entries count as empty).
  count       Number of items in the array.

Returns the width of the widest item, or 0 if count is zero.

listTextFit

How many leading characters of text fit whole in maxW pixels of font: the part of an item a list draws when the item is cut to its column. Stops at the end of the text; 0 when maxW is too narrow for the first character.

int32_t listTextFit(const BitmapFontT *font, const char *text, int32_t maxW);

widgetNavigateIndex

Compute a new selected index from a navigation key press. Handles Up, Down, Home, End, Page Up, and Page Down.

int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
  Parameter   Description
  ---------   -----------
  key         Keyboard scancode (Up, Down, Home, End, PgUp, PgDn).
  current     Currently selected index.
  count       Total number of items in the list.
  pageSize    Number of visible items (used for PgUp/PgDn step size).

Returns the new index, clamped to [0, count-1]. Returns -1 for unrecognized keys so callers can detect whether the key was consumed.

widgetGridNavigateIndex

The same keys in a grid of count items laid out row by row, columns items per row (icon views): Left and Right step through the items in order, Up, Down, Page Up, and Page Down move by rows (pageRows rows a page) the way widgetNavigateIndex moves through a list -- it is called on the row numbers -- keeping the column, or landing on the last item when the row below is shorter; Home and End go to the first and last item. With nothing selected (current below 0) every one of these keys goes to the first item. With one column it gives what widgetNavigateIndex gives.

int32_t widgetGridNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t columns, int32_t pageRows);

Returns the new index, or -1 for any other key.

widgetDropdownPopupRect

Calculate the screen rectangle for a dropdown popup list, positioning it below the owning widget and clamping to screen bounds.

void widgetDropdownPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t itemCount, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
  Parameter   Description
  ---------   -----------
  w           The widget that owns the popup (Dropdown, ComboBox, etc.).
  font        Bitmap font used for item text (determines row height).
  contentH    Height of the widget's content area in pixels.
  itemCount   Total number of items in the list.
  popX        Output: popup X position (screen coordinates).
  popY        Output: popup Y position (screen coordinates).
  popW        Output: popup width in pixels.
  popH        Output: popup height in pixels.

The popup is sized to show up to DROPDOWN_MAX_VISIBLE items.

widgetPaintPopupList

Render a popup item list with highlight, scrolling, and beveled border. When itemCount exceeds DROPDOWN_MAX_VISIBLE, draws a vertical scrollbar on the right edge sized according to scrollPos.

void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
  Parameter   Description
  ---------   -----------
  d           Display context.
  ops         Blit operations vtable.
  font        Bitmap font for rendering item text.
  colors      Color scheme for background, text, and highlight colors.
  popX        Popup X position (from widgetDropdownPopupRect).
  popY        Popup Y position.
  popW        Popup width.
  popH        Popup height.
  items       Array of null-terminated item strings.
  itemCount   Total number of items.
  hoverIdx    Index of the highlighted (hovered/selected) item, or -1 for none.
  scrollPos   Index of the first visible item (scroll offset).

widgetTypeAheadMatch

True when text starts with ch (case-insensitive). NULL text never matches. Used by widgetTypeAheadSearch and by ListView's first-column type-ahead.

bool widgetTypeAheadMatch(char ch, const char *text);

widgetTypeAheadSearch

Case-insensitive type-ahead search. Scans items forward from currentIdx + 1, wrapping around, for the next item whose first character matches ch. Used by list and dropdown widgets to jump to an item based on a single keystroke.

int32_t widgetTypeAheadSearch(char ch, const char **items, int32_t itemCount, int32_t currentIdx);
  Parameter     Description
  ---------     -----------
  ch            Character to match (case-insensitive).
  items         Array of null-terminated item strings.
  itemCount     Number of items in the array.
  currentIdx    Currently selected index (search starts at currentIdx + 1 and wraps).

Returns the index of the next matching item, or -1 if no match is found or itemCount is not positive.

widgetPopupScrollbarClick

Hit-test a popup scrollbar click. Tests whether the (x, y) coordinate falls on the popup's vertical scrollbar track; if so, updates scrollPos for up/down arrow clicks or page-up/page-down clicks in the trough. No-op on the thumb itself (callers handle drag separately).

bool widgetPopupScrollbarClick(int32_t x, int32_t y, int32_t popX, int32_t popY, int32_t popW, int32_t popH, int32_t itemCount, int32_t visibleItems, int32_t *scrollPos);
  Parameter       Description
  ---------       -----------
  x               Click X in the popup's coordinate space.
  y               Click Y in the popup's coordinate space.
  popX            Popup X origin.
  popY            Popup Y origin.
  popW            Popup width.
  popH            Popup height.
  itemCount       Total number of items.
  visibleItems    Number of visible rows in the popup.
  scrollPos       [in/out] Current scroll offset; updated in place when the click hits the scrollbar.

Returns true if the click landed on the scrollbar (even if scrollPos was not changed, for example clicking the thumb), false if the click was in the item list area. When itemCount is less than or equal to DROPDOWN_MAX_VISIBLE, always returns false (there is no scrollbar).

Icon Views

The rules of an icon view with large icons, small icons, or a list: Program Manager's view (progman/pmView.c) is built on them, and so are icon views written in BASIC (listhelp.bas ListIcon...), so both look and behave alike. They cover each view's cell and where an item's picture and name go in it, the grid inside a sunken frame with a scroll bar only when the rows do not fit (which narrows the grid), the two label lines under a large icon, the arrow keys among items in order or in cells of their own (gaps allowed), the scroll bar (a control's, as ListBox's: arrows, trough, thumb drag), when a press becomes a drag, the scrolling and the target cell while dragging, and where a dropped item lands. Positions are pixels from the view's top left (ListIconGridT) or the cell's (ListIconCellT).

  Constant                 Value  Description
  --------                 -----  -----------
  LIST_ICON_FRAME          2      Sunken frame around the view.
  LIST_ICON_PAD            2      Cell edge to its picture; inside of the frame to the empty text.
  LIST_ICON_LARGE          32     Large icon edge.
  LIST_ICON_SMALL          16     Small icon edge.
  LIST_ICON_LABEL_GAP      2      Large icon to its label.
  LIST_ICON_LABEL_LINES    2      Label lines under a large icon.
  LIST_ICON_TEXT_GAP       4      Small icon to its name.
  LIST_ICON_LARGE_CELL_W   80     Large icon cell width.
  LIST_ICON_SMALL_CHARS    14     Name beside a small icon, in characters.
  LIST_ICON_LIST_CHARS     24     The list's name column, in characters.
  LIST_ICON_COLUMN_GAP     1      The list's name column to its detail column, in characters.
  LIST_ICON_DRAG_SLOP      4      Pixels a press moves (either way) before it drags.
  LIST_ICON_WHEEL_ROWS     1      Rows a wheel notch scrolls.
  LIST_ICON_LINE_ROWS      1      Rows an arrow key or a scroll bar arrow moves.
  LIST_ICON_SB_W           WGT_SB_W  The scroll bar's width: a control's (ListBox's).
  LIST_ICON_NO_CELL        -1     No cell (outside the grid).
  LIST_ICON_BY_ORDER       -1     listIconGrid rowsTaken: the items fill the grid in order.
typedef enum { ListIconLargeE, ListIconSmallE, ListIconListE, ListIconViewCountE } ListIconViewE;
typedef enum { ListIconScrollNoneE, ListIconScrollMovedE, ListIconScrollThumbE } ListIconScrollE;

typedef struct {
    int32_t cellW;          // a list row spans the grid
    int32_t cellH;
    int32_t pictureX;       // the picture, pictureSize square
    int32_t pictureY;
    int32_t pictureSize;
    int32_t textX;          // the name (large icons: each line centred, textX 0)
    int32_t textY;
    int32_t nameChars;      // its length (a large icon's: per line)
    int32_t detailX;        // the list's detail column, 0 in the other views
} ListIconCellT;

typedef struct {
    ListIconCellT cell;
    int32_t       innerX;   // inside the frame, less the scroll bar
    int32_t       innerY;
    int32_t       innerW;
    int32_t       innerH;
    int32_t       cols;
    int32_t       rowsVisible;  // rows shown whole
    int32_t       totalRows;
    int32_t       maxScroll;    // the largest top row
    int32_t       emptyX;       // the empty view's text
    int32_t       emptyY;
    bool          needSb;       // the scroll bar stands right of innerW, innerH tall
} ListIconGridT;

void    listIconCellLayout(ListIconViewE view, int32_t charW, int32_t charH, int32_t innerW, ListIconCellT *out);
void    listIconGrid(ListIconViewE view, int32_t charW, int32_t charH, int32_t viewW, int32_t viewH, int32_t count, int32_t rowsTaken, ListIconGridT *out);
int32_t listIconGridMetric(const ListIconGridT *g, ListIconMetricE metric);
int32_t listIconCellAt(const ListIconGridT *g, int32_t x, int32_t y, int32_t scrollRow, int32_t *outCol, int32_t *outRow);
void    listIconLabelSplit(const char *name, int32_t maxChars, int32_t *len1, const char **line2, int32_t *len2);
int32_t listIconNavigate(int32_t key, int32_t current, const int32_t *cols, const int32_t *rows, int32_t count, int32_t gridCols, int32_t pageRows);
bool    listIconDragStarted(int32_t dx, int32_t dy);
int32_t listIconDragScroll(const ListIconGridT *g, int32_t y, int32_t scrollRow);
int32_t listIconDropCell(const ListIconGridT *g, int32_t x, int32_t y, int32_t scrollRow, int32_t *outCol, int32_t *outRow);
int32_t listIconDropDest(int32_t from, int32_t cell, int32_t count);
ListIconScrollE listIconScrollPress(const ListIconGridT *g, int32_t x, int32_t y, int32_t *pScrollRow, int32_t *outGrab);
int32_t listIconScrollDrag(const ListIconGridT *g, int32_t y, int32_t grab);
  Function               Description
  --------               -----------
  listIconCellLayout     The cell of a view whose grid is innerW wide. Large icons: LIST_ICON_LARGE_CELL_W wide, the picture centred at the top, two label lines under it. Small icons and the list: a row as tall as the font or the small picture (plus padding), the picture then the name; the list's row spans the grid and adds a detail column.
  listIconGrid           The grid of a view viewW x viewH (frame included) holding count items: laid out without a scroll bar first, with one LIST_ICON_SB_W wide (narrowing the grid) only when the rows do not fit. rowsTaken is LIST_ICON_BY_ORDER for items in order (count / columns rows, rounded up) or the rows items placed in cells of their own take.
  listIconGridMetric     One value of a grid by ListIconMetricE (ListIconCellWE ... ListIconWheelRowsE; the scroll bar as 1 / 0; 0 for an unknown metric). The BASIC binding reads the grid through it.
  listIconCellAt         The cell number (row * cols + col) under x, y of a view scrolled to scrollRow, with its column and row; LIST_ICON_NO_CELL outside the grid or right of the last column.
  listIconLabelSplit     A large icon's two label lines maxChars wide: the first ends at the last blank that fits (else it is cut at maxChars), the second starts after the blanks there and is cut to maxChars. *line2 points into name; *len2 is 0 when the name fits one line.
  listIconNavigate       The item an arrow key, Home, End, PgUp, or PgDn moves to when item i stands in cell (cols[i], rows[i]): Left / Right the nearest before / after in reading order, Up / Down the nearest row above / below with the closest column, PgUp / PgDn the same pageRows away, Home / End the first / last in reading order; with no current item the first. For items in order it gives what widgetGridNavigateIndex gives. -1 for another key or when no item is that way.
  listIconDragStarted    True when a press has moved LIST_ICON_DRAG_SLOP pixels either way.
  listIconDragScroll     The top row after a drag move to y: one row up above the grid, one down below it.
  listIconDropCell       The cell a dragged item would drop on with the pointer at x, y, held inside the grid's height (listIconCellAt).
  listIconScrollPress    A left press at x, y: ListIconScrollNoneE left of the scroll bar or without one; else widgetScrollbarApplyHit -- an arrow moves *pScrollRow LIST_ICON_LINE_ROWS, the trough a page (ListIconScrollMovedE), the thumb is grabbed (ListIconScrollThumbE, *outGrab how far below its top).
  listIconScrollDrag     The top row with the grabbed thumb dragged to y (widgetScrollbarThumbDragScroll).
  listIconDropDest       Items in order: the index an item dragged from `from` takes when dropped on cell number cell (that cell's, the last for a cell past the end); -1 when that changes nothing or from / cell is out of range.

charW and charH are the caller's character metrics (a font's charWidth and charHeight, or numbers from BASIC, which has no font handle here): rows are a line tall, and a name field or column N characters wide is N * charW. A large icon's label is cut by characters (nameChars, listIconLabelSplit): exact for a fixed-pitch font, an estimate for a proportional one.

Program Manager's pmView.c draws the scroll bar with widgetDrawScrollbarV; a BASIC icon view draws the same bar with DrawScrollBar's control argument True.

BASIC

src/include/basic/listhelp.bas gives BASIC programs -- above all UserControls that draw a list or an icon view themselves -- the rules of this library (listHelpBas.c), so their lists behave like ListBox and ListView. A selection handle (1, 2, ...; 0 = none) holds a ListSelT and the item count it covers; display order is data order (no order map). Each handle records the program that made it; the shell's app-cleanup hook frees what a program leaves open.

  BASIC                                                  C it calls
  -----                                                  ----------
  ListSelCreate(multi, count), ListSelFree, ListSelReset(h, count), ListSelCount(h)  listSelInit + listSelReset; listSelFree; listSelReset then listSelSetCursor (a cursor past the end becomes -1).
  ListSelClick h, index, shift                           listSelClick; shift is the event's Shift (vbShiftMask = range, vbCtrlMask = toggle).
  ListSelKey(h, keyCode, shift)                          listSelKey: LISTSEL_KEY_NONE, LISTSEL_KEY_ALL (Ctrl+A), LISTSEL_KEY_TOGGLE (Space).
  ListSelNavigate(h, keyCode, shift, columns, pageRows)  widgetGridNavigateIndex from the cursor, then listSelMove; the new cursor, or -1 when the key does not move it.
  ListSelMove h, index, shift                            listSelMove.
  ListSelCursor(h), ListSelSetCursor h, index            ListSelT.cursor; listSelSetCursor.
  ListSelIsSelected(h, index), ListSelSelectedCount(h)   listSelIsSelected; how many are.
  ListSelSetItem h, index, selected; ListSelSetAll h, selected  listSelSetItem; listSelSetAll.
  ListSelInsert h, index; ListSelRemove h, index         listSelInsert / listSelRemove (the handle's count follows).
  ListSelReorder h, fromIndex, dest                      listSelReorder.
  ListReorderDest(fromIndex, toSlot, count)              listReorderDest.
  ListNavigateIndex(keyCode, current, count, pageSize)   widgetNavigateIndex.
  ListGridNavigate(keyCode, current, count, columns, pageRows)  widgetGridNavigateIndex.
  ListTypeAhead(ch, items, current)                      widgetTypeAheadSearch over items separated by CHR$(10).
  ListTypeAheadMatch(ch, text)                           widgetTypeAheadMatch.
  ListIconMetric(mode, metric, cw, ch, w, h, count, rowsTaken)  listIconGrid, then listIconGridMetric; LISTICON_SCROLLBAR as True / False.
  ListIconCellAt(mode, cw, ch, w, h, count, rowsTaken, topRow, x, y)  listIconGrid, then listIconCellAt.
  ListIconDragScroll(mode, cw, ch, w, h, count, rowsTaken, topRow, y)  listIconGrid, then listIconDragScroll.
  ListIconDropCell(mode, cw, ch, w, h, count, rowsTaken, topRow, x, y)  listIconGrid, then listIconDropCell.
  ListIconDropDest(fromIndex, cell, count)               listIconDropDest.
  ListIconDragStarted(dx, dy)                            listIconDragStarted.
  ListIconLabel$(name, maxChars, lineNo)                 listIconLabelSplit: line 1 or 2 ("" for any other line number or a line the name does not need).
  ListIconNavigate(keyCode, current, cells, columns, pageRows)  listIconNavigate over cells given as text: each item's column and row, every number separated by a comma.
  ListIconScrollPress(mode, cw, ch, w, h, count, rowsTaken, topRow, x, y)  listIconScrollPress: the new top row, -1 when not on the scroll bar.
  ListIconScrollGrab(mode, cw, ch, w, h, count, rowsTaken, topRow, x, y)  listIconScrollPress: the grab offset when the thumb is hit, else -1.
  ListIconScrollDrag(mode, cw, ch, w, h, count, rowsTaken, grab, y)  listIconScrollDrag.

An icon view's grid is figured from the view's size on each call (no handle). The LISTSEL_KEY_ and LISTICON_ CONSTs of listhelp.bas are compared with ListSelKeyE, ListIconViewE, LIST_ICON_BY_ORDER, and ListIconMetricE by a formrt test.

Music Library

Music for DVX programs on whatever sound hardware the computer has: tracker modules (MOD, S3M, XM, IT), Standard MIDI Files (type 0 and 1), WAV and VOC sound files, and live MIDI a program sends itself. Every music belongs to the program that loaded it and is stopped and freed when the program ends, however it ends; any number of programs may play music at the same time.

Header: music/music.h. Loaded as: bin/libs/kpunch/music/music.lib (needs audio.lib); the General MIDI instrument bank for FM is bin/libs/kpunch/music/genmidi.op2, and the General MIDI SoundFont for wavetable music is an add-on (SHARED\BANKS\KPUNCH\DVXGM.SF2).

Playing a File

MusicT *tune = NULL;
int32_t rc   = musicLoad("THEME.MID", &tune);    // or musicLoadMem(data, size, &tune)

if (rc != MUSIC_OK) {
    dvxLog("%s", musicErrorText(rc));
} else {
    musicSetEndFn(tune, onTuneEnd, NULL);
    musicPlay(tune, false);                      // true: loop until stopped
}

musicLoad and musicLoadMem tell the kind by the contents: a WAV or VOC sound file (MUSIC_KIND_SOUND) or a MIDI file (or a RIFF RMID file around one) by its header, anything else is offered to the module player. A WAV loaded by its path is read from the disk as it plays (only its header is read at the load), so a long recording needs no memory; a VOC, or a WAV given in memory, is decoded into memory (audioDecode). Errors: MUSIC_ERR_OPEN (unreadable file), MUSIC_ERR_FORMAT (not music DVX plays, or a type 2 MIDI file), MUSIC_ERR_UNSUPPORTED (a sound file that is compressed or not 8- or 16-bit; its text is audio.lib's), MUSIC_ERR_CORRUPT, MUSIC_ERR_NO_MEMORY, MUSIC_ERR_NO_DEVICE (no sound device could start), MUSIC_ERR_BANK (MIDI with no usable instrument bank and no MPU-401 port). Nothing plays until musicPlay(music, loop), which plays from the current position (the start after a load or a stop; a music that played to its end starts over). musicPause / musicResume, musicStop (back to the start), musicIsPlaying, musicSetVolume (0..MUSIC_VOLUME_MAX, per music; the master volume applies too - on the FM chip and the MPU-401 port as it was when the music started or its volume was last set), musicGetPosition / musicSetPosition (milliseconds), musicGetInfo (kind, where it plays, a description of the device, title, length, position, state; the title is a module's own, a MIDI file's sequence name (the first track's name, control characters as spaces), and none for a sound), and musicFree complete the set. musicSetEndFn(music, fn, userData) asks for a call when a music that does not loop plays to its end; it comes in the shell's idle time as the program that owns the music, never from an interrupt.

musicLoadMacro(macro, &music) makes a MIDI music from a music macro string (the language PLAY takes, below): its notes play on the General MIDI instruments its I commands name, the piano for notes before any I or after I0, and it is then played, paused, and freed like a MIDI file (MUSIC_ERR_MACRO for a mistake in the string). musicSetBank(music, bank) plays one MIDI music on another instrument bank than DVX.INI's choice: bank names it as midiBank and soundFont do ("" the standard OP2 bank, NULL DVX.INI's choices again). A SoundFont (.SF2 or .SBK) or an UltraSound's ULTRASND.INI is for wavetable music (the FM bank stays DVX.INI's), any other bank for the FM synthesizers. The bank is read at once, so a missing or damaged one is MUSIC_ERR_BANK there and then; it must come before the music's first musicPlay (MUSIC_ERR_ARG for a module, live MIDI, or a music that has played). Control Panel's Instruments Test is musicLoadMacro and musicSetBank.

Setting the position of a MIDI file sends the instruments and controllers set before that point (no notes). A module goes to the start of the pattern that holds the time; a sound goes to the frame.

Where Music Plays

A module plays on the wavetable synthesizer's own voices (a Gravis UltraSound's, an AWE32's or AWE64's) when there is one and no other music plays on it (one music at a time, MIDI or a module; a stopped one gives it up): its samples go into the chip's memory and libxmp's voices are played on the chip's, nothing mixed in software (MUSIC_OUT_WAVE; see Modules). Otherwise libxmp-lite renders it into a stream of the sound device (16-bit stereo at the device's rate, 400 ms of buffer, linear interpolation), so it plays on any device that plays sampled sound (MUSIC_OUT_STREAM): with DVX.INI [audio] modules = software (audioModulesChoices; the Control Panel's Sounds page), when the module has more channels than the chip has voices or a stereo sample, or when its samples do not fit in the chip's memory (the SoundFont kept there for MIDI gives way first).

A sound always plays on a stream (MUSIC_OUT_STREAM) at its own rate and channels, 16-bit, with the same 400 ms of buffer; the mixer converts it to the device's rate. A pause, a seek, or a stop of any music on a stream is heard at once: the stream's voice is held where it is (audioVoicePause) and, for a seek or a stop, what it holds is dropped (audioStreamFlush).

MIDI goes to the best synthesizer there is, decided at the first musicPlay (or musicMidiOpen), with DVX.INI [audio] midi:

  midi       MIDI plays on
  ----       -------------
  auto       a wavetable synthesizer (a Gravis UltraSound, a Sound Blaster AWE32 or AWE64) when there is one, no other music is using it, and its SoundFont loads; else the FM chip (OPL2 / OPL3) when there is one and no program has taken the whole chip; else the software synthesizer
  mpu401     the MPU-401 port (mpuPort, default 330h) when it was found and no other music is using it; else as auto
  fm         the FM chip when there is one and it is free; else the software synthesizer
  software   the software synthesizer
  softwave   the SoundFont software synthesizer (below); else the software synthesizer

On the FM chip and the MPU-401 port the song is played by the timer service (500 times a second), so it keeps time however busy the programs are. On the FM chip every note takes a voice from audio.lib's shared pool; when none is free the note plays on the software synthesizer instead, which then runs beside the chip (its stream is always open for that). The software synthesizer runs in the stream's fill function (the shell's idle time), 64 frames between event checks, 150 ms ahead of what is heard; a program that keeps the desktop busy every frame should call audioPump once a frame, as for any stream.

General MIDI on FM

Instruments come from an OP2 bank (the DMX "GENMIDI" format: 128 melodic instruments and 47 percussion instruments for notes 35 to 81, each one or two two-operator voices, with a note offset, a fixed note for percussion, and a detune for the second voice). DVX ships Freedoom's GENMIDI bank (see Licences below); DVX.INI [audio] midiBank (MUSIC_INI_BANK) names another OP2 file instead: a bank in SHARED\BANKS or the user's BANKS folder by its name (an add-on package's as VENDOR\NAME), or any OP2 file by its path from the DVX folder, for example a bank made with an OPL editor. DVX also ships DMXOPL and DMXOPL3 (MIT, by ConSiGno and Sneakernets) in SHARED\BANKS, made to sound like a Roland Sound Canvas. Control Panel's Sounds page lists the banks to choose from. A bank that is not there falls back to the bundled one (MUSIC_BANK_DEFAULT). DVX.INI's choice is read as each MIDI music starts, so a new choice reaches the next one; a music keeps the bank it started with, and a bank is freed with the last music playing on it.

What is played: note on / off, velocity, program change, channel volume (CC7), expression (CC11), pan (CC10: left, both, or right on an OPL3 and the software synthesizer), sustain pedal (CC64), pitch bend with the bend range from RPN 0 (default 2 semitones), all notes off (CC123, CC120), reset controllers (CC121), and General MIDI System On (a reset). Channel 10 plays percussion. Loudness is velocity x volume x expression x the music's volume on a 40 log10 curve, added as attenuation to the voice's level. OP2 banks are tuned for DMX, which sounds note n where MIDI has note n + 12, so notes are played an octave up from the bank's offsets. Not played: modulation (CC1), aftertouch, the FM chip's own rhythm mode, four-operator voices.

The Software Synthesizer

When there is no FM chip (or it is busy), MIDI plays on DVX's own software FM synthesizer: an integer two-operator FM chip modelled on the OPL2 (the same log-sine and exponential tables, waveforms 0 to 3, feedback, the chip's envelope rates and key scaling, key scale level, tremolo, and vibrato) with 24 voices, rendering at the device's rate into a stream. It is driven with the same bank and the same register values as the chip, so music sounds alike on both. The host tests compare it with emu8950, an accurate OPL2 emulator, for several patches: pitch, loudness over time, and harmonics agree closely. Cost on a 486: roughly 1 to 2 percent of a 486DX2-66 per sounding voice at 22,050 Hz.

SoundFonts on a Wavetable Synthesizer

A wavetable synthesizer (audio.lib's gus and guswt drivers for a Gravis UltraSound, awe for the EMU8000 of a Sound Blaster AWE32 or AWE64) plays recorded instruments from its own memory. Its instruments come from a SoundFont, DVX.INI [audio] soundFont (MUSIC_INI_SOUND_FONT), named as midiBank names an OP2 bank; not set, the standard one (MUSIC_SOUND_FONT_DEFAULT, KPUNCH\DVXGM.SF2: DVX's General MIDI SoundFont add-on, made from FluidR3_GM by mksf, about 3.4 MB). SoundFont 2 files are read in full; SoundFont 1 (.SBK, the AWE32's own) as far as it is known (it has no public specification). soundFont may also name a Gravis UltraSound's ULTRASND.INI by its path, which plays that folder's .PAT patches as instruments; and where the standard SoundFont is not installed, the patches in the folder ULTRADIR names play (musicPatchSetPath), else the AWE's own bank for its sample ROM (musicRomBankPath: SFBANK\SYNTHGM.SF2 or SYNTHGM.SBK in the folder SOUND names, as Creative's drivers installed it). Control Panel's Sounds page lists all of these.

A song loads only the samples its notes play (the bank selects, programs, and notes are looked at first), each with the frames a chip's interpolation reads after it, into memory the synthesizer gives every program a share of; what a song loaded stays for the next while it fits, and room is made by giving up the samples used longest ago. When the samples still do not fit they are all loaded again smaller: 8-bit where the chip plays it, then at half the rate, then a quarter; at the least, the ones that do not fit are left out. Notes the bank lacks, or whose samples were left out, play on the FM chip or the software synthesizer, as do the notes beyond the synthesizer's voices. One music plays on the wavetable synthesizer at a time (another meanwhile plays on FM); its SoundFont and samples are kept 60 seconds after the last music (MUSIC_FONT_KEEP_SEC). Live MIDI loads a program's samples as it is chosen. A SoundFont made for an AWE's General MIDI ROM (ROM samples) plays from the card's ROM with nothing loaded; on any other synthesizer its notes go to FM.

The AWE runs the SoundFont's envelopes, LFOs, filter, and reverb and chorus sends itself; on the GUS the library runs the envelopes and LFOs (its volume and pitch changed as the note goes on), without a filter or effects.

The SoundFont Software Synthesizer

With DVX.INI [audio] midi = softwave, MIDI plays from the SoundFont in software on any device that plays sampled sound: the song's samples are read into memory at their own rates and mixed into the music's stream (linear interpolation, the envelopes and LFOs as on the GUS, no filter or effects), with the software FM synthesizer beside it for the notes the bank lacks. It needs the memory for the song's samples (a General MIDI song of ten instruments and a drum kit about 360 KB of DVXGM's) and more CPU time than FM.

The MPU-401 Port

With DVX.INI [audio] midi = mpu401, MIDI goes out of the MPU-401 port (audio.lib's mpu401 driver) to an external synthesizer, one music at a time (a second MIDI music meanwhile plays on the FM chip or the software synthesizer). Bytes are queued and sent by the timer as fast as the port takes them, with a 40 ms pause after every system exclusive message (first-generation MT-32s lose data without it). The music's volume scales every channel volume (CC7) sent. DVX.INI [audio] mpuMode says what is on the port:

  mpuMode    Treatment
  -------    ---------
  gm         A General MIDI synthesizer (Roland SC-55 and the like): the music as it is, after a General MIDI System On message.
  mt32       A Roland MT-32 (or CM-32L, LAPC-I) playing General MIDI music: the melodic channels move to the MT-32's eight parts (MIDI channels 2 to 9, in the order they are first used; a ninth channel shares the last part), percussion stays on channel 10, programs go through a General MIDI to MT-32 timbre table (our own nearest-sound choice), pan is mirrored (the MT-32 pans the other way), the bend range is set to 2 semitones by system exclusive messages at the start (and RPN 0 changes it the same way, since the MT-32 has no RPNs), and what it does not understand (other RPNs and NRPNs, bank select, reverb and chorus depth, aftertouch, system exclusive messages that are not Roland's) is left out.
  raw        Anything: the music exactly as it is, nothing added (for music written for an MT-32, with its own set-up messages).

Stopping, pausing, or freeing a music sends sustain off and all notes off on every channel the synthesizer hears.

Live MIDI

MusicT *keys = NULL;

musicMidiOpen(&keys);                          // plays at once
musicMidiProgram(keys, 0, 73);                 // flute on channel 0
musicMidiNoteOn(keys, 0, 60, 100);             // middle C
musicMidiNoteOff(keys, 0, 60);

musicMidiOpen opens a music with no file that plays at once, on the same synthesizer a MIDI file would use. musicMidiNoteOn, musicMidiNoteOff, musicMidiProgram, musicMidiControl, and musicMidiPitchBend (-8192..8191) send channel messages (channels 0 to 15, data 0 to 127) to it, or to a MIDI file that is playing (for dynamic game scores); MUSIC_ERR_ARG for a module or a value out of range. musicMidiSend(music, status, data1, data2) sends any channel message as MIDI input brings it (audioMidiInRead: status 80h..EFh with the channel in it; a data byte the message does not use is ignored), so what a keyboard plays sounds as it comes (MIDI thru).

Recording MIDI

MusicMidiRecordT *take = NULL;
AudioMidiMsgT     msg;

musicMidiRecordNew(&take);

while (audioMidiInRead(audio, &msg)) {         // each tick, while recording
    musicMidiSend(keys, msg.status, msg.data1, msg.data2);
    musicMidiRecordAdd(take, msg.ms, msg.status, msg.data1, msg.data2);
}

musicMidiRecordSave(take, "SONG.MID");
musicMidiRecordFree(take);

A MIDI recording keeps channel messages with their times (musicMidiRecordAdd(rec, ms, status, data1, data2), ms from the recording's start; a time before the last message's counts as the last's; MUSIC_ERR_ARG for anything but a channel message) and musicMidiRecordSave(rec, path) writes them as a Standard MIDI File: type 0, one track of 1,000 ticks a quarter note at 60 quarter notes a minute, so a tick is a millisecond. A note still held at the end is let go there, so the file never leaves a note sounding. MUSIC_ERR_WRITE when the file cannot be written (nothing is left of one not all written). musicMidiRecordCount and musicMidiRecordLength (the last message's time) say what it holds; musicMidiRecordFree frees it, and a program's recordings are freed when it ends. musicLoad plays the file. The music macros (below) are written into MIDI files by the same code.

Modules

MOD (ProTracker and compatibles), S3M, XM, and IT through libxmp-lite 4.7.3 (thirdparty/libxmp-lite with DVX's patches, listed in its DVX-VERSION.md; formats only, no depackers or ProWizard). Stereo separation is 50 percent (softer than the Amiga's hard left and right). The whole module is held in memory. libxmp works out pitches with floating point, so it plays modules in the shell's idle time, never at interrupt time; on a 486SX (no maths coprocessor) that goes through floating point emulation and is slow. On a stream libxmp mixes every channel in software (linear interpolation, three multiplies a channel a frame).

On a wavetable synthesizer (musicModHw.c) libxmp mixes nothing: DVX's hardware voice patch has it report each of its voices as every tick starts (the sample, where it is, its rate, volume, pan, and the loop it is in), and only move them on. Those ticks are made up to 400 ms ahead in the idle time, as the synthesizer's notes, and the music timer plays them on time (500 times a second: a note starts within 2 ms of its time). A voice starts on the chip where libxmp starts or moves it (a note, a sample offset, a retrigger, another sample or loop); otherwise its pitch, volume, and pan follow libxmp's each tick, and the chip's own interpolation plays it. The samples are stored as they are (an 8-bit one 8-bit where the chip plays those, as a GUS does), else all 8-bit. The chips loop only forward, so a ping-pong loop is stored unrolled: its way back after its end, each frame where libxmp reads it (an Impulse Tracker module's way back a frame shorter, as Impulse Tracker plays it). What the chips cannot do plays near enough: a ping-pong sustain loop goes forward, a sample played backwards with no loop goes forward, and Impulse Tracker's filters are not heard; a voice the chip has none free for (a program holds the rest) is not heard. A voice at full volume plays 8 dB down, with the chip's level (AudioWtInfoT) taken off too, so a module is about as loud as libxmp mixes it. A pause stops the voices, and the play goes on from the tick that was playing; musicGetPosition is the tick being heard.

Measured on an emulated 486DX2-66 at 22,050 Hz, a 4-channel MOD with all four channels sounding takes about 49 percent of the CPU mixed in software and about 14 percent on a Gravis UltraSound's voices, most of that libxmp's own work each tick (about 1.3 ms of the 20).

Music Macros

MusicMmlT *tune = NULL;

musicMmlOpen(&tune);
musicMmlPlay(tune, "MB T140 O3 L8 CDEC CDEC EFG4 EFG4");

A macro queue plays tunes written in QBasic's music macro language (the commands are listed in music.h and in the BASIC language reference: notes A-G with sharps and flats, N, O, > and <, L, P, dots, T, MN / ML / MS, MF / MB). musicMmlPlay parses the whole string first; a mistake anywhere returns MUSIC_ERR_MACRO and queues nothing. The settings last from one string to the next. musicMmlTone queues a square-wave tone of milliHz thousandths of a hertz (0: silence) for us microseconds (QBasic's SOUND). musicMmlQueued counts the notes not yet played, musicMmlStop silences the queue, musicMmlBusy says whether a caller that follows QBasic should still wait (foreground mode: until everything has played; background: while more than MUSIC_MML_BACKGROUND_MAX notes wait) and moves the queue along. A queue belongs to the program that opened it and is freed when that program ends.

Timing is QBasic's: a whole note is four beats of 60 / T seconds, each dot adds half again, and a note sounds for 7/8 (MN), all (ML), or 3/4 (MS) of its length. Octave 3 starts at middle C; octave 3's A is 440 Hz. The notes play as square waves on an audio.lib note queue, each to the exact sample (on the PC speaker as tones). DVX's own I command (I1 to I128) plays the notes that follow on that General MIDI instrument: each run of such notes becomes a small MIDI file that plays like any MIDI music (on the synthesizer DVX.INI [audio] midi chooses); I0 returns to tones. Runs play one after the other; when the instruments cannot play (no instrument bank, no device) the run plays as tones.

Several Programs at Once

Every music plays through one audio context of the library's own; audio.lib mixes all streams, so modules and software MIDI of any number of programs play together. FM voices are shared with every program's tones and FM music; the MPU-401 port serves one music, and so does the wavetable synthesizer (MIDI or a module). When a program ends, its musics stop and are freed and their FM voices and the port are given back; the others go on.

BASIC

src/include/basic/music.bas (DECLARE LIBRARY "music"): MusicLoad(fileName) and MusicMidiOpen() return a music number (0 on failure: MusicError$ says why); MusicPlay(music, loopIt), MusicStop, MusicPause, MusicResume, MusicPlaying, MusicVolume(music, percent), MusicPosition, MusicLength, MusicSeek(music, ms), MusicFree; live MIDI MusicNoteOn(music, channel, note, velocity), MusicNoteOff, MusicProgram, MusicControl, MusicPitchBend with channels 1 to 16 (10 for drums), and MusicMidiSend(music, midiMsg) (musicMidiSend of a message packed as MidiInMessage gives it, AUDIO_MIDI_PACKED_*). MIDI recordings: MidiRecordNew (a recording number, 0 when all 8 are in use), MidiRecordAdd(recording, milliseconds, midiMsg), MidiRecordCount, MidiRecordLength, MidiRecordSave(recording, fileName) (True / False with MusicError$), MidiRecordFree. A number only works for the program that got it. MusicLoadMacro(macro) (musicLoadMacro, a music number) and MusicSetBank(music, bank) (musicSetBank, "" the standard bank, a SoundFont or an ULTRASND.INI for wavetable music; MusicError$ says when it is missing or damaged); MusicPatchSet$ and MusicRomBank$ (musicPatchSetPath, musicRomBankPath: "" when there is none). MusicGetVolume(music) (musicGetVolume in percent) and MusicInfo$(music, field) (musicGetInfo: "title", "output", "kind" = "module" / "midi" / "live" / "sound" for MusicKindE, "where" = "" / "stream" / "mpu401" / "fm" / "software" / "wave" / "softwave" for MusicOutputE, "paused" and "looping" as "-1" / "0"; any case, "" for another field). music.bas also declares the raw MIDI port calls of audio.lib (MidiPortInfo$, MidiAcquire, MidiPut, MidiRelease) and its MIDI input (MidiInOpen, MidiInClose, MidiInMessage, MidiInTime, MidiInClock, MidiInLost, and the MIDI_* CONSTs); see the sound library.

The BASIC runtime itself calls musicBasBeep, musicBasSound(hz, ticks), musicBasPlay(macro), musicBasPlayCount, and musicBasBusy for the statements BEEP, SOUND, and PLAY and the PLAY(n) function; each BASIC program has its own macro queue. QBasic's X command and "=" numbers (given with VARPTR$) are filled in by the BASIC runtime before musicBasPlay is called, so the macro language itself has neither. SOUND's duration is in clock ticks (18.2 a second) and 0 stops the queue; BEEP is 800 Hz for a quarter second.

Licences

libxmp-lite (Extended Module Player Lite), (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr, MIT licence:

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

The DVX General MIDI SoundFont add-on (DVXGM.SF2) is made from FluidR3_GM by Frank Wen (MIT licence); its notice is installed beside it (SHARED\BANKS\KPUNCH\DVXGM.TXT).

The DMXOPL 1.11 instrument bank is by ConSiGno, and DMXOPL3 by Sneakernets (MIT licence); each one's licence and credits are installed beside it (SHARED\BANKS\DMXOPL.TXT and DMXOPL3.TXT).

The GENMIDI instrument bank is from Freedoom 0.13.0, Copyright (C) 2001-2024 Contributors to the Freedoom project. All rights reserved. BSD 3-clause licence:

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the Freedoom project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Package Server Library

The client of a DVX package server: what it offers (its catalogue), what it has that is newer than the packages installed (DVX itself among them), and fetching packages, with everything they need that is not installed, into a download folder; and, with an account on the server, publishing packages and listing what the account has sent. There the package library reads them (pkgOffersInFolder) and plans and installs them like any package file, so a package from the server is checked and trusted exactly as one from a disk. Software uses it for its Available and Updates pages.

Header: pkgnet/pkgNet.h

Loaded as: bin/libs/kpunch/pkgnet/pkgnet.lib (it needs the package, HTTP, and JSON libraries)

BASIC: pkgnet.bas (PkgNetOpen, PkgNetCatalogue, PkgNetUpdates, PkgNetFetch, PkgNetRunFor, PkgNetRows$, ...), with pkg.bas's PkgOfferFolder to plan what was fetched.

A Fetch

PkgNetT *n = pkgNetCreate(server, "", downloadDir);
const char *ids[] = { "acme.chess" };

if (n && pkgNetFetchStart(n, ids, 1, nowMs)) {
    while (pkgNetPoll(n, nowMs) == PkgNetBusyE) {
        // draw progress (pkgNetProgress, pkgNetCurrent), yield
    }
}

// PkgNetDoneE: plan and install from pkgNetDir(n) (pkgPlan.h), then pkgNetClear(n)
// PkgNetFailedE: pkgNetMessage(n) says why
pkgNetFree(n);

One job at a time; each poll does one piece of it (a piece of a request, or a slice of checking a file). nowMs is a millisecond clock, as for the HTTP library.

  Function                                    Description
  --------                                    -----------
  pkgNetCreate(server, root, dir)             A client of the server at server ("http://host[:port]"), for the DVX folder root ("" the current one), fetching into dir.
  pkgNetCatalogueStart(n, nowMs)              Read the catalogue: every package the server offers, at its newest version.
  pkgNetUpdatesStart(n, nowMs)                Ask what is newer than the packages installed (this DVX's version and ABI are sent with the list).
  pkgNetFetchStart(n, ids, count, nowMs)      Fetch packages by id, with what they need that is not installed in a version that will do.
  pkgNetPoll(n, nowMs)                        PkgNetBusyE, PkgNetDoneE, or PkgNetFailedE.
  pkgNetCancel(n)                             Stop the job; a download stopped part way is kept to resume.
  pkgNetEntries(n, &count)                    The answer: the catalogue (after reading it or fetching) or the updates, as PkgNetEntryT (the manifest's fields and needs, the package file's size and SHA-256, and whether the repository signed it).
  pkgNetDb(n)                                 The installed packages, as the last job read them.
  pkgNetProgress(n, &done, &total)            A fetch's bytes so far and in all.
  pkgNetCurrent(n), pkgNetMessage(n)          The package the job is on, and why it failed.
  pkgNetDir(n), pkgNetClear(n)                The download folder, and removing what the last fetch put there.
  pkgNetConfigRead(&c), pkgNetConfigWrite(&c)  DVX.INI's [packages]: server (a host name), port, user (an account's email), and password (scrambled by prefsSetSecret), as a PkgNetConfigT (the defaults: PKGNET_SERVER_DEFAULT, port 80, no account).
  pkgNetConfigUrl(&c, out, size)              The address they make: "http://host", with ":port" when not 80.
  pkgNetAccountSet(n, user, password)         The account the handle's account jobs use.
  pkgNetUploadStart(n, path, nowMs)           Publish the package file at path for the server's review.
  pkgNetMineStart(n, nowMs), pkgNetMine(n, &count)  What the account has sent, each version with its state (pending, approved, rejected, withdrawn) and the review's note.

What a Fetch Checks

Each package fetched must have exactly the size and SHA-256 the catalogue gives; one that does not is removed and the fetch fails. The repository's signature of it is fetched beside it (NAME.SIG), so whether DVX trusts it is decided as for any package: a package signed by a key in SYSTEM\KEYS (DVX's own repository key) is trusted by DVX. The server itself is never trusted: it is plain HTTP, and only the signatures count.

Each package's file is named from its id and version (eight hex digits, a DOS name), so a download that stops part way (NAME.PRT) goes on from where it stopped the next time the same version is fetched, and one fetched before is checked again rather than downloaded again.

What a package needs that no version on the server will do for is left for the planner to name. Installing what was fetched is the package library's job: read the folder's offers, plan, and run the install as a job.

An Account

The account's jobs sign in first (POST /v1/account/login) when the handle has no session yet, keep the session for the handle's later jobs, and sign in once more when the server answers that the session has gone. A wrong email or password fails the job saying so. The password is DVX.INI's (prefsGetSecret); the session's token is kept only in memory and wiped, with the password, when the handle is freed.

Package Library

Add-on packages: the .DPK file format, manifests, Ed25519 keys and signatures, the packages installed in a DVX folder, the keys DVX trusts, planning what an install or removal takes, and installing and removing a package a step at a time. Software, Package Builder, the IDE's File > Make Package, DVXPKG.EXE, the build machine's dvxpkg, and Setup all use it.

Header: pkg/pkg.h (it includes the others below)

Loaded as: bin/libs/kpunch/pkg/pkg.lib

BASIC: pkg.bas (sessions, offers, plans, install and remove jobs, building, signing, and keys; PkgFileOwner$ is pkgDbOwner).

A Package

A package is NAME.DPK: a ZIP read front to back, whose first entry is PACKAGE.INI, the manifest; then PACKAGE.SIG when the publisher signed it; then the files, named relative to the DVX folder with / between parts.

[package]
format    = 1
id        = acme.chess          ; vendor.name, each 1..8 of a-z 0-9 _
name      = Chess
version   = 1.2.0
kind      = app                 ; app library widget font bank theme wallpaper sound saver help bundle system
summary   = Play chess against the computer.
publisher = Acme Games          ; also author, licence, homepage
dvx       = 1.0.0               ; the oldest DVX it runs on
abi       = 10                  ; programs, libraries, widgets: DVX_ABI_VERSION
[depends]
acme.cards = 1.0.0              ; id = the oldest version that will do
[files]
APPS/ACME/CHESS/CHESS.APP = 61234 <SHA-256, 64 hex digits>

Every file is listed with its size and SHA-256 and every listed file is in the package, so the one signature over the manifest's bytes covers everything. Each kind keeps to its own folders (pkgKindFolder gives the folder for an id; pkgFileAllowed checks a name):

  Kind                  Folder
  ----                  ------
  app, help             APPS/VENDOR/NAME (help may also go beside a library or widget of its name)
  library, widget       LIBS/VENDOR/NAME, WIDGETS/VENDOR/NAME
  font, bank            SHARED/FONTS/VENDOR, SHARED/BANKS/VENDOR
  theme, wallpaper      SHARED/THEMES/VENDOR, SHARED/WPAPER/VENDOR
  sound                 SHARED/SOUNDS/VENDOR
  saver                 SHARED/SAVERS/VENDOR (a screen saver: a program, so it needs abi)
  bundle                no files: a set of packages, its dependencies
  system                DVX itself (kpunch.dvx): anywhere but the user folder

Every kind but bundle may also add files to SYSTEM (a DOS program run at the prompt, say), but not to SYSTEM/KEYS (the keys DVX trusts) or SYSTEM/PACKAGES (the package records, PKG_DB_DIR), which only DVX itself writes. A file there that another package has is refused when the package is installed, as anywhere (pkgPlanInstall), so an add-on still cannot replace one of DVX's.

Manifests (pkgManifest.h)

  Function                                       Description
  --------                                       -----------
  pkgManifestParse(text, len, &m, err, size)     A manifest's text; then pkgManifestCheck before trusting it.
  pkgManifestCheck(&m, err, size)                The id, the kind's rules, every file's name and folder, nothing listed twice, no dependency on itself.
  pkgManifestFormat(&m, &len)                    The text (CR LF lines), malloc'd.
  pkgTemplateRead(path, &m, err, size)           A template: a manifest whose format and [files] a build writes, so it may leave them out; other sections are ignored.
  pkgKindFolder(kind, id, out, size)             The folder a package id of kind keeps its files in (APPS/ACME/CHESS); false for bundle and system.
  pkgKindIsCode(kind)                            Programs, libraries, and widgets: their manifests need an abi.
  pkgIdValid(id), pkgIdSplit(id, vendor, name)   Package ids.
  pkgVersionParse, pkgVersionFormat, pkgVersionCompare   major.minor.patch versions.

Keys and Signatures (pkgSign.h)

A key pair is NAME.KEY (public: id, name, public key) and NAME.SEC (secret: id, name, seed), small INI files. A key's id is the first 8 bytes of its public key's SHA-256, so a signature names its key. The repository key that signs DVX's own packages is kept offline; the server never holds a secret key.

  Function                                             Description
  --------                                             -----------
  pkgRandomBytes(buf, len)                             Random bytes for a seed: the security library's generator topped up with fresh hardware entropy.
  pkgKeyWrite(seed, name, base, &key, err, size)       Make a pair from seed and write BASE.KEY and BASE.SEC.
  pkgKeyRead, pkgSecretRead, pkgSigRead                Read a key, secret key, or signature file.
  pkgSign(&secret, msg, len, &sig)                     Sign bytes (a manifest's).
  pkgSigCheck(&sig, &key, msg, len)                    True when key made sig over the bytes.
  pkgSecretWipe(&secret)                               Clear a secret key from memory once it has signed.

Building and Reading Packages (pkgDpk.h)

  Function                                                       Description
  --------                                                       -----------
  pkgDpkBuild(&templ, folder, out, secret, err, size)            Build from a template and every file under folder, named relative to it (a folder laid out like the DVX folder); secret NULL: unsigned. A program, library, or widget without abi gets DVX_ABI_VERSION.
  pkgDpkBuildFiles(&templ, files, count, out, secret, err, size) The same from a list of PkgDpkFileT (the name in the package, the file it comes from).
  pkgDpkFolderFiles(folder, prefix, &files)                      Every file under folder as that list, named prefix/...; pkgDpkFilesFree frees it.
  pkgDpkReadInfo(path, &info, err, size)                         The manifest and PACKAGE.SIG, checked; pkgDpkInfoFree.
  pkgDpkCheckFiles(path, &info, err, size)                       Every file's size and SHA-256 against the manifest.
  pkgDpkSign(path, &secret, detached, err, size)                 Sign into the package, or beside it as NAME.SIG.
  pkgDpkSigner(&info, detached, keys, count)                     Which of keys made the signature (or the detached one).
PkgManifestT templ;
PkgSecretT   secret;
char         err[PKG_ERR_MAX];
bool         ok;

ok = pkgTemplateRead("CHESS.INI", &templ, err, sizeof(err)) && pkgSecretRead("ACME.SEC", &secret, err, sizeof(err));
ok = ok && pkgDpkBuild(&templ, "STAGE", "CHESS.DPK", &secret, err, sizeof(err));
pkgSecretWipe(&secret);
pkgManifestFree(&templ);

Installed Packages and Trust (pkgDb.h, pkgTrust.h)

Each installed package has a record, SYSTEM/PACKAGES/VENDOR/NAME.INI: its manifest as installed, whether it was chosen or only needed by another, and the key that signed it. pkgDbLoad reads them all; pkgDbFind, pkgDbOwner (who has a file), pkgDbPut, and pkgDbDrop keep them. Setup writes DVX's own record (kpunch.dvx, with no files).

pkgKeyringLoad reads the keys DVX trusts: SYSTEM/KEYS (DVX's own) and the user's KEYS folder (publishers the user chose); pkgKeyringAdd trusts another. pkgTrustOf says how far a package is trusted (PkgTrustSystemE, PkgTrustUserE, PkgTrustNoneE). The rules the planner keeps: kpunch.* packages must be signed by a SYSTEM key; an upgrade must be signed by the key that signed the installed version (or a SYSTEM key); an unsigned package from a file is installed only after a warning.

Planning (pkgPlan.h)

An offer (PkgOfferT) is a package that can be installed: pkgOfferRead reads one file, pkgOffersFor reads a file and every package in its folder (what it may need), pkgOffersInFolder a whole folder. pkgPlanInstall(db, offers, count, wanted, wantedCount, &plan) plans installing the offers wanted with everything they need, in the order they go in, each checked (the DVX and ABI it needs, files another package has, a newer version installed, the signing rules); plan.err says why when it fails, plan.bytes is what it unpacks, and plan.stale lists the add-ons an update of DVX itself would leave unable to load. pkgPlanDependents lists the installed packages that need some going (they go too); pkgPlanOrphans those only they needed.

Installing and Removing (pkgOp.h)

pkgOpInstall(db, path, ring, explicitly) and pkgOpRemove(db, id, removeChanged) make an operation; pkgOpStep runs it a bounded piece at a time (PKG_OP_STEP bytes) until PkgOpDoneE or PkgOpFailedE (pkgOpMessage says why), with pkgOpProgress for a progress bar. Installing unpacks every file under a scratch name beside where it goes, checking each against the manifest, and only then swaps them all in, keeping the files they replace until every swap is done (and putting them back if one fails); then it removes what an older version had that this one does not, and writes the record. Removing deletes the files unchanged since they were installed (all of them with removeChanged), the folders that leaves empty, and the record; pkgOpKept lists the changed files it left. pkgOpFree before the swap cancels an install; after it, the operation is finished first.

The Package Tool

DVXPKG.EXE (DVX's SYSTEM folder) and bin/dvxpkg (the SDK, on the build machine) take the same commands:

dvxpkg build TEMPLATE.INI FOLDER OUT.DPK [KEY.SEC]
dvxpkg sign NAME.DPK KEY.SEC [-d]
dvxpkg verify NAME.DPK [-s NAME.SIG] [KEY.KEY ...]
dvxpkg info NAME.DPK
dvxpkg keygen "Key name" BASE
dvxpkg install NAME.DPK [-r DVXDIR] [-y]
dvxpkg record NAME.DPK DVXDIR [-n]
dvxpkg remove VENDOR.NAME [-r DVXDIR] [-f]
dvxpkg list [-r DVXDIR]

record writes a package's installed record into a DVX folder whose files came another way, and prints the record's name; with -n the package is recorded as needed by another (it goes when nothing needs it any more), not chosen. The release build records every package of DVX's in Setup's package that way (DVX itself, the base fonts, the optional bundles and their members: src/packages.ini), every file with its size and SHA-256, so an update from the package server replaces them as it would any package's, removing the files the new version dropped.

On DOS, DVXPKG remove is the way out when an add-on keeps DVX from starting (DVX /SAFE, or holding Shift, also starts DVX without add-ons). The SDK's make package builds NAME.dpk from a project (sdk.mk's PKG_* settings), and the IDE's File > Make Package from a BASIC project.

Printing Library

Printer ports and print jobs, text laid out on pages, pictures for dot-matrix and laser printers, the printer settings, and the common Print and Page Setup dialogs. Notepad, Image Viewer, Icon Editor, the Control Panel's Printer page, and BASIC's LPRINT all print through it; any program can.

Header: print/print.h

Loaded as: bin/libs/kpunch/print/print.lib

Settings

The printer is chosen once for everything, in DVX.INI [printer] (the Control Panel's Printer page and the Page Setup dialog edit it): where output goes (PrintDestE: LPT1 to LPT3, COM1 to COM4, or a file), a serial port's speed and handshake (baud, PrintHandshakeE), the printer model (PrintModelE: text only; Epson 9-pin or 24-pin, each with a black or a color ribbon; HP LaserJet at 150 dpi; HP LaserJet III or DeskJet at 300 dpi; HP DeskJet 500C; HP DeskJet 550C and later; PostScript, black or color; Apple ImageWriter II, black or color), and the page setup (PrintPageSetupT).

  Function                                   Description
  --------                                   -----------
  printSettingsLoad(&s)                      Read DVX.INI into a PrintSettingsT (defaults for anything missing; a page setup that cannot work is replaced by the defaults as a whole).
  printSettingsSave(&s)                      Write it back; false when the file could not be saved.
  printSettingsRead(h, &s) / Write(h, &s)    The same on a preferences handle you already have open.
  printSettingsDefaults(&s)                  LPT1, text only, PRINTER.PRN, 80 columns, margins 3 lines / 5 columns, tab 8, wrap, header "&f", footer "Page &p".
  printPageSetupProblem(&ps)                 NULL when the page setup can be used, else the reason (a static buffer).
  printPageLines(&ps, model)                 Lines per page used: the set value, or the model's usual length (66; 60 for a laser printer).
  printDestChoices[] / printModelChoices[]   The labels and INI spellings (PrefsChoiceT), indexed by PrintDestE / PrintModelE.
  printDestLabel(d) / printModelLabel(m)     The label of one choice (for programs that look the library up by name).
  printDeviceProblem(dest, file)             NULL when the destination can be used, else why not (printing to a file needs its name).
  printDeviceStore(h, model, dest, file)     Put model, destination, and file into the [printer] section of h with the page setup as it is in DVX.INI now (the caller saves h). The Control Panel's OK and print.bas use it.

Jobs and Printer Problems

A job sends bytes to a printer port or into a file. On DOS a parallel port is driven through the BIOS printer service (INT 17h); a byte is sent only when a status check says the printer is ready, so the BIOS never waits by itself. A serial port (COM1 to COM4, for serial printers such as the ImageWriter II) is opened through the serial library when the job starts, at the speed and handshake in the settings (8 data bits, no parity, 1 stop bit); its interrupt handler sends from a buffer as the printer's handshake allows (XON / XOFF, or the printer's DTR seen on the computer's CTS, or on its DSR as Apple's cable wires it), the port is busy while the buffer is well filled, and when the job closes it waits for the buffer to empty before closing the port. A PrintPortOpsT's open, pending, and close (NULL for none) are these steps. While the printer is busy the job calls the idle hook; a printer busy for PRINT_BUSY_TIMEOUT_MS (10 seconds), out of paper, off line, or reporting an error is a problem, and so is a file that cannot be written (disk full). A problem goes to the ask hook: Retry tries again, Cancel ends the job and every later write fails at once.

PrintHooksT hooks = { myAsk, myIdle, NULL, myState };
char        err[PRINT_MSG_MAX];
PrintJobT  *job = printJobOpen(PrintDestLpt1E, NULL, &hooks, err, sizeof(err));

if (!job) {
    // err: "This computer has no printer port LPT1."
} else {
    printJobPuts(job, "Hello, printer\r\n\f");
    printJobClose(job);     // true when everything was sent
}
  Hook                          Called
  ----                          ------
  ask(ctx, message)             For a problem; return PrintAnswerRetryE or PrintAnswerCancelE. NULL cancels.
  idle(ctx)                     While the printer is busy, and after every PRINT_YIELD_BYTES bytes (4096 into a file). Return false to cancel. NULL calls tsYield, so other programs keep running.
  finished(ctx, closing)        When a write that called idle returns, and (closing true) when the job is freed.

printJobWrite returns once every byte was taken, or false when the job is cancelled. printJobBytes gives the count sent, printJobCancelled whether it was cancelled. printStatusProblem(status, afterWrite, where, out, size) turns BIOS status bits (PRINT_STATUS_*) into the message the user sees. printSetPortOps replaces the port services (present / status / write) with your own, as the tests do to play a printer that is busy or out of paper; NULL restores the real ones. Elsewhere than DOS the real ports always exist, are always ready, and append to lpt1.prn .. lpt3.prn in the working directory.

Text

printTextJob(job, model, text, len, &pageSetup, &info) prints plain text on pages. Lines end with LF or CR LF; a form feed in the text starts a new page. Each printed line starts with marginLeft blanks and ends with CR LF, tabs are expanded to tabWidth, other control characters print as blanks, and a line longer than the text width is wrapped at the last blank before it (a word longer than a whole line is split) or, with wrap off, cut off. Every page ends with a form feed. For the Epson and LaserJet models the printer's reset command comes first (and, for the LaserJet, last). A PostScript printer prints no plain text, so on the PostScript models the pages are PostScript: Courier as large as lets the page setup's columns fill 8 inches and its lines 10, a show per line (CP437 letters and signs in ISO Latin-1; line and block characters as -, |, +, and #).

A page is marginTop lines with the header in their middle line, the text, then marginBottom lines with the footer in their middle line. Header and footer codes: &f file name (PrintTextInfoT.title), &p page number, &n number of pages, &d date, &t time (PrintTextInfoT.date / time, or today and now), && an ampersand; &l, &c, and &r put what follows at the left, centre, or right (text before any of them is centred). printFormatHeader expands a header for one page.

PrintTextInfoT also chooses a page range (firstPage, lastPage; 0 = all) and the number of copies. printTextPageCount returns the number of pages text takes.

Documents: Pictures and Drawn Pages

A document is pages of dot graphics on a picture-printing model. printDocOpen(job, model) starts one on a job (the document owns the job from then on, and printDocClose closes it). Each page is the whole printable area (8 by 10 inches): printDocPage(doc, fn, ctx) asks the program's PrintBandFnT for it a band at a time, top down, as R, G, B bytes at the printer's resolution (the band comes white), and printDocPicture(doc, rgb, w, h, pitch) prints a picture scaled to the largest size that fits, keeping its shape. Only one band of the page is ever in memory, so a page costs the same however large it is.

  Function                                   Description
  --------                                   -----------
  printModelPage(model, &info)               The page a model prints on (PrintPageInfoT: dots per inch across and down, the printable area in dots, color or not, and vector: a PostScript printer, which can draw the page itself); false for text only. info may be NULL to ask only whether the model prints pictures.
  printDocOpen(job, model)                   Start a document; NULL (the job closed) for a text-only model.
  printDocInfo(doc, &info)                   The document's page.
  printDocPage(doc, fn, ctx)                 Print a page the program draws. fn(ctx, rgb, width, y0, rows) fills rows y0 to y0 + rows - 1; false from it stops the page.
  printDocPicture(doc, rgb, w, h, pitch)     Print a picture as a page.
  printDocCancelled(doc)                     True when the job was cancelled.
  printDocClose(doc)                         End the document and close its job; true when every page printed. After a failure every later page fails at once.
  printUiDocOpen(ctx, title, &choice)        A document on the printer in the settings, at a Print dialog answer's destination (NULL: the settings'), with the Printing window up until it closes and the Retry box; tells the user why when it cannot open. Copies are the program's: print each page as often as wanted.
  printUiDocClose(ctx, doc)                  Close it, telling the user of a failure (not of a cancel).

Each band is dithered to the printer's inks with an ordered pattern (4x4, 17 levels, on the dot-matrix printers and the 150 dpi LaserJet; 8x8, 65 levels, at 300 dpi). A black-only printer prints the brightness. A color printer prints cyan, magenta, and yellow from the red, green, and blue; with black ink as well, black takes what the three have in common (full undercolor removal). Each ink is dithered with its own offset of the pattern, so dots of different inks do not land on each other in a regular way, and the pattern follows the row's place on the page, so it runs on from band to band.

static bool drawBand(void *ctx, uint8_t *rgb, int32_t width, int32_t y0, int32_t rows) {
    // a red bar one inch down, a quarter inch high
    const PrintPageInfoT *pi = (const PrintPageInfoT *)ctx;

    for (int32_t r = 0; r < rows; r++) {
        int32_t y = y0 + r;

        if (y >= pi->dpiY && y < pi->dpiY + pi->dpiY / 4) {
            for (int32_t x = 0; x < width; x++) {
                uint8_t *p = rgb + ((r * width) + x) * 3;

                p[0] = 0xFF;
                p[1] = 0;
                p[2] = 0;
            }
        }
    }

    return true;
}

PrintDocT     *doc = printUiDocOpen(ctx, "Report", NULL);
PrintPageInfoT pi;

if (doc) {
    printDocInfo(doc, &pi);
    printDocPage(doc, drawBand, &pi);
    printUiDocClose(ctx, doc);
}

Vector Pages

On a PostScript printer (PrintPageInfoT.vector) a page can be drawn by the printer itself, as lines, curves, fills, and its own letters, instead of being sent as dots: sharper at any size, and much smaller. printVecBegin(doc) starts such a page and printVecEnd(doc) prints it; in between, the drawing calls below go to the printer as they are made. Coordinates are the page's dots (y down, a dot's centre at whole numbers, so a shape covers the same dots it would in a band), colors are 0xRRGGBB (grey on the black-and-white model), and widths are in dots. printVecBegin is false, and the drawing calls do nothing, on a printer that cannot or a document that failed; a raster page (printDocPage, printDocPicture) is refused while a vector page is open, and printDocClose ends one left open.

  Function                                                     Description
  --------                                                     -----------
  printVecBegin(doc), printVecEnd(doc)                         Start a vector page; print it (false when it did not print).
  printVecLine(doc, x0, y0, x1, y1, color, width, dash)        A line, round at the ends; dash is a 16-step pattern from its top bit (0xFFFF solid), a step a dot along the line's longer side, as BASIC's LINE style.
  printVecRect(doc, x0, y0, x1, y1, color, fill)               A filled box, corners inclusive.
  printVecEllipseFill(doc, cx, cy, rx, ry, color, fill)        A filled ellipse, out to the dots rx and ry from its centre.
  printVecEllipse(doc, cx, cy, rx, ry, color, width, dash, arc, start, end)  An ellipse's outline, or (arc) the part from start to end radians counterclockwise; dash as printVecLine's, along the outline from angle 0.
  printVecDot(doc, x, y, color, width)                         A round dot width dots across.
  printVecText(doc, font, x, y, text, len, color)              Text with the top of its line at (x, y), with the font's underline and strike-through.
  printVecImage(doc, rgb, w, h, sx, sy, sw, sh, dx, dy, dw, dh)  The part sx, sy, sw, sh of a picture (R, G, B bytes) stretched over the dots dx, dy, dw, dh by the printer.

A fill (PrintFillE) is solid or a hatch of lines one dot wide every PRINT_HATCH_DOTS (8) dots, horizontal, vertical, either diagonal, or crossed, lined up across the page as BASIC's FillStyle hatches are. The pen (its color, width, and dashes) and the font are sent only when they change, and each of the printer's faces is made Latin-1 the first time the document uses it.

A font opened for a vector page (printFontOpen with page.vector set) is the printer's own Helvetica, Times, or Courier in the style asked for (FONT_STYLE_BOLD, FONT_STYLE_ITALIC): Courier for a fixed-pitch family or one with Mono or Courier in its name, Times for one with Serif, Times, Roman, Tinos, or Ledger in it (but not Sans), else Helvetica. It is exactly the size asked for, and printFontWidth, printFontHeight (1.2 times the size), and printFontAscent measure the printer's letters by Adobe's published widths, so text placed with them lines up as it prints. It draws only with printVecText (printFontSpans and printFontDraw draw nothing with it); for DVX's own letters drawn in bands on a PostScript printer, open the font with vector cleared in a copy of the page.

Text on a Page

A page a program draws can carry text in any font family (dvxText.h) at a size in points. printFontOpen(&page, family, decipoints, style) gives a PrintFontT for a page's resolution (its PrintPageInfoT): the size is in tenths of a point (120 for 12 points), the style any FONT_STYLE_ bits, underline and strike-through included. The font is the family's smallest strike at least as large as the size in printer dots (12 points is 50 dots at 300 dpi, 30 at 180), else its largest, scaled to the size across and down apart, since a 9-pin printer's dots are not square. Scaled down (or up one way only), a strike pixel becomes the dots it covers, and every pixel row and column of the strike still marks a dot, so thin strokes stay. Scaled up both ways, each dot is sampled between the four strike pixels around its centre and drawn when at least half covered, so edges come out smooth rather than in steps.

The base set's files carry print sizes beside their screen sizes (mkfont's print key; see Making Fonts): DejaVu Sans, Serif, and Sans Mono in every style at 25 to 100 pixels, about 6 to 24 points at 300 dpi and 10 to 40 at 180, so those families print crisp at those sizes. Other families, and larger text, are scaled up from their largest size.

  Function                                                    Description
  --------                                                    -----------
  printFontOpen(&page, family, decipoints, style)             The font; NULL when out of memory or for a size of 0 or less. printFontClose gives it up.
  printFontAscent(font), printFontHeight(font)                Dots from the top of a line to its baseline, and to the next line.
  printFontWidth(font, text, len)                             Dots across len characters of text.
  printFontSpans(font, x, y, text, len, top, bottom, fn, ctx) The text with the top of its line at (x, y) as runs of dots (PrintSpanFnT fn(ctx, x0, x1, y)), only rows top to bottom: a line that misses them is skipped at once, so a page drawn band by band costs little for the lines elsewhere.
  printFontDraw(font, x, y, text, len, color, rgb, width, y0, rows)  The same painted in color (0xRRGGBB) into the band a PrintBandFnT is filling.

Positions are worked out in the strike's pixels from the start of the text and only then turned into dots, so printFontWidth and what is drawn always agree.

Printer Drivers

  Model                                  Driver
  -----                                  ------
  Epson 9-pin (ESC/P)                    120 x 72 dots per inch (ESC * 1), 8-row bands, paper fed with ESC J; blank bands only feed paper, and each pass stops after its last black dot.
  Epson 24-pin (ESC/P)                   180 x 180 dots per inch (ESC * 39, 3 bytes per column), 24-row bands.
  HP LaserJet (PCL)                      150 dots per inch raster graphics, one uncompressed row transfer per dot row (every LaserJet understands it), trailing white bytes left out.
  HP LaserJet III or DeskJet (300 dpi)   300 dots per inch (PCL 5, and PCL 3 on DeskJets): each row packed (compression mode 2, TIFF PackBits) and blank rows skipped (ESC * b n Y) instead of sent, so a page with white space on it costs next to nothing.
  Epson 9-pin / 24-pin color             As the black ones, with a four-color ribbon: a pass of the head for each ink with ESC r n choosing its band of the ribbon, yellow first and black last (dark ink does not smear into the light ones).
  HP DeskJet 500C                        As the 300 dpi LaserJet, in three planes (ESC * r -3 U: cyan, magenta, yellow; black is the three together): ESC * b n V for each plane of a row but the last, ESC * b n W for the last.
  HP DeskJet 550C and later              As the 500C, in four planes (ESC * r -4 U: black, cyan, magenta, yellow).
  PostScript                             Level 2 at 300 dpi: each band an imagemask of its black dots, packed (RunLengthDecode, the same PackBits) and in ASCII85, which any port carries; a band with no ink is not sent. The 8 by 10 inch area is centred across the paper the printer has loaded, half an inch from its top.
  PostScript (color)                     As PostScript, each band a colorimage of 4 bits a dot (cyan, magenta, yellow, black) from the dithered planes. Both PostScript models also draw vector pages (see Vector Pages).
  Apple ImageWriter II                   144 x 144 dots per inch: 144 across (ESC p), and down by two passes of the 8 graphics wires 1/144 inch apart (the even rows of a 16-row band, a 1/144-inch line feed, the odd rows, then ESC T 15 and a line feed). A line is ESC G nnnn and its columns (bit 0 the top dot), placed at its first dot (ESC F nnnn). The document resets the printer (ESC c) and sets its switches whatever its DIP switches say: no line feed after CR, 8-bit data, no perforation skip; then unidirectional printing (ESC >), which keeps the passes in line.
  Apple ImageWriter II (color)           As the ImageWriter II with the four-color ribbon: a line for each ink with ESC K n choosing its band, yellow first and black last, CR between them.

The drivers are inside the library: each holds its resolution, the printable size in dots, the band height, the dither size, its inks, the text reset strings and usual page length, and begin / band / end functions that write the printer's commands. A new printer family is one more driver.

Dialogs and Ready-Made Printing

  Function                                              Description
  --------                                              -----------
  printDialog(ctx, title, pageCount, &choice)           The common Print dialog: print to a port or a file, copies and, when pageCount > 0, all pages or a range. Starts from the settings; true with the PrintChoiceT filled when the user chose Print.
  printPageSetupDialog(ctx)                             The Page Setup dialog; saves the page setup to DVX.INI.
  printUiText(ctx, text, title, askFirst)               Print text with the page setup on the printer in the settings (the Print dialog first when askFirst).
  printUiTextChoice(ctx, text, title, &choice)          Print text on the destination, copies, and page range of a PrintChoiceT (a Print dialog answer, or one the program filled) without asking; printUiText ends here.
  printUiImageFile(ctx, path, title, askFirst)          Print a picture file (BMP, PNG, JPEG, GIF).
  printUiWindow(ctx, win, askFirst)                     Print a window's contents as a picture.
  printUiOpenPort(ctx, port)                            A job with the user interface hooks on LPTn (0-based) or PRINT_PORT_DEFAULT.

Jobs opened by these ask about printer problems with a Retry / Cancel message box. While a write waits, a modal Printing window with a Cancel button appears (at once for a document, after half a second for other output) and the whole desktop keeps running under it. Problems are told to the user; the functions return true only when everything was printed. Pictures need a printer model that prints them; on a text-only printer the user is told to choose the model in the Control Panel.

BASIC

BASIC's Printer object (the form runtime, formPrinter.c) is built on the calls above: it records a page's drawing, prints it with printUiDocOpen / printDocPage (each band drawn again from the record), and prints text with printFontOpen and printFontDraw; on a PostScript printer it draws the record once more as a vector page (printVec*), in the printer's own faces; its document goes where the program's last PrintChoose chose (printBasChoice, the running program's own answer only).

src/include/basic/print.bas declares PrintTextDialog, PrintText, PrintPictureDialog, PrintPicture, PrintPageSetup, PrintCanPrintPictures, and PrintCanPrintColor (printModelPage). For serial printers it declares PrintDestIsSerial (printDestIsSerial), PrintBaudRate (printBaudRate), PrintHandshakeCount / PrintHandshakeLabel$ (printHandshakeLabel), PrintSettingBaud / PrintSettingHandshake (printSettingsRead), and PrintSerialStore (printSerialStore). For the printer settings it also declares PrintModelCount / PrintModelLabel$, PrintDestCount / PrintDestLabel$ / PrintDestIsFile, PrintSettingModel / PrintSettingDest / PrintSettingFile$ (printSettingsRead on a prefs.bas handle, through prefsBasHandle), PrintSettingsProblem$ (printDeviceProblem), and PrintSettingsStore (printDeviceStore).

  BASIC                                             C it calls
  -----                                             ----------
  PrintPageSetting(prefs, field), PrintPageText$(prefs, field)  printSettingsRead on a prefs.bas handle: one PrintPageSetupT field by name (Lines, Columns, MarginTop, MarginBottom, MarginLeft, MarginRight, TabWidth, Wrap; Header, Footer).
  PrintPageSetupProblem$(lines, ..., footer)        printPageSetupProblem ("" when usable).
  PrintPageSettingsStore(prefs, lines, ..., footer) printSettingsWrite of the page part (the printer part as read); False when printPageSetupProblem objects.
  PrintPageLines(prefs, model)                      printPageLines.
  PrintPageCount(text)                              printTextPageCount with the settings in DVX.INI.
  PrintTextPages(text, title, first, last, copies)  printUiTextChoice on the destination in the settings.
  PrintChoose(title, pageCount), PrintChoiceDest, PrintChoiceFile$, PrintChoiceCopies, PrintChoiceFirst, PrintChoiceLast  printDialog; the readers return the PrintChoiceT of the last PrintChoose.
  PrintForm(form), PrintFormDialog(form)            printUiWindow on a loaded form's window (these two live in the BASIC runtime, which knows the forms).
  PrintPortPresent(port), PrintPortStatus(port)     printPortPresent / printPortStatus; port 1-3 is LPT1-LPT3.
  PrintStatusProblem$(status, afterWrite, where)    printStatusProblem ("" when the status shows none).

print.bas also has CONSTs for the PRINT_STATUS_ bits and the page setup limits and defaults (PRINT_LINES_STANDARD ... PRINT_COPIES_MAX); a formrt test compares them with print.h. The BASIC runtime gives the library the GUI context (printBasSetContext) when a program starts, and runs LPRINT, LPRINT USING, and OPEN "LPT1:" / "PRN" through printUiOpenPort, printJobWrite, and printJobClose. See the BASIC language reference (LPRINT, File I/O, Include Libraries).

Styled Text Library

Styled text and RTF in and out: the document of the RichEdit widget, and of its undo steps and clipboard. A RichTextT is text in code page 437 with '\n' between paragraphs, covered by runs -- each from an offset to the next run's, in a style (font family, pixel size, bold, italic, underline, strike-through, and color) -- and one alignment per paragraph. It needs no widget: a program can build, edit, and convert styled text with it alone.

Header: richtext/richText.h

Loaded as: bin/libs/richtext.lib

Types

  Type          Fields
  ----          ------
  RichStyleT    family (FONT_FAMILY_MAX; "" = the default family), size (pixels; 0 = the default size), style (FONT_STYLE_*), color (0x00RRGGBB or RICH_COLOR_DEFAULT)
  RichRunT      start (the first character's offset), style (an index in RichTextT.styles)
  RichTextT     text, runs, styles, aligns: stb_ds arrays; the text has no NUL, the first run starts at 0, styles[0] is the default style, one alignment (FLOW_ALIGN_*) per paragraph
  RichChangeT   set (RICH_SET_FAMILY, RICH_SET_SIZE, RICH_SET_COLOR), styleMask (the FONT_STYLE_* bits to set to to.style's), to (the new values)

The default family, size, and color are a RichEdit control's own font and ForeColor, so text in them follows the control. Styles are interned: one look is one index, so neighboring runs never share a style and no run covers nothing (but the one of an empty text). Running out of memory ends the program, as stb_ds does everywhere in DVX.

Constants

  Constant             Value                Description
  --------             -----                -----------
  RICH_COLOR_DEFAULT   FLOW_COLOR_DEFAULT   A run in the default color
  RICH_TAB_SPACES      4                    The spaces a tab in plain text or RTF becomes (DVX text has no tabs)
  RICH_SET_FAMILY      0x0001               RichChangeT.set: change the family
  RICH_SET_SIZE        0x0002               RichChangeT.set: change the size
  RICH_SET_COLOR       0x0004               RichChangeT.set: change the color

Styled Text

void       richTextInit(RichTextT *rt);
void       richTextClear(RichTextT *rt);
void       richTextFree(RichTextT *rt);
int32_t    richTextLength(const RichTextT *rt);
int32_t    richTextIntern(RichTextT *rt, const RichStyleT *style);
int32_t    richTextRunAt(const RichTextT *rt, int32_t offset);
int32_t    richTextStyleAt(const RichTextT *rt, int32_t offset);
int32_t    richTextParaCount(const RichTextT *rt);
int32_t    richTextParaOf(const RichTextT *rt, int32_t offset);
int32_t    richTextParaStart(const RichTextT *rt, int32_t para);
int32_t    richTextParaEnd(const RichTextT *rt, int32_t para);
int32_t    richTextAlign(const RichTextT *rt, int32_t para);
void       richTextSetAlign(RichTextT *rt, int32_t first, int32_t last, int32_t align);
void       richTextCopy(const RichTextT *src, int32_t from, int32_t to, RichTextT *dst);
int32_t    richTextReplace(RichTextT *rt, int32_t from, int32_t to, const RichTextT *what);
int32_t    richTextInsertPlain(RichTextT *rt, int32_t at, const char *text, int32_t len, int32_t style);
void       richTextFromPlain(RichTextT *rt, const char *text, int32_t len, const RichStyleT *style);
void       richTextRestyle(RichTextT *rt, int32_t from, int32_t to, const RichChangeT *change);
RichStyleT richStyleChanged(const RichStyleT *style, const RichChangeT *change);
  Function               Description
  --------               -----------
  richTextInit / Clear   An empty text: one empty, left-aligned paragraph in the default style. Free releases the arrays.
  richTextIntern         The index of a style, added when the text has none like it.
  richTextRunAt          The run the character at an offset is in (at the end, the last run); richTextStyleAt its style.
  richTextPara*          Paragraph count; the paragraph an offset is in (its '\n' belongs to it); where one starts and ends (its '\n' or the text's end); its alignment.
  richTextCopy           A range as a text of its own: its characters, runs, the styles they use, and the alignments of the paragraphs it touches.
  richTextReplace        Replace a range with a text (NULL deletes). Joined paragraphs keep the first one's alignment; each paragraph the new text starts takes its alignment from it. Returns the length inserted.
  richTextInsertPlain    Insert plain text in a style: '\r' dropped, a tab as RICH_TAB_SPACES spaces, '\n' starting a paragraph aligned as the one it is in.
  richTextFromPlain      The whole text as plain text in one style, made the default style.
  richTextRestyle        Apply a RichChangeT to a range; richStyleChanged applies one to a single style.

Undo works by these: before a change, richTextCopy saves the range it replaces; undoing replaces what the change put there with the saved copy. A copy taken, deleted, and put back gives the text it came from exactly -- characters, runs, styles, and alignments.

RTF

bool  richRtfIs(const char *text, int32_t len);
char *richRtfWrite(const RichTextT *rt, const char *defaultFamily, int32_t *outLen);
bool  richRtfRead(RichTextT *rt, const char *text, int32_t len);

richRtfWrite returns a NUL-terminated RTF document (malloc'd; NULL when out of memory): a font table whose font 0 is defaultFamily (the default family is written as font 0 with no size, and read back as the default), a color table whose entry 0 is the default color, then each paragraph as \pard and its alignment, ended by \par, each run as \plain and its whole style. ASCII characters are themselves; the rest are \uN with a '?' for readers without Unicode. richRtfRead replaces rt with what an RTF document holds and returns false, leaving rt alone, when the text is not RTF.

The subset read: \fonttbl and \f, \fs (half points; a pixel is taken as 3/4 point, so \fs24 is 16 pixels), \b, \i, \ul, \strike, and their ends, \ulnone, \plain, \colortbl and \cf, \par, \pard, \ql, \qc, \qr (\qj as left; \line as a new paragraph), \tab, \'hh (Windows-1252), \uN with \ucN, the escapes \\ \{ \} \~ \_, and quotes and dashes as their ASCII forms. Characters are stored as code page 437, '?' for those it lacks. Destinations such as \info, \stylesheet, \pict, headers, footers, and every \* group are skipped.

Styled Text Library: BASIC

Source: richtext/richTextBas.c; BASIC declarations: src/include/basic/richtext.bas (DECLARE LIBRARY "richtext").

const char *RtfToText(const char *rtf);
const char *TextToRtf(const char *text);

RtfToText$ is the text of an RTF document, a line per paragraph (text that is not RTF comes back as it is); TextToRtf$ is plain text as RTF in the system font, a paragraph per line. A result stays valid until the next call. The RichEdit control's TextRTF, SelRTF, LoadFile, and SaveFile cover RTF in a control.

Serial Stack

The DVX serial/networking stack provides reliable, optionally encrypted communication over RS-232 serial ports. It is composed of four layers, each building on the one below:

Loaded as: bin/libs/kpunch/serial/serial.lib

Layered Architecture

+--------------------------------------------------+
|  Application                                     |
|                                                  |
|  secLinkSend()  -- send on channel, opt. encrypt |
|  secLinkPoll()  -- receive + dispatch            |
+--------------------------------------------------+
|  secLink  (handshake, channels, cipher + MAC)    |
+--------------------------------------------------+
|  packet   (HDLC framing, CRC-16, Go-Back-N ARQ)  |
+--------------------------------------------------+
|  security -- DH, XTEA-CTR, SHA-256/HMAC, DRBG    |
|            (used by secLink during handshake)    |
+--------------------------------------------------+
|  rs232    (ISR-driven UART, 2048-byte rings)     |
+--------------------------------------------------+
|  UART hardware (8250 / 16450 / 16550 / 16550A)   |
+--------------------------------------------------+

The security layer is invoked by secLink during the Diffie-Hellman handshake and for per-packet cipher operations. It has no runtime coupling to rs232 or packet, so it can be used standalone for cryptographic operations independent of the serial stack.

Layer 1: RS-232 UART Driver

Layer 2: Packet Transport

Layer 3: Security (DH, XTEA, SHA-256)

Layer 4: Secure Link

End-to-End Example

RS-232 UART Driver

ISR-driven serial port driver supporting up to 4 simultaneous COM ports. A shared ISR drains UART FIFOs into per-port ring buffers (2048 bytes, power-of-2 for fast index wrapping). Flow control (XON/XOFF, RTS/CTS, DTR/DSR) operates within the ISR using watermark thresholds. All ISR data structures are DPMI-locked to prevent page faults. XON/XOFF bytes are queued for the transmit interrupt instead of being written over a byte still in the transmit register, and every cached register (IER, MCR) is changed with interrupts off so the ISR and the application never lose each other's updates.

Header: rs232/rs232.h

Port Constants

  Constant          Value   Description
  --------          -----   -----------
  RS232_COM1        0       First serial port.
  RS232_COM2        1       Second serial port.
  RS232_COM3        2       Third serial port.
  RS232_COM4        3       Fourth serial port.
  RS232_NUM_PORTS   4       Total number of supported COM ports.

Handshake Modes

  Constant                  Value   Description
  --------                  -----   -----------
  RS232_HANDSHAKE_NONE      0       No flow control.
  RS232_HANDSHAKE_XONXOFF   1       Software flow control (XON/XOFF characters).
  RS232_HANDSHAKE_RTSCTS    2       Hardware flow control via RTS/CTS lines.
  RS232_HANDSHAKE_DTRDSR    3       Hardware flow control via DTR/DSR lines.

UART Types

Detected automatically by probing the scratch register and FIFO capability.

  Constant            Value   Description
  --------            -----   -----------
  RS232_UART_UNKNOWN  0       Detection failed.
  RS232_UART_8250     1       Original IBM PC UART. No FIFO.
  RS232_UART_16450    2       Has scratch register, no FIFO.
  RS232_UART_16550    3       Has FIFO but buggy (rare).
  RS232_UART_16550A   4       Working 16-byte FIFO. Most common in 486+ hardware.

Error Codes

  Constant                     Value   Description
  --------                     -----   -----------
  RS232_SUCCESS                0       Operation succeeded.
  RS232_ERR_UNKNOWN            -1      Unknown error.
  RS232_ERR_NOT_OPEN           -2      Port is not open.
  RS232_ERR_ALREADY_OPEN       -3      Port is already open.
  RS232_ERR_NO_UART            -4      No UART detected at port address.
  RS232_ERR_INVALID_PORT       -5      Invalid port number.
  RS232_ERR_INVALID_BASE       -6      Invalid base I/O address.
  RS232_ERR_INVALID_IRQ        -7      Invalid IRQ number.
  RS232_ERR_INVALID_BPS        -8      Invalid baud rate.
  RS232_ERR_INVALID_DATA       -9      Invalid data bits value.
  RS232_ERR_INVALID_PARITY     -10     Invalid parity character.
  RS232_ERR_INVALID_STOP       -11     Invalid stop bits value.
  RS232_ERR_INVALID_HANDSHAKE  -12     Invalid handshake mode.
  RS232_ERR_INVALID_FIFO       -13     Invalid FIFO threshold.
  RS232_ERR_NULL_PTR           -14     NULL pointer argument.
  RS232_ERR_IRQ_NOT_FOUND      -15     Could not detect IRQ for port.
  RS232_ERR_LOCK_MEM           -16     DPMI memory lock failed.

rs232Open

Open a COM port with the specified line parameters.

int32_t rs232Open(int32_t com, int32_t bps, int32_t dataBits, char parity, int32_t stopBits, int32_t handshake);
  Parameter   Description
  ---------   -----------
  com         Port index (RS232_COM1..RS232_COM4).
  bps         Baud rate (e.g. 9600, 19200, 38400, 57600, 115200).
  dataBits    Data bits per character (5, 6, 7, or 8).
  parity      Parity mode: 'N' (none), 'O' (odd), 'E' (even), 'M' (mark), 'S' (space).
  stopBits    Stop bits (1 or 2).
  handshake   Flow control mode (RS232_HANDSHAKE_*).

Returns RS232_SUCCESS or a negative error code. The UART type is probed once here (RS232_ERR_NO_UART when nothing answers at the port address); only a 16550A gets its FIFO enabled and is fed 16 bytes per transmit interrupt, every other UART one byte.

rs232Close

Close a COM port and release its ISR resources. The modem control lines are left as they are: closing never drops DTR or RTS, so a modem stays connected and the next program to open the port can carry on. To hang up, call rs232SetDtr(com, false) before closing; the line stays down after the close. Opening a port raises DTR and RTS.

int32_t rs232Close(int32_t com);

rs232Read

Non-blocking read from the receive ring buffer.

int32_t rs232Read(int32_t com, char *data, int32_t len);
  Parameter   Description
  ---------   -----------
  com         Port index.
  data        Buffer to receive data.
  len         Maximum bytes to read.

Returns the number of bytes actually read (may be less than len).

rs232Write

Blocking polled write directly to UART transmit holding register. Bypasses the TX ring buffer. Polls LSR_TX_HOLD_EMPTY before writing each byte (the check and the write happen with interrupts off, so a flow-control byte from the ISR is never overwritten). Use for small, immediate writes where order matters (the packet layer uses this for frame transmission).

int32_t rs232Write(int32_t com, const char *data, int32_t len);

Returns the number of bytes actually written (may be less than len while flow control holds the line), or a negative error code (RS232_ERR_INVALID_PORT, RS232_ERR_NOT_OPEN, RS232_ERR_NULL_PTR).

rs232WriteBuf

Non-blocking write to the transmit ring buffer. The ISR drains buffered data to the UART asynchronously.

int32_t rs232WriteBuf(int32_t com, const char *data, int32_t len);

Returns the number of bytes actually queued (may be less than len if the buffer is full).

rs232Set

Change all line parameters on an open port. Internally calls rs232SetBps, rs232SetData, rs232SetParity, rs232SetStop, and rs232SetHandshake in sequence. Returns RS232_SUCCESS on success or the first negative error code encountered.

int32_t rs232Set(int32_t com, int32_t bps, int32_t dataBits, char parity, int32_t stopBits, int32_t handshake);

Configuration Getters

  Function                   Returns
  --------                   -------
  rs232GetBase(com)          I/O base address.
  rs232GetBps(com)           Current baud rate (int32_t).
  rs232GetData(com)          Data bits setting (5-8).
  rs232GetParity(com)        Parity character: 'n', 'o', 'e', 'm', or 's' (lowercase).
  rs232GetStop(com)          Stop bits setting (1 or 2).
  rs232GetHandshake(com)     Handshake mode (RS232_HANDSHAKE_*).
  rs232GetIrq(com)           IRQ number.
  rs232GetUartType(com)      UART type probed when the port was opened (RS232_UART_*).

rs232GetParity returns a lowercase character on success. Callers comparing against uppercase constants must use either both cases or tolower().

Status Getters

  Function                   Returns
  --------                   -------
  rs232GetRxBuffered(com)    Bytes waiting in the receive ring buffer.
  rs232GetTxBuffered(com)    Bytes waiting in the transmit ring buffer.
  rs232GetCts(com)           CTS line state (0 or 1).
  rs232GetDcd(com)           DCD (carrier detect) line state (0 or 1), as last reported by the modem status interrupt.
  rs232GetDsr(com)           DSR line state (0 or 1).
  rs232GetDtr(com)           DTR line state (0 or 1).
  rs232GetRts(com)           RTS line state (0 or 1).
  rs232GetLsr(com)           Line status register value as last read; RS232_LSR_OVERRUN, RS232_LSR_PARITY_ERR, RS232_LSR_FRAME_ERR, and RS232_LSR_BREAK are its error bits.
  rs232GetMcr(com)           Modem control register value.
  rs232GetMsr(com)           Modem status register value; RS232_MSR_RI is the ring indicator.

Configuration Setters

  Function                          Description
  --------                          -----------
  rs232SetBase(com, base)           Set I/O base address (before open).
  rs232SetBps(com, bps)             Change baud rate.
  rs232SetData(com, dataBits)       Change data bits.
  rs232SetParity(com, parity)       Change parity mode.
  rs232SetStop(com, stopBits)       Change stop bits.
  rs232SetHandshake(com, handshake) Change flow control mode. Releases any throttle the old mode applied (raises RTS/DTR or sends XON) and starts the transmit side from the live CTS/DSR line.
  rs232SetIrq(com, irq)             Move an open port to another IRQ (for a wrong auto-detection). Interrupt enables and DTR/RTS are kept.
  rs232SetMcr(com, mcr)             Set modem control register directly.
  rs232SetDtr(com, dtr)             Set DTR line state. The state is kept when the port is closed (dropping DTR is how a modem is hung up).
  rs232SetBreak(com, on)            Hold the transmit line in the break (spacing) state (on = true) or release it. The caller times the break, typically 250-500 ms.
  rs232SetRts(com, rts)             Set RTS line state. The state is kept when the port is closed.
  rs232SetFifoThreshold(com, thr)   Set FIFO trigger level (1, 4, 8, or 14 bytes) or 0 to turn the FIFO off. Levels other than 0 return RS232_ERR_INVALID_FIFO unless the UART is a 16550A. rs232Open sets 8 on a 16550A (the 8 free bytes allow 0.7 ms of interrupt latency at 115200 bps; 14 allows only 0.17 ms) and 0 on other UARTs.

Buffer Management

  Function                   Description
  --------                   -----------
  rs232ClearRxBuffer(com)    Discard all data in the receive ring buffer.
  rs232ClearTxBuffer(com)    Discard all data in the transmit ring buffer.

Packet Transport

Reliable, ordered packet delivery over an RS-232 serial link. Uses HDLC-style byte-stuffed framing for frame delimiting, CRC-16-CCITT for error detection, and Go-Back-N ARQ with a configurable sliding window for retransmission.

Header: packet/packet.h

Constants

  Constant            Value   Description
  --------            -----   -----------
  PKT_MAX_PAYLOAD     255     Maximum bytes per packet payload.
  PKT_DEFAULT_WINDOW  4       Default sliding window size (unacknowledged frames in flight).
  PKT_MAX_WINDOW      8       Maximum sliding window size.

Error Codes

  Constant                Value   Description
  --------                -----   -----------
  PKT_SUCCESS             0       Operation succeeded.
  PKT_ERR_INVALID_PARAM   -1      Invalid parameter (NULL handle, bad data pointer, length out of range).
  PKT_ERR_TX_FULL         -2      Transmit window is full (non-blocking send only).
  PKT_ERR_DISCONNECTED    -3      Serial port returned a negative read (disconnect/error), or the connection was closed from its own callback.
  PKT_ERR_TIMEOUT         -4      A blocking send got no acknowledgement for PKT_SEND_TIMEOUT_MS (15000 ms).

PktRecvCallbackT

Callback type for received packets.

typedef void (*PktRecvCallbackT)(void *ctx,
    const uint8_t *data, int len);
  Parameter   Description
  ---------   -----------
  ctx         User context pointer (passed to pktOpen).
  data        Payload bytes (valid only during the callback).
  len         Payload length.

pktOpen

Open a packetized connection over an already-open COM port. Only one packet connection may exist on a given COM port at a time; a second pktOpen call on the same port returns NULL to avoid silent framing corruption.

PktConnT *pktOpen(int com, int windowSize,
    PktRecvCallbackT callback, void *callbackCtx);
  Parameter     Description
  ---------     -----------
  com           COM port index (RS232_COM1..RS232_COM4). Must already be open via rs232Open.
  windowSize    Sliding window size (1..PKT_MAX_WINDOW). Pass 0 for PKT_DEFAULT_WINDOW. Values greater than PKT_MAX_WINDOW are clamped.
  callback      Called when a complete, CRC-verified, in-order packet is received. The data pointer is valid only during the callback.
  callbackCtx   User pointer passed to the callback.

Returns an opaque PktConnT handle, or NULL on failure (invalid port, duplicate open, or allocation failure).

pktOpenStream

Open a packetized connection over any byte stream instead of a COM port -- a TCP socket, for example. The stream is described by two non-blocking calls and a context pointer, copied into the connection. Any number of stream connections may be open.

typedef struct {
    int32_t (*read)(void *ctx, uint8_t *buf, int32_t len);
    int32_t (*write)(void *ctx, const uint8_t *data, int32_t len);
    void     *ctx;
} PktStreamT;

PktConnT *pktOpenStream(const PktStreamT *stream, int windowSize, PktRecvCallbackT callback, void *callbackCtx);
  Member   Description
  ------   -----------
  read     Up to len bytes into buf: the count, 0 when nothing has arrived, negative when the stream is gone (pktPoll then reports PKT_ERR_DISCONNECTED).
  write    Bytes taken from data (0 = none right now; a frame is retried until it is all out or nothing was taken for a while), negative when the stream is gone.
  ctx      Passed to both.

Returns the handle, or NULL for a NULL stream or call, or on allocation failure. pktClose never closes the stream.

pktClose

Close a packetized connection and release its registry slot. Does not close the underlying COM port. The caller is responsible for calling rs232Close separately when no longer needed. May be called from inside the receive callback: the connection is then freed when the outermost pktPoll returns (which reports PKT_ERR_DISCONNECTED).

void pktClose(PktConnT *conn);

pktSend

Send a data packet. The payload is copied into an internal retransmit slot before transmission, so the caller may reuse the supplied buffer immediately.

int pktSend(PktConnT *conn, const uint8_t *data,
    int len, bool block);
  Parameter   Description
  ---------   -----------
  conn        Connection handle from pktOpen.
  data        Payload bytes (1..PKT_MAX_PAYLOAD).
  len         Payload length.
  block       If true, wait for transmit window space via pktWaitSpace. If false, return PKT_ERR_TX_FULL immediately when the window is full.

Returns PKT_SUCCESS on success, PKT_ERR_INVALID_PARAM for NULL/out-of-range input, PKT_ERR_TX_FULL for a non-blocking call with a full window, PKT_ERR_DISCONNECTED if the serial port drops during a blocking wait, or PKT_ERR_TIMEOUT if the peer stops acknowledging. A frame the port accepts only partially (flow control) is completed before the call returns.

pktPoll

Poll for incoming data, process received frames, and handle retransmits. Must be called frequently in the main loop. The callback is invoked synchronously for each complete, CRC-verified, in-order data frame and may call pktSend or pktPoll again: received bytes are consumed from a per-connection buffer, so a nested call continues the same byte stream in order. After a gap in the sequence only one NAK is sent until the missing frame arrives (go-back-N resends the whole window on one NAK).

int pktPoll(PktConnT *conn);

Returns the number of valid data packets delivered to the callback this call, or PKT_ERR_INVALID_PARAM if conn is NULL, or PKT_ERR_DISCONNECTED if the serial layer returned a negative read.

pktReset

Reset the local connection state (sequence numbers, transmit slots, RX state machine) and send a RST frame to the remote side. The remote's RST handler clears its own state (discarding its unacknowledged frames), runs its reset callback, and answers with a RST reply. A RST reply that answers no reset of ours is ignored. Useful for recovering from a desynchronized connection.

int pktReset(PktConnT *conn);

Returns PKT_SUCCESS on success or PKT_ERR_INVALID_PARAM if conn is NULL.

pktCanSend

Check whether there is room in the transmit window for another packet. Returns false when conn is NULL or the transmit window is full.

bool pktCanSend(PktConnT *conn);

pktGetPending

Get the number of unacknowledged packets currently in the transmit window. Ranges from 0 (all sent packets acknowledged) to windowSize (window full).

int pktGetPending(PktConnT *conn);

Returns the pending count on success, or PKT_ERR_INVALID_PARAM if conn is NULL.

pktSetResetCallback

Register a callback run when the remote side resets the connection (its unacknowledged frames are gone at that point, so an upper layer with per-frame state must resynchronize). The callback receives the ctx passed to pktOpen. NULL removes it.

typedef void (*PktResetCallbackT)(void *ctx);
void pktSetResetCallback(PktConnT *conn, PktResetCallbackT callback);

pktWaitSpace

Poll until the transmit window has room. The wait restarts whenever an acknowledgement frees a slot, so only a peer that stops acknowledging times out.

int pktWaitSpace(PktConnT *conn);

Returns PKT_SUCCESS, PKT_ERR_INVALID_PARAM, PKT_ERR_DISCONNECTED, or PKT_ERR_TIMEOUT after PKT_SEND_TIMEOUT_MS without progress.

Security (DH, XTEA, SHA-256)

Cryptographic primitives for the serial stack: 1024-bit Diffie-Hellman key exchange (RFC 2409 Group 2), XTEA block cipher in CTR mode, SHA-256, HMAC-SHA256, and HKDF-SHA256, and a SHA-256 based random number generator.

XTEA requires no lookup tables and compiles to approximately 20 instructions per round, making it suitable for 486-class hardware where AES S-box tables would thrash the 8KB cache. SHA-256 needs only 32-bit arithmetic and a 256-byte constant table.

Headers: security/security.h, security/secHash.h

Constants

  Constant               Value   Description
  --------               -----   -----------
  SEC_DH_KEY_SIZE        128     Size of a DH public key or shared secret in bytes (1024 bits).
  SEC_XTEA_KEY_SIZE      16      Size of an XTEA key in bytes (128 bits).
  SEC_RNG_SEED_SIZE      32      Bytes stored in a seed file.
  SEC_SHA256_SIZE        32      SHA-256 digest size.
  SEC_SHA256_BLOCK_SIZE  64      SHA-256 block size.
  SEC_HKDF_MAX_OUT       8160    Largest HKDF output (255 digests).

Error Codes

  Constant           Value   Description
  --------           -----   -----------
  SEC_SUCCESS        0       Operation succeeded.
  SEC_ERR_PARAM      -1      Invalid parameter.
  SEC_ERR_NOT_READY  -2      DH context not ready (keys not generated or secret not computed).
  SEC_ERR_IO         -3      Seed file could not be read or written.

RNG Functions

A SHA-256 hash DRBG over a 32-byte entropy pool. secRngAddEntropy hashes new bytes into the pool and never discards what is already there, so it is safe to call at any time, before or after the first output. Each output block is a hash of the pool and a counter; after every request the pool is re-hashed so earlier output cannot be recomputed from a later state.

The first secRngBytes call gathers hardware entropy automatically. On DOS that is 256 PIT samples, each taken after a busy wait sized by the previous sample, plus the full time stamp counter on CPUs that have one (Pentium and later) and the BIOS tick count. On a CPU without a time stamp counter the samples depend mostly on the PIT phase and time of day, which an observer can narrow down, so applications should feed user-input timings to secRngAddEntropy and keep a seed file: call secRngLoadSeed at startup and secRngSaveSeed at shutdown. Host builds read /dev/urandom.

Under the DVX shell this is automatic when the serial library is installed: the shell loads the seed file SECLINK.RND in the user folder at startup, feeds the timing of every key press and mouse event to secRngAddEntropy, and saves a fresh seed at shutdown. Applications only need their own calls when they run without the shell.

void    secRngAddEntropy(const uint8_t *data, int32_t len);
void    secRngBytes(uint8_t *buf, int32_t len);
int32_t secRngGatherEntropy(uint8_t *buf, int32_t len);
int     secRngLoadSeed(const char *path);
int     secRngSaveSeed(const char *path);
void    secSecureZero(void *ptr, int len);
  Function              Description
  --------              -----------
  secRngAddEntropy      Hash bytes into the pool (key timings, mouse positions, anything unpredictable).
  secRngBytes           Generate pseudorandom bytes.
  secRngGatherEntropy   Fill buf with condensed hardware entropy (one fresh gather per 32 bytes). Returns the byte count (0 if a host build cannot read its random device).
  secRngLoadSeed        Mix a seed file into the pool, then immediately rewrite it with fresh output so a seed is never used twice. SEC_SUCCESS or SEC_ERR_IO.
  secRngSaveSeed        Write SEC_RNG_SEED_SIZE bytes of fresh output for the next run. SEC_SUCCESS or SEC_ERR_IO.
  secSecureZero         Zero memory in a way the compiler cannot optimize away (for key material).

Hash Functions

void secSha256(const uint8_t *data, int32_t len, uint8_t digest[32]);
void secSha256Init(SecSha256T *ctx);
void secSha256Update(SecSha256T *ctx, const uint8_t *data, int32_t len);
void secSha256Final(SecSha256T *ctx, uint8_t digest[32]);
void secHmacSha256(const uint8_t *key, int32_t keyLen, const uint8_t *data, int32_t len, uint8_t mac[32]);
void secHmacInit(SecHmacT *h, const uint8_t *key, int32_t keyLen);
void secHmacUpdate(SecHmacT *h, const uint8_t *data, int32_t len);
void secHmacFinal(SecHmacT *h, uint8_t mac[32]);
int  secHkdf(const uint8_t *salt, int32_t saltLen, const uint8_t *ikm, int32_t ikmLen, const uint8_t *info, int32_t infoLen, uint8_t *okm, int32_t okmLen);
bool secConstTimeEqual(const void *a, const void *b, int32_t len);

SHA-256 follows FIPS 180-4, HMAC RFC 2104, and HKDF RFC 5869 (extract and expand; a NULL or empty salt means 32 zero bytes). secHkdf returns SEC_ERR_PARAM for okmLen outside 1..SEC_HKDF_MAX_OUT. secConstTimeEqual compares without revealing where the inputs differ; use it for every MAC check.

Diffie-Hellman Key Exchange

1024-bit DH with 256-bit private exponents using the RFC 2409 Group 2 safe prime. The modular exponentiation is a Montgomery ladder with branch-free operand swaps, so its timing does not depend on the private key.

SecDhT *secDhCreate(void);
int     secDhGenerateKeys(SecDhT *dh);
int     secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len);
int     secDhComputeSecret(SecDhT *dh, const uint8_t *remotePub, int len);
int     secDhGetSecret(SecDhT *dh, uint8_t *buf, int *len);
void    secDhDestroy(SecDhT *dh);
  Function            Description
  --------            -----------
  secDhCreate         Allocate a new DH context.
  secDhGenerateKeys   Generate a private exponent and public key from the DRBG.
  secDhGetPublicKey   Copy the local public key into buf. *len receives the key size.
  secDhComputeSecret  Compute the shared secret from the remote public key. Keys outside [2, p-2] are rejected with SEC_ERR_PARAM (0, 1, and p-1 would force a trivially known secret).
  secDhGetSecret      Copy the shared secret (SEC_DH_KEY_SIZE big-endian bytes) into buf. It is input for a key derivation function such as secHkdf, never a key by itself.
  secDhDestroy        Securely zero and free the DH context.

Typical usage order: secDhCreate, secDhGenerateKeys, exchange public keys, secDhComputeSecret, secDhGetSecret, secHkdf, secDhDestroy.

XTEA-CTR Cipher

XTEA in counter mode. Encrypt and decrypt are the same operation (XOR with keystream). The counter must never repeat with the same key.

SecCipherT *secCipherCreate(const uint8_t *key);
void        secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
void        secCipherSetCounter(SecCipherT *c, uint64_t counter);
void        secCipherDestroy(SecCipherT *c);
  Function             Description
  --------             -----------
  secCipherCreate      Allocate a cipher context with a 128-bit key. Returns NULL on allocation failure or NULL key. Initial counter is zero.
  secCipherCrypt       Encrypt or decrypt data in place. The 64-bit counter increments by one every 8 bytes (one XTEA block).
  secCipherSetCounter  Position the 64-bit block counter (secLink derives it from the frame sequence number).
  secCipherDestroy     Securely zero the cipher context (key and counter state) and free its memory.

secCipherCreate returns NULL if the key pointer is NULL or allocation fails. secCipherCrypt is a no-op when any of c/data is NULL or len <= 0.

BASIC

src/include/basic/security.bas (DECLARE LIBRARY "serial") declares the BASIC functions (security/secBas.c). Keys, data, and results are BINARY STRING (pointer and length in, a result buffer and length out), so every byte value passes; the result buffer is wiped before it is reused. A handle (1, 2, ...; 0 = none) is a key exchange, a cipher, or an incremental SHA-256 / HMAC; each records the program that made it and the shell's app-cleanup hook frees (and wipes) what a program leaves open.

  BASIC                                          C it calls
  -----                                          ----------
  SecRandom$(count), SecAddEntropy bytes         secRngBytes; secRngAddEntropy.
  SecDhCreate()                                  secDhCreate + secDhGenerateKeys.
  SecDhPublicKey$(h), SecDhSecret$(h)            secDhGetPublicKey; secDhGetSecret ("" before SecDhComputeSecret).
  SecDhComputeSecret(h, remotePublicKey)         secDhComputeSecret: SEC_OK or a SEC_ERR_ code.
  SecDhFree h                                    secDhDestroy.
  SecCipherCreate(key)                           secCipherCreate; 0 unless key is SEC_XTEA_KEY_SIZE bytes.
  SecCipherCrypt$(h, bytes)                      secCipherCrypt on a copy.
  SecCipherSetCounter h, counter                 secCipherSetCounter (a DOUBLE: exact up to 2^53).
  SecCipherFree h                                secCipherDestroy.
  SecSha256$(bytes); SecSha256Open, SecSha256Add, SecSha256End$  secSha256; secSha256Init / Update / Final (End frees the handle).
  SecHmac$(key, bytes); SecHmacOpen, SecHmacAdd, SecHmacEnd$  secHmacSha256; secHmacInit / Update / Final.
  SecHkdf$(salt, secret, info, length)           secHkdf ("" for a length outside 1..SEC_HKDF_MAX_OUT).
  SecEqual(a, b)                                 secConstTimeEqual (False for different lengths).

The SEC_ CONSTs of security.bas are compared with security.h and secHash.h by a formrt test, which also calls every function for real.

End-to-End Example

Below is a complete secure BBS client that opens COM1, runs the authenticated key exchange, and then shuttles terminal traffic over a single encrypted channel. It demonstrates the typical serial stack usage: load the RNG seed file, open the link, set the passphrase, run the handshake, then enter a poll+send loop.

#include "rs232/rs232.h"
#include "packet/packet.h"
#include "security/security.h"
#include "seclink/secLink.h"
#include <stdio.h>
#include <string.h>

#define CHAN_TERMINAL 0

#define SEED_FILE     "SECLINK.RND"

static void onPacket(void *ctx, const uint8_t *data, int len, uint8_t channel, bool encrypted) {
    (void)ctx;
    if (channel == CHAN_TERMINAL && encrypted) {
        fwrite(data, 1, len, stdout);
        fflush(stdout);
    }
}


int main(void) {
    // Carry entropy over from the last run (hardware entropy is
    // gathered automatically; feed key timings with secRngAddEntropy).
    secRngLoadSeed(SEED_FILE);

    // Open the link at 115200 8N1 with RTS/CTS flow control.
    SecLinkT *link = secLinkOpen(RS232_COM1, 115200, 8, 'N', 1, RS232_HANDSHAKE_RTSCTS, onPacket, NULL);
    if (!link) {
        fprintf(stderr, "secLinkOpen failed\n");
        return 1;
    }

    // Authenticated key exchange. The proxy must use the same passphrase.
    secLinkSetPassphrase(link, "shared secret words");
    if (secLinkHandshake(link) != SECLINK_SUCCESS) {
        fprintf(stderr, "Handshake failed\n");
        secLinkClose(link);
        return 1;
    }

    // Send an encrypted banner and then relay keyboard input.
    const char *hello = "Hello, secure BBS!\r\n";
    secLinkSend(link, (const uint8_t *)hello, (int)strlen(hello), CHAN_TERMINAL, true, true);

    while (!feof(stdin)) {
        // Deliver any incoming packets to onPacket().
        secLinkPoll(link);

        // If the user typed something, forward it encrypted.
        int ch = getchar();
        if (ch == EOF) {
            break;
        }
        uint8_t byte = (uint8_t)ch;
        secLinkSend(link, &byte, 1, CHAN_TERMINAL, true, true);
    }

    secLinkClose(link);
    secRngSaveSeed(SEED_FILE);
    return 0;
}

Notes:

Task Manager

System-level task manager for DVX. Displays all running applications with a six-column ListView (Name, Title, File, Type, Memory, Status) and exposes Switch To, End Task, and Run... operations. Always accessible via Ctrl+Esc regardless of which application is focused. Persists independently of the desktop app (Program Manager).

Header: taskmgr/shellTaskMgr.h

Loaded as: bin/libs/taskmgr.lib

Integration

The task manager is a shell-level library, not a regular DXE app. At DXE load time a GCC constructor function registers the Task Manager with the shell by setting the shellCtrlEscFn function pointer (declared extern in dvxshell/shellApp.h). The shell invokes shellCtrlEscFn on every Ctrl+Esc keystroke; if taskmgr.lib is not loaded the pointer stays NULL and Ctrl+Esc does nothing.

The Task Manager owns its window on behalf of the shell itself (appId = 0), not any running app, which is why it survives when the desktop app terminates. It registers shellTaskMgrRefresh with shellRegisterDesktopUpdate so the list refreshes live as apps load, reap, or crash.

Integration with libtasks

The Task Manager reads from the shell's app slot table (shellGetApp, shellAppSlotCount) rather than from the libtasks scheduler directly; the list and the "Applications: N" status count both show the running apps. Each ShellAppT slot carries its cooperative task ID when hasMainLoop is true; End Task calls shellForceKillApp, which in turn calls tsKill on that task ID (for main-loop apps) after destroying the app's windows. If the app is still inside one of its own dialogs, the unload is finished once that dialog's code has returned.

Features

shellTaskMgrOpen

Open the Task Manager window, or raise it to the front if already open.

void shellTaskMgrOpen(AppContextT *ctx);
  Parameter   Description
  ---------   -----------
  ctx         Application context (from dvxInit). Required for window creation, stack manipulation, and registering the desktop-update callback.

Called by the shell's global Ctrl+Esc handler via the shellCtrlEscFn hook.

shellTaskMgrRefresh

Rebuild the task list display from the current shell app slot table and refresh the status bar. Registered with shellRegisterDesktopUpdate at open time so it is invoked whenever the shell detects app state changes.

void shellTaskMgrRefresh(void);

Takes no parameters. Safe to call even if the Task Manager window is not currently open (the call is a no-op in that case).

Text Help Library

Shared text editing infrastructure library for DVX widget DXEs. Provides cursor blink management, cross-widget selection clearing, word boundary logic, and a complete single-line text editing engine. Used by TextInput, Spinner, ComboBox, AnsiTerm, and other widgets that need text editing behavior.

Header: texthelp/textHelp.h

Loaded as: bin/libs/texthelp.lib

How Widgets Use This Library

Widget DXEs that implement text editing delegate to textHelp for the four high-cost behaviors:

The multi-line engine (TextArea) works the same way with a TextEditMultiT: widgetTextEditMultiOnKey(w, key, mod, te, visRows, visCols, opts) runs a key on it. Its clipboard and undo commands are functions of their own, which the keys and a TextArea's Copy / Cut / Paste / SelectAll / Undo / SelText run alike: textEditMultiCopy (false when nothing is selected), textEditMultiCut, textEditMultiPaste, textEditMultiReplaceSel(te, text, textLen) (replaces the selection or inserts at the cursor, as much as fits, undo saved first), textEditMultiSelectAll, and textEditMultiUndo (a second one is redo). The editing ones return true when the text changed; the caller then fires Change, scrolls to the cursor, and repaints.

Constants

  Constant             Value    Description
  --------             -----    -----------
  TEXT_INPUT_PAD              3     Pixel padding inside text editing fields.
  TEXT_EDIT_UNDO_NONE        -1     Undo length meaning "no snapshot". Set it at creation and whenever the text is replaced wholesale.
  TEXT_EDIT_DEFAULT_MAX_LEN  256    Capacity in characters of a single-line editor created with maxLen <= 0; also the BASIC default for TextBox and ComboBox.
  TEXT_EDIT_SINGLE_MAX_LEN   32767  Largest capacity of a single-line editor; a larger maxLen is reduced to it.
  TEXT_EDIT_MULTI_MAX_LEN    262144 Largest capacity of a multi-line editor (textEditMultiInit).
  TEXT_CURSOR_BLINK_MS       250    Caret blink half-period in milliseconds.

clearOtherSelections

Clears the text selection on whichever widget previously had one, unless that widget is the one passed as the argument. Tracks the last selected widget in a static pointer for O(1) lookup instead of walking the widget tree. Validates that the previous widget's window is still in the window stack before touching it, so stale pointers from closed windows are handled safely. If the previous selection was in a different window, that window gets a full repaint to clear the stale highlight.

void clearOtherSelections(WidgetT *except);
  Parameter    Type         Description
  ---------    ----         -----------
  except       WidgetT *    The widget that now owns the selection. Its selection is preserved.

isWordChar

Returns true if the character is alphanumeric or underscore. Used by word boundary functions to define what constitutes a "word" for double-click selection and Ctrl+arrow navigation.

bool isWordChar(char c);
  Parameter    Type    Description
  ---------    ----    -----------
  c            char    The character to test.

Returns true if c is alphanumeric (a-z, A-Z, 0-9) or underscore.

wordEnd

Scans forward from the given position, returning the index of the first non-word character. Used to find the right boundary of a word for double-click word selection.

int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  len          int32_t        Length of the text in the buffer.
  pos          int32_t        Starting position (character index).

Returns the index one past the last word character.

wordStart

Scans backward from the given position, returning the index of the first character of the current word. Used to find the left boundary of a word for double-click word selection.

int32_t wordStart(const char *buf, int32_t pos);
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  pos          int32_t        Starting position (character index).

Returns the index of the first character of the word containing pos.

wordBoundaryLeft

Finds the left word boundary for Ctrl+Left navigation. Skips non-word characters first, then skips word characters, leaving the cursor at the start of the previous word.

int32_t wordBoundaryLeft(const char *buf, int32_t pos);
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  pos          int32_t        Starting position (character index).

Returns the index of the previous word boundary. Returns 0 if pos is already at or before the start of the buffer.

wordBoundaryRight

Finds the right word boundary for Ctrl+Right navigation. Skips word characters first, then skips non-word characters, leaving the cursor at the start of the next word.

int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
  Parameter    Type           Description
  ---------    ----           -----------
  buf          const char *   The text buffer to scan.
  len          int32_t        Length of the text buffer.
  pos          int32_t        Starting position (character index).

Returns the index of the next word boundary. Returns len if pos is already at or past the end of the buffer.

textEditSaveUndo

Capture a snapshot of the current buffer contents and cursor position into an undo buffer. Called by the single-line engine before every destructive operation (insert, delete, paste, cut). Truncates to bufSize - 1 characters so the null terminator always fits.

void textEditSaveUndo(char *buf, int32_t len,
    int32_t cursor, char *undoBuf,
    int32_t *pUndoLen, int32_t *pUndoCursor,
    int32_t bufSize);
  Parameter       Type         Description
  ---------       ----         -----------
  buf             char *       Source buffer to snapshot.
  len             int32_t      Current length of source contents.
  cursor          int32_t      Current cursor position to save.
  undoBuf         char *       Destination undo buffer. If NULL the call is a no-op.
  pUndoLen        int32_t *    [out] Length of saved undo contents.
  pUndoCursor     int32_t *    [out] Saved cursor position.
  bufSize         int32_t      Size of the destination undo buffer including the null terminator.

textEditUndoSwap

Exchange the live buffer with the undo snapshot (single-slot undo; a second swap is redo). buf and undoBuf must both be bufSize bytes. Returns false and changes nothing when there is no snapshot (*pUndoLen is TEXT_EDIT_UNDO_NONE). On success *pCursor is the restored cursor, clamped to the restored length, and the undo slot holds the previous text and cursor.

bool textEditUndoSwap(char *buf, int32_t bufSize,
    int32_t *pLen, int32_t *pCursor, char *undoBuf,
    int32_t *pUndoLen, int32_t *pUndoCursor);

Two related helpers:

  Function              Description
  --------              -----------
  textEditCopyText      int32_t textEditCopyText(char *buf, int32_t bufSize, const char *text). Copy text into buf (truncated to bufSize - 1), terminate it, and return the length. Writes only the copied bytes.
  textEditMoveCursor    void textEditMoveCursor(int32_t newPos, bool shift, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd). Move a single-line cursor; with shift extend the selection (anchoring at the old cursor), otherwise clear it. The selection pointers may be NULL.

TextEditSingleT

Per-editor state of a single-line field. selStart/selEnd are -1 when nothing is selected; undoLen is TEXT_EDIT_UNDO_NONE until the first edit takes a snapshot.

typedef struct TextEditSingleT {
    char    *buf;
    char    *undoBuf;
    int32_t  bufSize;
    int32_t  len;
    int32_t  cursorPos;
    int32_t  scrollOff;
    int32_t  selStart;
    int32_t  selEnd;
    int32_t  undoLen;
    int32_t  undoCursor;
} TextEditSingleT;
  Function                                                                  Description
  --------                                                                  -----------
  bool textEditSingleInit(TextEditSingleT *te, int32_t maxLen)              Allocate buf and undoBuf for maxLen characters plus the terminator (TEXT_EDIT_DEFAULT_MAX_LEN when maxLen <= 0, at most TEXT_EDIT_SINGLE_MAX_LEN): empty text, no selection, no undo snapshot. Returns false (both pointers NULL) when allocation fails.
  void textEditSingleFree(TextEditSingleT *te)                              Free both buffers.
  bool textEditSingleResize(TextEditSingleT *te, int32_t maxLen)            New buffers for maxLen characters (same rule as Init) holding the old text, cut to fit. Returns false, leaving the field as it was, when allocation fails.
  void textEditSingleSetText(TextEditSingleT *te, const char *text)         Replace the text (NULL = empty, truncated to the buffer): cursor at the end, view scrolled home, no selection, undo slot emptied.
  bool textEditSingleClearSelection(TextEditSingleT *te)                    Drop the selection. Returns true when a non-empty selection was cleared.
  void textEditSingleSelRange(const TextEditSingleT *te, int32_t *lo, int32_t *hi)  The selection as [lo, hi), clamped to the text; lo == hi == the cursor when nothing is selected.
  bool textEditSingleReplaceSel(TextEditSingleT *te, const char *text, int32_t textLen)  Replace the selection (or insert at the cursor) with text, its newlines dropped and cut to what fits; the cursor after it, nothing selected, the old text in the undo slot. Returns false, with nothing changed, when nothing is selected and nothing fits. The key handler's typing and Ctrl+V use it, and a text box's SelText.
  bool textEditKeyEdits(int32_t key)                                        True for keys that modify the text (printable characters, Backspace, Delete, Ctrl+V, Ctrl+X, Ctrl+Z). Read-only editors drop these before calling the engine.
  int32_t textEditVisibleChars(int32_t fieldWidth, const BitmapFontT *font)  Whole columns visible in a field fieldWidth pixels wide, inside the text padding.
  void textEditScrollToCursor(int32_t cursor, int32_t visibleChars, int32_t *pScrollOff)  Adjust a horizontal scroll offset so the cursor is inside the visible columns.
  uint32_t textEditDefaultSyntaxRgb(uint8_t idx)                            Default syntax color (0x00RRGGBB) for a TEXT_SYNTAX_* index; 0 for TEXT_SYNTAX_DEFAULT, which uses the caller's foreground.

The key handler, the drag update, and the widgets' paint code all use textEditVisibleChars, so they agree on the visible width.

widgetTextEditDragUpdateLine

Called during mouse drag to extend the selection in a single-line text field. Converts a viewport-relative pixel x coordinate to a character position and updates the cursor and selection end accordingly. Auto-scrolls the text when the mouse moves past the left or right visible edge.

void widgetTextEditDragUpdateLine(TextEditSingleT *te, int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font);
  Parameter     Type                Description
  ---------     ----                -----------
  te            TextEditSingleT *   [in/out] Editor state: cursorPos, scrollOff, and selEnd are updated.
  vx            int32_t             Mouse x position in viewport coordinates.
  leftEdge      int32_t             Pixel x of the left edge of the text area.
  maxChars      int32_t             Maximum visible characters in the field.
  font          const BitmapFontT * Bitmap font used for character width.

widgetTextEditMouseClick

Handles mouse click events for single-line text widgets. Converts pixel coordinates to a character position using the font's character width. Detects multi-click sequences via the core multiClickDetect() function:

void widgetTextEditMouseClick(WidgetT *w, TextEditSingleT *te, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, bool wordSelect, bool dragSelect);
  Parameter     Type                Description
  ---------     ----                -----------
  w             WidgetT *           The widget receiving the click.
  te            TextEditSingleT *   [in/out] Editor state: cursorPos, selStart, and selEnd are set.
  vx            int32_t             Mouse x in viewport coordinates.
  vy            int32_t             Mouse y in viewport coordinates.
  textLeftX     int32_t             Pixel x of the left edge of the text area.
  font          const BitmapFontT * Bitmap font for character width calculation.
  wordSelect    bool                If true, double-click selects word; if false, selects all.
  dragSelect    bool                If true, single-click begins drag selection.

widgetTextEditOnKey

The core single-line text editing engine. Processes a single keystroke and updates the editor's buffer, cursor, scroll offset, selection, and undo state. Shared by TextInput, Spinner, and ComboBox.

void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, TextEditSingleT *te, int32_t fieldWidth);
  Parameter     Type                Description
  ---------     ----                -----------
  w             WidgetT *           The widget receiving the key event.
  key           int32_t             Key code (ASCII for printable, control code, or scan|0x100 for extended).
  mod           int32_t             Modifier flags (KEY_MOD_SHIFT, etc.).
  te            TextEditSingleT *   [in/out] Editor state. Ctrl+Z does nothing while undoLen is TEXT_EDIT_UNDO_NONE.
  fieldWidth    int32_t             Field width in pixels including the text padding (subtracted internally). 0 = use widget width.

Supported Keys

  Key              Action
  ---              ------
  Printable        Insert character at cursor (replaces selection if active).
  Backspace        Delete character before cursor or delete selection.
  Delete           Delete character after cursor or delete selection.
  Left             Move cursor left. With Shift, extend selection.
  Right            Move cursor right. With Shift, extend selection.
  Ctrl+Left        Move cursor to previous word boundary. With Shift, extend selection.
  Ctrl+Right       Move cursor to next word boundary. With Shift, extend selection.
  Home             Move cursor to start. With Shift, extend selection.
  End              Move cursor to end. With Shift, extend selection.
  Ctrl+A           Select all text.
  Ctrl+C           Copy selection to clipboard.
  Ctrl+V           Paste from clipboard (newlines stripped).
  Ctrl+X           Cut selection to clipboard.
  Ctrl+Z           Undo (swaps current and undo buffers).

Fires w->onChange after any mutation (insert, delete, paste, cut, undo). Automatically adjusts scroll offset to keep cursor visible after every operation.

widgetTextEditPaintLine

Renders a single line of text with optional selection highlighting and a blinking cursor. Splits the visible text into up to three segments (pre-selection, selection, post-selection) and draws each with appropriate colors. The selection highlight uses the menu highlight colors from the color scheme. The cursor is drawn as a one-pixel vertical line when sCursorBlinkOn is true.

void widgetTextEditPaintLine(
    DisplayT *d, const BlitOpsT *ops,
    const BitmapFontT *font,
    const ColorSchemeT *colors,
    int32_t textX, int32_t textY,
    const char *buf, int32_t visLen,
    int32_t scrollOff, int32_t cursorPos,
    int32_t selStart, int32_t selEnd,
    uint32_t fg, uint32_t bg,
    bool showCursor,
    int32_t cursorMinX, int32_t cursorMaxX);
  Parameter     Type                  Description
  ---------     ----                  -----------
  d             DisplayT *            Display context for drawing.
  ops           const BlitOpsT *      Blit operations table for the current pixel format.
  font          const BitmapFontT *   Bitmap font for text rendering.
  colors        const ColorSchemeT *  Color scheme (used for selection highlight colors).
  textX         int32_t               Pixel x of the start of visible text.
  textY         int32_t               Pixel y of the text baseline.
  buf           const char *          Visible portion of the text buffer (buf + scrollOff).
  visLen        int32_t               Number of visible characters to draw.
  scrollOff     int32_t               Scroll offset (used to map selection to visible range).
  cursorPos     int32_t               Absolute cursor position in the full buffer.
  selStart      int32_t               Selection start (-1 if no selection).
  selEnd        int32_t               Selection end (-1 if no selection).
  fg            uint32_t              Foreground (text) color.
  bg            uint32_t              Background color.
  showCursor    bool                  Whether to draw the cursor (false when widget lacks focus).
  cursorMinX    int32_t               Left clipping bound for cursor drawing.
  cursorMaxX    int32_t               Right clipping bound for cursor drawing.

File Transfer Library

XMODEM, XMODEM-1K, YMODEM, and ZMODEM senders and receivers that work over any byte stream: a serial port, the secure link, and later a network connection. The engines are non-blocking state machines: start a transfer with read and write callbacks, then call xferPoll with the current time in milliseconds as often as you can (every frame). A poll hands queued output to the transport, consumes the input that has arrived, and runs the timeouts. Nothing blocks and nothing touches the screen, so a program stays responsive and draws its own progress.

Loaded as: bin/libs/kpunch/xfer/xfer.lib (needs the serial library only for the BASIC functions)

Header: xfer/xfer.h

C API

Protocol details

Using transfers from BASIC

Example

static int32_t ioRead(void *ctx, uint8_t *buf, int32_t max)      { return connRead((ConnT *)ctx, buf, max); }
static int32_t ioWrite(void *ctx, const uint8_t *d, int32_t len) { return connWrite((ConnT *)ctx, d, len); }

XferT       *x = xferCreate();
XferIoT      io = { conn, ioRead, ioWrite, NULL };
XferOptionsT opt;

xferOptionsDefaults(&opt);
opt.exists = XferExistRenameE;
xferStartReceive(x, XferZmodemE, &io, &opt, "DOWNLOAD", nowMs());

// every frame:
if (xferPoll(x, nowMs()) != XferRunningE) {
    printf("%s\n", xferProgress(x)->message);
}

File Transfer C API

Types

  Type           Description
  ----           -----------
  XferT          Opaque transfer. One XferT runs one transfer at a time and can be started again when it has ended.
  XferIoT        The transport: ctx, read (never blocks; returns the bytes available, 0 when none), write (returns the bytes accepted, may be fewer), pending (optional: bytes accepted but not yet sent; a ZMODEM sender keeps it small so it reacts quickly to an error report on a slow line).
  XferOptionsT   exists (what happens to an incoming file that already exists), rxWindow (ZMODEM receiving: bytes the sender may stream before it waits; 0 = no limit), escapeCtl (ZMODEM: escape every control character), resume (ZMODEM sending: ask the receiver to continue partial files).
  XferProgressT  Status, protocol, direction, file index and count, file size and bytes done, batch size and bytes done, resume offset, retries, current file name, and the outcome message. While the status is XferAskingE, askLocalSize, askRemoteSize, and askCanResume describe the question.
  XferDetectT    State of the ZMODEM auto-start detector (zero it, or call xferDetectReset).

Enumerations

  Enumeration  Values
  -----------  ------
  XferProtoE   XferXmodemE (128-byte blocks, CRC or checksum), XferXmodem1kE (1K blocks), XferYmodemE (batch with names and sizes; block 0 carries every file-information field: size, date 0 = unknown, mode 0 = a DOS file, serial 0, files and bytes left, so receivers such as lrz never read unset fields), XferZmodemE (batch, streaming, CRC-32, resume).
  XferStatusE  XferIdleE, XferRunningE, XferAskingE (waiting for xferAnswer), XferDoneE, XferFailedE, XferCancelledE.
  XferExistE   XferExistAskE, XferExistRenameE (store as NAME~1.EXT), XferExistResumeE (ZMODEM: continue a shorter file; otherwise rename), XferExistOverwriteE, XferExistSkipE (ZMODEM skips the file; YMODEM receives and discards it).

Functions

  Function                                                  Description
  --------                                                  -----------
  XferT *xferCreate(void)                                   Allocate an idle transfer (NULL when out of memory).
  void xferDestroy(x)                                       Close any open file and free the transfer.
  void xferOptionsDefaults(opt)                             Ask when a file exists, no window limit, no extra escaping, no resume request.
  bool xferStartSend(x, proto, io, opt, paths, count, now)  Send count files (XMODEM sends the first). False, with the reason in the message, when a file cannot be opened or is larger than 2 GB. opt may be NULL.
  bool xferStartReceive(x, proto, io, opt, target, now)     XMODEM: target is the file to create. YMODEM and ZMODEM: target is the folder for the files.
  XferStatusE xferPoll(x, now)                              Move the transfer along. now is a millisecond clock (wrapping is fine).
  void xferCancel(x)                                        Send the cancel sequence to the other side and end with XferCancelledE.
  void xferAnswer(x, action, now)                           Answer XferAskingE: XferExistResumeE, RenameE, OverwriteE, or SkipE.
  const XferProgressT *xferProgress(x)                      Progress and outcome.
  bool xferDetectFeed(d, byte)                              Feed received terminal bytes; true when a ZMODEM sender's start frame (ZRQINIT) has arrived, so a terminal can start a ZMODEM receive by itself.
  void xferSafeName(name, longNames, out)                   Reduce a sender's file name to a safe one: no path, no drive, no DOS device name (CON, PRN, COM1, and so on get a leading _). With longNames (the receiving disk takes long names, platformLongNames) a long name keeping its case, blanks, and dots, where a character a long name cannot hold becomes _ and leading blanks and trailing dots and blanks go (RECEIVED when nothing is left); otherwise an 8.3 name of portable characters in upper case. out holds XFER_NAME_MAX bytes (PLATFORM_LONG_NAME_MAX + 1).
  uint16_t xferCrc16(data, len)                             CRC-16/XMODEM (polynomial 0x1021).
  uint32_t xferCrc32(data, len)                             CRC-32 (IEEE 802.3), as ZMODEM uses it.
  int32_t xferBasNew(&x)                                    For the BASIC functions of other libraries: a new xfer.bas handle owning a new idle transfer (in x) that belongs to the running program; 0 when no handle is free. The library starts the transfer over its own transport; the program follows it with the Xfer functions (the connection library's ConnSendFiles and ConnReceiveFiles work this way).
  XferT *xferBasTransfer(handle)                            The transfer of an xfer.bas handle, or NULL once the program closed it or ended.

Files

Received names are made safe with xferSafeName and stored in the receive folder. An existing file is never replaced silently: XferOptionsT.exists decides, and with XferExistAskE the transfer pauses in XferAskingE (the other side waits) until xferAnswer. A failed or cancelled XMODEM or YMODEM download deletes its partial file. A ZMODEM download keeps it, so the same file can be continued later: the receiver continues a shorter file of the same name when the sender asks for it (resume) or when exists is XferExistResumeE.

Protocol Details

XMODEM and XMODEM-1K

Packets are SOH (128 bytes) or STX (1024 bytes), the block number and its complement, the data, and a CRC-16 or a one-byte checksum. The receiver asks for CRC mode with 'C' every 3 seconds and falls back to checksums (NAK) after three tries, unless the sender has already started. Every packet is answered with ACK or NAK; two CANs cancel. After 10 failed tries of one packet the transfer gives up and cancels the other side. A damaged packet is refused only once the line has been quiet for a second, so the rest of it cannot be mistaken for the next packet. An EOT counts only when the line stays quiet after it (a stray 0x04 inside a damaged packet does not end the file early). A sender whose 1K block fails three times sends that block and the rest of the file in 128-byte blocks. XMODEM pads the last block with 0x1A (SUB); the receiver keeps the padding because the protocol does not carry the file size.

YMODEM

A batch of files, always with CRC and 1K blocks (a tail of at most 128 bytes goes in a 128-byte block). Block 0 carries the file name and size before each file (the long name when it fits in the 128-byte block beside the other fields, else the 8.3 name), and an empty block 0 ends the batch. The receiver refuses the first EOT of each file and accepts the second, and cuts the padding using the size.

ZMODEM

Headers are sent in hex (to start and answer) or binary with CRC-16 or CRC-32; data travels in subpackets with a CRC over the data and the frame end. ZDLE escaping protects ZDLE, DLE, XON, and XOFF (with and without the high bit), and a CR after '@'; with escapeCtl every control character is escaped (the sender announces it with ZSINIT; the receiver asks for it in ZRINIT). Five CANs in a row cancel.

The sender streams: it keeps at most 8 KB unacknowledged, asks for a progress acknowledgement every 4 KB, honours the receiver's buffer size (a receiver without flow control should set rxWindow to what its hardware buffer holds, so a slow frame never loses data), and builds a subpacket only when the transport has sent nearly everything it was given (XferIoT.pending). On an error the receiver sends ZRPOS with the last good position; the sender goes back there and halves its subpacket size (down to 64 bytes), then doubles it again after eight clean subpackets. A sender that gets no acknowledgement for the window goes back to the last acknowledged position. File names (long names as they are) travel in the ZFILE subpacket with the size and the files and bytes left in the batch, which the receiver shows as batch progress. Commands from the other side (ZCOMMAND) are never run.

Files are at most 2 GB (sizes and positions are 32-bit): a send refuses a larger file, a size offered past 2 GB reads as unknown, and a receive that passes 2 GB stops with "A file larger than 2 GB cannot be transferred". A file already on the disk is never mistaken for a missing one, whatever its size.

Interoperability: the engines are tested against lrz and lsz (lrzsz 0.12.20) in both directions for every protocol, including escaping, resume, and long file names.

File Transfers from BASIC

Add xfer.bas (and comm.bas, for SerOpen) to a BASIC project to send and receive files over a serial port. Start a transfer, then call XferPoll often, for example from a Timer, until it no longer returns XFER_RUNNING. While it runs, do not read the port yourself and keep it detached from any Terminal control. Received files never replace existing ones: the new file gets a new name.

Transfers over a connection (conn.bas: ConnSendFiles, ConnReceiveFiles) are the same handles, with the options a terminal needs (what to do with an existing file -- XFER_EXIST_ASK stops in XFER_ASKING for XferAnswer --, escaping every control character, continuing interrupted files); ConnPoll moves them along. Every handle belongs to the program that made it: whatever a program leaves open is cancelled and freed when it ends.

  Function                             Description
  --------                             -----------
  XferSendFile(com, proto, path)       Send one file over COM port com (1-4). Returns a handle, or 0.
  XferReceive(com, proto, target)      Receive: XMODEM into the file target; YMODEM and ZMODEM into the folder target. Returns a handle, or 0.
  XferPoll(handle)                     Move the transfer along. Returns XFER_RUNNING, XFER_ASKING, XFER_DONE, XFER_FAILED, or XFER_CANCELLED.
  XferStatus(handle)                   The same, without moving it along (XFER_IDLE for a bad handle).
  XferSending(handle), XferProtocol(handle)  True for a send; the protocol.
  XferBytes(handle), XferSize(handle)  Bytes of the current file done, and its size (-1 when unknown).
  XferFile$(handle)                    The file being transferred, as stored on this computer.
  XferFileIndex(handle), XferFileCount(handle)  Which file of the batch (from 0), and how many (-1 while unknown).
  XferBatchBytes(handle), XferBatchSize(handle)  Bytes of the whole batch done, and its size (-1 when unknown).
  XferResumedAt(handle), XferErrors(handle)  Where the current file continued from; retries so far.
  XferAskSize(handle), XferCanResume(handle)  XFER_ASKING: the size of the file already there; True when XFER_EXIST_RESUME can continue it.
  XferAskRemoteSize(handle)            XFER_ASKING: the size the sender offers (XferProgressT.askRemoteSize; -1 when unknown).
  XferAnswer handle, action            Answer XFER_ASKING with XFER_EXIST_RENAME, _RESUME, _OVERWRITE, or _SKIP.
  XferMessage$(handle)                 How the transfer ended, or why it failed.
  XferCancel handle                    Stop a running transfer (the other side is told).
  XferClose handle                     Free the handle.
  XferCrc16(bytes), XferCrc32(bytes)   xferCrc16 / xferCrc32 of a BINARY STRING (every byte value counts); the CRC-32 as a LONG (negative when its top bit is set).
  XferSafeName$(fileName, folder)      xferSafeName: the name a receive into folder stores a sender's file name as (a long name where folder's disk takes them, else 8.3).

XFER_WINDOW_MAX (xfer.bas) is the largest ZMODEM receive window conn.bas's ConnSetXferWindow takes.

Protocols: XFER_XMODEM, XFER_XMODEM1K, XFER_YMODEM, XFER_ZMODEM. At most eight transfers run at once (XFER_MAX_FILES files in one batch).

Dim h As Long
If SerOpen(1, 115200, 8, "N", 1, SER_HANDSHAKE_RTSCTS) Then
    h = XferReceive(1, XFER_ZMODEM, "C:\DOWNLOAD")
End If

Sub Timer1_Timer ()
    If h <> 0 Then
        If XferPoll(h) <> XFER_RUNNING Then
            Print XferFile$(h); ": "; XferMessage$(h)
            XferClose h
            h = 0
        End If
    End If
End Sub

DVX Widget System

Complete reference for the DVX GUI widget toolkit. All widgets are implemented as dynamically loaded DXE modules. They are created via convenience macros that wrap the per-widget API function tables. The base WidgetT structure is defined in libdvx/dvxWgt.h; individual widget headers live in widgets/.

Individual widgets are documented in their own sections. See the table of contents for the full list.

Base WidgetT (Common Properties, Events, and Operations)

Every widget inherits from the WidgetT structure defined in libdvx/dvxWgt.h. The fields and callbacks listed here are available on all widget types.

Common Properties

  Field              Type                Description
  -----              ----                -----------
  name               char[32]            Widget name for lookup via wgtFind().
  x, y, w, h         int32_t             Computed geometry relative to the window content area (set by layout).
  minW, minH          int32_t (tagged)    Minimum size hints: the widget is never laid out smaller. Use wgtPixels(), wgtChars(), or wgtPercent(). 0 = auto. A fixed width in a row (a button's, a column of labels) is minW with weight 0.
  maxW, maxH          int32_t (tagged)    Maximum size constraints: the widget never grows larger; across its box a capped widget is centred. 0 = no limit.
  weight             int32_t             Extra-space distribution weight. 0 = fixed, 100 = normal. A widget with weight=200 gets twice the extra space of one with weight=100.
  align              WidgetAlignE        Main-axis alignment for children: AlignStartE, AlignCenterE, AlignEndE.
  spacing            int32_t (tagged)    Spacing between children (containers only). WGT_GAP_DEFAULT = the class's default; 0 = none.
  padding            int32_t (tagged)    Internal padding (containers only). WGT_GAP_DEFAULT = the class's default; 0 = none.
  fgColor            uint32_t            Foreground color override. 0 = use color scheme default.
  bgColor            uint32_t            Background color override. 0 = use color scheme default.
  visible            bool                Visibility state.
  enabled            bool                Enabled state. Disabled widgets are grayed out and ignore input.
  readOnly           bool                Read-only mode: allows scrolling/selection but blocks editing.
  swallowTab         bool                When true, Tab key goes to the widget instead of navigating focus.
  accelKey           char                Lowercase accelerator character. 0 if none.
  tooltip            const char *        Tooltip text. NULL = none. Caller owns the string.
  contextMenu        MenuT *             Right-click context menu. NULL = none. Caller owns.
  userData           void *              Application-defined user data pointer.

Size Specification Macros

  Macro              Description
  -----              -----------
  wgtPixels(v)       Size in pixels.
  wgtChars(v)        Size in character widths (multiplied by font charWidth).
  wgtPercent(v)      Size as a percentage of parent dimension.

Common Events (Callbacks)

These callback function pointers are available on every WidgetT. Set them directly on the widget struct.

  Callback           Signature                                                   Description
  --------           ---------                                                   -----------
  onClick            void (*)(WidgetT *w)                                        Fires on mouse click / activation.
  onDblClick         void (*)(WidgetT *w)                                        Fires on double-click.
  onChange           void (*)(WidgetT *w)                                        Fires when the widget's value changes (text, selection, check state, etc.).
  onFocus            void (*)(WidgetT *w)                                        Fires when the widget receives keyboard focus.
  onBlur             void (*)(WidgetT *w)                                        Fires when the widget loses keyboard focus.
  onKeyPress         void (*)(WidgetT *w, int32_t keyAscii)                      Fires on a printable key press (ASCII value).
  onKeyDown          void (*)(WidgetT *w, int32_t keyCode, int32_t shift)        Fires on key down (scan code + shift state).
  onKeyUp            void (*)(WidgetT *w, int32_t keyCode, int32_t shift)        Fires on key up.
  onMouseDown        void (*)(WidgetT *w, int32_t button, int32_t x, int32_t y)  Fires on mouse button press.
  onMouseUp          void (*)(WidgetT *w, int32_t button, int32_t x, int32_t y)  Fires on mouse button release.
  onMouseMove        void (*)(WidgetT *w, int32_t button, int32_t x, int32_t y)  Fires on mouse movement over the widget.
  onScroll           void (*)(WidgetT *w, int32_t delta)                         Fires on mouse wheel scroll.
  onValidate         bool (*)(WidgetT *w)                                        Validation callback. Return false to cancel a pending write.
  onEvent            void (*)(WidgetT *w, const char *name, WgtValueT *args, int32_t argc)  A named event the widget fires that has no callback above (ListView ColumnClick, TextArea GutterClick, HelpView LinkClick, an event a BASIC control raises). args may be written back.

wgtFireEvent(w, name, args, argc) fires an event by name: the names above (Click, DblClick, Change, GotFocus, LostFocus, KeyPress, KeyDown, KeyUp, MouseDown, MouseUp, MouseMove, Scroll) go to their callback, every other name to onEvent (see dvxWgt.h, Custom Events).

Generic Access

Every widget type can be created, read, written, and called by name through its interface descriptor, with values as WgtValueT -- the way the BASIC form runtime and the designer drive widgets, and the way a C program uses a widget it has no header for (a BASIC control, for one):

WidgetT  *list = wgtCreateByName("ListBox", parent);
WgtValueT item = wgtValueStr("first");
WgtValueT count;

wgtCallMethod(list, "AddItem", &item, 1, NULL);
wgtCallMethod(list, "ListCount", NULL, 0, &count);   // count.i == 1

wgtGetProp / wgtSetProp work the same way. See dvxWgt.h, Generic Widget Access.

Common Operations

  Function                                                    Description
  --------                                                    -----------
  WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win)      Initialize widgets for a window. Returns the root VBox container.
  AppContextT *wgtGetContext(const WidgetT *w)                 Walk up from any widget to retrieve the AppContextT.
  void wgtInvalidate(WidgetT *w)                               Mark widget for re-layout and repaint. Propagates to ancestors.
  void wgtInvalidatePaint(WidgetT *w)                          Mark widget for repaint only (no layout recalculation).
  void wgtSetText(WidgetT *w, const char *text)                Set widget text (label, button, textinput, etc.).
  const char *wgtGetText(const WidgetT *w)                     Get widget text.
  void wgtSetEnabled(WidgetT *w, bool enabled)                 Enable or disable a widget.
  void wgtSetReadOnly(WidgetT *w, bool readOnly)               Set read-only mode.
  void wgtSetFocused(WidgetT *w)                               Set keyboard focus to a widget.
  WidgetT *wgtGetFocused(void)                                 Get the currently focused widget.
  void wgtSetVisible(WidgetT *w, bool visible)                 Show or hide a widget.
  void wgtSetName(WidgetT *w, const char *name)                Set widget name for lookup.
  WidgetT *wgtFind(WidgetT *root, const char *name)            Find a widget by name in the subtree.
  void wgtDestroy(WidgetT *w)                                  Destroy a widget and all its children.
  void wgtSetTooltip(WidgetT *w, const char *text)             Set tooltip text. Pass NULL to remove.

AnsiTerm

A VT100/ANSI-compatible terminal emulator widget designed for connecting to BBS systems over the serial link. Uses a traditional text-mode cell buffer (character + attribute byte pairs) with the CP437 character set and 16-color CGA palette. Supports cursor movement, screen/line erase, scrolling regions, SGR colors, DEC private modes, ANSI music, and a configurable scrollback buffer. Communication is abstracted through read/write function pointers, allowing the terminal to work with raw serial ports, the secLink encrypted channel, or any other byte-oriented transport.

WCLASS_FIXED_FONT: it edits by columns, so its font must be fixed-pitch. Any size works (the column math uses the font's charWidth and charHeight); a proportional font set with wgtSetFont, or inherited, is replaced by the system font when it draws.

The widget renders through two paint paths: a full paint used by the normal widget pipeline, and a fast incremental repaint (wgtAnsiTermRepaint) that pushes dirty rows directly to the window content buffer for low-latency serial echo.

Header: widgets/ansiTerm.h

Creation

WidgetT *term = wgtAnsiTerm(parent, 80, 25);

API Functions

  Function                                                      Description
  --------                                                      -----------
  WidgetT *wgtAnsiTerm(parent, cols, rows)                      Create an ANSI terminal widget with the given column/row dimensions.
  void wgtAnsiTermWrite(w, data, len)                           Write raw bytes into the terminal's ANSI parser. data is a const uint8_t * buffer.
  void wgtAnsiTermClear(w)                                      Clear the terminal screen and reset the cursor to the home position.
  void wgtAnsiTermSetComm(w, ctx, readFn, writeFn)              Attach a communication channel. readFn and writeFn are I/O callbacks; ctx is passed as their first argument.
  void wgtAnsiTermSetScrollback(w, maxLines)                    Set the maximum number of scrollback lines.
  int32_t wgtAnsiTermPoll(w)                                    Poll the communication channel for incoming data and feed it into the parser. Returns number of bytes consumed.
  int32_t wgtAnsiTermRepaint(w, outY, outH)                     Fast repaint path that renders dirty rows directly into the window's content buffer, bypassing the widget pipeline. Returns the number of rows repainted and the dirty region via outY/outH.

API Struct (wgtRegisterApi "ansiterm")

The sApi struct exposes these function pointers:

  Slot             Function
  ----             --------
  header           WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create           wgtAnsiTerm
  write            wgtAnsiTermWrite
  clear            wgtAnsiTermClear
  setComm          wgtAnsiTermSetComm
  setScrollback    wgtAnsiTermSetScrollback
  poll             wgtAnsiTermPoll
  repaint          wgtAnsiTermRepaint

Extension API Struct (wgtRegisterApi "ansitermex")

Features added after the original table live in a second table so AnsiTermApiT keeps its layout for modules already built against it. dvxAnsiTermExApi() returns it (NULL when the loaded widget is older).

  Slot             Function                                   Description
  ----             --------                                   -----------
  header           WGT_API_HEADER                             ABI version and size, checked by wgtGetApi.
  copySelection    bool wgtAnsiTermCopy(w)                    Copy the selection to the clipboard and clear it (a menu's Copy). Returns false when nothing is selected.
  setPalette       void wgtAnsiTermSetPalette(w, rgb)         Replace the 16 attribute colors with ANSITERM_PALETTE_BYTES bytes of red, green, blue triplets (a color scheme). NULL restores the standard CGA colors.
  setMusic         void wgtAnsiTermSetMusic(w, on)            Recognize ANSI music (on at first). Off, ESC[M is always Delete Line and ESC[N and ESC[| do nothing.

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Cols         Integer    Read/Write   Number of columns. Setting it (or Rows) resizes the terminal (1-512): screen and scrollback start empty and the modes the host set (wrap, origin, cursor visible, scroll region) are reset; palette, scrollback length, and comm callbacks stay; the window is laid out again.
  Music        Boolean    Read/Write   Recognize ANSI music (setMusic; True at first).
  Rows         Integer    Read/Write   Number of rows.
  Scrollback   Integer    Read/Write   Maximum number of scrollback lines. Setting it discards the existing history.

Methods (BASIC Interface)

  Method       Description
  ------       -----------
  Clear        Clear the terminal screen.
  Copy         Copy the selection (copySelection); returns False when nothing is selected.
  Paste        Send the clipboard to the comm write callback as Ctrl+V does (new lines as CR, 64-byte chunks).
  Poll         Process any pending bytes on the attached communication channel.
  ResetColors  The standard CGA colors again (setPalette NULL).
  SetColor     index, color: one attribute color (0-15) from a BASIC color (&HBBGGRR, converted with wgtBasicColor).
  Write        Write a string into the terminal (with ANSI escape processing).

Events

Besides the common events, two of its own, fired through wgtFireEvent (a C host gets them in onEvent with the names ANSITERM_EVT_MUSIC and ANSITERM_EVT_BELL):

  Event   Parameters          Description
  -----   ----------          -----------
  Music   Macro As String     An ANSI music string arrived: the BASIC PLAY text between ESC[| or ESC[N, or ESC[MF (MB, MN, ML, MS), and Ctrl+N (0x0E). For the ESC[M form the macro begins with that M command (e.g. "MFT120O4L8CDE"). Line ends are left out; a string longer than 4096 characters, an empty one, or one cut short by ESC fires nothing.
  Bell    (none)              A BEL character (Ctrl+G) arrived.

The widget plays nothing itself: a handler plays the macro (BASIC: PLAY Macro; C: the music library's musicMmlPlay). While a Music or Bell handler runs, the terminal takes no input: Poll leaves the bytes in the connection, and Write keeps them, in order, until the handler returns. A tune played in the foreground (MF) therefore holds back the text after it, as on a DOS terminal, while the window stays responsive. Bare ESC[M (no parameter) followed by anything but F, B, N, L, or S is still Delete Line. A handler may close the terminal's window; the input after the event is then dropped.

Keyboard Shortcuts

Within the terminal widget:

  Key              Action
  ---              ------
  Ctrl+C           Copy selection to the clipboard (when a selection exists).
  Ctrl+V           Send the clipboard contents to the attached write function.
  Arrows           Send VT100 cursor escape sequences.
  Home/End         Send VT100 Home/End sequences.
  PgUp/PgDn/Del    Send the corresponding VT100 escape sequences.
  Shift+PgUp       Page back through the scrollback history.
  Shift+PgDn       Page forward through the scrollback history.

Any key sent to the host returns the view to the live screen. The mouse wheel scrolls through the scrollback history locally; it does not send keys to the host. Clearing the screen (ESC[2J, form feed, reset, or Clear) saves the screen into the scrollback only up to its last non-blank row.

Box (VBox / HBox / Frame)

Container widgets that arrange their children in a vertical column (VBox), horizontal row (HBox), or a titled group box (Frame). These are the primary layout building blocks. Children are laid out using a weight-based algorithm with configurable spacing, padding, and alignment.

Frame is a labelled grouping container with a Motif-style beveled border. Its title text sits centered vertically on the top border line with a small background-filled gap behind the title, giving the classic Windows 3.1 / Motif group box appearance. Internally, Frame behaves like a VBox for layout purposes.

The widget DXE registers three separate interface entries ("vbox", "hbox", "frame") so the form designer can create each type independently.

Header: widgets/box.h

Creation

WidgetT *row   = wgtHBox(parent);
WidgetT *col   = wgtVBox(parent);
WidgetT *group = wgtFrame(parent, "Options");

API Functions

  Function                                            Description
  --------                                            -----------
  WidgetT *wgtVBox(parent)                            Create a vertical box container. Children stack top-to-bottom.
  WidgetT *wgtHBox(parent)                            Create a horizontal box container. Children stack left-to-right.
  WidgetT *wgtFrame(parent, title)                    Create a titled group box. Children stack vertically inside the bordered frame. The title string may include a '&' prefix for an accelerator key.

API Struct (wgtRegisterApi "box")

  Slot     Function
  ----     --------
  header   WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  vBox     wgtVBox
  hBox     wgtHBox
  frame    wgtFrame

The designer also registers per-type APIs: "vbox", "hbox", and "frame" each expose a single create slot.

Properties

Box containers use the common WidgetT fields for layout control. There are no widget-specific properties registered with the interface system.

  Field      Description
  -----      -----------
  align      Main-axis alignment of children. HBox: Start=left, Center=center, End=right. VBox: Start=top, Center=center, End=bottom.
  spacing    Gap between children (tagged size). WGT_GAP_DEFAULT (the default): 4 pixels for VBox/HBox, 0 for Frame; wgtPixels(0) is none.
  padding    Internal padding around children (tagged size). WGT_GAP_DEFAULT (the default): 4 pixels; wgtPixels(0) is none.
  weight     Controls how the box itself stretches within its parent.

Frame text is managed via the standard wgtSetText() / wgtGetText() interface (the widget has WCLASS_HAS_TEXT). BASIC code can set its title via the generic "Caption" or "Text" property.

Events

Containers use the common events only. No widget-specific events or methods are registered.

Default Event

"Click" on all three types.

Button

A push button with a text label. Fires onClick when pressed and released. Uses a two-phase press model: the button visually depresses on mouse-down and fires onClick only when the mouse is released while still inside the button bounds -- dragging the mouse off cancels the press. Supports accelerator keys via '&' prefix in the text (e.g. "&OK" underlines 'O' and binds Alt+O).

Disabled buttons use the classic "embossed" rendering (highlight text offset by +1,+1 and shadow text at 0,0) for a chiseled appearance.

Header: widgets/button.h

Creation

WidgetT *btn = wgtButton(parent, "&OK");
btn->onClick = onOkClicked;

API Functions

  Function                                      Description
  --------                                      -----------
  WidgetT *wgtButton(parent, text)              Create a push button with the given label text. Pass NULL for no text. Text is copied into the widget.

API Struct (wgtRegisterApi "button")

  Slot     Function
  ----     --------
  header   WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create   wgtButton

Properties

Uses common WidgetT properties. Label text is managed via wgtSetText() / wgtGetText(). Set accelKey for keyboard shortcut (automatically parsed from '&' prefix in the text). The button is focusable (WCLASS_FOCUSABLE) and draws a focus rectangle when it holds keyboard focus.

No widget-specific properties are registered with the interface system. BASIC code sets the label via the generic "Caption" or "Text" property.

Methods

No widget-specific methods.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when the button is clicked (mouse press and release inside, or Space/Enter when focused).

Default Event

"Click" (VB basName: CommandButton, namePrefix: Command).

Canvas

A freeform drawing surface with a fixed-size pixel buffer stored in the display's native pixel format. Provides drawing primitives (lines, rectangles, circles, text, individual pixels; coordinates may lie anywhere, drawing is clipped to the buffer) and supports loading BMP/PNG/JPEG/GIF files and saving BMP or PNG files (via dvxLoadImage/dvxSaveImage). Mouse interaction is available via a callback. The buffer is rendered to screen as a straight blit with no per-pixel conversion.

Canvas coordinates are independent of widget position: (0,0) is the top-left of the canvas content, not the widget. By default the widget frames the buffer with a sunken bevel CANVAS_BORDER_WIDTH (2) pixels wide; wgtCanvasSetBorderStyle(w, CANVAS_BORDER_NONE) drops it, so the widget is exactly the buffer's size (a drawing surface for a UserControl or a form). The frame width is the widget's contentOffX / contentOffY, so mouse coordinates are relative to the buffer either way.

Header: widgets/canvas.h

Creation

WidgetT *cv = wgtCanvas(parent, 320, 200);

API Functions

  Function                                                             Description
  --------                                                             -----------
  WidgetT *wgtCanvas(parent, w, h)                                     Create a canvas with the given pixel dimensions. Buffer is initialized to white.
  void wgtCanvasClear(w, color)                                        Fill the entire canvas with a packed color.
  void wgtCanvasSetPenColor(w, color)                                  Set the drawing pen color (packed display color).
  void wgtCanvasSetPenSize(w, size)                                    Set the drawing pen size in pixels (affects DrawLine dot thickness). Clamped to 255; sizes below 1 are ignored.
  void wgtCanvasDrawLine(w, x0, y0, x1, y1)                           Draw a line using Bresenham's algorithm with the current pen color and size.
  void wgtCanvasDrawRect(w, x, y, width, height)                      Draw a 1-pixel rectangle outline using the current pen color.
  void wgtCanvasFillRect(w, x, y, width, height)                      Fill a rectangle using the current pen color.
  void wgtCanvasFillCircle(w, cx, cy, radius)                         Fill a circle using the current pen color (integer sqrt; no FPU needed).
  void wgtCanvasSetPixel(w, x, y, color)                              Set a single pixel to the given packed color.
  uint32_t wgtCanvasGetPixel(w, x, y)                                 Read a single pixel. Returns packed color.
  void wgtCanvasDrawText(w, x, y, text)                               Draw text at the given canvas coordinates using the current pen color.
  void wgtCanvasDrawTextScaled(w, x, y, text, scale)                  Draw text scale times as large (each font pixel a scale x scale block) in the pen color: titles. Scale 1 is wgtCanvasDrawText. BASIC: FontScale and Print on a PictureBox.
  void wgtCanvasSetMouseCallback(w, cb)                                Set a mouse callback. Signature: void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag). cx/cy are canvas-relative, drag=true on mouse-move during press.
  int32_t wgtCanvasSave(w, path)                                       Save the canvas buffer to an image file: BMP when path ends in .bmp, PNG otherwise. Returns 0 on success, -1 on error.
  int32_t wgtCanvasLoad(w, path)                                       Load an image file into the canvas (resizes buffer to match). Returns 0 on success, -1 on error.
  void wgtCanvasResize(w, newW, newH)                                 Resize the canvas buffer and relayout. The old content is discarded; the new buffer is white.
  void wgtCanvasSetBorderStyle(w, style)                               CANVAS_BORDER_NONE (0) drops the sunken frame, any other value (CANVAS_BORDER_FIXED, 1, the default) shows it. The buffer keeps its size; the widget relays out.
  int32_t wgtCanvasGetBorderStyle(w)                                   CANVAS_BORDER_FIXED or CANVAS_BORDER_NONE.
  bool wgtCanvasGetDisplay(w, out)                                     Fill a DisplayT aimed at the canvas pixels (the screen's pixel format, clipped to the canvas), so any libdvx drawing call -- drawText, drawBevel, drawArrowGlyph, wmDrawScrollbarAt, rectCopyTransparent -- draws on the canvas with the context's blit ops; call wgtInvalidatePaint afterwards. False (out untouched) for a widget that is not a canvas. The BASIC DrawBevel, DrawButtonFace, DrawArrow, DrawScrollBar, DrawScrollArrow, and PrintAccel methods use it.

API Struct (wgtRegisterApi "canvas")

  Slot                 Function
  ----                 --------
  header               WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create               wgtCanvas
  clear                wgtCanvasClear
  setPenColor          wgtCanvasSetPenColor
  setPenSize           wgtCanvasSetPenSize
  setMouseCallback     wgtCanvasSetMouseCallback
  save                 wgtCanvasSave
  load                 wgtCanvasLoad
  drawLine             wgtCanvasDrawLine
  drawRect             wgtCanvasDrawRect
  fillRect             wgtCanvasFillRect
  fillCircle           wgtCanvasFillCircle
  setPixel             wgtCanvasSetPixel
  getPixel             wgtCanvasGetPixel
  drawText             wgtCanvasDrawText
  resize               wgtCanvasResize
  setBorderStyle       wgtCanvasSetBorderStyle
  getBorderStyle       wgtCanvasGetBorderStyle
  getDisplay           wgtCanvasGetDisplay
  drawTextScaled       wgtCanvasDrawTextScaled

Properties

Drawing state (pen color, pen size) is managed by the API functions above. The BASIC interface registers these properties:

  Property      Type      Access      Description
  --------      ----      ------      -----------
  BorderStyle   Enum      Read/Write  None (0) or FixedSingle (1, the default): wgtCanvasSetBorderStyle / wgtCanvasGetBorderStyle.
  ImageHeight   Integer   Read-only   Height of the canvas buffer in pixels (0 when there is no buffer).
  ImageWidth    Integer   Read-only   Width of the canvas buffer in pixels (0 when there is no buffer).

Methods (BASIC Interface)

  Method                                        Description
  ------                                        -----------
  Clear color%                                  Fill the entire canvas with a color (&HBBGGRR, as RGB() makes it).
  DrawLine x0%, y0%, x1%, y1%, color%           Draw a line between two points.
  DrawRect x%, y%, w%, h%, color%               Draw a rectangle outline.
  DrawText x%, y%, text$                        Draw text at the given position.
  FillCircle cx%, cy%, radius%, color%          Fill a circle.
  FillRect x%, y%, w%, h%, color%               Fill a rectangle.
  GetPixel(x%, y%)                              Returns the color at a pixel (&HBBGGRR). With fewer than 16 million colors on the screen it is the color the screen shows: black, white, and full colors such as RGB(255, 0, 0) read back exactly, others may differ slightly.
  Load(path$)                                   Load a BMP, PNG, JPEG, or GIF image (the canvas takes its size). Returns 0 on success, -1 on error (WGT_SIG_RET_INT_STR).
  Resize w%, h%                                 Resize the canvas (the drawing is cleared to white).
  Save(path$)                                   Save the canvas: BMP when path$ ends in .bmp, PNG otherwise. Returns 0 on success, -1 on error (WGT_SIG_RET_INT_STR).
  SetPenColor color%                            Set the drawing color (&HBBGGRR).
  SetPenSize size%                              Set the pen size in pixels (1 to 255).
  SetPixel x%, y%, color%                       Set a single pixel.

Events

  Callback                                          Description
  --------                                          -----------
  onClick                                           Fires when the canvas is clicked.
  Mouse callback (via wgtCanvasSetMouseCallback)    Fires on mouse down and drag with canvas-local coordinates.

Default Event

"Click" (VB basName: PictureBox, namePrefix: Picture).

Checkbox

A toggle control with a text label. Clicking toggles between checked and unchecked states. Fires onChange each time the state flips. Supports accelerator keys (via '&' in the text) and keyboard toggle via Space or Enter when focused.

The check mark is drawn as an "X" pattern rather than a traditional checkmark glyph. The focus rectangle wraps the label text (not the box), matching the Windows 3.1 convention.

Header: widgets/checkbox.h

Creation

WidgetT *cb = wgtCheckbox(parent, "Enable &logging");

API Functions

  Function                                                Description
  --------                                                -----------
  WidgetT *wgtCheckbox(parent, text)                      Create a checkbox with the given label text.
  bool wgtCheckboxIsChecked(w)                            Returns true if the checkbox is checked.
  void wgtCheckboxSetChecked(w, checked)                  Set the checked state programmatically. Triggers a repaint but does NOT fire onChange.

API Struct (wgtRegisterApi "checkbox")

  Slot          Function
  ----          --------
  header        WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create        wgtCheckbox
  isChecked     wgtCheckboxIsChecked
  setChecked    wgtCheckboxSetChecked

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Value        Boolean    Read/Write   Whether the checkbox is checked.

Label text is managed via the standard wgtSetText() / wgtGetText() interface. BASIC code uses the generic "Caption" or "Text" property.

Methods

No widget-specific methods.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when clicked (after the toggle has already applied).
  onChange     Fires when the checked state changes (mouse click or keyboard toggle).

Default Event

"Click" (VB basName: CheckBox).

ComboBox

A combination of a single-line text input and a drop-down list. The user can type a value or select one from the list. When the user picks a list item, its text is copied into the edit buffer. Supports full text editing (cursor movement, selection, clipboard, undo) via the texthelp library and a popup overlay list via the listhelp library. Setting the text or picking a list item clears the undo history. Keyboard: Down, F4, or Alt+Down/Alt+Up opens the list; while it is open Down/Up move the highlight and Enter, F4, or Alt+Down/Alt+Up pick the item and close it (Esc closes without picking).

A read-only combo box (wgtSetReadOnly) ignores the editing keys (typing, Backspace, Delete, paste, cut, undo) but still allows cursor movement, selection, copy, and picking an item from the list.

Depends on "texthelp" and "listhelp" helper libraries (declared in combobox.dep).

WCLASS_FIXED_FONT: it edits by columns, so its font must be fixed-pitch. Any size works (the column math uses the font's charWidth and charHeight); a proportional font set with wgtSetFont, or inherited, is replaced by the system font when it draws.

Header: widgets/comboBox.h

Creation

WidgetT *cb = wgtComboBox(parent, 128);
wgtComboBoxAddItem(cb, "Arial");
wgtComboBoxAddItem(cb, "Courier");
wgtComboBoxAddItem(cb, "Times");

API Functions

  Function                                                Description
  --------                                                -----------
  WidgetT *wgtComboBox(parent, maxLen)                    Create a combo box. maxLen is the maximum editable text length (0 => default 256).
  void wgtComboBoxSetItems(w, items, count)               Set the dropdown items from a const char ** array. Items are not copied -- caller owns them.
  int32_t wgtComboBoxGetSelected(w)                       Get the index of the last selected item (-1 if the text was typed freely).
  void wgtComboBoxSetSelected(w, idx)                     Select an item by index. Copies its text into the edit buffer.
  void wgtComboBoxAddItem(w, text)                        Append an item to the owned list (strdup'd).
  void wgtComboBoxRemoveItem(w, idx)                      Remove the item at idx. The selection stays on the same item; removing the selected item leaves no selection (the edit text is kept).
  void wgtComboBoxClear(w)                                Remove all owned items and reset the selection.
  const char *wgtComboBoxGetItem(w, idx)                  Get the text of an item by index.
  int32_t wgtComboBoxGetItemCount(w)                      Get the total number of items.
  void wgtComboBoxSetMaxLength(w, maxLen)                 Change the maximum editable text length (0 => default 256); longer text is cut.
  int32_t wgtComboBoxGetMaxLength(w)                      Get the maximum editable text length.

API Struct (wgtRegisterApi "combobox")

  Slot           Function
  ----           --------
  header         WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create         wgtComboBox
  setItems       wgtComboBoxSetItems
  getSelected    wgtComboBoxGetSelected
  setSelected    wgtComboBoxSetSelected
  addItem        wgtComboBoxAddItem
  removeItem     wgtComboBoxRemoveItem
  clear          wgtComboBoxClear
  getItem        wgtComboBoxGetItem
  getItemCount   wgtComboBoxGetItemCount
  setMaxLength   wgtComboBoxSetMaxLength
  getMaxLength   wgtComboBoxGetMaxLength

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  ListIndex    Integer    Read/Write   Index of the currently selected item (-1 if none).
  MaxLength    Integer    Read/Write   Most characters the edit field holds (0 = the default, 256).

Editable text is accessible via the generic "Text" property. "ListCount" is available through the ListCount method.

Methods (BASIC Interface)

  Method              Description
  ------              -----------
  AddItem text$       Append an item to the dropdown list.
  Clear               Remove all items and clear the selection.
  List(index%)        Return the text of the item at the given index.
  ListCount()         Return the total number of items.
  RemoveItem index%   Remove the item at the given index. The selection stays on the same item; removing the selected item leaves no selection (the text is kept).

Events

  Callback     Description
  --------     -----------
  onChange     Fires when the selection or text changes.

Default Event

"Click" (VB basName: ComboBox).

DataCtrl

A VB3-style Data control for database binding. Displays a visible navigation bar (|< < > >|) that connects to a SQLite database via dvxSql* functions. Reads all rows from the RecordSource query into an in-memory cache for bidirectional navigation. Fires a Reposition event each time the current row changes so bound controls can update. Supports master-detail linking between Data controls (via MasterSource / MasterField / DetailField), INSERT via AddNew, and DELETE via Delete. UPDATE and DELETE identify the row by the KeyColumn value it had when it was loaded, so editing the key column changes the key instead of writing to another row; an UPDATE writes only the changed columns and fails (the row stays unsaved) when no database row matches. A key the quoted text match cannot find (an integer in a column without a declared type, a REAL, or NULL) is matched by its text form instead, as long as that names exactly one row. Setting a field to the value it already has is not an edit. With a read-only source, pending edits are dropped when the cursor moves. Refresh saves a pending edit first; if that save fails or is cancelled the edit is discarded. With a SELECT RecordSource the control is read-only: Update and Delete do nothing, and a master-detail filter is applied to the query as a subquery.

The widget depends on the "dvxsql" library DXE; the navigation logic caches the full result set so there is no round-trip for MoveNext/MovePrev.

Header: widgets/dataCtrl.h

Creation

WidgetT *data = wgtDataCtrl(parent);

Set properties (DatabaseName, RecordSource, KeyColumn, etc.) and then call dataCtrlRefresh to populate.

API Functions

  Function                                                   Description
  --------                                                   -----------
  WidgetT *dataCtrlCreate(parent)                            Create a Data control. Exposed as create in the API struct.
  void dataCtrlRefresh(w)                                    Re-execute the RecordSource query and rebuild the row cache.
  void dataCtrlMoveFirst(w)                                  Move the cursor to the first row.
  void dataCtrlMovePrev(w)                                   Move the cursor to the previous row.
  void dataCtrlMoveNext(w)                                   Move the cursor to the next row.
  void dataCtrlMoveLast(w)                                   Move the cursor to the last row.
  const char *dataCtrlGetField(w, colName)                   Get the value of a column in the current row (case-insensitive).
  void dataCtrlSetField(w, colName, value)                   Set the value of a column in the current row. Marks the row dirty unless the value is unchanged.
  void dataCtrlUpdate(w)                                     Flush pending changes (INSERT or UPDATE) to the database.
  void dataCtrlAddNew(w)                                     Append a blank row and move the cursor to it. The row is dirty and new until Update is called.
  void dataCtrlDelete(w)                                     Delete the current row from the cache and the database.
  void dataCtrlSetMasterValue(w, val)                        Set the master-detail filter value. Normally managed by the form runtime.
  int32_t dataCtrlGetRowCount(w)                             Get the total number of cached rows.
  int32_t dataCtrlGetColCount(w)                             Get the number of columns in the result set.
  const char *dataCtrlGetColName(w, col)                     Get the name of a column by index.
  const char *dataCtrlGetCellText(w, row, col)               Get the text of a specific cell.
  void dataCtrlSetCurrentRow(w, row)                         Set the current row by index. Auto-saves the previous row if dirty.
  int32_t dataCtrlGetCurrentRow(w)                           Get the current row index (-1 if there are no rows).
  uint32_t dataCtrlGetGeneration(w)                          A counter that changes whenever the cached rows or cell values change (Refresh, AddNew, Delete, SetField) but not on cursor moves. Bound views use it to rebuild derived state only when needed.

API Struct (wgtRegisterApi "data")

  Slot                 Function
  ----                 --------
  header               WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create               dataCtrlCreate
  refresh              dataCtrlRefresh
  moveFirst            dataCtrlMoveFirst
  movePrev             dataCtrlMovePrev
  moveNext             dataCtrlMoveNext
  moveLast             dataCtrlMoveLast
  getField             dataCtrlGetField
  setField             dataCtrlSetField
  updateRow            dataCtrlUpdate (same as update; kept so later slots keep their positions)
  update               dataCtrlUpdate
  addNew               dataCtrlAddNew
  delete               dataCtrlDelete
  setMasterValue       dataCtrlSetMasterValue
  getRowCount          dataCtrlGetRowCount
  getColCount          dataCtrlGetColCount
  getColName           dataCtrlGetColName
  getCellText          dataCtrlGetCellText
  setCurrentRow        dataCtrlSetCurrentRow
  getCurrentRow        dataCtrlGetCurrentRow
  getGeneration        dataCtrlGetGeneration

Properties (BASIC Interface)

  Property       Type       Access       Description
  --------       ----       ------       -----------
  DatabaseName   String     Read/Write   Path to the SQLite database file.
  RecordSource   String     Read/Write   SQL query or table name for the result set.
  KeyColumn      String     Read/Write   Primary key column name (used for UPDATE/DELETE).
  MasterSource   String     Read/Write   Name of the master Data control for master-detail linking.
  MasterField    String     Read/Write   Column in the master control to read for the filter value.
  DetailField    String     Read/Write   Column in this table to filter by the master value.
  Caption        String     Read/Write   Text displayed on the navigator bar.
  BOF            Boolean    Read-only    True when the cursor is at the first row (or no rows).
  EOF            Boolean    Read-only    True when the cursor is past the last row (or no rows).

Methods (BASIC Interface)

  Method         Description
  ------         -----------
  AddNew         Append a blank row and move to it.
  Delete         Delete the current row.
  MoveFirst      Move the cursor to the first row.
  MoveLast       Move the cursor to the last row.
  MoveNext       Move the cursor to the next row.
  MovePrevious   Move the cursor to the previous row.
  Refresh        Save a pending edit, then re-execute the query and rebuild the cache.
  Update         Write pending changes to the database.

Events

  Event          Description
  -----          -----------
  Reposition     Fires when the current row changes. Default event.
  Validate       Fires before writing. Return false to cancel the write.

While the Validate handler runs, the moves (MoveFirst/MoveLast/MoveNext/MovePrevious, dataCtrlSetCurrentRow), AddNew, Refresh, and Update on the same control are refused and return without doing anything: each would save, and therefore validate, the same row again. Delete is still allowed; Update re-checks the row after the handler returns.

DbGrid

A database grid widget that displays all records from a Data control in a scrollable, sortable table. Columns auto-populate from the Data control's column names and can be hidden, resized (by dragging a column border), and renamed by the application. Clicking a column header sorts the display ascending/descending. The highlighted row is always the Data control's current row: selecting a row moves the Data control's cursor, and the highlight is read from the cursor whenever the grid paints. The Data control does not repaint the grid itself, so a C application that moves the cursor elsewhere (the Data control's own buttons or API) calls dbGridRefresh (or wgtInvalidatePaint on the grid) from the Data control's onChange; the form runtime does this for BASIC programs. The grid reads directly from the Data control's cached rows, so there is no separate copy of the data. Re-binding the same Data control (as the form runtime does on every Reposition) keeps the sort order and column settings while the column set is unchanged (the sort order and automatic widths are rebuilt when the Data control's rows or values changed), and a width set by dragging a column border is kept like one set with dbGridSetColumnWidth. Cell and header text is clipped to its column.

The grid supports alternating row shading, vertical and horizontal scrollbars, keyboard navigation (arrows, Page Up/Down, Home, End), and double-click activation (fires onDblClick). The mouse wheel scrolls the view without moving the record cursor.

Header: widgets/dbGrid.h

Creation

WidgetT *grid = wgtDbGrid(parent);
dbGridSetDataWidget(grid, dataCtrl);

API Functions

  Function                                                   Description
  --------                                                   -----------
  WidgetT *dbGridCreate(parent)                              Create a database grid widget. Exposed as create in the API struct.
  void dbGridSetDataWidget(w, dataWidget)                    Bind the grid to a Data control. Auto-populates columns from the Data control's column names.
  void dbGridRefresh(w)                                      Re-read the Data control's state and repaint the grid. The sort index and automatic column widths are rebuilt only when the Data control's rows or values changed since the last build; a cursor move just repaints.
  void dbGridSetColumnVisible(w, fieldName, visible)         Show or hide a column by field name.
  void dbGridSetColumnHeader(w, fieldName, header)           Set a display header for a column (overrides the field name).
  void dbGridSetColumnWidth(w, fieldName, width)             Set a column's width (tagged size, 0 = auto).
  int32_t dbGridGetSelectedRow(w)                            Get the selected data row: the bound Data control's current row (-1 if unbound or no rows).
  void dbGridSetSelectedRow(w, row)                          Move the bound Data control's cursor to a data row (clamped to the row range). A negative row is ignored.

API Struct (wgtRegisterApi "dbgrid")

  Slot                 Function
  ----                 --------
  header               WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create               dbGridCreate
  setDataWidget        dbGridSetDataWidget
  refresh              dbGridRefresh
  setColumnVisible     dbGridSetColumnVisible
  setColumnHeader      dbGridSetColumnHeader
  setColumnWidth       dbGridSetColumnWidth
  getSelectedRow       dbGridGetSelectedRow
  setSelectedRow       dbGridSetSelectedRow

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  GridLines    Boolean    Read/Write   Whether to draw grid lines between cells (default: true).
  SelectedRow  Integer    Read/Write   The bound Data control's current row (-1 if none). Setting it moves the Data cursor.

Methods (BASIC Interface)

  Method       Description
  ------       -----------
  Refresh      Re-read the Data control and repaint.

Events

  Event        Description
  -----        -----------
  Click        Fires when a row is clicked (selection change).
  DblClick     Fires when a row is double-clicked. Default event.

Default Event

"DblClick" (VB basName: DBGrid, namePrefix: DBGrid).

Dropdown

A drop-down list that displays a single selected item and expands to a popup list when clicked. Unlike ComboBox, the user cannot type free text -- only list items can be selected. Keyboard: while closed, Down/Up change the selection and type-ahead picks the next item starting with a printable key; Space, Enter, F4, or Alt+Down/Alt+Up open the list. While open, Down/Up move the highlight and Enter, Space, F4, or Alt+Down/Alt+Up select it and close the list (Esc closes without selecting). Ctrl and Alt chords never change the selection. The popup overlay is rendered above all other widgets via the widget paint overlay mechanism.

Depends on the "listhelp" helper library (declared in dropdown.dep).

Header: widgets/dropdown.h

Creation

WidgetT *dd = wgtDropdown(parent);
wgtDropdownAddItem(dd, "Red");
wgtDropdownAddItem(dd, "Green");
wgtDropdownAddItem(dd, "Blue");

API Functions

  Function                                            Description
  --------                                            -----------
  WidgetT *wgtDropdown(parent)                        Create a dropdown list.
  void wgtDropdownSetItems(w, items, count)           Set the list from a caller-owned const char ** array.
  int32_t wgtDropdownGetSelected(w)                   Get the index of the selected item (-1 if none).
  void wgtDropdownSetSelected(w, idx)                 Set the selected item by index.
  void wgtDropdownAddItem(w, text)                    Append an owned item (strdup'd).
  void wgtDropdownRemoveItem(w, idx)                  Remove the item at idx. The selection stays on the same item; removing the selected item leaves no selection.
  void wgtDropdownClear(w)                            Remove all owned items.
  const char *wgtDropdownGetItem(w, idx)              Get the text of an item by index.
  int32_t wgtDropdownGetItemCount(w)                  Get the total number of items.

API Struct (wgtRegisterApi "dropdown")

  Slot           Function
  ----           --------
  header         WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create         wgtDropdown
  setItems       wgtDropdownSetItems
  getSelected    wgtDropdownGetSelected
  setSelected    wgtDropdownSetSelected
  addItem        wgtDropdownAddItem
  removeItem     wgtDropdownRemoveItem
  clear          wgtDropdownClear
  getItem        wgtDropdownGetItem
  getItemCount   wgtDropdownGetItemCount

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  ListIndex    Integer    Read/Write   Index of the currently selected item (-1 if none).

Methods (BASIC Interface)

  Method              Description
  ------              -----------
  AddItem text$       Append an item to the drop-down list.
  Clear               Remove all items.
  List(index%)        Return the text of the item at the given index.
  ListCount()         Return the total number of items.
  RemoveItem index%   Remove the item at the given index. The selection stays on the same item; removing the selected item leaves no selection.

Events

  Callback     Description
  --------     -----------
  onChange     Fires when the selected item changes.

Default Event

"Click" (VB basName: DropDown).

HelpView

One topic of a compiled help file, shown the way the DVX Help Viewer shows it -- the Help Viewer itself uses this widget. Headings (three levels), word-wrapped paragraphs and list items, notes (info, tip, and warning), code blocks and tables (preformatted), pictures (left, centered, or right), horizontal rules and links, in a scrolling pane, in the theme's colours. Text re-wraps when the widget changes width.

Links take the keyboard focus (Tab) and are followed with a click, Enter, or Space: the widget keeps the link's target topic ID (wgtHelpViewGetLinkTarget) and calls the link callback with it. The widget does not change topic by itself; the callback usually finds the topic (hlpFileFindTopic) and shows it, which may rebuild the pane from inside the link's own handler. Everything a topic shows is copied when it is shown, so the help file may be closed afterwards.

The help file is read with the help file library (hlpfile/hlpFile.h).

Header: widgets/helpView.h

BASIC name: HelpPane (see the HelpPane control in the BASIC reference)

Depends on: hlpfile.lib

Creation

HlpFileT *file = hlpFileOpen("MYAPP.HLP");
WidgetT  *view = wgtHelpView(parent);

view->weight = WGT_WEIGHT_FILL;
wgtHelpViewSetLinkCallback(view, onLink);
wgtHelpViewShowTopic(view, file, hlpFileStartTopic(file, NULL));

static void onLink(WidgetT *w, const char *target) {
    wgtHelpViewShowTopic(w, file, hlpFileFindTopic(file, target));
}

Macros

  Macro                                      Description
  -----                                      -----------
  wgtHelpView(parent)                        Create an empty help view.
  wgtHelpViewShowTopic(w, file, topic)       Show topic (0 .. hlpFileTopicCount - 1) of an open help file, scrolled to the top. A NULL file or a topic out of range shows nothing.
  wgtHelpViewClear(w)                        Show nothing.
  wgtHelpViewGetTopic(w)                     The topic shown, -1 for none.
  wgtHelpViewGetLinkTarget(w)                The target topic ID of the last link followed ("" before any).
  wgtHelpViewSetLinkCallback(w, fn)          Call fn(w, target) when a link is followed. Signature: void (*fn)(WidgetT *w, const char *target); target is valid during the call. After it (unless it rebuilt the pane), the HelpView fires its own event LinkClick (Topic As String) with wgtFireEvent, which reaches the widget's onEvent (BASIC: CtrlName_LinkClick).

Events

  Callback          Description
  --------          -----------
  link callback     A link was clicked, or Enter or Space was pressed on a focused link.

ImageButton

A clickable button that displays an image instead of text. Has the same press/release visual feedback and focus handling as a regular Button. Can be created from raw pixel data or loaded from an image file. Typically used to populate a Toolbar.

Header: widgets/imgBtn.h

Creation

WidgetT *btn = wgtImageButtonFromFile(parent, "icons/save.bmp");
btn->onClick = onSaveClicked;

API Functions

  Function                                                             Description
  --------                                                             -----------
  WidgetT *wgtImageButton(parent, data, w, h, pitch)                   Create from raw pixel data (in display's native format).
  WidgetT *wgtImageButtonFromFile(parent, path)                        Create by loading an image file.
  void wgtImageButtonSetData(w, data, imgW, imgH, pitch)               Replace the image with new raw pixel data.
  void wgtImageButtonLoadFile(w, path)                                 Replace the image by loading a new file.

API Struct (wgtRegisterApi "imagebutton")

  Slot         Function
  ----         --------
  header       WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create       wgtImageButton
  fromFile     wgtImageButtonFromFile
  setData      wgtImageButtonSetData
  loadFile     wgtImageButtonLoadFile

Properties (BASIC Interface)

  Property      Type       Access       Description
  --------      ----       ------       -----------
  Picture       String     Read/Write   Setting writes a file path (loads image). Reading returns the path of the loaded file (also set by wgtImageButtonFromFile); in BASIC the path comes as a Picture object, which stands for the path where a value is needed (see LOADPICTURE); wgtImageButtonSetData clears it. Setting "" clears the image and the path; a file that cannot be loaded leaves both unchanged.
  ImageWidth    Integer    Read-only    Width of the currently loaded image in pixels.
  ImageHeight   Integer    Read-only    Height of the currently loaded image in pixels.

Methods

No widget-specific BASIC methods. Setting Picture loads a new image.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when the image button is clicked (press and release inside).

Default Event

"Click" (VB basName: ImageButton).

Image

Displays a bitmap image. Can be created from raw pixel data or loaded from a file on disk (BMP/PNG/etc. via dvxLoadImage). Supports optional scaling down to fit the widget and transparent color keys for chroma-key style masking (also when scaled). A disabled image is drawn grayscale; its transparent color key still applies.

Header: widgets/image.h

Creation

WidgetT *img = wgtImageFromFile(parent, "logo.bmp");
// or from raw pixels:
// WidgetT *img = wgtImage(parent, pixelData, w, h, pitch);

API Functions

  Function                                                             Description
  --------                                                             -----------
  WidgetT *wgtImage(parent, data, w, h, pitch)                         Create from raw pixel data (in display's native format).
  WidgetT *wgtImageFromFile(parent, path)                              Create by loading an image file.
  void wgtImageSetData(w, data, imgW, imgH, pitch)                     Replace the displayed image with new raw pixel data.
  void wgtImageLoadFile(w, path)                                       Replace by loading a new file. Static but exposed via the API struct.
  void wgtImageSetTransparent(w, hasTransparency, keyColor)            Set chroma-key color. Pixels matching keyColor are not drawn.

API Struct (wgtRegisterApi "image")

  Slot             Function
  ----             --------
  header           WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create           wgtImage
  fromFile         wgtImageFromFile
  setData          wgtImageSetData
  loadFile         wgtImageLoadFile
  setTransparent   wgtImageSetTransparent

Properties (BASIC Interface)

  Property      Type       Access       Description
  --------      ----       ------       -----------
  Picture       String     Read/Write   Setting writes a file path (loads image). Reading returns the path of the loaded file (also set by wgtImageFromFile); in BASIC the path comes as a Picture object, which stands for the path where a value is needed (see LOADPICTURE); wgtImageSetData clears it. Setting "" clears the image and the path; a file that cannot be loaded leaves both unchanged.
  Stretch       Boolean    Read/Write   When true, an image larger than the widget is scaled down to fit (aspect preserved, nearest neighbour); a smaller image stays at natural size. When false, shown at natural size. Changing it relayouts the widget.
  ImageWidth    Integer    Read-only    Width of the currently loaded image in pixels.
  ImageHeight   Integer    Read-only    Height of the currently loaded image in pixels.

Methods

No widget-specific BASIC methods. Setting Picture loads a new image.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when the image is clicked.

Default Event

"Click" (VB basName: Image).

Label

A static text label. Does not accept keyboard focus directly, but supports accelerator keys that forward focus to the next focusable sibling (WCLASS_FOCUS_FORWARD). Useful for describing other widgets. Supports left, center, and right text alignment. The label fills its rectangle before drawing: with its own bgColor if set, else the nearest ancestor's bgColor, else the face of a Toolbar it sits in, else the scheme's content background.

Header: widgets/label.h

Creation

WidgetT *lbl = wgtLabel(parent, "&Name:");

API Functions

  Function                                            Description
  --------                                            -----------
  WidgetT *wgtLabel(parent, text)                     Create a text label.
  void wgtLabelSetAlign(w, align)                     Set the text alignment (AlignStartE = left, AlignCenterE = center, AlignEndE = right) and repaint. Out-of-range values are ignored.
  WidgetAlignE wgtLabelGetAlign(w)                    Get the current text alignment.

API Struct (wgtRegisterApi "label")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtLabel
  setAlign   wgtLabelSetAlign

Properties (BASIC Interface)

  Property     Type                         Access       Description
  --------     ----                         ------       -----------
  Alignment    Enum (Left, Center, Right)   Read/Write   Text alignment within the label.

Label text is managed via the standard wgtSetText() / wgtGetText() interface. BASIC code uses the generic "Caption" or "Text" property. Place an '&' before a character in the caption to mark an accelerator key.

Methods

No widget-specific methods.

Events

Labels are not focusable, so they typically have no event callbacks. The common onClick callback will fire if the label is clicked.

Default Event

"Click" (VB basName: Label).

ListBox

A scrollable list of text items. Supports single and multi-select modes (Shift-click for range, Ctrl-click to toggle), drag-to-reorder, mouse wheel scroll, and keyboard navigation (Up/Down/PgUp/PgDn/Home/End with type-ahead search on printable keys). A horizontal scrollbar appears when the box is laid out narrower than its widest item (a maxW below it); it and Left/Right scroll a character at a time. The bars follow widgetScrollbarNeeds. Depends on the "listhelp" helper library (declared in listbox.dep).

Items can be set from a caller-owned array (wgtListBoxSetItems, which keeps a pointer to it) or managed with AddItem/RemoveItem/Clear. AddItem and RemoveItem on a list set from an array first copy that array, so they extend or shorten the caller's list without modifying it; drag-to-reorder also works on the copy.

Header: widgets/listBox.h

Creation

WidgetT *lb = wgtListBox(parent);
wgtListBoxAddItem(lb, "Alpha");
wgtListBoxAddItem(lb, "Beta");
wgtListBoxAddItem(lb, "Gamma");

API Functions

  Function                                                     Description
  --------                                                     -----------
  WidgetT *wgtListBox(parent)                                  Create a list box.
  void wgtListBoxSetItems(w, items, count)                     Set items from a caller-owned const char ** array.
  int32_t wgtListBoxGetSelected(w)                             Get the primary selected index (-1 if none).
  void wgtListBoxSetSelected(w, idx)                           Set the selected index. Scrolls the item into view.
  void wgtListBoxSetMultiSelect(w, multi)                      Enable or disable multi-select mode.
  bool wgtListBoxIsItemSelected(w, idx)                        Returns true if the item is selected (multi-select mode).
  void wgtListBoxSetItemSelected(w, idx, selected)             Select or deselect a specific item (multi-select mode).
  void wgtListBoxSelectAll(w)                                  Select every item (multi-select mode).
  void wgtListBoxClearSelection(w)                             Deselect all items.
  void wgtListBoxSetReorderable(w, r)                          Enable drag-to-reorder of items.
  void wgtListBoxAddItem(w, text)                              Append an owned item (strdup'd).
  void wgtListBoxRemoveItem(w, idx)                            Remove the item at idx. Items after it keep their selection; removing the current item leaves no current item.
  void wgtListBoxClear(w)                                      Remove all owned items.
  const char *wgtListBoxGetItem(w, idx)                        Get the text of an item by index.
  int32_t wgtListBoxGetItemCount(w)                            Get the total number of items.

API Struct (wgtRegisterApi "listbox")

  Slot               Function
  ----               --------
  header             WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create             wgtListBox
  setItems           wgtListBoxSetItems
  getSelected        wgtListBoxGetSelected
  setSelected        wgtListBoxSetSelected
  setMultiSelect     wgtListBoxSetMultiSelect
  isItemSelected     wgtListBoxIsItemSelected
  setItemSelected    wgtListBoxSetItemSelected
  selectAll          wgtListBoxSelectAll
  clearSelection     wgtListBoxClearSelection
  setReorderable     wgtListBoxSetReorderable
  addItem            wgtListBoxAddItem
  removeItem         wgtListBoxRemoveItem
  clear              wgtListBoxClear
  getItem            wgtListBoxGetItem
  getItemCount       wgtListBoxGetItemCount

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  ListIndex    Integer    Read/Write   Index of the currently selected item.

Methods (BASIC Interface)

  Method                            Description
  ------                            -----------
  AddItem text$                     Append an item.
  Clear                             Remove all items.
  ClearSelection                    Deselect all items.
  IsItemSelected(index%)            Returns True if the item is selected (multi-select mode).
  List(index%)                      Return the text of the item at the given index.
  ListCount()                       Return the total number of items.
  RemoveItem index%                 Remove the item at the given index. Items after it keep their selection; removing the selected item leaves no selection.
  SelectAll                         Select all items.
  SetItemSelected index%, sel       Select or deselect a specific item.
  SetItems spec$                    Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue"); replaces existing items. Every "|" separates two items ("a|" is "a" and an empty item); "" empties the list.
  SetMultiSelect multi              Enable or disable multi-select mode.
  SetReorderable r                  Enable or disable drag-to-reorder.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when an item is clicked.
  onDblClick   Fires when an item is double-clicked.
  onChange     Fires when the selection changes.

Default Event

"Click" (VB basName: ListBox).

ListView

A multi-column list with sortable headers. Supports single and multi-selection, column alignment, header click sorting, column resizing (drag a header border; double-click it to fit the contents), and drag-to-reorder. Data is provided as a flat array of strings (row-major order, one string per cell). wgtListViewSetData keeps a pointer to the caller's array; AddItem, RemoveRow, Clear, SetCell, and drag-to-reorder work on a copy the widget makes first, so the caller's array is never modified.

Sorting and drag-to-reorder are exclusive: enabling reorder clears the sort, and while reorder is enabled wgtListViewSetSort and header clicks do not sort. A column width set by dragging or double-clicking a header border is kept when the data changes, until new columns are set.

Header: widgets/listView.h

Creation

WidgetT *lv = wgtListView(parent);
ListViewColT cols[] = {
    { "Name", wgtChars(20), ListViewAlignLeftE },
    { "Size", wgtChars(10), ListViewAlignRightE }
};
wgtListViewSetColumns(lv, cols, 2);
const char *cells[] = { "file.txt", "1234", "readme.md", "5678" };
wgtListViewSetData(lv, cells, 2);

Column Definition

typedef struct {
    const char      *title;
    int32_t          width;   // tagged size (wgtPixels/wgtChars/wgtPercent, 0 = auto)
    ListViewAlignE   align;   // ListViewAlignLeftE, ListViewAlignCenterE, ListViewAlignRightE
} ListViewColT;

Sort Direction

typedef enum {
    ListViewSortNoneE,
    ListViewSortAscE,
    ListViewSortDescE
} ListViewSortE;

Macros

  Macro                                              Description
  -----                                              -----------
  wgtListView(parent)                                Create a list view.
  wgtListViewSetColumns(w, cols, count)              Define columns from an array of ListViewColT.
  wgtListViewSetData(w, cellData, rowCount)          Set row data. cellData is a flat const char ** array of size rowCount * colCount.
  wgtListViewGetSelected(w)                          Get the index of the selected row (-1 if none).
  wgtListViewSetSelected(w, idx)                     Set the selected row by index (outside the list = none) and scroll it into view.
  wgtListViewSetSort(w, col, dir)                    Set the sort column and direction. A column outside the defined columns, or ListViewSortNoneE, clears the sort. Ignored while reorder is enabled.
  wgtListViewSetHeaderClickCallback(w, cb)           Set a callback for header clicks. Signature: void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir). After it, the ListView fires its own event ColumnClick (Column As Integer, 0-based) with wgtFireEvent, which reaches the widget's onEvent (BASIC: CtrlName_ColumnClick).
  wgtListViewSetMultiSelect(w, multi)                Enable or disable multi-selection.
  wgtListViewIsItemSelected(w, idx)                  Check if a specific row is selected.
  wgtListViewSetItemSelected(w, idx, selected)       Select or deselect a specific row.
  wgtListViewSelectAll(w)                            Select all rows.
  wgtListViewClearSelection(w)                       Deselect all rows.
  wgtListViewSetReorderable(w, reorderable)          Enable drag-to-reorder of rows.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when a row is clicked.
  onDblClick   Fires when a row is double-clicked.
  onChange     Fires when the selection changes.

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  ListIndex    Integer    Read/Write   Index of the currently selected row.

Methods (BASIC Interface)

  Method                          Description
  ------                          -----------
  AddItem text$                   Add a row (sets first column text).
  Clear                           Remove all rows.
  ClearSelection                  Deselect all rows.
  GetCell(row%, col%)             Returns the text of a cell.
  IsItemSelected(index%)          Check if a specific row is selected.
  RemoveItem index%               Remove a row by index. Rows after it keep their selection; removing the selected row leaves no selection.
  RowCount()                      Returns the number of rows.
  SelectAll                       Select all rows.
  SetCell row%, col%, text$       Set the text of a cell.
  SetColumns spec$                Define columns from a pipe-delimited spec string ("Name,Width|Name,Width").
  SetItemSelected idx%, sel       Select or deselect a specific row by index.
  SetMultiSelect multi            Enable or disable multi-selection.
  SetReorderable reorderable      Enable or disable drag-to-reorder.
  SetSort col%, dir%              Set the sort column and direction (0=none, 1=ascending, 2=descending). An invalid column or direction clears the sort; ignored while reorder is enabled.

Default Event

"Click" (VB basName: ListView).

ProgressBar

A visual indicator of progress, displayed as a filled bar. Supports both horizontal and vertical orientations. Value ranges from 0 to 100.

Header: widgets/progress.h

Creation

  Macro                    Description
  -----                    -----------
  wgtProgressBar(parent)   Create a horizontal progress bar.
  wgtProgressBarV(parent)  Create a vertical progress bar.

API Functions

  Function                            Description
  --------                            -----------
  WidgetT *wgtProgressBar(parent)     Create a horizontal progress bar.
  WidgetT *wgtProgressBarV(parent)    Create a vertical progress bar.
  void wgtProgressBarSetValue(w, val) Set the progress value (0-100). Clamped to 0..maxValue.
  int32_t wgtProgressBarGetValue(w)   Get the current progress value.
  void wgtProgressBarSetVertical(w, v) Make the bar vertical (true) or horizontal (false); the layout is redone.
  bool wgtProgressBarGetVertical(w)   True when the bar is vertical.

API Struct (wgtRegisterApi "progressbar")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtProgressBar
  createV    wgtProgressBarV
  setValue   wgtProgressBarSetValue
  getValue      wgtProgressBarGetValue
  setVertical   wgtProgressBarSetVertical
  getVertical   wgtProgressBarGetVertical

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Value        Integer    Read/Write   Current progress value (0-100).
  Vertical     Boolean    Read/Write   True: the bar stands upright and fills from the bottom.

Methods

No widget-specific methods.

Events

ProgressBar is a display-only widget. Typically no callbacks are set.

Default Event

None. (VB basName: ProgressBar.)

Radio Button

A mutually exclusive selection control. Radio buttons are normally placed inside a radio group container, which holds the selected index. Radio buttons placed directly in any other container (as BASIC forms do) form a group with their radio siblings, each button keeping its own selected state. Only one radio button within a group can be selected at a time. A button's index is its position among the radio buttons of its container, so it stays correct when buttons are added or destroyed.

Header: widgets/radio.h

Creation

WidgetT *grp = wgtRadioGroup(parent);
WidgetT *r1  = wgtRadio(grp, "Option A");
WidgetT *r2  = wgtRadio(grp, "Option B");

API Functions

  Function                                       Description
  --------                                       -----------
  WidgetT *wgtRadioGroup(parent)                 Create a radio group container.
  WidgetT *wgtRadio(parent, text)                Create a radio button inside a group.
  void wgtRadioGroupSetSelected(group, idx)      Set the selected radio button by index within the group.
  int32_t wgtRadioGroupGetSelected(group)        Index of the selected radio button among group's radio children (group may be a RadioGroup or any container), or -1 when none is selected.
  int32_t wgtRadioGetIndex(w)                    Get the index of this radio button within its group.
  bool wgtRadioIsSelected(w)                     True when this radio button is the selected one.
  void wgtRadioSetSelected(w, selected)          Select this button (clearing its siblings), or deselect it leaving none selected. Fires no event.

wgtRadioGroupSetSelected and wgtRadioSetSelected repaint the affected buttons and do not fire onChange. Arrow-key navigation moves focus through the standard focus path, so the blur/focus callbacks fire.

API Struct (wgtRegisterApi "radio")

  Slot                Function
  ----                --------
  header              WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  group               wgtRadioGroup
  create              wgtRadio
  groupSetSelected    wgtRadioGroupSetSelected
  getIndex            wgtRadioGetIndex
  isSelected          wgtRadioIsSelected
  setSelected         wgtRadioSetSelected
  groupGetSelected    wgtRadioGroupGetSelected

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Value        Boolean    Read/Write   True when this button is selected. Setting True selects it and clears its siblings; setting False deselects it.
  OptionIndex  Integer    Read-only    Index of this radio button within its group.

Caption is managed via the standard wgtSetText() / wgtGetText() interface (WCLASS_HAS_TEXT).

Methods (BASIC Interface)

  Method                  Description
  ------                  -----------
  SetSelected idx%        Select the radio button at index idx% within this button's group (called on a group, within that group).

Events

  Callback     Description
  --------     -----------
  onClick      Fires on the radio button when clicked.
  onChange     Fires when the selection changes.

Default Event

"Click" (VB basName: OptionButton, namePrefix: Option).

RichEdit

A text box for styled text: runs in fonts, sizes, styles, and colors, in paragraphs aligned left, centered, or right, edited with the keyboard and the mouse, with undo and redo, a clipboard that keeps styles between RichEdit controls, find, and RTF in and out. The document is a RichTextT of the richtext library (richtext/richText.h); a style's family "" and size 0 are the widget's own font (wgtSetFont, or its container's) and RICHEDIT_COLOR_DEFAULT its fgColor, so such text follows the widget.

Each paragraph is laid out by a text flow of its own (dvxFlow.h): an edit lays out only the paragraphs it touched, and the paragraphs' tops are kept for binary searches from an offset or a row. The widget polls (WCLASS_NEEDS_POLL) and has a quick repaint (WGT_METHOD_QUICK_REPAINT): an edit that left every paragraph's height alone, a caret or selection move, and the caret's blink repaint only the rows they touched, painted straight into the window's content buffer (widgetQuickPaintBegin); a change of height, a scroll, or a new width repaints the widget. Undo keeps for each edit what it replaced and the length it put in its place (alignment changes keep the old alignments); typed characters join one step; 100 steps are kept.

Depends on the "texthelp" (word boundaries, the caret blink time, scroll bar drags) and "richtext" libraries (declared in richedit.dep).

Header: widgets/richEdit.h

Creation

WidgetT *re = wgtRichEdit(parent);

wgtSetText(re, "Dear friend,");
wgtRichEditSetSelStart(re, 0);
wgtRichEditSetSelLength(re, 4);
wgtRichEditSetSelStyle(re, FONT_STYLE_BOLD, FONT_STYLE_BOLD);
wgtRichEditSetSelColor(re, DVX_RGB(0, 0, 160));

API Functions

  Function                                              Description
  --------                                              -----------
  WidgetT *wgtRichEdit(parent)                          Create an empty rich edit box.
  const char *wgtRichEditGetRtf(w)                      The whole text as RTF (valid until the next call that returns RTF).
  void wgtRichEditSetRtf(w, rtf)                        Replace the whole text from RTF (plain text when it is not RTF); undo is emptied.
  const char *wgtRichEditGetSelRtf(w)                   The selection as RTF.
  void wgtRichEditSetSelRtf(w, rtf)                     Replace the selection from RTF.
  int32_t wgtRichEditGetSelStart(w) / SetSelStart       Where the selection starts; setting it places the caret.
  int32_t wgtRichEditGetSelLength(w) / SetSelLength     The selection's length; setting it selects from SelStart.
  const char *wgtRichEditGetSelText(w) / SetSelText     The selection's text; setting it replaces the selection with plain text.
  const char *wgtRichEditGetSelFontName(w)              The selection's family (the widget's own when it uses that).
  int32_t wgtRichEditGetSelFontSize(w)                  The selection's pixel size (as opened: the nearest the family has).
  void wgtRichEditSetSelFont(w, family, size)           The family (NULL: unchanged; "": the widget's) and size (0: unchanged).
  uint32_t wgtRichEditGetSelStyle(w)                    The selection's FONT_STYLE_* bits.
  void wgtRichEditSetSelStyle(w, style, mask)           Make the bits in mask style's.
  uint32_t wgtRichEditGetSelColor(w) / SetSelColor      0x00RRGGBB or RICHEDIT_COLOR_DEFAULT.
  int32_t wgtRichEditGetSelAlign(w) / SetSelAlign       FLOW_ALIGN_* of the selection's paragraphs.
  bool wgtRichEditCanUndo(w) / CanRedo                  Whether there is a step to undo or redo.
  void wgtRichEditUndo(w) / Redo                        Undo or redo a step.
  void wgtRichEditCut(w) / Copy / Paste                 The clipboard (plain text there; the styled copy kept for a paste of the same text).
  void wgtRichEditSelectAll(w)                          Select everything.
  int32_t wgtRichEditFind(w, text, matchCase)           Select the next match from the selection's end, around past the end; its offset or -1.
  int32_t wgtRichEditLoadFile(w, path)                  Load RTF or plain text; 0 or -1.
  int32_t wgtRichEditSaveFile(w, path)                  Save RTF (path ending in .rtf) or plain text; 0 or -1.

The style and alignment setters change the selection, or with none, the style typing uses next (alignment: the caret's paragraph); each change is an undo step and fires Change. wgtGetText / wgtSetText are the plain text, a line per paragraph. RICHEDIT_EVT_SELCHANGE ("SelChange") is fired with wgtFireEvent when the selection or the caret moves.

API Struct (wgtRegisterApi "richedit")

The slots are the functions above in order: create, getRtf, setRtf, getSelRtf, setSelRtf, getSelStart, setSelStart, getSelLength, setSelLength, getSelText, setSelText, getSelFontName, getSelFontSize, setSelFont, getSelStyle, setSelStyle, getSelColor, setSelColor, getSelAlign, setSelAlign, canUndo, canRedo, undo, redo, cut, copy, paste, selectAll, find, loadFile, and saveFile.

BASIC Interface

Properties: TextRTF and SelRTF (wgtRichEditGetRtf / SetRtf, GetSelRtf / SetSelRtf), SelStart, SelLength, SelText, SelBold, SelItalic, SelUnderline, SelStrikethru (one FONT_STYLE_* bit of SetSelStyle each), SelColor (a BASIC color; -1 for ForeColor), SelFontName and SelFontSize (wgtRichEditSetSelFont with the other part left alone), SelAlignment, and the read-only CanUndo and CanRedo. Methods: Undo, Redo, Cut, Copy, Paste, SelectAll, Find (any case), LoadFile, and SaveFile. Event: SelChange; default event Change.

Methods Used

PAINT, CALC_MIN_SIZE (20 by 4 characters of its font, with border and scroll bar), ON_MOUSE, ON_KEY, ON_DRAG_UPDATE, ON_DRAG_END, ON_WHEEL, ON_FOCUS, ON_BLUR, POLL, QUICK_REPAINT, GET_TEXT, SET_TEXT, CLEAR_SELECTION, DESTROY. Flags: WCLASS_FOCUSABLE, WCLASS_NEEDS_POLL.

RichLabel

Text in many fonts, styles, and colors, wrapped and aligned paragraph by paragraph. The text is built by calls: wgtRichLabelSetFont, wgtRichLabelSetStyle, and wgtRichLabelSetColor choose what the following wgtRichLabelAddText uses; wgtRichLabelAddLineBreak starts a new line and wgtRichLabelAddParagraph a new paragraph with its own alignment. Text starts in the widget's own font (wgtSetFont, or its container's). The widget is laid out at its width hint (minW, which BASIC's Width sets), wrapping at spaces (a word too wide for a line breaks inside it); without one each paragraph is one line. Every line is as tall as the tallest font on it, its text standing on one baseline. Not focusable; drawn the same enabled or not. It fills its rectangle as a Label does: with the nearest explicit bgColor, else the content background. Built on the text flow in libdvx (dvxFlow.h).

Header: widgets/richLbl.h

Creation

WidgetT *rl = wgtRichLabel(parent);

wgtRichLabelSetFont(rl, "DejaVu Serif", 18);
wgtRichLabelSetStyle(rl, FONT_STYLE_BOLD);
wgtRichLabelAddText(rl, "Chapter One");
wgtRichLabelAddParagraph(rl, FLOW_ALIGN_LEFT);
wgtRichLabelSetFont(rl, "", 0);
wgtRichLabelSetStyle(rl, 0);
wgtRichLabelAddText(rl, "It was a ");
wgtRichLabelSetColor(rl, DVX_RGB(160, 0, 0));
wgtRichLabelAddText(rl, "dark");
wgtRichLabelSetColor(rl, RICHLABEL_COLOR_DEFAULT);
wgtRichLabelAddText(rl, " and stormy night.");

API Functions

  Function                                       Description
  --------                                       -----------
  WidgetT *wgtRichLabel(parent)                  Create an empty rich label.
  void wgtRichLabelClear(w)                      Remove all text; the font, style, and color chosen go back to the widget's.
  void wgtRichLabelSetFont(w, family, size)      The font family and pixel size of the text added next ("" and 0: the widget's own).
  void wgtRichLabelSetStyle(w, style)            FONT_STYLE_BOLD, ITALIC, UNDERLINE, and STRIKE bits for the text added next.
  void wgtRichLabelSetColor(w, rgb)              0x00RRGGBB color of the text added next; RICHLABEL_COLOR_DEFAULT: the widget's fgColor (else the scheme's).
  void wgtRichLabelAddText(w, text)              Add text in the font, style, and color chosen.
  void wgtRichLabelAddLineBreak(w)               Start a new line in the same paragraph.
  void wgtRichLabelAddParagraph(w, align)        Start a new paragraph aligned FLOW_ALIGN_LEFT (0), FLOW_ALIGN_CENTER (1), or FLOW_ALIGN_RIGHT (2), as a Label's alignment; others are ignored.

Setting the font, style, or color changes only the text added afterwards.

API Struct (wgtRegisterApi "richlabel")

  Slot           Function
  ----           --------
  header         WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create         wgtRichLabel
  clear          wgtRichLabelClear
  setFont        wgtRichLabelSetFont
  setStyle       wgtRichLabelSetStyle
  setColor       wgtRichLabelSetColor
  addText        wgtRichLabelAddText
  addLineBreak   wgtRichLabelAddLineBreak
  addParagraph   wgtRichLabelAddParagraph

Methods (BASIC Interface)

  Method         Arguments              Description
  ------         ---------              -----------
  Clear          (none)                 wgtRichLabelClear
  SetFont        family, size           wgtRichLabelSetFont
  SetStyle       style                  wgtRichLabelSetStyle
  SetColor       color                  A BASIC color (RGB()); -1 for ForeColor
  AddText        text                   wgtRichLabelAddText
  AddLineBreak   (none)                 wgtRichLabelAddLineBreak
  AddParagraph   alignment              wgtRichLabelAddParagraph

Default Event

"Click" (VB basName: RichLabel).

ScrollPane

A scrollable container that provides vertical and/or horizontal scrollbars when its content exceeds the visible area. Place a single child (typically a VBox or HBox) inside the scroll pane. A click on content that does not handle the mouse itself (a label, an image, or empty space) gives the scroll pane the keyboard focus, so the arrow and page keys scroll it.

Header: widgets/scrlPane.h

Creation

WidgetT *sp = wgtScrollPane(parent);
WidgetT *content = wgtVBox(sp);
// add children to content...

API Functions

  Function                                          Description
  --------                                          -----------
  WidgetT *wgtScrollPane(parent)                    Create a scroll pane container.
  void wgtScrollPaneScrollToChild(sp, child)        Scroll so that the given child widget is visible.
  void wgtScrollPaneScrollToTop(w)                  Scroll back to the top-left of the content and re-lay out.
  void wgtScrollPaneSetNoBorder(w, noBorder)        When true, removes the border around the scroll pane.
  int32_t wgtScrollPaneGetScrollTop(w)              Vertical scroll position in pixels.
  void wgtScrollPaneSetScrollTop(w, pos)            Set the vertical scroll position; the layout clamps it to the content.
  int32_t wgtScrollPaneGetScrollLeft(w)             Horizontal scroll position in pixels.
  void wgtScrollPaneSetScrollLeft(w, pos)           Set the horizontal scroll position; the layout clamps it to the content.

Padding and spacing are the common WidgetT padding / spacing fields (WGT_GAP_DEFAULT, the default, is none); wgtSetPaddingPx / wgtSetSpacingPx set them in pixels.

When the user moves the scroll position (scrollbar arrows and trough, dragging a thumb, the arrow, page, Home, and End keys) the scroll pane fires onChange (widgetFireChange) after the position is clamped; the setters, wgtScrollPaneScrollToTop, and scrolling a focused child into view do not. BASIC sees it as the ScrollPane's Change event.

API Struct (wgtRegisterApi "scrollpane")

  Slot             Function
  ----             --------
  header           WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create           wgtScrollPane
  scrollToChild    wgtScrollPaneScrollToChild
  setNoBorder      wgtScrollPaneSetNoBorder
  scrollToTop      wgtScrollPaneScrollToTop
  getScrollLeft    wgtScrollPaneGetScrollLeft
  setScrollLeft    wgtScrollPaneSetScrollLeft
  getScrollTop     wgtScrollPaneGetScrollTop
  setScrollTop     wgtScrollPaneSetScrollTop

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  NoBorder     Boolean    Read/Write   Whether the border around the scroll pane is hidden.
  Padding      Integer    Read/Write   Pixels between the edge and the children (wgtGetPaddingPx / wgtSetPaddingPx).
  Spacing      Integer    Read/Write   Pixels between the children (wgtGetSpacingPx / wgtSetSpacingPx).
  ScrollTop    Integer    Read/Write   Vertical scroll position in pixels.
  ScrollLeft   Integer    Read/Write   Horizontal scroll position in pixels.

Methods

  Method        Description
  ------        -----------
  ScrollToTop   wgtScrollPaneScrollToTop.

Events

  Callback     Description
  --------     -----------
  onScroll     Fires when the scroll position changes.

Default Event

None. (VB basName: ScrollPane, namePrefix: Scroll.)

Separator

A visual dividing line used to separate groups of widgets. Available in horizontal and vertical orientations.

Header: widgets/separatr.h

API Functions

  Function                            Description
  --------                            -----------
  WidgetT *wgtSeparator(parent)       Create a separator oriented from the parent: vertical in a horizontal container, horizontal otherwise.
  WidgetT *wgtHSeparator(parent)      Create a horizontal separator line.
  WidgetT *wgtVSeparator(parent)      Create a vertical separator line.

API Struct (wgtRegisterApi "separator")

  Slot           Function
  ----           --------
  header         WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  hSeparator     wgtHSeparator
  vSeparator     wgtVSeparator
  create         wgtSeparator

The BASIC Line control is registered as interface "line" with its own one-slot API (create = wgtSeparator).

Properties

No widget-specific properties.

Methods

No widget-specific methods.

Events

Separator is a visual-only widget. No events.

Default Event

None. (VB basName: Line.)

Slider

A horizontal slider (track bar) for selecting an integer value within a range. The user drags the thumb or clicks the track to change the value.

Header: widgets/slider.h

Creation

WidgetT *sl = wgtSlider(parent, 0, 100);

API Functions

  Function                                          Description
  --------                                          -----------
  WidgetT *wgtSlider(parent, minVal, maxVal)        Create a slider with the given integer range.
  void wgtSliderSetValue(w, value)                  Set the slider value programmatically.
  int32_t wgtSliderGetValue(w)                      Get the current slider value.

API Struct (wgtRegisterApi "slider")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtSlider
  setValue   wgtSliderSetValue
  getValue   wgtSliderGetValue

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Value        Integer    Read/Write   Current slider value.
  Min          Integer    Read/Write   Lowest value; raising it above Max raises Max too.
  Max          Integer    Read/Write   Highest value; lowering it below Min lowers Min too.

Methods

No widget-specific methods.

Events

  Callback     Description
  --------     -----------
  onChange     Fires when the slider value changes (not when a key, click, or drag leaves it unchanged).

Default Event

"Change" (VB basName: HScrollBar, namePrefix: HScroll).

Spacer

An invisible widget used for layout purposes. By default it has weight=100, so it absorbs available extra space. Useful for pushing other widgets apart or aligning them to edges.

Header: widgets/spacer.h

Creation

WidgetT *row = wgtHBox(parent);
wgtButton(row, "OK");
wgtSpacer(row);           // pushes Cancel to the right
wgtButton(row, "Cancel");

API Functions

  Function                       Description
  --------                       -----------
  WidgetT *wgtSpacer(parent)     Create an invisible spacer widget.

API Struct (wgtRegisterApi "spacer")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtSpacer

Properties, Methods, Events

Spacer is an invisible layout widget. No type-specific properties, methods, or events.

Default Event

None. (VB basName: Spacer.)

Spinner

A numeric input with up/down buttons for incrementing and decrementing a value within a range. Supports both integer and floating-point (real) modes.

Typing, Backspace/Delete, paste, cut, and undo start an edit of the text; Enter or focus loss commits it and Esc cancels it. Undo (Ctrl+Z) covers edits of the typed text only: every reformat from the value (creation, SetText, commit, step) empties the undo slot. A paste that is not a valid number is rolled back.

WCLASS_FIXED_FONT: it edits by columns, so its font must be fixed-pitch. Any size works (the column math uses the font's charWidth and charHeight); a proportional font set with wgtSetFont, or inherited, is replaced by the system font when it draws.

Header: widgets/spinner.h

Creation

WidgetT *sp = wgtSpinner(parent, 0, 100, 1);

API Functions

  Function                                                Description
  --------                                                -----------
  WidgetT *wgtSpinner(parent, minVal, maxVal, step)       Create a spinner with the given integer range and step size.
  void wgtSpinnerSetValue(w, value)                       Set the integer value.
  int32_t wgtSpinnerGetValue(w)                           Get the current integer value; an edit in progress is parsed but not committed.
  void wgtSpinnerSetRange(w, minVal, maxVal)              Set the range for both modes (the real range starts as the creation range).
  void wgtSpinnerSetStep(w, step)                         Set the integer step size.
  void wgtSpinnerSetRealMode(w, enable)                   Switch to floating-point mode.
  double wgtSpinnerGetRealValue(w)                        Get the current floating-point value.
  void wgtSpinnerSetRealValue(w, value)                   Set the floating-point value.
  void wgtSpinnerSetRealRange(w, minVal, maxVal)          Set the floating-point range only (call after wgtSpinnerSetRange).
  void wgtSpinnerSetRealStep(w, step)                     Set the floating-point step size.
  void wgtSpinnerSetDecimals(w, decimals)                 Set the number of decimal places displayed in real mode.

API Struct (wgtRegisterApi "spinner")

  Slot           Function
  ----           --------
  header         WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create         wgtSpinner
  setValue       wgtSpinnerSetValue
  getValue       wgtSpinnerGetValue
  setRange       wgtSpinnerSetRange
  setStep        wgtSpinnerSetStep
  setRealMode    wgtSpinnerSetRealMode
  getRealValue   wgtSpinnerGetRealValue
  setRealValue   wgtSpinnerSetRealValue
  setRealRange   wgtSpinnerSetRealRange
  setRealStep    wgtSpinnerSetRealStep
  setDecimals    wgtSpinnerSetDecimals

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Value        Integer    Read/Write   Current integer value.
  RealMode     Boolean    Read/Write   Whether floating-point mode is active.
  Decimals     Integer    Read/Write   Number of decimal places displayed in real mode.

Methods (BASIC Interface)

  Method                Description
  ------                -----------
  SetRange min%, max%   Set the range (integer and real mode).
  SetStep step%         Set the integer step size.

Events

  Callback     Description
  --------     -----------
  onChange     Fires when the value changes and on each typed edit, but not when a step or commit leaves the value unchanged.

Default Event

"Change" (VB basName: SpinButton, namePrefix: Spin).

Splitter

A two-pane container with a draggable divider. The user drags the splitter bar to resize the two panes. Can be oriented vertically (left/right panes) or horizontally (top/bottom panes). Add exactly two children.

Header: widgets/splitter.h

Creation

WidgetT *sp = wgtSplitter(parent, true);  // vertical = left|right
WidgetT *left  = wgtVBox(sp);
WidgetT *right = wgtVBox(sp);

API Functions

  Function                                       Description
  --------                                       -----------
  WidgetT *wgtSplitter(parent, vertical)         Create a splitter. vertical=true for left/right panes (vertical divider), false for top/bottom.
  void wgtSplitterSetPos(w, pos)                 Set the divider position in pixels from the leading edge.
  int32_t wgtSplitterGetPos(w)                   Get the current divider position in pixels.
  void wgtSplitterSetVertical(w, vertical)       Change the orientation (true = left/right panes); the position is kept and clamped to the new axis at the next layout.
  bool wgtSplitterGetVertical(w)                 True when the panes are side by side.

API Struct (wgtRegisterApi "splitter")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtSplitter
  setPos     wgtSplitterSetPos
  getPos        wgtSplitterGetPos
  setVertical   wgtSplitterSetVertical
  getVertical   wgtSplitterGetVertical

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Position     Integer    Read/Write   Divider position in pixels.
  Vertical     Boolean    Read/Write   True (default): panes side by side. False: panes above and below.

Methods

No widget-specific methods.

Events

Splitter has no widget-specific events. Common events apply.

Default Event

None. (VB basName: Splitter.)

StatusBar

A horizontal bar typically placed at the bottom of a window for displaying status text and informational widgets. Children are laid out horizontally, each inside a 1-pixel sunken panel border. Padding defaults to 1 pixel and spacing to 2 pixels; the common padding/spacing fields override them.

Header: widgets/statBar.h

Creation

WidgetT *sb = wgtStatusBar(parent);
wgtLabel(sb, "Ready");

API Functions

  Function                          Description
  --------                          -----------
  WidgetT *wgtStatusBar(parent)     Create a status bar container.

API Struct (wgtRegisterApi "statusbar")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtStatusBar

Properties, Methods, Events

StatusBar has no widget-specific properties, methods, or events. Add child widgets (labels, progress bars, etc.) to populate.

Default Event

None. (VB basName: StatusBar.)

TabControl

A tabbed container that displays one page at a time with clickable tabs along the top. Each tab page is a container that holds its own child widgets.

Header: widgets/tabCtrl.h

Creation

WidgetT *tabs = wgtTabControl(parent);
WidgetT *page1 = wgtTabPage(tabs, "General");
WidgetT *page2 = wgtTabPage(tabs, "Advanced");
// add children to page1, page2...

API Functions

  Function                                       Description
  --------                                       -----------
  WidgetT *wgtTabControl(parent)                 Create a tab control.
  WidgetT *wgtTabPage(parent, title)             Add a tab page with the given title. Returns the page container widget.
  void wgtTabControlSetActive(w, idx)            Set the active tab by index (0-based). Negative selects the first tab; an index past the last page selects the last page (with no pages yet it is kept and clamped at layout, so it may be set before the pages are added). Closes a popup owned by a control on the hidden page and moves focus off that page; does not fire onChange.
  int32_t wgtTabControlGetActive(w)              Get the index of the active tab.

API Struct (wgtRegisterApi "tabcontrol")

  Slot         Function
  ----         --------
  header       WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create       wgtTabControl
  page         wgtTabPage
  setActive    wgtTabControlSetActive
  getActive    wgtTabControlGetActive

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  TabIndex     Integer    Read/Write   Index of the currently active tab (0-based).

Methods (BASIC Interface)

  Method                Description
  ------                -----------
  AddPage title$        Add a new tab page with the given title.
  GetActive()           Returns the index of the active tab.
  SetActive idx%        Set the active tab by index (0-based).

Events

  Callback     Description
  --------     -----------
  onChange     Fires when the user changes the active tab (mouse, keyboard, or accelerator); not for wgtTabControlSetActive.

Default Event

"Click" (VB basName: TabStrip).

TextInput / TextArea

Single-line text input, password input, masked input, and multi-line text area with optional syntax colorization (built-in BASIC and help-source colorizers, the BASIC one shared with the DVX BASIC IDE), line numbers, find/replace, line markers, and gutter clicks.

WCLASS_FIXED_FONT: it edits by columns, so its font must be fixed-pitch. Any size works (the column math uses the font's charWidth and charHeight); a proportional font set with wgtSetFont, or inherited, is replaced by the system font when it draws.

Header: widgets/textInpt.h

Creation

  Macro                            Description
  -----                            -----------
  wgtTextInput(parent, maxLen)     Create a single-line text input. maxLen is the maximum number of characters (0 => default 256).
  wgtPasswordInput(parent, maxLen) Create a password input (characters displayed as bullets).
  wgtMaskedInput(parent, mask)     Create a masked input field. The mask string defines the input format ('#' digit, 'A' letter, '*' any printable; other characters are literals). The mask is copied. Setting the text keeps the mask layout: text with the mask's shape fills it slot by slot, other text fills the slots in order.
  wgtTextArea(parent, maxLen)      Create a multi-line text area.

API Functions (TextArea-specific)

  Function                                                    Description
  --------                                                    -----------
  void wgtTextAreaAppendText(w, text)                         Append text to the end of the buffer and invalidate the widget.
  void wgtTextAreaSetColorize(w, fn, ctx)                     Set a syntax colorization callback. The callback receives each line and fills a color index array.
  void wgtTextAreaGoToLine(w, line)                           Scroll to the given line number and select the whole line.
  void wgtTextAreaSetAutoIndent(w, enable)                    Enable or disable automatic indentation on newline.
  void wgtTextAreaSetShowLineNumbers(w, show)                 Show or hide line numbers in the gutter.
  void wgtTextAreaSetCaptureTabs(w, capture)                  When true, Tab key inserts a tab/spaces instead of moving focus.
  void wgtTextAreaSetTabWidth(w, width)                       Set the tab stop width in characters.
  void wgtTextAreaSetUseTabChar(w, useChar)                   When true, insert literal tab characters; when false, insert spaces.
  bool wgtTextAreaFindNext(w, needle, caseSens, fwd)          Search from the cursor and select the match (a forward match at the cursor counts; repeating moves past it). Returns true if found.
  int32_t wgtTextAreaReplaceAll(w, needle, repl, caseSens)    Replace all occurrences. Returns the number of replacements made.
  bool wgtTextAreaReplaceSelection(w, needle, repl, caseSens, fwd)  If the current selection matches needle, replace it, then find the next occurrence in direction fwd. Returns true if anything was replaced or found.
  void wgtTextAreaSetLineMarker(w, line, gutterRgb, backRgb)  Mark 1-based line: a dot of gutterRgb (0xRRGGBB) in the line-number gutter and / or the line's background in backRgb; TEXTAREA_MARKER_NONE leaves a part out, both NONE removes the marker. Markers keep their line numbers when the text changes (the owner moves them). The DVX BASIC IDE marks breakpoints and the paused line this way.
  void wgtTextAreaClearLineMarkers(w)                         Remove every line marker.
  void wgtTextAreaSetSyntaxMode(w, mode)                      Use a built-in colorizer: "bas" / "basic" (BASIC: the compiler's keyword list, date literals by the lexer's shape rule, &H / &O and exponent numbers, REM and ' comments; the IDE's code editor uses it), "dhs" / "help" (help source), or "custom" (each painted line goes to the "Colorize" event through wgtFireEvent: args LineText and Colors, a string of one digit per character, "0" = TEXT_SYNTAX_DEFAULT; the host writes the TEXT_SYNTAX_* digits back into Colors; the BASIC Colorize event); NULL or "" turns colouring off.
  int32_t wgtTextAreaGetCursorLine(w)                         Get the current cursor line number.
  void wgtTextAreaSetGutterClickCallback(w, fn)               Set a callback for gutter clicks (e.g. for breakpoint toggling).
  int32_t wgtTextAreaGetWordAtCursor(w, buf, bufSize)         Copy the word under the cursor into buf. Returns its length.
  void wgtTextAreaSetSyntaxColors(w, colors, count)           Set the color palette used by the colorizer (0xRRGGBB per TEXT_SYNTAX_ index; 0 = the default).
  void wgtTextAreaCursorToEnd(w)                              Move the cursor to the very end of the buffer (past the last character).
  void wgtTextAreaSetCursor(w, line, col)                     Move the cursor to 1-based line/column without selecting; scrolls it into view. Out-of-range values clamp to the text.
  int32_t wgtTextInputGetSelStart(w)                          A single-line box's selection start (0-based), or its cursor when nothing is selected.
  int32_t wgtTextInputGetSelLength(w)                         Characters selected (0 = none).
  const char *wgtTextInputGetSelText(w)                       The selected text, valid until the next call.
  void wgtTextInputSetSelStart(w, start)                      Cursor to offset start, nothing selected.
  void wgtTextInputSetSelLength(w, length)                    Select length characters from SelStart (clamped to the text).
  void wgtTextInputSetSelText(w, text)                        Replace the selection (or insert at the cursor) with text (texthelp textEditSingleReplaceSel; a masked box fills its slots); fires onChange.
  void wgtTextInputCut(w), wgtTextInputCopy(w), wgtTextInputPaste(w), wgtTextInputSelectAll(w), wgtTextInputUndo(w)   The box's Ctrl+X / C / V / A / Z keys, run through its own key handler (password, mask, and read-only rules included).

API Struct (wgtRegisterApi "textinput")

The combined "textinput" API exposes all four constructors plus the TextArea functions and, appended at its end (inputGetSelStart ... inputUndo), the TextBox selection and clipboard functions. The designer uses per-type APIs registered under the names "textbox" (single-line) and "textarea" (multi-line), each exposing only the header and a create slot. Every API struct starts with a header (WGT_API_HEADER: ABI version and size, checked by wgtGetApi).

Properties (BASIC Interface)

Text is managed via the generic "Text" property. TextBox exposes:

  Property       Type       Access       Description
  --------       ----       ------       -----------
  MaxLength      Integer    Read/Write   Characters the box holds (its buffer size less one). Setting it gives a new buffer of that size (0 = TEXT_EDIT_DEFAULT_MAX_LEN, at most TEXT_EDIT_SINGLE_MAX_LEN), the text cut to fit; a masked box keeps its mask's length.
  PasswordChar   String     Read/Write   "" for a plain box; one character makes it a password box (as wgtPasswordInput, which shows a small bullet) showing that character for each one typed. Ignored on a masked box.
  Mask           String     Read/Write   "" for a plain box; a mask makes the live box a masked input (as wgtMaskedInput) holding what fits of its text; "" makes it plain again with the formatted text and its earlier MaxLength. Replaces PasswordChar.
  SelStart       Integer    Read/Write   wgtTextInputGetSelStart / wgtTextInputSetSelStart.
  SelLength      Integer    Read/Write   wgtTextInputGetSelLength / wgtTextInputSetSelLength.
  SelText        String     Read/Write   wgtTextInputGetSelText / wgtTextInputSetSelText.

TextBox methods: Copy, Cut, Paste, SelectAll, and Undo (wgtTextInputCopy ...).

TextArea exposes:

  Property     Type       Access       Description
  --------     ----       ------       -----------
  CursorLine   Integer    Read-only    Current cursor line number (1-based).
  CursorCol    Integer    Read-only    Current cursor column (1-based, in characters).
  LineCount    Integer    Read-only    Number of lines.
  SelStart     Integer    Read/Write   Selection start offset (0-based), or the cursor's when none; setting it moves the cursor there.
  SelLength    Integer    Read/Write   Characters selected; setting it selects from SelStart.
  SelText      String     Read/Write   The selection; setting it replaces it (textEditMultiReplaceSel), firing Change.
  MaxLength    Integer    Read/Write   Buffer capacity; setting it makes a new buffer (0 = TEXTAREA_DEFAULT_BUF, at most TEXT_EDIT_MULTI_MAX_LEN) with the text cut to fit.

Methods (BASIC Interface, TextArea)

  Method                                            Description
  ------                                            -----------
  AppendText text$                                  Append text to the end of the buffer.
  ClearLineMarkers                                  wgtTextAreaClearLineMarkers.
  Copy / Cut / Paste / SelectAll / Undo             The Ctrl+C / X / V / A / Z commands (the texthelp textEditMulti* functions the key handler runs); Cut, Paste, and Undo fire Change and do nothing when read-only.
  CursorToEnd                                       wgtTextAreaCursorToEnd.
  FindNext needle$, caseSensitive, forward          Search from the cursor (a match at the cursor counts; repeating moves past it). Returns True if found.
  GetLine(line%)                                    Returns a line's text (1-based), "" past the end.
  GetSyntaxColor(index%)                            Returns an index's colour (BASIC &HBBGGRR), -1 for the foreground.
  GetWordAtCursor()                                 Returns the word under the cursor.
  GoToLine line%                                    Scroll to a line and select it.
  ReplaceAll needle$, replacement$, caseSensitive   Replace all occurrences. Returns the count.
  ReplaceSelection(needle$, repl$, caseSens, fwd)   wgtTextAreaReplaceSelection (WGT_SIG_STR_STR_BOOL_BOOL).
  SetAutoIndent enabled                             Enable or disable automatic indentation on newline.
  SetCaptureTabs enabled                            When True, Tab inserts whitespace instead of moving focus.
  SetCursor line%, col%                             Move the cursor to a 1-based line and column without selecting.
  SetLineMarker line%, gutter&, back&               wgtTextAreaSetLineMarker with BASIC colours (&HBBGGRR, -1 = TEXTAREA_MARKER_NONE).
  SetShowLineNumbers show                           Show or hide line numbers in the gutter.
  SetSyntaxColor index%, color&                     One palette entry (BASIC colour, -1 = the default).
  SetSyntaxMode mode$                               Activate built-in syntax highlighting (e.g. "dhs", "bas"; "" to disable).
  SetTabWidth width%                                Set the tab stop width in characters.
  SetUseTabChar useChar                             When True, Tab inserts a literal tab; when False, spaces.

Events

  Callback     Description
  --------     -----------
  onChange     Fires when the text content changes.
  onKeyPress   Fires on each key press (ASCII value).
  onValidate   Called before committing a change. Return false to cancel.

The TextArea interface lists the event GutterClick (LineNumber As Integer, 1-based). The TextArea fires it with wgtFireEvent after its gutter-click callback, so it reaches the widget's onEvent (the form runtime's CtrlName_GutterClick); the gutter takes clicks when either the callback or onEvent is set.

Default Event

"Change" for both TextBox (basName: TextBox) and TextArea (basName: TextArea).

Timer

A non-visual widget that fires its onChange callback at a regular interval. Useful for animations, polling, and periodic updates. A new timer is running; call wgtTimerStop to keep it dormant.

Header: widgets/timer.h

Creation

WidgetT *tmr = wgtTimer(parent, 1000, true);  // 1 second, repeating
tmr->onChange = onTimerTick;

API Functions

  Function                                          Description
  --------                                          -----------
  WidgetT *wgtTimer(parent, intervalMs, repeat)     Create a timer. intervalMs is the interval in milliseconds. repeat: true for repeating, false for one-shot.
  void wgtTimerStart(w)                             Start the timer.
  void wgtTimerStop(w)                              Stop the timer.
  void wgtTimerSetInterval(w, intervalMs)           Change the timer interval and restart the countdown. An interval of 0 or less never fires.
  int32_t wgtTimerGetInterval(w)                    Returns the current interval in milliseconds.
  bool wgtTimerIsRunning(w)                         Returns true if the timer is currently running.
  void wgtTimerSetEnabled(w, enabled)               Start or stop the timer based on enabled flag.
  void wgtUpdateTimers(void)                        Global tick function. Called by the event loop to advance all active timers.

API Struct (wgtRegisterApi "timer")

  Slot           Function
  ----           --------
  header         WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create         wgtTimer
  start          wgtTimerStart
  stop           wgtTimerStop
  setInterval    wgtTimerSetInterval
  isRunning      wgtTimerIsRunning
  updateTimers   wgtUpdateTimers
  testReset      wgtTimerTestReset (present only in host test builds, DVX_HOST_TEST)

Events

  Callback     Description
  --------     -----------
  onChange     Fires each time the timer elapses (the BASIC layer surfaces this as the "Timer" event). A one-shot timer is stopped before the callback runs, so the callback may restart it.

Properties (BASIC Interface)

  Property     Type       Access       Description
  --------     ----       ------       -----------
  Enabled      Boolean    Read/Write   Whether the timer is running. Reading returns the running state; writing starts or stops it.
  Interval     Integer    Read/Write   Timer interval in milliseconds. Setting it restarts the countdown; 0 or less disables firing.

Methods (BASIC Interface)

  Method       Description
  ------       -----------
  Start        Start the timer.
  Stop         Stop the timer.

Extra Events (BASIC Interface)

  Event        Description
  -----        -----------
  Timer        Fires each time the timer elapses. Default event.

Toolbar

A horizontal container for toolbar buttons and controls. Typically placed at the top of a window. Children (usually ImageButtons or Buttons) are laid out horizontally with toolbar-appropriate spacing: 2 pixels of padding and spacing by default, overridable through the common padding/spacing fields.

Header: widgets/toolbar.h

Creation

WidgetT *tb = wgtToolbar(parent);
wgtImageButtonFromFile(tb, "icons/save.bmp");
wgtImageButtonFromFile(tb, "icons/open.bmp");

API Functions

  Function                          Description
  --------                          -----------
  WidgetT *wgtToolbar(parent)       Create a toolbar container.
  bool wgtToolbarGetFace(w, colors, face)   True when w is a toolbar; sets *face to the fill color its children sit on.

API Struct (wgtRegisterApi "toolbar")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wgtToolbar
  getFace    wgtToolbarGetFace

Properties, Methods, Events

Toolbar has no widget-specific properties, methods, or events. Add children (buttons, separators, etc.) to populate.

Default Event

None. (VB basName: Toolbar.)

TreeView

A hierarchical tree control with expandable/collapsible nodes. Supports single and multi-selection and drag-to-reorder. Tree items are added as children of the TreeView or of other tree items to create nested hierarchies.

Header: widgets/treeView.h

Creation

WidgetT *tv    = wgtTreeView(parent);
WidgetT *root  = wgtTreeItem(tv, "Root");
WidgetT *child = wgtTreeItem(root, "Child");

API Functions

  Function                                              Description
  --------                                              -----------
  WidgetT *wgtTreeView(parent)                          Create a tree view control.
  WidgetT *wgtTreeItem(parent, text)                    Add a tree item as a child of the tree view or another tree item.
  WidgetT *wgtTreeViewGetSelected(w)                    Get the currently selected tree item (NULL if none).
  void wgtTreeViewSetSelected(w, item)                  Set the selected tree item (expanding its ancestors and scrolling it into view). Items of other trees are ignored.
  void wgtTreeViewSetMultiSelect(w, multi)              Enable or disable multi-selection. Turning it off clears the per-item selection.
  void wgtTreeViewSetReorderable(w, reorderable)        Enable drag-to-reorder of items.
  void wgtTreeItemSetExpanded(w, expanded)              Expand or collapse a tree item. Collapsing over the selected item moves the selection to w.
  bool wgtTreeItemIsExpanded(w)                         Check if a tree item is expanded.
  bool wgtTreeItemIsSelected(w)                         Check if a tree item is selected (single-select: it is the selected item).
  void wgtTreeItemSetSelected(w, selected)              Select or deselect a tree item. In single-select mode this sets or clears the tree's selected item.

API Struct (wgtRegisterApi "treeview")

  Slot              Function
  ----              --------
  header            WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create            wgtTreeView
  getSelected       wgtTreeViewGetSelected
  setSelected       wgtTreeViewSetSelected
  setMultiSelect    wgtTreeViewSetMultiSelect
  setReorderable    wgtTreeViewSetReorderable
  item              wgtTreeItem
  itemSetExpanded   wgtTreeItemSetExpanded
  itemIsExpanded    wgtTreeItemIsExpanded
  itemIsSelected    wgtTreeItemIsSelected
  itemSetSelected   wgtTreeItemSetSelected

Properties

No widget-specific properties.

Methods (BASIC Interface)

  Method                                Description
  ------                                -----------
  AddChildItem parentIdx%, text$        Add a child node under the node at the given index.
  AddItem text$                         Add a root-level node.
  Clear                                 Remove all nodes.
  GetItemText(index%)                   Returns the text of the node at the given depth-first index.
  IsExpanded(index%)                    Returns True if the node is expanded.
  IsItemSelected(index%)                Returns True if the node is selected.
  ItemCount()                           Returns the total number of nodes.
  SetExpanded index%, expanded          Expand or collapse the node (collapsing moves a hidden selection to the node).
  SetItemSelected index%, selected      Select or deselect the node (single-select: sets or clears the selection).
  SetMultiSelect multi                  Enable or disable multi-selection.
  SetReorderable reorderable            Enable or disable drag-to-reorder.

Events

  Callback     Description
  --------     -----------
  onClick      Fires when a tree item is clicked.
  onDblClick   Fires when a tree item is double-clicked.
  onChange     Fires when the selection changes (including Space toggles in multi-select mode) and after a drag-reorder.

Default Event

"Click" (VB basName: TreeView).

WrapBox

A flow/wrap layout container that arranges children left-to-right, wrapping to the next row when the available width is exceeded. Each row's height is the maximum child height in that row. Supports configurable spacing between items and rows, and per-row alignment for short rows.

Header: widgets/wrapBox.h

Creation

WidgetT *wrap = wgtWrapBox(parent);
wgtButton(wrap, "Tag 1");
wgtButton(wrap, "Tag 2");
wgtButton(wrap, "Tag 3");

API Functions

  Function               Description
  --------               -----------
  wgtWrapBox(parent)     Create a wrap box container. Macro that calls the registered "wrapbox" create slot.

API Struct (wgtRegisterApi "wrapbox")

  Slot       Function
  ----       --------
  header     WGT_API_HEADER (ABI version and size, checked by wgtGetApi)
  create     wrapBoxCreate

Properties (Common Container Fields)

WrapBox uses the common WidgetT container fields for layout control:

  Property     Description
  --------     -----------
  spacing      Gap between children in both the horizontal and vertical directions (tagged size). Default is 4 pixels; wgtPixels(0) is none.
  padding      Internal padding around children (tagged size). Default is 2 pixels; wgtPixels(0) is none.

Properties (BASIC Interface)

  Property     Type                         Access       Description
  --------     ----                         ------       -----------
  Alignment    Enum (Left, Center, Right)   Read/Write   Row alignment for rows that do not fill the full width.

Methods

No widget-specific methods.

Events

WrapBox is a container widget. It uses the common events only. No widget-specific events are defined.

Default Event

"Click" (VB basName: WrapBox, namePrefix: WrapBox).