Package system!

This commit is contained in:
Scott Duensing 2026-10-15 17:45:45 -05:00
parent 7540e9e34f
commit f8ec4586e3
461 changed files with 35366 additions and 2104 deletions

View file

@ -218,14 +218,15 @@ include src/sdkHeaders.mk
# The SDK's own files (README, sdk.mk, newProject.sh, project templates)
# live in src/sdk; its bin/ holds the host tools a project build runs.
SDK_SRC = src/sdk
SDK_TOOLS = bin/host/dvxres bin/host/dvxhlpc bin/host/bascomp bin/host/mkfont src/tools/dxeGen.sh
SDK_TOOLS = bin/host/dvxres bin/host/dvxhlpc bin/host/bascomp bin/host/mkfont bin/host/dvxpkg src/tools/dxeGen.sh
# The host tools come from tools (dvxres, dvxhlpc, mkfont) and apps (bascomp).
deploy-sdk: tools apps
# The host tools come from tools (dvxres, dvxhlpc, mkfont), apps (bascomp),
# and installer (dvxpkg).
deploy-sdk: tools apps installer
@echo "Building SDK..."
@# Start from an empty tree so a header dropped from the lists goes away.
rm -rf $(SDKDIR)
@mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/richtext $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow
@mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/richtext $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/pkg $(SDKDIR)/include/json $(SDKDIR)/include/http $(SDKDIR)/include/pkgnet $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow
cp $(SDK_CORE_H) $(SDKDIR)/include/core/
cp $(SDK_SHELL_H) $(SDKDIR)/include/shell/
cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/
@ -242,6 +243,10 @@ deploy-sdk: tools apps
cp $(SDK_JOYSTICK_H) $(SDKDIR)/include/joystick/
cp $(SDK_AUDIO_H) $(SDKDIR)/include/audio/
cp $(SDK_MUSIC_H) $(SDKDIR)/include/music/
cp $(SDK_PKG_H) $(SDKDIR)/include/pkg/
cp $(SDK_JSON_H) $(SDKDIR)/include/json/
cp $(SDK_HTTP_H) $(SDKDIR)/include/http/
cp $(SDK_PKGNET_H) $(SDKDIR)/include/pkgnet/
@# Networking: dvxNet.h and the standard socket headers, which keep their
@# subdirectories (sys/, netinet/, arpa/); sdk.mk adds include/net only
@# for programs that set NET = 1.

View file

@ -23,3 +23,4 @@ CLS = apps/kpunch/dvxbasic/dvxbasic.app
CTL = apps/kpunch/dvxbasic/dvxbasic.app
HLP = apps/kpunch/dvxhelp/dvxhelp.app
DHS = apps/kpunch/dvxhelp/helpedit.app
DPK = apps/kpunch/software/software.app

View file

@ -23,6 +23,7 @@ the shipped list.
| `wpaper/blueglow.jpg` | `bin/shared/wpaper/blueglow.jpg` | Wallpaper image |
| `wpaper/swoop.jpg` | `bin/shared/wpaper/swoop.jpg` | Wallpaper image |
| `wpaper/triangle.jpg` | `bin/shared/wpaper/triangle.jpg` | Wallpaper image |
| `keys/*.KEY` | `bin/system/KEYS/*.KEY` | DVX's repository key: packages it signs (DVX's own, and everything the package server publishes) are trusted as DVX's |
The desktop sounds (`bin/shared/sounds/`) are made by mksounds and the
fonts (`bin/shared/fonts/`) by mkfont (src/tools).
@ -193,3 +194,18 @@ ignored. Names are case-insensitive.
Wallpapers support BMP, PNG, JPEG, and GIF formats. They are
pre-rendered to screen dimensions in native pixel format at load time.
## The Repository Key
DVX trusts as its own the packages signed by the keys in `SYSTEM\KEYS`,
and refuses an update of DVX itself (`kpunch.dvx`) signed by any other.
The key pair is made once, on a computer the package server never
touches:
bin/host/dvxpkg keygen "DVX Repository" KPUNCH
`KPUNCH.KEY` (the public half) goes in `config/keys/`, so every build
ships it. `KPUNCH.SEC` stays offline, with the operator who approves
packages (server/README.md); `config/keys/.gitignore` keeps a stray copy
of it out of the repository.

View file

@ -51,6 +51,19 @@ dns2 =
hostname = DVX
vector = auto
; The package server (Software's File > Package Server): Software gets
; packages and updates from it, and publishes with the account.
; server: its host name, port: its port (both empty: DVX's own server)
; user, password: an account there, to publish packages (empty: none).
; Software saves the password scrambled; one typed here by hand is read
; as it is and scrambled the next time Software saves the settings.
[packages]
server =
port =
user =
password =
; Printer settings (Control Panel's Printer page and File > Page Setup).
; port: LPT1, LPT2, LPT3 or FILE (print into the file named by file)
; model: text (any printer, text only), epson9, epson24 (Epson ESC/P
@ -96,8 +109,9 @@ footer = Page &p
; mpuMode: what is on the MIDI port: gm (a General MIDI synthesizer),
; mt32 (a Roland MT-32) or raw (send music unchanged)
; Optional: oplPort (default 388) and mpuPort (default 330), in hex;
; midiBank = the instrument bank file for MIDI music (OP2 format, path
; from the DVX folder; default libs\kpunch\music\genmidi.op2)
; midiBank = the instrument bank for MIDI music (OP2 format): its name in
; SHARED\BANKS or USER\BANKS (an add-on's as VENDOR\NAME), or a path from
; the DVX folder; not set, the bundled one. Control Panel picks it.
; The Sound Blaster's port, IRQ and DMA come from the BLASTER setting (for
; example SET BLASTER=A220 I5 D1 H5 in AUTOEXEC.BAT).

3
config/keys/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Secret keys never belong in the repository: only the public KPUNCH.KEY goes here
*.SEC
*.sec

4
config/keys/KPUNCH.KEY Normal file
View file

@ -0,0 +1,4 @@
[key]
name = DVX Repository
id = f32a2ff2e9471fd7
public = 1f67ffe49191d83792c8bba8d10b5abaa890db05421e6001d7d3b9b6093c2f10

View file

@ -46,6 +46,7 @@ img { max-width: 100%; }
<li><a href="#ide.editor">Code Editor</a></li>
<li><a href="#ide.designer">Form Designer</a></li>
<li><a href="#ide.project">Project System</a></li>
<li><a href="#ide.package">Making a Package</a></li>
<li><a href="#ide.usercontrols">UserControls in the IDE</a></li>
<li><a href="#ide.properties">Properties Panel</a></li>
<li><a href="#ide.toolbox">Toolbox</a></li>
@ -181,6 +182,7 @@ img { max-width: 100%; }
<li><a href="#ctrl.form">ActiveControl</a></li>
<li><a href="#lang.collection">Add</a></li>
<li><a href="#ide.menu.file">Add File</a></li>
<li><a href="#lang.runtime">Add-on packages</a></li>
<li><a href="#ctrl.treeview">AddChildItem</a></li>
<li><a href="#ctrl.listbox">AddItem</a></li>
<li><a href="#ctrl.treeview">AddItem</a></li>
@ -425,6 +427,7 @@ img { max-width: 100%; }
<li><a href="#lang.runtime">DlgColor</a></li>
<li><a href="#lang.runtime">DlgFileOpenIn$</a></li>
<li><a href="#lang.runtime">DlgFileSaveIn$</a></li>
<li><a href="#lang.runtime">DlgFolder$</a></li>
<li><a href="#lang.runtime">DlgFont</a></li>
<li><a href="#lang.runtime">DlgFontName$</a></li>
<li><a href="#lang.runtime">DlgFontSize</a></li>
@ -436,6 +439,7 @@ img { max-width: 100%; }
<li><a href="#lang.forms">DoEvents</a></li>
<li><a href="#lang.forms">DOEVENTS</a></li>
<li><a href="#lang.datatypes">Double</a></li>
<li><a href="#ide.package">DPK files</a></li>
<li><a href="#ctrl.common.props">Drag</a></li>
<li><a href="#ctrl.common.props">Drag and Drop</a></li>
<li><a href="#ctrl.common.props">DragDrop</a></li>
@ -637,6 +641,9 @@ img { max-width: 100%; }
<li><a href="#ide.shortcuts">Hotkeys</a></li>
<li><a href="#lang.func.date">HOUR</a></li>
<li><a href="#ctrl.hscrollbar">HScrollBar</a></li>
<li><a href="#lang.runtime">http.bas</a></li>
<li><a href="#lang.runtime">HttpOpen</a></li>
<li><a href="#lang.runtime">HttpRunFor</a></li>
<li><a href="#ctrl.form">Icon (form)</a></li>
<li><a href="#lang.runtime">Icon views you draw yourself</a></li>
<li><a href="#ide.overview">IDE</a></li>
@ -688,6 +695,9 @@ img { max-width: 100%; }
<li><a href="#lang.runtime">joystick.bas</a></li>
<li><a href="#lang.runtime">JoyX</a></li>
<li><a href="#lang.runtime">JoyY</a></li>
<li><a href="#lang.runtime">JSON</a></li>
<li><a href="#lang.runtime">json.bas</a></li>
<li><a href="#lang.runtime">JsonParse</a></li>
<li><a href="#lang.clipboard">Key Codes</a></li>
<li><a href="#ide.shortcuts">Keyboard Shortcuts</a></li>
<li><a href="#lang.filesystem">KILL</a></li>
@ -745,6 +755,7 @@ img { max-width: 100%; }
<li><a href="#lang.func.string">LTRIM$</a></li>
<li><a href="#ide.menu.file">Make Control</a></li>
<li><a href="#ide.usercontrols">Make Control</a></li>
<li><a href="#ide.package">Make Package</a></li>
<li><a href="#ctrl.textbox">Mask</a></li>
<li><a href="#ctrl.image">MaskColor</a></li>
<li><a href="#ctrl.textbox">Masked input</a></li>
@ -847,6 +858,8 @@ img { max-width: 100%; }
<li><a href="#ctrl.optionbutton">OptionIndex</a></li>
<li><a href="#lang.operators">OR</a></li>
<li><a href="#ide.output">Output Window</a></li>
<li><a href="#ide.package">Package name</a></li>
<li><a href="#ide.package">Packages</a></li>
<li><a href="#ctrl.form">Padding</a></li>
<li><a href="#ctrl.hbox">Padding</a></li>
<li><a href="#lang.screen">PAINT</a></li>
@ -863,6 +876,16 @@ img { max-width: 100%; }
<li><a href="#lang.screen">PEN ON</a></li>
<li><a href="#ctrl.image">Picture</a></li>
<li><a href="#ctrl.picturebox">PictureBox</a></li>
<li><a href="#lang.runtime">pkg.bas</a></li>
<li><a href="#lang.runtime">PkgBuild</a></li>
<li><a href="#lang.runtime">PkgInstallStart</a></li>
<li><a href="#lang.runtime">PkgKeygen</a></li>
<li><a href="#lang.runtime">pkgnet.bas</a></li>
<li><a href="#lang.runtime">PkgOpen</a></li>
<li><a href="#lang.runtime">PkgPlan</a></li>
<li><a href="#lang.runtime">PkgRemoveStart</a></li>
<li><a href="#lang.runtime">PkgRunFor</a></li>
<li><a href="#lang.runtime">PkgSign</a></li>
<li><a href="#lang.sound">PLAY</a></li>
<li><a href="#lang.sound">PLAY function</a></li>
<li><a href="#lang.runtime">PlayNoise</a></li>
@ -886,9 +909,11 @@ img { max-width: 100%; }
<li><a href="#lang.runtime">PrefsDvxIni$</a></li>
<li><a href="#lang.runtime">PrefsGetChoice</a></li>
<li><a href="#lang.runtime">PrefsGetChoiceFrom</a></li>
<li><a href="#lang.runtime">PrefsGetSecret$</a></li>
<li><a href="#lang.runtime">PrefsKeyName$</a></li>
<li><a href="#lang.runtime">PrefsLoad</a></li>
<li><a href="#lang.runtime">PrefsSaveAs</a></li>
<li><a href="#lang.runtime">PrefsSetSecret</a></li>
<li><a href="#lang.runtime">PrefsVideoWidth</a></li>
<li><a href="#lang.screen">PRESET</a></li>
<li><a href="#lang.io">PRINT</a></li>
@ -1109,6 +1134,7 @@ img { max-width: 100%; }
<li><a href="#lang.forms">Show</a></li>
<li><a href="#ctrl.form">Show</a></li>
<li><a href="#ctrl.helppane">ShowTopic</a></li>
<li><a href="#ide.package">Signing a package</a></li>
<li><a href="#lang.func.math">SIN</a></li>
<li><a href="#lang.datatypes">Single</a></li>
<li><a href="#lang.runtime">SizeChars</a></li>
@ -1409,6 +1435,7 @@ img { max-width: 100%; }
<li><a href="#ctrl.terminal">VT100</a></li>
<li><a href="#ide.debug.watch">Watch Expressions</a></li>
<li><a href="#ide.debug.watch">Watch Window</a></li>
<li><a href="#lang.runtime">Web requests</a></li>
<li><a href="#lang.func.date">WEEKDAY</a></li>
<li><a href="#lang.func.date">WEEKDAYNAME</a></li>
<li><a href="#lang.loops">WEND</a></li>
@ -1502,6 +1529,7 @@ img { max-width: 100%; }
---
Make Executable... Compile the project and save it as a standalone .app file. Prompts for a debug or release build. A release build leaves out debugging information and gives forms and controls short internal names, except in a program that uses names as text (the Name property, Controls, DesignSelection, or a DesignMouse event procedure), which keeps them.
Make Control... In a control project (Project Properties, Type Control) this item replaces Make Executable: it compiles the project and saves its controls as a control file (.wgt, named after the project). See UserControls in the IDE.
Make Package... Make the program (or, in a control project, the control file) and its help file into an add-on package (.DPK) that anyone can install with Software. See Making a Package.
---
Rename File... Give the file selected in the Project Explorer a new name, on disk and in the project. See Renaming a File under Project System.
Remove File Remove the selected file from the project (prompts to save if modified).
@ -1745,7 +1773,7 @@ Count = 1</code></pre>
New UserControl Adds a new UserControl to the project and opens it (see UserControls in the IDE).
Rename File Gives a file a new name on disk and in the project file (see Renaming a File below).
Remove File Removes a file from the project (prompts to save if modified). Removing the startup form clears the Startup Form setting.
Project Properties Opens a dialog for editing project metadata (name, type, author, company, version, copyright, description, icon, help file, startup form), the fonts the program carries, whether more than one copy may run at once, and the Arguments for runs from the IDE.</pre>
Project Properties Opens a dialog for editing project metadata (name, type, author, company, version, copyright, description, icon, help file, startup form), the fonts the program carries, whether more than one copy may run at once, the Arguments for runs from the IDE, and the Package and Needs that File &gt; Make Package uses.</pre>
<h2>Renaming a File</h2>
<p>Select a file in the Project Explorer and choose File &gt; Rename File. Type the new file name; the extension may be left out (a module stays .bas, a class module .cls, a UserControl .ctl, and a form .frm, and cannot be given another kind's extension). The file is renamed in the project's directory and the project file is saved at once with the new name, so the two always agree. Nothing changes, and a message says why, when the name is not a valid file name, when another project file or any file in the directory already has it, or when the file or the project file cannot be written -- an existing file is never overwritten. A file that has never been saved is simply given the new name, and is written under it when you save.</p>
<p>A class module named after its file (it has no Attribute VB_Name line, or one that matches the file name, as File &gt; New Class Module writes it) takes the new name as its class name: its Attribute VB_Name line is changed to match (an edit that is saved with the file, like any other). The new name must then be a valid name that no other form, class module, or UserControl uses. Code that uses the old class name is not changed. A UserControl named after its file (as File &gt; New UserControl makes it) takes the new name too, on its Begin UserControl line; forms that use the old name are not changed. A form keeps its Name property; rename the form itself in the Properties window.</p>
@ -1770,6 +1798,16 @@ Option Explicit</code></pre>
<p><a href="#ide.usercontrols">UserControls in the IDE</a></p>
<p><a href="#ide.overview">Back to Overview</a></p>
</div>
<div class="topic" id="ide.package">
<h1>Making a Package</h1>
<p>File &gt; Make Package turns the project into an add-on package: one .DPK file holding the program (built as a release build) and its help file, which anyone can install with the Software program -- it puts the program in Program Manager's Installed Programs -- and remove again. In a control project the package holds the control file instead, and installs it for every program to use.</p>
<p>The first time, Make Package asks for the package name: who made it and what it is, with a dot between them, such as acme.chess (each part 1 to 8 lower-case letters, digits, or _). It suggests one from the project's Publisher and Name. Keep the same package name for every version, so that Software installs a new version over the old one. The name is kept in the project and can be changed under File &gt; Project Properties (Package).</p>
<p>It then asks where to save the package and which key to sign it with. Signing tells the people who install the package that it came from you and was not changed on the way; the list offers the keys in your KEYS folder (make one with Package Builder's New Key) and Not signed. The key chosen is offered first the next time.</p>
<p>The package takes the rest from the project: its name, Version (give each version you hand out a higher one, such as 1.0.0, then 1.1.0), Description (shown as its summary), Publisher, and Author. When the program needs other add-on packages to work, list them under Project Properties, Needs: each package name with the oldest version that will do, with commas between them, such as acme.cards 1.0, acme.dice 2.1 (a package name alone takes any version). DVX's own libraries need not be listed.</p>
<p>A program that needs more files than its help -- data files, or files in other folders -- is packaged with Package Builder, which takes a whole folder.</p>
<p><a href="#ide.project">Project System</a></p>
<p><a href="#ide.overview">Back to Overview</a></p>
</div>
<div class="topic" id="ide.usercontrols">
<h1>UserControls in the IDE</h1>
<p>A UserControl is a control you write in BASIC (see UserControls: Your Own Controls in the BASIC Language Reference for how to write one). This topic covers making one in the IDE, editing it, trying it on a form, and turning it into a control file every program can use.</p>
@ -1801,7 +1839,7 @@ End Sub</code></pre>
Width, Height Enter a whole number of pixels (at least 1): the control's natural size.
CanGetFocus Each double-click switches between True and False.
BackColor Pick a color in the color dialog.
ToolboxIcon Type the name of a 24 x 24 picture file in the project's folder.
ToolboxIcon Type the name of a 24 x 24 picture file in the project's folder, or click Browse... to pick it.
Description Type the toolbox tooltip.
DefaultEvent Pick one of the control's Events (or none): the event the designer opens when a placed control is double-clicked.</pre>
<p>The change is written into the header lines of the file (the rest of the file stays as it is) and saved with the file. The Code Editor shows the new header; breakpoints in the code move with the lines below it. You can also edit the header lines in the Code Editor by hand.</p>
@ -1827,7 +1865,7 @@ End Sub</code></pre>
<p>The Properties panel (Window &gt; Properties) has two sections:</p>
<ul>
<li>Control tree -- a TreeView at the top listing the form and all its controls in layout order. Click a control name to select it in both the Properties panel and the Form Designer. Drag items in the tree to reorder controls in the form's layout.</li>
<li>Property list -- a two-column ListView below the tree showing property names and values for the selected control. Double-click a property value to edit it via an InputBox dialog. Changes take effect immediately in the designer preview.</li>
<li>Property list -- a two-column ListView below the tree showing property names and values for the selected control. Double-click a property value to edit it via an InputBox dialog. A file property (Picture, DragIcon, the form's Icon, and a Data control's DatabaseName) has Browse... beside its box: a file picked in the project's folder (or a folder inside it) is kept relative to the project, so the project still works when it is moved. Changes take effect immediately in the designer preview.</li>
</ul>
<p>Each control type exposes different properties (e.g., Caption, Text, Width, Height, MaxWidth, MaxHeight, Weight, Alignment, Enabled, Visible, and type-specific properties like DataSource and DataField for data-bound controls). A UserControl placed on a form lists the properties its code defines.</p>
<p>The Font row shows the font the selected control, or the form, draws with: its family, size in pixels, and styles, such as &quot;DejaVu Sans, 14, Bold&quot;. Double-click it to choose a font in the Font dialog; the design surface shows it at once. The choice is kept as the six Font properties (FontName, FontSize, FontBold, FontItalic, FontUnderline, and FontStrikethru), which then appear in the list and can be changed one at a time too. Choosing the font the control would have anyway (its container's or the form's) removes them, so the control follows the form's font again. A font chosen for the form is used by every control that has none of its own. For a TextBox, TextArea, ComboBox, SpinButton, or Terminal the dialog lists only fixed-pitch families.</p>
@ -2258,6 +2296,7 @@ End Sub</code></pre>
<li>Paths may be written with / or \ wherever a path is taken (OPEN, KILL, NAME, MKDIR, CHDIR, DIR$, FILELEN, and the file and folder functions of the libraries), mixed too; paths DVX gives back (CURDIR$, App.Path, App.Data, the file dialogs, the FileOps results) use \, as DOS shows them. New: FileOpsPathNative$ gives a path of your own that form. DIR$(&quot;C:\*.*&quot;) lists the root of C: (before, it listed C:'s current folder). SHELL commands still go to DOS as written, where / starts a switch.</li>
<li>The IDE shows a project's file paths with \ and keeps writing them with / in the .dbp file, so a project reads the same everywhere.</li>
<li>The Open and Save As dialogs (DlgFileOpen$, DlgFileSave$, and the IDE's own) have a Drive list beside the Path box (Alt+D); a drive with no disk in it says it is not ready.</li>
<li>New: DlgFolder$(title, folder) in commdlg.bas lets the user choose a folder, existing or new, for a Browse button beside a folder box. A title of &quot;&quot; gives any of the file dialogs its usual title (it used to leave the title bar empty).</li>
<li>Changed: FileOpsValidName takes the folder the name is for, FileOpsValidName(folder, itemName), since the rules depend on its disk; XferSafeName$ takes the folder a file is received into, XferSafeName$(name, folder). New: FileOpsLongNames, FileOpsShortPath$, FileOpsValidLongName, and FileOpsValidShortName (fileops.bas), and ShellArgQuote$ (shell.bas), which puts a path with spaces in quotes for a command line.</li>
<li>Programs compiled with an older release must be rebuilt. A program whose procedure table records more than 64 locals for a procedure is refused when it is loaded.</li>
<li>A class may have at most 65536 member variables and a form at most 65536 form-level variables (as many as the compiled program can number); more is a compile error (&quot;Too many member variables in X&quot;, &quot;Too many form-level variables&quot;), and a program file that records more is refused when it is loaded.</li>
@ -2300,6 +2339,7 @@ End Sub</code></pre>
</ul>
<h2>IDE</h2>
<ul>
<li>Properties window: Picture, DragIcon, the form's Icon, DatabaseName, and a UserControl's ToolboxIcon have Browse... beside the box; a file in the project's folder goes in relative to it.</li>
<li>Immediate window: Enter evaluates the line it is pressed on, assignments accept any expression, and a statement that does not finish is stopped.</li>
<li>A compile error puts the caret on the column of the error, not just its line.</li>
<li>Output window: always shows the newest output and drops the oldest lines when full; Output Window to Log also logs errors.</li>
@ -4902,7 +4942,7 @@ End Sub</code></pre>
<p>DVX ships a set of BASIC include files (.bas) in the sdk/include/basic directory. Each file is a small library of DECLARE LIBRARY wrappers around native DVX runtime functions. Add an include file to your project (the IDE's Add File command, or editing the .dbp with another tool) and the declared functions become available to your code.</p>
<pre> File Provides
---- --------
commdlg.bas File Open/Save dialogs (DlgFileOpenIn$ and DlgFileSaveIn$ start in a folder you name), input dialogs, choice dialogs, save-prompt dialogs, the color dialog, the font dialog (DlgFont), message boxes with your own buttons (DlgButtons)
commdlg.bas File Open/Save dialogs (DlgFileOpenIn$ and DlgFileSaveIn$ start in a folder you name), the folder dialog (DlgFolder$, for a Browse button), input dialogs, choice dialogs, save-prompt dialogs, the color dialog, the font dialog (DlgFont), message boxes with your own buttons (DlgButtons)
sql.bas SQLite database access (SQLOpen, SQLQuery, SQLNext, SQLField$, etc.)
comm.bas Serial-port I/O (raw and packet) and optional encrypted links; port settings read back and changed, why a call failed, breaks, ring, and line errors (SerOpen, SerSet, SerLastError, CommOpen, CommHandshakeStep, ...)
conn.bas Connections the way Terminal makes them: a serial port, a modem that dials, the secure link to the DVX host proxy, or Telnet, shown on a Terminal control, with capture, answerback, and file transfers (ConnCreate, ConnOpen, ConnPoll, ConnAttach, ConnSendFiles, ...)
@ -4927,7 +4967,11 @@ End Sub</code></pre>
compiler.bas The BASIC compiler: compile a project or files you give it, find compile errors in their files, list a program's procedures and the language's keywords, build an .app or a control file; put a project's UserControls on a form designer's toolbox; read source text as the compiler does (tokens, code or string or comment, renaming a control in code, WithEvents variables, class Events), F1 help topics; open, change, and save project files and UserControl headers (CompSourceFromProject, CompSourceAdd, CompCompile, CompError$, CompMapFile$, CompProcList$, CompBuildApp, CompDesignControls, CompTokens$, CompRenameInCode$, CompHelpTopic$, ProjOpen, ProjSave, ProjCtlSetHeader$, ...)
debug.bas Run a compiled program inside yours and debug it: breakpoints, stepping, its variables, expressions, Immediate-window lines, its PRINT output (RunCreate, RunStart, RunPoll, DbgSetBreakpoints, DbgStepOver, DbgLocals$, DbgEval$, DbgExec$, ...)
designer.bas Form designers: the control types, whether each holds other controls and can be placed, the names of new controls, default events, toolbox tooltips and icons, and each type's properties (with their types and value names), methods, and events (with each event's parameters); the names in .frm and class module text; .frm files as data to read, edit, and write, controls copied and pasted as text, and a loaded form written back as .frm text (CtlTypeCount, CtlTypeName$, CtlTypeFlags, CtlPropCount, CtlPropName$, CtlPropType, CtlEventList$, CtlEventParams$, CtlTypeEventParams$, FrmFormName$, FrmControlNames$, ClsClassName$, FrmOpen, FrmText$, FrmCtlAdd, FrmCtlText$, FrmCtlPaste, FrmMenuAdd, FormText$, ...)
shell.bas DVX itself, everything Program Manager and Task Manager do: start programs and open documents, command lines (split into their parts) and the Run dialog, the installed programs and their names, file types, the running programs (find one, bring it to the front, ask it to close, or end it), this program's priority, keys that work in every program, Task Manager, system information, Exit DVX, the desktop and the program started after it, the DVX log; Program Manager's program groups with their items, shortcut keys, and icons (ShellRun, ShellRunProgram, ShellProgramScan, ShellAssocCount, ShellRunningId, ShellRunningActivate, ShellRunningClose, ShellArg$, ShellSetPriority, HotkeyAdd, ShellExitDvx, ShellGroupsLoad, ShellItemGet$, ShellItemIcon$, ...)</pre>
shell.bas DVX itself, everything Program Manager and Task Manager do: start programs and open documents, command lines (split into their parts) and the Run dialog, the installed programs and their names, file types, the running programs (find one, bring it to the front, ask it to close, or end it), this program's priority, keys that work in every program, Task Manager, system information, Exit DVX, the desktop and the program started after it, the DVX log; Program Manager's program groups with their items, shortcut keys, and icons (ShellRun, ShellRunProgram, ShellProgramScan, ShellAssocCount, ShellRunningId, ShellRunningActivate, ShellRunningClose, ShellArg$, ShellSetPriority, HotkeyAdd, ShellExitDvx, ShellGroupsLoad, ShellItemGet$, ShellItemIcon$, ...)
pkg.bas Add-on packages: the packages installed in DVX and their details, files, and needs; reading .DPK files and planning what installing them takes; installing and removing as a job with progress; trusting a publisher's key; and making packages, signing them, and making keys (PkgOpen, PkgInstalledRows$, PkgOfferFile, PkgPlan, PkgInstallStart, PkgRunFor, PkgRemoveStart, PkgBuild, PkgSign, PkgKeygen, ...)
json.bas JSON: read a document (a web service's reply, a settings file) and find values in it by path such as packages[3].version, and quote text for JSON of your own (JsonParse, JsonText$, JsonNumber, JsonCount, JsonKey$, JsonQuote$, ...)
http.bas Web requests: GET a page, a file, or a web service's answer, or POST to one, over the network a little at a time with progress, to memory or to a file (resuming one left part-way), following redirects (HttpOpen, HttpStart, HttpRunFor, HttpStatus, HttpBody$, HttpSaveTo, ...)
pkgnet.bas A package server: what it offers, what it has that is newer than what is installed, and fetching packages with what they need, for pkg.bas to install (PkgNetOpen, PkgNetCatalogue, PkgNetUpdates, PkgNetFetch, PkgNetRunFor, PkgNetRows$, ...)</pre>
<h2>Using an Include Library</h2>
<pre><code>' With commdlg.bas added to the project
Dim path As String
@ -5104,7 +5148,7 @@ End Sub</code></pre>
<p>Paths: FileOpsPathNormalize$ (the full path of an existing folder, or &quot;&quot;), FileOpsPathParent$, FileOpsPathJoin$, FileOpsPathSame, FileOpsPathIsRoot, FileOpsPathIsAbsolute (True for a path that is complete by itself, such as &quot;C:\DOCS&quot;, False for one that counts from the current folder), FileOpsPathIsInside(path, folder) (True when path is folder itself or anywhere inside it: check it before copying or moving a folder, so it is never copied into itself), FileOpsPathSep$ (the character between folder names, &quot;\&quot; on DOS), FileOpsPathNative$ (a path written with that character, for showing it: &quot;C:/DOCS/A.TXT&quot; becomes &quot;C:\DOCS\A.TXT&quot;), FileOpsBaseName$, and FileOpsExtension$. Every one of them takes / and \ alike and gives back \. FileOpsFilterMatch(itemName, filter) tells whether a name matches a filter the way the file dialogs match it: wildcards * and ?, several patterns separated by ; such as &quot;*.bmp;*.png&quot;, case not mattering.</p>
<p>Files: FileOpsMakeDir and FileOpsRename (FileOpsError$ says why one failed), FileOpsTempPath$ and FileOpsReplaceFile (save safely: write the new file to FileOpsTempPath$(path$), then FileOpsReplaceFile swaps it in; the old file comes back if that fails), FileOpsValidName(folder, itemName) (whether a name can be used for a new file or folder in folder, by the rules of its disk; FileOpsError$ says why not), FileOpsValidLongName and FileOpsValidShortName (the long-name or the 8.3 rules by themselves, whatever the disk takes), FileOpsShortPath$ (the short 8.3 form of an existing path, for an old program that cannot take long names or blanks), FileOpsMakePath (a folder and every missing folder above it, which MKDIR does not make), FileOpsIsFolder, FileOpsGetAttr, and FileOpsSetAttr (the FILEOPS_ATTR_ bits), FileOpsModified$ and FileOpsSetModified (when a file was last changed, read and set in the form &quot;YYYY-MM-DD HH:MM&quot;; FileOpsSetModified returns False for a date that does not exist), FileOpsTreeSize with FileOpsTreeBytes, FileOpsTreeFiles, and FileOpsTreeFolders (the size of a whole folder), and FileOpsFormatSize$.</p>
<p>Drives: FileOpsDriveList$ (the drive letters), FileOpsDriveType (FILEOPS_DRIVE_FIXED for a hard disk, FILEOPS_DRIVE_REMOVABLE for a floppy disk drive, FILEOPS_DRIVE_REMOTE for a network drive or a CD-ROM; asking never makes a drive spin up), FileOpsBootDrive$ (the drive the computer started from), FileOpsFreeBytes, FileOpsFreeKb, and FileOpsTotalBytes (the size of the disk; right for disks past 2 GB too), FileOpsLongNames(path) (True when the disk holding path takes long file names: up to 255 characters, with spaces and several dots; otherwise names are 8.3). FileOpsIsWritable(folder) is False where files cannot be made (a CD-ROM, a write-protected floppy), and FileOpsReadOnlySystem() is True when nothing can be saved: DVX runs from such a disk and there is no TEMP folder to keep the user folder in. FileOpsTempDir$() is the folder for scratch files (&quot;&quot; when there is none).</p>
<p>DVX's folders: FileOpsUserDir$() is the user folder (USER in the DVX folder, or the folder DVXUSER names), where everything DVX and its programs save goes; FileOpsUserPath$(name) is a file or folder in it, for example FileOpsUserPath$(&quot;DOWNLOAD&quot;); FileOpsUserInTemp() is True when DVX runs from a disk it cannot write to and keeps the user folder in the TEMP folder. A program's own folder there is App.User. The fonts, sounds, themes, and desktop pictures come in two folders each, DVX's own in SHARED and the user's in the user folder; the kinds are FILEOPS_KIND_FONTS, FILEOPS_KIND_SOUNDS, FILEOPS_KIND_THEMES, and FILEOPS_KIND_WPAPER. FileOpsKindDir$(kind, user) is DVX's folder of a kind (user False) or the user's (True); FileOpsKindBrowseDir$(kind) is the one a file dialog starts in (the user's when it exists); FileOpsKindFind$(kind, fileName) finds a file by name, the user's first (&quot;&quot; when there is none); FileOpsKindName$(kind, path) is the name alone of a file in one of the two folders, the way settings store it (&quot;&quot; for any other path); FileOpsKindList(kind) lists both folders as one, sorted by name, a user's file hiding DVX's of the same name (a list for FileOpsListCount, FileOpsListName$, and the rest; FileOpsListFree when done). Programs read DVX's SHARED folder but never write in it.</p>
<p>DVX's folders: FileOpsUserDir$() is the user folder (USER in the DVX folder, or the folder DVXUSER names), where everything DVX and its programs save goes; FileOpsUserPath$(name) is a file or folder in it, for example FileOpsUserPath$(&quot;DOWNLOAD&quot;); FileOpsUserInTemp() is True when DVX runs from a disk it cannot write to and keeps the user folder in the TEMP folder. A program's own folder there is App.User. The fonts, sounds, themes, desktop pictures, and MIDI instrument banks come in two folders each, DVX's own (and add-on packages') in SHARED and the user's in the user folder; the kinds are FILEOPS_KIND_FONTS, FILEOPS_KIND_SOUNDS, FILEOPS_KIND_THEMES, FILEOPS_KIND_WPAPER, and FILEOPS_KIND_BANKS. An add-on's files are in a folder of its vendor's inside a kind's folder and are named VENDOR\NAME (for example ACME\SPACE.THM). FileOpsKindDir$(kind, user) is DVX's folder of a kind (user False) or the user's (True); FileOpsKindBrowseDir$(kind) is the one a file dialog starts in (the user's when it exists); FileOpsKindFind$(kind, fileName) finds a file by name, the user's first (&quot;&quot; when there is none); FileOpsKindName$(kind, path) is the name alone of a file in one of the two folders, the way settings store it (&quot;&quot; for any other path); FileOpsKindList(kind) lists both folders as one, sorted by name, a user's file hiding DVX's of the same name (a list for FileOpsListCount, FileOpsListName$, and the rest; FileOpsListFree when done). Programs read DVX's SHARED folder but never write in it.</p>
<p>FileOpsCopyTree, FileOpsMoveTree, and FileOpsDeleteTree do a whole folder at once. For a long copy, move, or delete the program stays usable when it runs the work as a job, a little at a time from a Timer: FileOpsJobCreate, then FileOpsJobAddSource for each file or folder, then FileOpsJobRunFor from the timer until it returns FILEOPS_STEP_DONE or FILEOPS_STEP_CANCELLED. When it returns FILEOPS_STEP_ASK (a file already exists, a file is read-only) or FILEOPS_STEP_ERROR, show FileOpsJobMessage$ and pass the user's choice to FileOpsJobAnswer. FileOpsJobCurrent$, FileOpsJobFilesDone, FileOpsJobFilesTotal, FileOpsJobPercent, FileOpsJobBytesDone, and FileOpsJobBytesTotal show the progress, and FileOpsJobCancel stops the job. Files copied from a CD-ROM are read-only; FileOpsJobClearAttr job, FILEOPS_ATTR_READONLY before the job runs makes every copy changeable (FILEOPS_ATTR_EDITABLE clears all four attribute bits). Whatever a program leaves open is freed when it ends.</p>
<pre><code>Dim job As Long
@ -5189,6 +5233,57 @@ End Sub</code></pre>
id = HotkeyNext()
Loop
End Sub</code></pre>
<h2>Add-on Packages</h2>
<p>With pkg.bas in the project, a program installs, removes, and makes add-on packages -- the .DPK files Software installs -- the way Software and Package Builder (both written in BASIC) do. PkgOpen() starts a session (a number; PkgClose ends it), and PkgError$(session) says why the last call that failed did. Lists come back as rows for a ListView's SetRows: cells separated by TAB, each row ending in a line feed.</p>
<p>What is installed. PkgInstalledRows$ lists every installed package (DVX itself is kpunch.dvx): id, name, version, kind, and summary. A package is named by its id, vendor.name (acme.chess). PkgField$(s, id, field) gives one detail (PKG_FIELD_NAME, PKG_FIELD_VERSION, PKG_FIELD_PUBLISHER, PKG_FIELD_SIGNER_NAME, ...); PkgFilesRows$, PkgDependsRows$, and PkgNeededBy$ its files, what it needs, and what needs it.</p>
<p>Installing. PkgOfferFile(s, path) reads a package file and every package beside it (what it may need) as offers, numbered from 0, and returns the number of the one at path (-1 when it cannot be read). PkgOfferField$ and PkgOfferFilesRows$ describe an offer before it is installed; PKG_FIELD_TRUST says whether DVX trusts who signed it. PkgPlan(s, &quot;0&quot;) plans installing it with everything it needs, checked against this DVX and the packages installed (False, with PkgError$ saying why, when it cannot be installed); PkgPlanRows$ lists what will go in, PkgPlanBytes the disk space, and PkgPlanRestart whether libraries or widgets are among them (they work from the next time DVX starts). PkgInstallStart(s) then starts the job, and PkgRunFor(s, milliseconds) runs it a little at a time, returning PKG_RUN_BUSY until it is PKG_RUN_DONE or PKG_RUN_FAILED (PkgRunMessage$ says why); PkgRunPercent and PkgRunCurrent$ show how far it is.</p>
<pre><code>offer = PkgOfferFile(s, path)
If offer &gt;= 0 And PkgPlan(s, CStr(offer)) Then
If PkgInstallStart(s) Then
Do
rc = PkgRunFor(s, 60)
pbJob.Value = PkgRunPercent(s)
DoEvents
Loop While rc = PKG_RUN_BUSY
End If
End If</code></pre>
<p>Removing. PkgDependents$(s, ids) lists the installed packages that need the ones going (they must go too), and PkgOrphans$(s, ids) those only they needed. PkgRemoveStart(s, ids, removeChanged) starts removing, run with PkgRunFor like an install; files changed since they were installed stay unless removeChanged, and PkgRunKept$ lists them.</p>
<p>Trust. A publisher signs a package with a key so people can tell it came from them. PkgTrustKey(s, keyPath) trusts a publisher's key file (.KEY) from then on.</p>
<p>From a package server. With pkgnet.bas in the project too, a program gets packages from a package server the way Software does. PkgNetOpen(&quot;&quot;) returns a handle on the server DVX's settings name, with the account they give; PkgNetClose frees it. PkgNetServer$, PkgNetPort, PkgNetUser$, and PkgNetPassword$ read those settings (PkgNetUrl$ is the address they make), and PkgNetSaveSettings(server, port, user, password) changes them. A job is started with PkgNetCatalogue (every package the server offers), PkgNetUpdates (what it has that is newer than the packages installed, DVX itself among them), or PkgNetFetch(n, ids) (packages by id, with everything they need that is not installed), and run with PkgNetRunFor(n, milliseconds) until it is PKGNET_DONE or PKGNET_FAILED (PkgNetError$ says why); PkgNetPercent and PkgNetCurrent$ show how far a fetch is. PkgNetRows$ lists the answer (id, name, version, kind, summary, size, and the version installed, if any), and PkgNetField$ and PkgNetDependsRows$ describe one package in it. Each package fetched is checked against the size and fingerprint the server gives, and the server's signature of it comes with it, so it is trusted or not exactly as a package file is. What was fetched is in PkgNetDir$: PkgOfferFolder(s, folder) reads every package there as the offers, and PkgOfferIndex(s, id) gives the number of the one wanted, to plan and install as above; PkgNetClear removes them afterwards. With an account (the settings', or PkgNetAccount(n, email, password) for another server), PkgNetUpload(n, path) publishes a package file for the server's review, and PkgNetMine(n) asks what the account has sent: PkgNetMineRows$ lists it (id, name, version, state, and the review's note); both sign in first.</p>
<pre><code>n = PkgNetOpen(&quot;&quot;)
If PkgNetFetch(n, &quot;acme.chess&quot;) Then
Do
rc = PkgNetRunFor(n, 60)
pbJob.Value = PkgNetPercent(n)
DoEvents
Loop While rc = PKGNET_BUSY
End If
If rc = PKGNET_DONE And PkgOfferFolder(s, PkgNetDir$(n)) &gt; 0 Then
offer = PkgOfferIndex(s, &quot;acme.chess&quot;)
' plan and install offer as above, then PkgNetClear n
End If</code></pre>
<p>Making packages. PkgBuild(s, template, files, out, secret) builds a package: template is a settings file with a [package] section (id, name, version, kind, and summary, publisher, author, licence, homepage, dvx as wanted) and a [depends] section (id = oldest version for each package it needs); files are rows of a name in the package (from the DVX folder, such as APPS\ACME\CHESS\CHESS.APP) and the file it is made from; secret is the secret key file (.SEC) to sign with, or &quot;&quot;. PkgFolderRows$(s, folder, prefix) gives those rows for every file in a folder, and PkgKindFolder$(kind, id) the folder a package of that kind keeps its files in. PkgSign signs a package already built, PkgKeygen(s, name, base) makes a key pair (base.KEY to give out, base.SEC to keep private), and PkgKeyInfo$ reads a key file's name and id. PkgIdValid and PkgVersionValid check an id and a version before building; PkgKindName$ lists the kinds.</p>
<h2>Web Requests and JSON</h2>
<p>With http.bas in the project, a program asks web servers for pages, files, and web services' answers. HttpOpen(method, url) makes a request (&quot;GET&quot; to fetch, &quot;POST&quot; to send) and returns a handle; HttpClose frees it. Before starting it, HttpAddHeader adds a header, HttpSetBody gives a body to send with its type (&quot;application/json&quot;, say), HttpSaveTo writes the answer's body to a file instead of keeping it (with keepPart True, a file left part-way is finished rather than started again), and HttpSetTimeout sets how many seconds without progress mean the server is not answering (30 to start with). HttpStart starts it (False for an address that is not http://; there is no encryption), and HttpRunFor(h, milliseconds) runs it a little at a time, returning HTTP_BUSY until it is HTTP_DONE or HTTP_FAILED:</p>
<pre><code>h = HttpOpen(&quot;GET&quot;, &quot;http://example.com/news.txt&quot;)
IF HttpStart(h) THEN
DO
rc = HttpRunFor(h, 60)
lblProgress.Caption = CStr(HttpBytes(h)) &amp; &quot; bytes&quot;
DoEvents
LOOP WHILE rc = HTTP_BUSY
END IF
IF rc = HTTP_DONE AND HttpStatus(h) = 200 THEN txtNews.Text = HttpBody$(h)
IF rc = HTTP_FAILED THEN MsgBox HttpError$(h)
HttpClose h</code></pre>
<p>HTTP_DONE means the server answered: HttpStatus is how (200 OK, 404 not found, ...), HttpBody$ what it sent, HttpReplyHeader$ one of its headers, and HttpUrl$ where the answer came from when the server sent the request elsewhere (redirects are followed). HTTP_FAILED means no answer came, and HttpError$ says why. HttpBytes and HttpTotal (-1 until the server says) show progress.</p>
<p>With json.bas in the project, JsonParse(text) reads JSON -- what most web services answer with -- and returns a handle (0 when it is not JSON: JsonError$ says why and on which line); JsonFree frees it. The other functions find a value by its path from the top: member names and item numbers from 0, such as &quot;packages[3].version&quot;. JsonType says what is there (JSON_STRING, JSON_NUMBER, JSON_ARRAY, ..., JSON_NONE for nothing), JsonText$ gives a string, JsonNumber a number, JsonBool True or False, JsonCount how many items an array or members an object has, and JsonKey$(h, path, i) the name of an object's member i. JsonQuote$(text) puts text in quotes with its special characters escaped, for building JSON to send:</p>
<pre><code>h = JsonParse(HttpBody$(req))
FOR i = 0 TO JsonCount(h, &quot;packages&quot;) - 1
lstPackages.AddItem JsonText$(h, &quot;packages[&quot; &amp; CStr(i) &amp; &quot;].name&quot;)
NEXT i
JsonFree h
body = &quot;{&quot; &amp; JsonQuote$(&quot;name&quot;) &amp; &quot;: &quot; &amp; JsonQuote$(txtName.Text) &amp; &quot;}&quot;</code></pre>
<h2>Changing DVX's Settings</h2>
<p>With system.bas in the project, a program changes what Control Panel changes, at once for the whole desktop. Add prefs.bas too: the settings are saved through a prefs.bas handle.</p>
<p>Screen mode: SysVideoModeCount() says how many modes the display offers (numbered from 0), and SysVideoModeWidth, SysVideoModeHeight, and SysVideoModeBpp (bits per pixel) describe each. SysSetVideoMode(wide, high, bpp) changes the mode and keeps every window; it returns False when the mode could not be set (the old one stays), also while a program has the whole screen to itself. A mode the monitor cannot show leaves the user unable to answer, so do as Control Panel does: ask &quot;Keep this mode?&quot; and go back to the old mode by itself after some seconds. Screen.Width, Screen.Height, and Screen.BitsPerPixel tell the mode in use, and SYS_VIDEO_DEFAULT_WIDTH, SYS_VIDEO_DEFAULT_HEIGHT, and SYS_VIDEO_DEFAULT_BPP the mode DVX starts in when its settings name none. SysScreenshot(path) saves the whole screen as a picture file (BMP when path ends in .bmp, PNG otherwise) and returns True when it was written.</p>
@ -5579,6 +5674,7 @@ SecDhFree dh</code></pre>
<p>Handles a program leaves open are freed, and their keys wiped, when it ends.</p>
<h2>Settings Files</h2>
<p>With prefs.bas in the project, a program keeps its settings in an INI file the way DVX's own programs do. PrefsLoad(path) reads the file once (a file that is not there reads as empty); PrefsGet$, PrefsGetInt, and PrefsGetBool read a key of a section (each with the value to use when it is missing), and PrefsSet, PrefsSetInt, PrefsSetBool, and PrefsRemove change it in memory. PrefsSave writes it back, keeping comments and every line it did not change. To write a whole new file -- dropping sections that are no longer wanted -- fill a PrefsCreate handle and save it with PrefsSaveAs: the old file is replaced only once the new one is completely written. PrefsClose frees a handle. IniRead and IniWrite (built in) do one value at a time and read or write the whole file each time; prefs.bas is the faster choice for many settings.</p>
<p>A password is better kept with PrefsSetSecret handle, section, key, value and read back with PrefsGetSecret$(handle, section, key) (&quot;&quot; when it is missing). The file then holds a &quot;*&quot; and a row of letters and digits that change each time it is saved, not the password itself, so it cannot be read over someone's shoulder. This scrambles the password; it does not lock it away: anyone who knows how DVX scrambles it can unscramble it, so treat the file with the same care as the password. A value typed into the file by hand, without the &quot;*&quot;, is read as it is.</p>
<pre><code>Dim ini As Long
ini = PrefsLoad(App.User &amp; &quot;\MYAPP.INI&quot;)
userName = PrefsGet$(ini, &quot;User&quot;, &quot;Name&quot;, &quot;Unknown&quot;)
@ -7189,7 +7285,7 @@ End Sub</code></pre>
------ -----------
Clear Remove all the text. The font, style, and color go back to the control's own.
SetFont family, size The font family (for example &quot;DejaVu Serif&quot;) and size in pixels of the text added next. &quot;&quot; and 0 go back to the control's own font and size.
SetStyle style FONT_BOLD, FONT_ITALIC, FONT_UNDERLINE, and FONT_STRIKE added together (from font.bas; 0 for plain) for the text added next.
SetStyle style FONT_BOLD, FONT_ITALIC, FONT_UNDERLINE, and FONT_STRIKE added together (from font.bas; FONT_REGULAR for none) for the text added next.
SetColor color The color of the text added next, as RGB() makes it; -1 for the control's ForeColor.
AddText text Add text in the font, style, and color chosen.
AddLineBreak Start a new line in the same paragraph.

View file

@ -46,6 +46,7 @@ img { max-width: 100%; }
<li><a href="#user.progman">The Program Manager</a></li>
</ul>
</li>
<li><a href="#user.addons">Adding and Removing Software</a></li>
<li><a href="#user.settings">Changing Settings</a></li>
<li><a href="#user.taskmgr">Task Manager</a></li>
<li><a href="#user.help">Getting Help</a></li>
@ -158,7 +159,9 @@ img { max-width: 100%; }
<li><a href="#lib.hlpfile.basic">Help Files from BASIC</a></li>
</ul>
</li>
<li><a href="#lib.http">HTTP Library</a></li>
<li><a href="#lib.joystick">Joystick Library</a></li>
<li><a href="#lib.json">JSON Library</a></li>
<li><a href="#libtasks">libtasks -- Cooperative Task Switching</a></li>
<li><a href="#lib.listhelp">List Helper Library</a>
<ul>
@ -166,6 +169,8 @@ img { max-width: 100%; }
</ul>
</li>
<li><a href="#lib.music">Music Library</a></li>
<li><a href="#lib.pkgnet">Package Server Library</a></li>
<li><a href="#lib.pkg">Package Library</a></li>
<li><a href="#lib.print">Printing Library</a></li>
<li><a href="#lib.richtext">Styled Text Library</a>
<ul>
@ -307,6 +312,8 @@ img { max-width: 100%; }
<li><a href="#api.draw">accelParse</a></li>
<li><a href="#api.types">AccelTableT</a></li>
<li><a href="#lib.dvxnet.api">accept</a></li>
<li><a href="#lib.pkg">Add-on packages</a></li>
<li><a href="#user.addons">Add-ons</a></li>
<li><a href="#games.fullscreen">Alt+Enter</a></li>
<li><a href="#user.windows">Alt+Tab</a></li>
<li><a href="#widget.ansiterm">ANSI Music</a></li>
@ -426,6 +433,7 @@ img { max-width: 100%; }
<li><a href="#user.windows">Cascade</a></li>
<li><a href="#user.controls">Check Boxes</a></li>
<li><a href="#widget.checkbox">Checkbox</a></li>
<li><a href="#user.filedlg">Choosing a Folder</a></li>
<li><a href="#arch.windows">Chrome</a></li>
<li><a href="#lib.texthelp.clearother">clearOtherSelections</a></li>
<li><a href="#user.input">Clicking</a></li>
@ -527,6 +535,8 @@ img { max-width: 100%; }
<li><a href="#arch.pipeline">Double Buffer</a></li>
<li><a href="#user.input">Double-click</a></li>
<li><a href="#arch.events">Double-Click</a></li>
<li><a href="#user.addons">DPK files</a></li>
<li><a href="#lib.pkg">DPK files</a></li>
<li><a href="#arch.overview">DPMI</a></li>
<li><a href="#api.app">Drag Feedback</a></li>
<li><a href="#lib.texthelp.dragupdate">Drag Select</a></li>
@ -704,6 +714,8 @@ img { max-width: 100%; }
<li><a href="#lib.dvxnet">dvxnet</a></li>
<li><a href="#lib.dvxnet.config">dvxNet.h</a></li>
<li><a href="#api.palette">dvxPal.h</a></li>
<li><a href="#sdk.packaging">dvxpkg</a></li>
<li><a href="#lib.pkg">DVXPKG</a></li>
<li><a href="#arch.platform">dvxPlat.h</a></li>
<li><a href="#api.platform">dvxPlat.h</a></li>
<li><a href="#api.app">dvxPlaySound</a></li>
@ -913,6 +925,12 @@ img { max-width: 100%; }
<li><a href="#shell.basic">HotkeyRemove</a></li>
<li><a href="#api.app">Hotkeys</a></li>
<li><a href="#shell.basic">HotkeyText</a></li>
<li><a href="#lib.http">http</a></li>
<li><a href="#lib.http">HTTP</a></li>
<li><a href="#lib.http">http.bas</a></li>
<li><a href="#lib.http">httpCreate</a></li>
<li><a href="#lib.http">httpPoll</a></li>
<li><a href="#lib.http">httpSaveTo</a></li>
<li><a href="#widget.image">Image</a></li>
<li><a href="#widget.imagebutton">ImageButton</a></li>
<li><a href="#sdk.quickstart">include/net</a></li>
@ -921,6 +939,7 @@ img { max-width: 100%; }
<li><a href="#arch.events">Input</a></li>
<li><a href="#arch.dialogs">Input Box</a></li>
<li><a href="#user.trouble">Installing DVX</a></li>
<li><a href="#user.addons">Installing programs</a></li>
<li><a href="#arch.platform">INT 16h</a></li>
<li><a href="#lib.print">INT 17h</a></li>
<li><a href="#api.platform">INT 24h</a></li>
@ -970,6 +989,12 @@ img { max-width: 100%; }
<li><a href="#lib.joystick">joystick</a></li>
<li><a href="#lib.joystick">joystick.bas</a></li>
<li><a href="#lib.joystick">joystick.h</a></li>
<li><a href="#lib.json">json</a></li>
<li><a href="#lib.json">JSON</a></li>
<li><a href="#lib.json">json.bas</a></li>
<li><a href="#lib.json">jsonFind</a></li>
<li><a href="#lib.json">jsonParse</a></li>
<li><a href="#lib.json">jsonQuote</a></li>
<li><a href="#lib.texthelp.onkey">Key Handling</a></li>
<li><a href="#user.input">Keyboard</a></li>
<li><a href="#arch.events">Keyboard</a></li>
@ -1083,6 +1108,11 @@ img { max-width: 100%; }
<li><a href="#lib.audio">OPL</a></li>
<li><a href="#user.controls">Option Buttons</a></li>
<li><a href="#user.trouble">Out of Memory</a></li>
<li><a href="#user.addons">Package Builder</a></li>
<li><a href="#user.addons">Package server</a></li>
<li><a href="#lib.pkgnet">Package server</a></li>
<li><a href="#lib.pkg">PACKAGE.INI</a></li>
<li><a href="#user.addons">Packages</a></li>
<li><a href="#sdk.packaging">Packaging</a></li>
<li><a href="#api.video">packColor</a></li>
<li><a href="#lib.serial.packet">packet</a></li>
@ -1095,6 +1125,22 @@ img { max-width: 100%; }
<li><a href="#arch.colors">Pixel Format</a></li>
<li><a href="#arch.colors">PixelFormatT</a></li>
<li><a href="#api.types">PixelFormatT</a></li>
<li><a href="#lib.pkg">pkg</a></li>
<li><a href="#lib.pkg">pkg.bas</a></li>
<li><a href="#sdk.packaging">PKG_KEY</a></li>
<li><a href="#sdk.packaging">PKG_VERSION</a></li>
<li><a href="#lib.pkg">pkgDpkBuild</a></li>
<li><a href="#lib.pkg">pkgDpkBuildFiles</a></li>
<li><a href="#lib.pkg">pkgDpkSign</a></li>
<li><a href="#lib.pkg">pkgKeyringLoad</a></li>
<li><a href="#lib.pkgnet">pkgnet</a></li>
<li><a href="#lib.pkgnet">pkgnet.bas</a></li>
<li><a href="#lib.pkgnet">pkgNetCreate</a></li>
<li><a href="#lib.pkgnet">pkgNetFetchStart</a></li>
<li><a href="#lib.pkgnet">pkgNetPoll</a></li>
<li><a href="#lib.pkg">pkgOpInstall</a></li>
<li><a href="#lib.pkg">pkgOpRemove</a></li>
<li><a href="#lib.pkg">pkgPlanInstall</a></li>
<li><a href="#lib.serial.packet">pktClose</a></li>
<li><a href="#lib.dvxnet.inside">pktDrv</a></li>
<li><a href="#lib.serial.packet">pktOpen</a></li>
@ -1166,6 +1212,7 @@ img { max-width: 100%; }
<li><a href="#api.platform">platformValidateShortName</a></li>
<li><a href="#api.platform">platformVideoInit</a></li>
<li><a href="#api.platform">platformVideoReadBack</a></li>
<li><a href="#api.platform">platformWriteFile</a></li>
<li><a href="#lib.audio">PlayTone</a></li>
<li><a href="#lib.audio">PlayWave</a></li>
<li><a href="#shell.groups">pmHotkeyParse</a></li>
@ -1190,6 +1237,7 @@ img { max-width: 100%; }
<li><a href="#api.prefs">prefsGetBool</a></li>
<li><a href="#api.prefs">prefsGetChoice</a></li>
<li><a href="#api.prefs">prefsGetInt</a></li>
<li><a href="#api.prefs">prefsGetSecret</a></li>
<li><a href="#api.prefs">prefsGetString</a></li>
<li><a href="#api.prefs">prefsGetVideoMode</a></li>
<li><a href="#arch.prefs">PrefsHandleT</a></li>
@ -1199,6 +1247,7 @@ img { max-width: 100%; }
<li><a href="#api.prefs">prefsSave</a></li>
<li><a href="#api.prefs">prefsSetBool</a></li>
<li><a href="#api.prefs">prefsSetInt</a></li>
<li><a href="#api.prefs">prefsSetSecret</a></li>
<li><a href="#api.prefs">prefsSetString</a></li>
<li><a href="#api.prefs">prefsSetVideoMode</a></li>
<li><a href="#api.prefs">prefsWallpaperChoices</a></li>
@ -1239,8 +1288,11 @@ img { max-width: 100%; }
<li><a href="#api.draw">rectFill</a></li>
<li><a href="#api.comp">rectIntersect</a></li>
<li><a href="#api.types">RectT</a></li>
<li><a href="#user.trouble">Removing DVX</a></li>
<li><a href="#user.addons">Removing programs</a></li>
<li><a href="#arch.platform">rep movsd</a></li>
<li><a href="#arch.platform">rep stosl</a></li>
<li><a href="#user.trouble">Repairing DVX</a></li>
<li><a href="#games.input">Reserved keys</a></li>
<li><a href="#api.video">resetClipRect</a></li>
<li><a href="#user.windows">Resizing Windows</a></li>
@ -1451,6 +1503,7 @@ img { max-width: 100%; }
<li><a href="#user.controls">Sliders</a></li>
<li><a href="#lib.dvxnet.api">socket</a></li>
<li><a href="#lib.dvxnet">Sockets</a></li>
<li><a href="#user.addons">Software</a></li>
<li><a href="#lib.audio">sound</a></li>
<li><a href="#games.sound">Sound</a></li>
<li><a href="#lib.audio">Sound Blaster</a></li>
@ -1562,6 +1615,7 @@ img { max-width: 100%; }
<li><a href="#lib.texthelp.undoswap">Undo</a></li>
<li><a href="#lib.texthelp.onkey">Undo</a></li>
<li><a href="#user.trouble">Uninstalling DVX</a></li>
<li><a href="#user.addons">Updating DVX</a></li>
<li><a href="#user.trouble">Upgrading DVX</a></li>
<li><a href="#user.settings">User Folder</a></li>
<li><a href="#arch.folders">User folder</a></li>
@ -1583,6 +1637,7 @@ img { max-width: 100%; }
<li><a href="#user.settings">Wallpaper</a></li>
<li><a href="#api.widget">WCLASS_FIXED_FONT</a></li>
<li><a href="#api.widget">WCLASS_RAISES_EVENTS</a></li>
<li><a href="#lib.http">Web requests</a></li>
<li><a href="#user.welcome">Welcome</a></li>
<li><a href="#shell.startup">Welcome Program</a></li>
<li><a href="#api.widget">WGT_API_HEADER</a></li>
@ -2213,6 +2268,8 @@ img { max-width: 100%; }
<p>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.</p>
<h2>Saving a File</h2>
<p>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.</p>
<h2>Choosing a Folder</h2>
<p>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.</p>
<blockquote><strong>Tip:</strong> 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.</blockquote>
</div>
<div class="topic" id="user.clipboard">
@ -2237,7 +2294,7 @@ img { max-width: 100%; }
<h1>Printing</h1>
<p>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.</p>
<h2>The Print Dialog</h2>
<p>File &gt; Print... opens the Print dialog. Choose where the printout goes (a printer port such as LPT1, 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.</p>
<p>File &gt; Print... opens the Print dialog. Choose where the printout goes (a printer port such as LPT1, 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.</p>
<p>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.</p>
<h2>When the Printer Has a Problem</h2>
<p>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.</p>
@ -2249,7 +2306,7 @@ img { max-width: 100%; }
<div class="topic" id="user.programs">
<h1>Running Programs</h1>
<h2>Finding and Starting a Program</h2>
<p>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. Select an icon to see a short description of the program in the Program Manager's status bar.</p>
<p>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.</p>
<p>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.</p>
<p>To start a program file that has no icon, choose Run... from the Program Manager's File menu and type or pick the file.</p>
<h2>Opening Documents</h2>
@ -2277,6 +2334,7 @@ img { max-width: 100%; }
<p>If a program has no help of its own, F1 opens this guide instead.</p>
<p><a href="#user.help">Getting Help</a></p>
<p><a href="#user.taskmgr">Task Manager</a></p>
<p><a href="#user.addons">Adding and Removing Software</a></p>
</div>
<div class="topic" id="user.progman">
<h1>The Program Manager</h1>
@ -2290,6 +2348,26 @@ img { max-width: 100%; }
<p>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.</p>
<blockquote><strong>Warning:</strong> Closing the Program Manager window leaves DVX, after asking you first.</blockquote>
</div>
<div class="topic" id="user.addons">
<h1>Adding and Removing Software</h1>
<p>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.</p>
<h2>Installing a Package</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>Keeping DVX Up to Date</h2>
<p>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.</p>
<h2>Publishing Your Own Packages</h2>
<p>With an account on the package server (its email and password given in the same Package Server window), Software's File &gt; 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.</p>
<h2>Removing a Package</h2>
<p>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.</p>
<h2>Making Your Own Packages</h2>
<p>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 &gt; Make Package. Their help explains how.</p>
<h2>If DVX Will Not Start</h2>
<p>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.</p>
<p><a href="#user.programs">Running Programs</a></p>
<p><a href="#user.trouble">Troubleshooting</a></p>
</div>
<div class="topic" id="user.settings">
<h1>Changing Settings</h1>
<p>The mouse, the screen colors, the desktop picture, 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.</p>
@ -2378,15 +2456,19 @@ img { max-width: 100%; }
<h2>Safe video mode</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>Add-ons that could not be loaded</h2>
<p>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.</p>
<p>You can pick another resolution at any time with the settings program (see Changing Settings).</p>
<h2>There is no sound</h2>
<p>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 (the card's manual gives the numbers). 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.</p>
<h2>An empty floppy or CD-ROM drive</h2>
<p>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 &quot;Abort, Retry, Fail&quot; question. Put the disk in and try again.</p>
<h2>Installing, upgrading, and removing DVX</h2>
<p>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. Setup shows the licence, asks for the folder to install DVX in and which fonts to install, 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.</p>
<p>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) and which fonts to install. 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.</p>
<p>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 (about 2 MB); and All adds every font that comes with DVX (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 or the base set, Setup does not ask for the last floppy disks, which hold only fonts.</p>
<p>To upgrade or repair DVX, run Setup again and choose the same folder: answer Yes to keep your settings and saved files (the USER folder), or No to remove them and start over. Setup starts with the fonts that folder already has; choose others to add fonts or remove them. To remove DVX, run SETUP in the DVX folder (for example C:\DVX\SETUP) and choose Uninstall. Files you added to the folder yourself are not removed, and the USER folder is only removed if you ask.</p>
<p>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 that folder already has; choose others to add fonts or remove them. If you choose Install DVX for a folder that already has DVX, Setup offers to upgrade it instead.</p>
<p>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.</p>
<h2>Running DVX from a CD-ROM or a write-protected disk</h2>
<p>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.</p>
<h2>A program is already running</h2>
@ -5964,21 +6046,23 @@ int32_t pick = dvxButtonBox(ctx, &quot;Copy&quot;, &quot;The disk is full.&quot;
<pre> Define Value Description
------ ----- -----------
FD_OPEN 0x0000 File open dialog (default)
FD_SAVE 0x0001 File save dialog (with overwrite prompt)</pre>
FD_SAVE 0x0001 File save dialog (with overwrite prompt)
FD_FOLDER 0x0002 Choose a folder, new or existing</pre>
<h3>dvxFileDialog</h3>
<pre><code>bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);</code></pre>
<p>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 &quot;Drive X: is not ready.&quot; and leaves the dialog where it was. Typing &quot;D:&quot; or &quot;D:folder&quot; in the path box changes drive/folder; the file name box also accepts a relative or absolute path (&quot;sub\x.txt&quot;, &quot;C:\X\A.TXT&quot;) and navigates to its folder. A folder that does not exist shows an &quot;Invalid Path&quot; error. The dialog closes at once if the app that owns it is ended.</p>
<p>With FD_FOLDER the dialog chooses a folder: the list shows folders only (filters are not used), the name box is &quot;Folder name&quot;, 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 &quot;Invalid Folder&quot; 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 &quot;Choose a Folder&quot;.</p>
<pre> Parameter Description
--------- -----------
ctx Application context
title Window title
flags FD_OPEN or FD_SAVE
title Window title (NULL = &quot;Open&quot;, &quot;Save As&quot;, or &quot;Choose a Folder&quot;)
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</pre>
<p>Returns: true if the user selected a file, false if cancelled or closed.</p>
<p>Returns: true if the user selected a file (or folder), false if cancelled or closed.</p>
<h2>Input Boxes</h2>
<h3>dvxInputBox</h3>
<pre><code>bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize);</code></pre>
@ -6193,7 +6277,7 @@ if (dvxFontDialog(ctx, NULL, 0, &amp;choice)) {
<div class="topic" id="api.prefs">
<h1>dvxPrefs.h -- INI Preferences System</h1>
<p>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.</p>
<p>BASIC programs reach the same functions through prefs.bas (dvxPrefsBas.c in libdvx): PrefsLoad, PrefsCreate, PrefsGet$, PrefsGetInt, PrefsGetBool, PrefsSet, PrefsSetInt, PrefsSetBool, PrefsRemove, PrefsKeyName$ (prefsKeyName, &quot;&quot; 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 &quot;|&quot;-separated names (each choice's iniName and label is its name, its value its index).</p>
<p>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, &quot;&quot; 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 &quot;|&quot;-separated names (each choice's iniName and label is its name, its value its index).</p>
<pre><code>PrefsHandleT *prefsBasHandle(int32_t handle);</code></pre>
<p>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.</p>
<h2>The system INI</h2>
@ -6273,6 +6357,16 @@ if (dvxFontDialog(ctx, NULL, 0, &amp;choice)) {
key Key name
defaultVal Returned if the key is absent or unrecognised</pre>
<p>Returns: Parsed bool, or defaultVal.</p>
<h3>prefsGetSecret</h3>
<pre><code>void prefsGetSecret(PrefsHandleT *h, const char *section, const char *key, char *out, int32_t outSize);</code></pre>
<p>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 &quot;&quot;. A value prefsSetSecret did not write (one typed into the file by hand: no leading &quot;*&quot;, 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.</p>
<pre> Parameter Description
--------- -----------
h Preferences handle
section Section name
key Key name
out Receives the text
outSize Size of out in bytes</pre>
<h3>prefsGetVideoMode / prefsSetVideoMode</h3>
<pre><code>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);</code></pre>
@ -6287,6 +6381,9 @@ void prefsSetVideoMode(PrefsHandleT *h, int32_t width, int32_t height, int32_t b
<h3>prefsSetBool</h3>
<pre><code>void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value);</code></pre>
<p>Set a boolean value (stored as &quot;true&quot;/&quot;false&quot;).</p>
<h3>prefsSetSecret</h3>
<pre><code>void prefsSetSecret(PrefsHandleT *h, const char *section, const char *key, const char *value);</code></pre>
<p>Store a secret, such as a password, so it cannot be read at a glance: &quot;*&quot;, 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 &quot;&quot;. prefsGetSecret reads it back.</p>
<h3>prefsRemove</h3>
<pre><code>void prefsRemove(PrefsHandleT *h, const char *section, const char *key);</code></pre>
<p>Remove a key from a section. No-op if the key does not exist.</p>
@ -6784,7 +6881,10 @@ bool platformPathSibling(char *out, int32_t outSize, const char *path, const cha
<p>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 &quot;.BAK&quot; 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).</p>
<h3>platformReplaceFile</h3>
<pre><code>bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);</code></pre>
<p>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 preferences system, the resource writer, and the help compiler all save this way, with the PLATFORM_TMP_EXT and PLATFORM_BAK_EXT siblings from platformPathSibling.</p>
<p>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.</p>
<h3>platformWriteFile</h3>
<pre><code>bool platformWriteFile(const char *path, const void *data, int32_t len);</code></pre>
<p>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.</p>
<h3>platformReadFile</h3>
<pre><code>char *platformReadFile(const char *path, int32_t *outLen);</code></pre>
<p>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.</p>
@ -6829,12 +6929,15 @@ char platformBootDrive(void);</code></pre>
#define DVX_SOUNDS_DIR &quot;SOUNDS&quot;
#define DVX_THEMES_DIR &quot;THEMES&quot;
#define DVX_WPAPER_DIR &quot;WPAPER&quot;
#define DVX_BANKS_DIR &quot;BANKS&quot;
#define DVX_VENDOR_DIR &quot;KPUNCH&quot;
typedef enum {
DvxKindFontsE,
DvxKindSoundsE,
DvxKindThemesE,
DvxKindWpaperE,
DvxKindBanksE,
DvxKindCountE
} DvxKindE;
@ -6857,12 +6960,12 @@ char **platformKindReadDir(DvxKindE kind);</code></pre>
SYSTEM DVX's own tools and files, and the shipped DVX.INI and ASSOC.INI
LIBS, WIDGETS The libraries and widgets
APPS\&lt;vendor&gt;\&lt;program&gt; A program, its help, and DATA (the files it ships; read-only)
SHARED FONTS, SOUNDS, THEMES, and WPAPER: what DVX ships for every program
USER (the user folder) DVX.INI, ASSOC.INI (the user's changes to SYSTEM's), DVX.LOG, the user's own FONTS, SOUNDS, THEMES, and WPAPER, APPS\&lt;program path&gt; (each program's settings and saved files), and DOWNLOAD</pre>
<p>Only DVX's own package puts files in SHARED. 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.</p>
SHARED FONTS, SOUNDS, THEMES, WPAPER, and BANKS: 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, and BANKS, KEYS (publishers' keys the user trusts), APPS\&lt;program path&gt; (each program's settings and saved files), and DOWNLOAD</pre>
<p>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.</p>
<p>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.</p>
<p>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\&lt;its file name without the extension&gt;. It is not made here (the shell's userDir and BASIC's App.User are made on first use).</p>
<p>SHARED and the user folder have the same four kinds of folder (DvxKindE). 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, the user's first, so a user's file of the same name wins; a name with a folder in it is taken as the path it is. platformKindNameOf is the other way: a file directly in one of the kind's folders gives its name alone, which is what settings store (DVX.INI [sounds] and [desktop] wallpaper), 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; free it with dvxReadDirFree. BASIC: FileOpsUserDir$, FileOpsUserPath$, FileOpsUserInTemp, FileOpsKindDir$, FileOpsKindBrowseDir$, FileOpsKindFind$, FileOpsKindName$, and FileOpsKindList (fileops.bas), and App.User for a program's own folder.</p>
<p>SHARED and the user folder have the same five kinds of folder (DvxKindE): fonts, sounds, themes, desktop pictures, and MIDI instrument banks. 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 [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_BANKS), and App.User for a program's own folder.</p>
<h3>Read-only media: platformSetReadOnly / platformIsReadOnly / platformDirIsWritable / platformTempDir / platformSetLogMemory / platformLogMemory</h3>
<pre><code>#define PLATFORM_LOG_MEM_MAX 16384
@ -7031,7 +7134,7 @@ audioFmFree(audio, v); // key off, back to everyone</co
midi where MIDI music plays: auto (default: the FM chip, else music.lib's software synthesizer), mpu401, fm, software
mpuPort the MPU-401's port, hex (default 330); the port is only looked for with midi = 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), a path from the DVX folder</pre>
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)</pre>
<p>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), audioSettingVolume(prefs) (percent, clamped to 0..100, 100 when missing), and audioSettingsStore(prefs, driver, midi, volume) writes all three (an empty driver stores auto, a midi index out of range stores auto). 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). 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.</p>
<h2>Desktop Sounds</h2>
<p>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 (&quot;&quot; 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.</p>
@ -8821,6 +8924,44 @@ free(matches);</code></pre>
<p>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.</p>
<p>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).</p>
</div>
<div class="topic" id="lib.http">
<h1>HTTP Library</h1>
<p>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.</p>
<p>Header: http/http.h</p>
<p>Loaded as: bin/libs/kpunch/http/http.lib (it needs the network library, dvxnet)</p>
<p>BASIC: http.bas (HttpOpen, HttpStart, HttpRunFor, HttpStatus, HttpBody$, ...).</p>
<h2>A Request</h2>
<pre><code>HttpT *h = httpCreate(&quot;GET&quot;, &quot;http://example.com/v1/catalogue&quot;);
if (h &amp;&amp; httpStart(h, nowMs)) {
while (httpPoll(h, nowMs) == HttpBusyE) {
// draw progress (httpProgress), yield to other programs
}
}
// HttpDoneE: httpStatus(h) is 200, 404, ...; httpBody(h, &amp;len) the reply
// HttpFailedE: httpMessage(h) says why
httpFree(h);</code></pre>
<p>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.</p>
<pre> 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, &amp;done, &amp;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, &amp;len) The body in memory, NUL-terminated (&quot;&quot; when it went to a file).
httpUrl(h) Where the reply came from, after redirects.
httpUrlParse(url, &amp;parts, err, size) Split an address into host, port, and path.</pre>
<h2>What Comes Back</h2>
<p>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.</p>
<p>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.</p>
<p>Only plain http:// works: there is no encryption, so send nothing secret, and check what comes back by its signature or hash.</p>
</div>
<div class="topic" id="lib.joystick">
<h1>Joystick Library</h1>
<p>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.</p>
@ -8881,6 +9022,37 @@ free(matches);</code></pre>
<p>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) (&quot;&quot; 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.</p>
<p>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.</p>
</div>
<div class="topic" id="lib.json">
<h1>JSON Library</h1>
<p>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.</p>
<p>Header: json/json.h</p>
<p>Loaded as: bin/libs/kpunch/json/json.lib</p>
<p>BASIC: json.bas (JsonParse, JsonText$, JsonNumber, JsonCount, JsonKey$, JsonQuote$, ...).</p>
<h2>Reading</h2>
<p>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 &quot;&quot; (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.</p>
<pre> Function Description
-------- -----------
jsonType(doc, node) JsonNullE, JsonBoolE, JsonNumberE, JsonStringE, JsonArrayE, JsonObjectE; JsonNoneE for no node.
jsonText(doc, node) A string's text (&quot;&quot; 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 (&quot;&quot; for anything else).</pre>
<p>Strings are held decoded: the escapes \n, \t, \&quot;, \\, \/, \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.</p>
<pre><code>char err[JSON_ERR_MAX];
JsonDocT *doc = jsonParse(text, len, err, sizeof(err));
for (int32_t i = 0; doc &amp;&amp; i &lt; jsonCount(doc, jsonFind(doc, 0, &quot;packages&quot;)); i++) {
int32_t pkg = jsonChild(doc, jsonFind(doc, 0, &quot;packages&quot;), i);
printf(&quot;%s %s\n&quot;, jsonText(doc, jsonFind(doc, pkg, &quot;id&quot;)), jsonText(doc, jsonFind(doc, pkg, &quot;version&quot;)));
}
jsonFree(doc);</code></pre>
<h2>Writing</h2>
<p>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.</p>
</div>
<div class="topic" id="games.intro">
<h1>Writing Games for DVX</h1>
<p>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.</p>
@ -9164,7 +9336,7 @@ End Sub</code></pre>
<pre> 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
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</pre>
<p>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.</p>
@ -9415,7 +9587,7 @@ End Sub</code></pre>
</div>
<div class="topic" id="sdk.packaging">
<h1>Packaging and Installing</h1>
<p>make package lays out pkg/ exactly as the files go into the DVX directory; make install DVX_DIR=... copies it there. VENDOR (default local) names the vendor directory; use a short name of your own (8 characters at most).</p>
<p>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).</p>
<pre> Kind Directory Files
---- --------- -----
Program APPS/&lt;VENDOR&gt;/&lt;NAME&gt;/ NAME.app, NAME.hlp, DATA/ with the DATA files (if any)
@ -9425,9 +9597,28 @@ End Sub</code></pre>
<li>Programs carry their compiled help. Widgets and libraries carry help sources, which DVX compiles into its system help when the set of installed modules changes.</li>
<li>.dep files are written with DOS line endings.</li>
<li>A program's data files (levels, graphics, sounds it loads at run time) go in the Makefile's DATA variable, for example DATA = levels sounds; the package puts them in the DATA folder beside NAME.app, where the program finds them through shellDataPath (BASIC: App.Data). Small ones can instead travel inside the .app as binary resources (see the .res file).</li>
<li>To ship a module, zip the contents of pkg/; the user unpacks it into the DVX directory. A library's header and .bas file go to the developers who use it, not into the DVX directory.</li>
<li>Removing a module is deleting its directory.</li>
<li>To ship a module, give out NAME.dpk: Software installs it (and what it needs from the same folder), records it, and removes it cleanly again. A library's header and .bas file go to the developers who use it, not into the package.</li>
</ul>
<h2>The Add-on Package</h2>
<p>The package's details come from sdk.mk settings, on the make command line or in the project Makefile:</p>
<pre> 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=&quot;acme.cards=1.0 acme.dice=2.1&quot;
PKG_KEY The secret key file to sign it with</pre>
<p>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:</p>
<pre><code>bin/dvxpkg keygen &quot;Acme Games&quot; 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</code></pre>
<p>Give acme.KEY to the people who install your packages (Software, File &gt; 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.</p>
<h2>Publishing on the Package Server</h2>
<p>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 &gt; Package Server, and choose File &gt; 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:</p>
<pre><code>curl -H &quot;Authorization: Bearer TOKEN&quot; -H &quot;Content-Type: application/octet-stream&quot; \
--data-binary @NAME.dpk http://SERVER/v1/upload</code></pre>
<p>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.</p>
</div>
<div class="topic" id="libtasks">
<h1>libtasks -- Cooperative Task Switching</h1>
@ -9889,7 +10080,7 @@ if (rc != MUSIC_OK) {
software the software synthesizer</pre>
<p>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.</p>
<h2>General MIDI on FM</h2>
<p>Instruments come from an OP2 bank (the DMX &quot;GENMIDI&quot; 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 names another OP2 file instead (a path from the DVX folder), for example a bank made with an OPL editor, or another freely licensed bank such as DMXOPL. The bank is read once, at the first MIDI music that needs it.</p>
<p>Instruments come from an OP2 bank (the DMX &quot;GENMIDI&quot; 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, or another freely licensed bank such as DMXOPL. 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). The bank is read once, at the first MIDI music that needs it, so a new choice takes effect when DVX next starts.</p>
<p>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.</p>
<h2>The Software Synthesizer</h2>
<p>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.</p>
@ -9929,6 +10120,142 @@ musicMmlPlay(tune, &quot;MB T140 O3 L8 CDEC CDEC EFG4 EFG4&quot;);</code></pre>
<p>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:</p>
<p>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 &quot;AS IS&quot; 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.</p>
</div>
<div class="topic" id="lib.pkgnet">
<h1>Package Server Library</h1>
<p>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.</p>
<p>Header: pkgnet/pkgNet.h</p>
<p>Loaded as: bin/libs/kpunch/pkgnet/pkgnet.lib (it needs the package, HTTP, and JSON libraries)</p>
<p>BASIC: pkgnet.bas (PkgNetOpen, PkgNetCatalogue, PkgNetUpdates, PkgNetFetch, PkgNetRunFor, PkgNetRows$, ...), with pkg.bas's PkgOfferFolder to plan what was fetched.</p>
<h2>A Fetch</h2>
<pre><code>PkgNetT *n = pkgNetCreate(server, &quot;&quot;, downloadDir);
const char *ids[] = { &quot;acme.chess&quot; };
if (n &amp;&amp; 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);</code></pre>
<p>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.</p>
<pre> Function Description
-------- -----------
pkgNetCreate(server, root, dir) A client of the server at server (&quot;http://host[:port]&quot;), for the DVX folder root (&quot;&quot; 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, &amp;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, &amp;done, &amp;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(&amp;c), pkgNetConfigWrite(&amp;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(&amp;c, out, size) The address they make: &quot;http://host&quot;, with &quot;:port&quot; 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, &amp;count) What the account has sent, each version with its state (pending, approved, rejected, withdrawn) and the review's note.</pre>
<h2>What a Fetch Checks</h2>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<h2>An Account</h2>
<p>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.</p>
</div>
<div class="topic" id="lib.pkg">
<h1>Package Library</h1>
<p>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 &gt; Make Package, DVXPKG.EXE, the build machine's dvxpkg, and Setup all use it.</p>
<p>Header: pkg/pkg.h (it includes the others below)</p>
<p>Loaded as: bin/libs/kpunch/pkg/pkg.lib</p>
<p>BASIC: pkg.bas (sessions, offers, plans, install and remove jobs, building, signing, and keys).</p>
<h2>A Package</h2>
<p>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.</p>
<pre><code>[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 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 &lt;SHA-256, 64 hex digits&gt;</code></pre>
<p>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):</p>
<pre> 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
bundle no files: a set of packages, its dependencies
system DVX itself (kpunch.dvx): anywhere but the user folder</pre>
<h2>Manifests (pkgManifest.h)</h2>
<pre> Function Description
-------- -----------
pkgManifestParse(text, len, &amp;m, err, size) A manifest's text; then pkgManifestCheck before trusting it.
pkgManifestCheck(&amp;m, err, size) The id, the kind's rules, every file's name and folder, nothing listed twice, no dependency on itself.
pkgManifestFormat(&amp;m, &amp;len) The text (CR LF lines), malloc'd.
pkgTemplateRead(path, &amp;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.</pre>
<h2>Keys and Signatures (pkgSign.h)</h2>
<p>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.</p>
<pre> 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, &amp;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(&amp;secret, msg, len, &amp;sig) Sign bytes (a manifest's).
pkgSigCheck(&amp;sig, &amp;key, msg, len) True when key made sig over the bytes.
pkgSecretWipe(&amp;secret) Clear a secret key from memory once it has signed.</pre>
<h2>Building and Reading Packages (pkgDpk.h)</h2>
<pre> Function Description
-------- -----------
pkgDpkBuild(&amp;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(&amp;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, &amp;files) Every file under folder as that list, named prefix/...; pkgDpkFilesFree frees it.
pkgDpkReadInfo(path, &amp;info, err, size) The manifest and PACKAGE.SIG, checked; pkgDpkInfoFree.
pkgDpkCheckFiles(path, &amp;info, err, size) Every file's size and SHA-256 against the manifest.
pkgDpkSign(path, &amp;secret, detached, err, size) Sign into the package, or beside it as NAME.SIG.
pkgDpkSigner(&amp;info, detached, keys, count) Which of keys made the signature (or the detached one).</pre>
<pre><code>PkgManifestT templ;
PkgSecretT secret;
char err[PKG_ERR_MAX];
bool ok;
ok = pkgTemplateRead(&quot;CHESS.INI&quot;, &amp;templ, err, sizeof(err)) &amp;&amp; pkgSecretRead(&quot;ACME.SEC&quot;, &amp;secret, err, sizeof(err));
ok = ok &amp;&amp; pkgDpkBuild(&amp;templ, &quot;STAGE&quot;, &quot;CHESS.DPK&quot;, &amp;secret, err, sizeof(err));
pkgSecretWipe(&amp;secret);
pkgManifestFree(&amp;templ);</code></pre>
<h2>Installed Packages and Trust (pkgDb.h, pkgTrust.h)</h2>
<p>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).</p>
<p>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.</p>
<h2>Planning (pkgPlan.h)</h2>
<p>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, &amp;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.</p>
<h2>Installing and Removing (pkgOp.h)</h2>
<p>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.</p>
<h2>The Package Tool</h2>
<p>DVXPKG.EXE (DVX's SYSTEM folder) and bin/dvxpkg (the SDK, on the build machine) take the same commands:</p>
<pre><code>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 &quot;Key name&quot; BASE
dvxpkg install NAME.DPK [-r DVXDIR] [-y]
dvxpkg remove VENDOR.NAME [-r DVXDIR] [-f]
dvxpkg list [-r DVXDIR]</code></pre>
<p>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 &gt; Make Package from a BASIC project.</p>
</div>
<div class="topic" id="lib.print">
<h1>Printing Library</h1>
<p>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.</p>

BIN
screenshots/86box/addons-addon-no.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/apps-clock.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-cpanel.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-desktop.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-dvxbasic.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-iconed.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-imgview.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-notepad.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-resedit.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/banked-bk-back.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/banked-bk-desk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/banked-bk-drag.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/banked-bk-note.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/boot-boot.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-color.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-desk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-joy.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-mouse.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-net.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-print.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-sound.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-types.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-video.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/exit-cancel.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/exit-savechg.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/f1-h-cpanel.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/filedlg-fd-back.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/filedlg-fd-list.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/filedlg-fd-nordy.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/filedlg-fd-open.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/fullmode-fm-desk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/fullscr-fs-desk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/hang-ended.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/hang-taskmgr.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/install-in-desk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/install-su-fold.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/install-su-start.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/install-su-unin.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/instbase-su-fold.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/instbase-su-start.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/instbase-su-unin.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-altdn.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-altup.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-design.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-drop.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-file.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-run.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/net-netsmk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/pardsgn-pardsgn.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/parhlp-parhlp-l.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/parhlp-parhlp.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/parpm-parpm.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/paths-paths.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/pkgbuild-pb-built.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgbuild-pb-files.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgbuild-pb-start.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-avail.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-done.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-insta.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-mine.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-plan.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-pub.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-updat.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-updon.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pkgsrv-ps-uplan.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/print-print1.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/safevid-sv-desk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/sdk-sdkapp.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/sdk-sdkbas.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/sdk-sdkgreet.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/sdk-sdktbx.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/software-sw-done.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/software-sw-empty.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/software-sw-list.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/software-sw-needs.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/software-sw-plan.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/sql-sqlsmk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/ucdemo-ucdemo.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/ucide-ucide-ds.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/ucide-ucide-ru.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/ucide-ucide-tb.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/upgrade-su-fold.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/upgrade-su-start.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/upgrade-su-unin.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/upgrade-su-upgr.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/video-v-800.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/video-v-pick.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/winops-w-min.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/winops-w-move.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/addons-addon-no.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/apps-clock.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/apps-cpanel.png (Stored with Git LFS)

Binary file not shown.

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