Event handling now more closely matches Visual Basic.
This commit is contained in:
parent
3c7b93bf7a
commit
5b7fdbbde6
65 changed files with 1008 additions and 194 deletions
|
|
@ -1609,7 +1609,7 @@ img { max-width: 100%; }
|
|||
<li>Immediate Window -- an interactive REPL for evaluating expressions and modifying variables at runtime.</li>
|
||||
<li>Debug Windows -- Locals, Call Stack, Watch, and Breakpoints windows that appear automatically when debugging.</li>
|
||||
</ul>
|
||||
<p>The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine. Programs execute in cooperative slices, yielding to DVX between slices so the GUI remains responsive. Call DoEvents in long-running loops to keep the event loop flowing.</p>
|
||||
<p>The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine. Programs execute in cooperative slices, yielding to DVX between slices so DVX remains responsive. The program's own events wait until the code that is running ends or calls DoEvents, as in Visual Basic, so a long-running loop that should answer its own buttons calls DoEvents now and then.</p>
|
||||
<p><a href="#ide.menu.file">File Menu</a></p>
|
||||
<p><a href="#ide.menu.edit">Edit Menu</a></p>
|
||||
<p><a href="#ide.menu.run">Run Menu</a></p>
|
||||
|
|
@ -4258,7 +4258,7 @@ END IF</code></pre>
|
|||
UNLOAD FormName
|
||||
UNLOAD Me
|
||||
UNLOAD formVariable</code></pre>
|
||||
<p>LOAD creates the form and its controls in memory. It fires the form's Load event (Form1_Load for a form named Form1) when the form is first loaded. UNLOAD destroys the form, firing its QueryUnload event (which can cancel the close) and then its Unload event. The form name here is the literal name of the form as it appears in its .frm file; UNLOAD Me unloads the form whose event handler is running. UNLOAD also takes a Form variable, or anything else that gives a form (for example a form made with CreateForm and kept in an object's property), and unloads that form.</p>
|
||||
<p>LOAD creates the form and its controls in memory. It fires the form's Load event (Form1_Load for a form named Form1) when the form is first loaded. UNLOAD destroys the form, firing its QueryUnload event (which can cancel the close) and then its Unload event. The form name here is the literal name of the form as it appears in its .frm file; UNLOAD Me unloads the form whose event handler is running. UNLOAD also takes a Form variable, or anything else that gives a form (for example a form made with CreateForm and kept in an object's property), and unloads that form. Once a form is unloaded, LOAD or any use of its name (a property, a control, Show) makes a new copy of it with its Load event, as in Visual Basic, even while code that ran the UNLOAD is still running; a second UNLOAD of the same form does nothing.</p>
|
||||
<h2>Showing and Hiding Forms</h2>
|
||||
<pre><code>FormName.Show [mode]
|
||||
FormName.Hide
|
||||
|
|
@ -4359,7 +4359,7 @@ Sub OnCloseClick
|
|||
Unload Me
|
||||
End Sub</code></pre>
|
||||
<h2>DoEvents</h2>
|
||||
<p>Yields control to DVX so the GUI can process pending events. Call this in long-running loops to keep the UI responsive (otherwise the window will appear frozen).</p>
|
||||
<p>Lets your program's waiting events (a click, a key, a Timer) run now. While your code runs, DVX and the other programs keep going (they get their turn many times a second), but your program's own events wait until the code that is running ends or calls DoEvents, as in Visual Basic. A long loop that should answer a Cancel button, or let a Timer run, calls DoEvents now and then; after it, check whatever an event may have changed (a Cancel flag, a form that was closed). MsgBox, InputBox, a form shown with vbModal, SLEEP, and the statements that wait for sound or a frame run your program's events too, as do the long calls described as working "as with DoEvents" (FileCopy, DIR$, long SQL statements, and others).</p>
|
||||
<pre><code>DOEVENTS</code></pre>
|
||||
<pre><code>For i = 1 To 10000
|
||||
' process data
|
||||
|
|
|
|||
|
|
@ -3119,7 +3119,11 @@ prefsClose(h);</code></pre>
|
|||
|
|
||||
+-- Every other library loads when something first needs one of
|
||||
| its names: a module being opened (platformModuleOpen), or a
|
||||
| dlsym(NULL) (BASIC's DECLARE LIBRARY, dvxLibraryLoaded)
|
||||
| dlsym(NULL) (BASIC's DECLARE LIBRARY, dvxLibraryLoaded).
|
||||
| platformLoadedSymbol looks a name up without loading; what
|
||||
| binds to a library once it is loaded hears of each one
|
||||
| through the shell's hook (platformSetLibraryLoadHook: the
|
||||
| cryptography library's random numbers are seeded then)
|
||||
|
|
||||
+-- dvxInit() -- video mode, input, font, colors, cursors
|
||||
|
|
||||
|
|
@ -7888,7 +7892,8 @@ void basVmSetStepLimit(BasVmT *vm, int32_t limit);</code></pre>
|
|||
<p>The host provides these callbacks for PRINT, INPUT, and DoEvents statements.</p>
|
||||
<pre><code>void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
|
||||
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
||||
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);</code></pre>
|
||||
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);
|
||||
bool basVmAtSliceEnd(const BasVmT *vm);</code></pre>
|
||||
<h3>Callback Types</h3>
|
||||
<pre><code>typedef void (*BasPrintFnT)(void *ctx, const char *text, bool newline);
|
||||
typedef bool (*BasInputFnT)(void *ctx, const char *prompt,
|
||||
|
|
@ -7898,7 +7903,7 @@ typedef bool (*BasDoEventsFnT)(void *ctx);</code></pre>
|
|||
---- -----------
|
||||
BasPrintFnT Called for PRINT output. text is null-terminated. newline indicates line advance.
|
||||
BasInputFnT Called for INPUT. Fill buf (up to bufSize-1 chars). Return true on success, false on cancel.
|
||||
BasDoEventsFnT Called for DoEvents. Process pending events and return. Return false to stop the program.</pre>
|
||||
BasDoEventsFnT Called for DoEvents and the waits that act like it (SLEEP, the sound and FRAMEWAIT waits, FileCopy, DIR$): run the program's pending events and return. Also called at each slice end of a running handler, when basVmAtSliceEnd(vm) is true: then give DVX and the other programs their turn but run none of the program's own events, which wait for DoEvents or the handler's end as in Visual Basic (basFormRtUpdate does this by itself). Return false to stop the program.</pre>
|
||||
<h2>UI Callbacks</h2>
|
||||
<p>For form and control integration. The VM resolves all UI operations through these callbacks, keeping it independent of any specific GUI toolkit.</p>
|
||||
<pre><code>void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);</code></pre>
|
||||
|
|
@ -10082,7 +10087,7 @@ End Sub</code></pre>
|
|||
mathx.dhs Developer help, added to the system help
|
||||
Makefile KIND = library</pre>
|
||||
<h2>How the Library Works</h2>
|
||||
<p>DVX loads a library when something first needs it: a program, widget, or library that calls one of its functions (DVX loads the library just before that module), or a lookup by name (dlsym(NULL), a BASIC DECLARE LIBRARY call, dvxLibraryLoaded). Libraries with code that runs as they load (constructors) are loaded when DVX starts, so they can hook into the shell. Every function that is not static is exported: a program, widget, or library calls it directly, and DVX connects the call when it loads that module. A library that calls another library's functions lists it in its .dep file, which DVX loads first. A module that opens another module calls platformModuleOpen (dvxPlat.h), never dlopen, which dvx.exe does not export.</p>
|
||||
<p>DVX loads a library when something first needs it: a program, widget, or library that calls one of its functions (DVX loads the library just before that module), or a lookup by name (dlsym(NULL), a BASIC DECLARE LIBRARY call, dvxLibraryLoaded). Code that uses a library only once something else has loaded it looks its names up with platformLoadedSymbol (dvxPlat.h), which never loads one. Libraries with code that runs as they load (constructors) are loaded when DVX starts, so they can hook into the shell. Every function that is not static is exported: a program, widget, or library calls it directly, and DVX connects the call when it loads that module. A library that calls another library's functions lists it in its .dep file, which DVX loads first. A module that opens another module calls platformModuleOpen (dvxPlat.h), never dlopen, which dvx.exe does not export.</p>
|
||||
<p>BASIC programs add mathx.bas to their project. DECLARE LIBRARY looks up each function by exactly the name it declares, without a trailing $ (a $ marks a function returning a string), so mathxGreet$ calls the C function mathxGreet. Keep the declarations and the C functions in step; strings are passed as const char * and returned as const char *.</p>
|
||||
<p>A string declared AS STRING is text and stops at the first zero byte. For bytes (network data, samples, encrypted data) declare BINARY STRING: each BINARY STRING parameter becomes two C parameters at its place, the bytes (const uint8_t *, a private copy valid during the call, followed by a zero byte) and their count (int32_t); a BINARY STRING result adds one last C parameter, int32_t *outLen, where the function stores the length of the bytes it returns (the runtime copies them right after the call, so a static buffer is fine; NULL, a negative length, or a length never set give an empty string). The template's tplNameXor shows both:</p>
|
||||
<pre><code> // tplNameXor$(BYVAL bytes AS BINARY STRING, BYVAL key AS LONG) AS BINARY STRING
|
||||
|
|
|
|||
BIN
screenshots/86box/cpanel-cp-types.png
(Stored with Git LFS)
BIN
screenshots/86box/cpanel-cp-types.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/f1-h-dvxbas.png
(Stored with Git LFS)
BIN
screenshots/86box/f1-h-dvxbas.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/hang-ended.png
(Stored with Git LFS)
BIN
screenshots/86box/hang-ended.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/hang-taskmgr.png
(Stored with Git LFS)
BIN
screenshots/86box/hang-taskmgr.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/instadd-add-sw.png
(Stored with Git LFS)
BIN
screenshots/86box/instadd-add-sw.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/instadd-su-what.png
(Stored with Git LFS)
BIN
screenshots/86box/instadd-su-what.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/lfn-fm-old.png
(Stored with Git LFS)
BIN
screenshots/86box/lfn-fm-old.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/menus-m-design.png
(Stored with Git LFS)
BIN
screenshots/86box/menus-m-design.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/sdk-sdktbx.png
(Stored with Git LFS)
BIN
screenshots/86box/sdk-sdktbx.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/shellcmd-sc-back.png
(Stored with Git LFS)
Normal file
BIN
screenshots/86box/shellcmd-sc-back.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
screenshots/86box/sql-sqlsmk.png
(Stored with Git LFS)
BIN
screenshots/86box/sql-sqlsmk.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/ucide-ucide-ds.png
(Stored with Git LFS)
BIN
screenshots/86box/ucide-ucide-ds.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/ucide-ucide-tb.png
(Stored with Git LFS)
BIN
screenshots/86box/ucide-ucide-tb.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/f1-h-dvxbas.png
(Stored with Git LFS)
BIN
screenshots/dosbox/f1-h-dvxbas.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/player-p-wav.png
(Stored with Git LFS)
BIN
screenshots/dosbox/player-p-wav.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/sql-sqlsmk.png
(Stored with Git LFS)
BIN
screenshots/dosbox/sql-sqlsmk.png
(Stored with Git LFS)
Binary file not shown.
|
|
@ -342,6 +342,7 @@ The host provides these callbacks for PRINT, INPUT, and DoEvents statements.
|
|||
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
|
||||
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
||||
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);
|
||||
bool basVmAtSliceEnd(const BasVmT *vm);
|
||||
.endcode
|
||||
|
||||
.h3 Callback Types
|
||||
|
|
@ -358,7 +359,7 @@ typedef bool (*BasDoEventsFnT)(void *ctx);
|
|||
---- -----------
|
||||
BasPrintFnT Called for PRINT output. text is null-terminated. newline indicates line advance.
|
||||
BasInputFnT Called for INPUT. Fill buf (up to bufSize-1 chars). Return true on success, false on cancel.
|
||||
BasDoEventsFnT Called for DoEvents. Process pending events and return. Return false to stop the program.
|
||||
BasDoEventsFnT Called for DoEvents and the waits that act like it (SLEEP, the sound and FRAMEWAIT waits, FileCopy, DIR$): run the program's pending events and return. Also called at each slice end of a running handler, when basVmAtSliceEnd(vm) is true: then give DVX and the other programs their turn but run none of the program's own events, which wait for DoEvents or the handler's end as in Visual Basic (basFormRtUpdate does this by itself). Return false to stop the program.
|
||||
.endtable
|
||||
|
||||
.h2 UI Callbacks
|
||||
|
|
|
|||
|
|
@ -393,7 +393,8 @@ typedef struct {
|
|||
|
||||
static CommSecLinkApiT sCommApi;
|
||||
|
||||
// The printing library's job functions (printerBind)
|
||||
// The printing library's job functions (printerResolve: looked up the
|
||||
// first time a program prints)
|
||||
typedef struct {
|
||||
__typeof__(printUiOpenPort) *open;
|
||||
__typeof__(printJobWrite) *write;
|
||||
|
|
@ -405,7 +406,8 @@ static PrintApiT sPrintApi;
|
|||
|
||||
// The music, joystick, game, and shell libraries' functions behind BEEP,
|
||||
// SOUND, PLAY, STICK, STRIG, FRAMEWAIT, and SHELL (deviceBind; a group
|
||||
// stays NULL when its library is not loaded)
|
||||
// stays NULL when its library is not installed). The game library's are
|
||||
// looked up at the first FRAMEWAIT (deviceFrameStart).
|
||||
typedef struct {
|
||||
__typeof__(musicBasBeep) *beep;
|
||||
__typeof__(musicBasBusy) *busy;
|
||||
|
|
@ -423,6 +425,29 @@ typedef struct {
|
|||
|
||||
static DeviceApiT sDeviceApi;
|
||||
|
||||
// The libraries whose BASIC functions need the GUI context: each gets it
|
||||
// (its setContext) for a program at the program's start when the library
|
||||
// is loaded, else at the program's first call into it (basExternResolve,
|
||||
// printerResolve), so a program that never uses one does not load it.
|
||||
// For printing it also marks the program as one that has made no printer
|
||||
// choice yet. rt->ctxLibsBound has a bit for each.
|
||||
typedef void (*CtxLibSetFnT)(AppContextT *ctx);
|
||||
|
||||
typedef struct {
|
||||
const char *lib; // its DECLARE LIBRARY name
|
||||
const char *setContext;
|
||||
} CtxLibT;
|
||||
|
||||
#define CTX_LIB_GAME 0
|
||||
#define CTX_LIB_PRINT 1
|
||||
|
||||
static const CtxLibT sCtxLibs[] = {
|
||||
[CTX_LIB_GAME] = { "game", DVX_SYM("gameBasSetContext") },
|
||||
[CTX_LIB_PRINT] = { "print", DVX_SYM("printBasSetContext") },
|
||||
};
|
||||
|
||||
#define CTX_LIB_COUNT ((int32_t)(sizeof(sCtxLibs) / sizeof(sCtxLibs[0])))
|
||||
|
||||
static bool sCommApiResolved = false;
|
||||
|
||||
#define COMM_BITS_PER_BYTE 8
|
||||
|
|
@ -956,6 +981,7 @@ static int32_t commTermWrite(void *ctx, const uint8_t *data, int32_t len);
|
|||
static BasValueT contentBoxProp(BasFormRtT *rt, BasFormT *form, const char *propName, const BasValueT *value);
|
||||
static const char *ctrlDisplayName(const BasControlT *ctrl);
|
||||
static BasControlT *ctrlFromWidget(WidgetT *w, BasFormRtT **outRt);
|
||||
static void ctxLibBind(BasFormRtT *rt, int32_t idx);
|
||||
static BasFormRtT *curRt(void);
|
||||
static void curRtForget(const BasFormRtT *rt);
|
||||
static BasFormRtT *curRtSet(BasFormRtT *rt);
|
||||
|
|
@ -965,7 +991,7 @@ static void detachFormForUnload(BasFormRtT *rt, BasFormT *form);
|
|||
static void deviceBeep(void *ctx);
|
||||
static void deviceBind(BasFormRtT *rt);
|
||||
static bool deviceFrameBusy(void *ctx);
|
||||
static void deviceFrameStart(void *ctx, int32_t hz);
|
||||
static bool deviceFrameStart(void *ctx, int32_t hz);
|
||||
static bool devicePlay(void *ctx, const char *macro);
|
||||
static int32_t devicePlayCount(void *ctx);
|
||||
static bool deviceShellBusy(void *ctx);
|
||||
|
|
@ -1054,7 +1080,6 @@ static void frmLoadOnMenuBegin(void *userData, const char *name, int32_t level);
|
|||
static void frmLoadOnMenuEnd(void *userData);
|
||||
static void frmLoadOnMenuProp(void *userData, const char *key, const char *value);
|
||||
static void frmLoadQueueContextMenu(BasFrmLoadCtxT *ctx, BasControlT *ctrl, const char *menuName);
|
||||
static void gameBind(BasFormRtT *rt);
|
||||
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled);
|
||||
static BasValueT getFormProp(BasFormRtT *rt, BasFormT *frm, const char *propName);
|
||||
static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled);
|
||||
|
|
@ -1116,6 +1141,7 @@ static const char *popupMenuNameFor(const BasFormT *form, const MenuT *menu);
|
|||
static void printerBind(BasFormRtT *rt);
|
||||
static bool printerClose(void *ctx, void *job);
|
||||
static void *printerOpen(void *ctx, int32_t port);
|
||||
static bool printerResolve(BasFormRtT *rt);
|
||||
static bool printerWrite(void *ctx, void *job, const char *data, int32_t len);
|
||||
int32_t PrintForm(const char *formName);
|
||||
int32_t PrintFormDialog(const char *formName);
|
||||
|
|
@ -1141,6 +1167,7 @@ static void rtEventEnter(BasFormRtT *rt);
|
|||
static void rtEventLeave(BasFormRtT *rt);
|
||||
static const BasProcEntryT *rtFindProc(BasFormRtT *rt, const char *name);
|
||||
static bool rtPump(BasFormRtT *rt);
|
||||
static bool rtSliceTurn(BasFormRtT *rt);
|
||||
static BasVmResultE runSliced(BasFormRtT *rt);
|
||||
static void runtimeErrorEnd(BasFormRtT *rt, const char *summary, const char *details);
|
||||
static BasValueT screenMethod(BasFormRtT *rt, const char *name, BasValueT *args, int32_t argc);
|
||||
|
|
@ -1465,14 +1492,24 @@ BasValueT basExternCall(void *ctx, void *funcPtr, const char *libName, const cha
|
|||
}
|
||||
|
||||
|
||||
// dlsym(NULL) loads the library that has the function when it is not
|
||||
// loaded yet; one of sCtxLibs then gets the program's GUI context.
|
||||
void *basExternResolve(void *ctx, const char *libName, const char *funcName) {
|
||||
(void)ctx;
|
||||
(void)libName;
|
||||
|
||||
char mangledName[BAS_MANGLED_NAME_LEN];
|
||||
snprintf(mangledName, sizeof(mangledName), DVX_SYM_PREFIX "%s", funcName);
|
||||
BasFormRtT *rt = curRt();
|
||||
void *fn;
|
||||
|
||||
return dlsym(NULL, mangledName);
|
||||
(void)ctx;
|
||||
snprintf(mangledName, sizeof(mangledName), DVX_SYM_PREFIX "%s", funcName);
|
||||
fn = dlsym(NULL, mangledName);
|
||||
|
||||
for (int32_t i = 0; fn && rt && libName && i < CTX_LIB_COUNT; i++) {
|
||||
if (strcasecmp(libName, sCtxLibs[i].lib) == 0) {
|
||||
ctxLibBind(rt, i);
|
||||
}
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1570,9 +1607,12 @@ void basFormRtBindVm(BasFormRtT *rt) {
|
|||
|
||||
basVmSetUiCallbacks(rt->vm, &ui);
|
||||
printerBind(rt);
|
||||
gameBind(rt);
|
||||
deviceBind(rt);
|
||||
basFormRtScreenBind(rt);
|
||||
|
||||
for (int32_t i = 0; i < CTX_LIB_COUNT; i++) {
|
||||
ctxLibBind(rt, i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2598,11 +2638,14 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
|||
}
|
||||
|
||||
// A form whose Unload is committed but deferred (a handler is still
|
||||
// on the stack) keeps answering to its name until the frees flush:
|
||||
// 'Unload X' compiles to a Load-then-Unload pair, and re-parsing
|
||||
// the cached .frm here would resurrect the form and unload it a
|
||||
// second time. Unload on the returned form is a no-op (unloading).
|
||||
for (int32_t i = 0; !sNewFormCopy && i < (int32_t)arrlen(rt->pendingUnload); i++) {
|
||||
// on the stack, or events wait) answers to its name for another
|
||||
// Unload only: 'Unload X' compiles to a Load-then-Unload pair, and
|
||||
// re-parsing the cached .frm here would resurrect the form and unload
|
||||
// it a second time (Unload on the returned form is a no-op). Load or
|
||||
// a reference gets a new form, as in VB, not the one going away.
|
||||
bool forUnload = rt->vm && basVmFormLookupForUnload(rt->vm);
|
||||
|
||||
for (int32_t i = 0; forUnload && !sNewFormCopy && i < (int32_t)arrlen(rt->pendingUnload); i++) {
|
||||
if (strcasecmp(rt->pendingUnload[i]->name, formName) == 0) {
|
||||
return rt->pendingUnload[i];
|
||||
}
|
||||
|
|
@ -3754,10 +3797,16 @@ void basFormRtUnloadForm(void *ctx, void *formRef) {
|
|||
|
||||
|
||||
// The waiting events of every runtime of this task run (rt's, and those
|
||||
// of programs it runs), then a frame.
|
||||
// of programs it runs), then a frame. At a slice end of the program's
|
||||
// code (the host's DoEvents callback, not DoEvents itself) only DVX and
|
||||
// the other programs get their turn (rtSliceTurn).
|
||||
bool basFormRtUpdate(BasFormRtT *rt) {
|
||||
uint32_t task = basFormRtTaskId();
|
||||
|
||||
if (rt->vm && basVmAtSliceEnd(rt->vm)) {
|
||||
return rtSliceTurn(rt);
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sRuntimes); i++) {
|
||||
if (sRuntimes[i]->ownTask == task) {
|
||||
queueDrain(sRuntimes[i]);
|
||||
|
|
@ -5044,6 +5093,24 @@ static BasControlT *ctrlFromWidget(WidgetT *w, BasFormRtT **outRt) {
|
|||
}
|
||||
|
||||
|
||||
// sCtxLibs[idx] gets the program's GUI context once, if it is loaded
|
||||
// (platformLoadedSymbol never loads it).
|
||||
static void ctxLibBind(BasFormRtT *rt, int32_t idx) {
|
||||
CtxLibSetFnT setContext;
|
||||
|
||||
if (rt->ctxLibsBound & (1u << idx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setContext = (CtxLibSetFnT)platformLoadedSymbol(sCtxLibs[idx].setContext);
|
||||
|
||||
if (setContext) {
|
||||
setContext(rt->ctx);
|
||||
rt->ctxLibsBound |= 1u << idx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The runtime current on this task, or NULL.
|
||||
static BasFormRtT *curRt(void) {
|
||||
uint32_t task = basFormRtTaskId();
|
||||
|
|
@ -5185,8 +5252,6 @@ static void deviceBind(BasFormRtT *rt) {
|
|||
sDeviceApi.sound = (__typeof__(sDeviceApi.sound))dlsym(NULL, DVX_SYM("musicBasSound"));
|
||||
sDeviceApi.stick = (__typeof__(sDeviceApi.stick))dlsym(NULL, DVX_SYM("joyBasStick"));
|
||||
sDeviceApi.strig = (__typeof__(sDeviceApi.strig))dlsym(NULL, DVX_SYM("joyBasStrig"));
|
||||
sDeviceApi.frameInit = (__typeof__(sDeviceApi.frameInit))dlsym(NULL, DVX_SYM("gameFrameInit"));
|
||||
sDeviceApi.frameNext = (__typeof__(sDeviceApi.frameNext))dlsym(NULL, DVX_SYM("gameFrameNext"));
|
||||
sDeviceApi.dosFor = (__typeof__(sDeviceApi.dosFor))dlsym(NULL, DVX_SYM("shellRunDosFor"));
|
||||
sDeviceApi.dosDone = (__typeof__(sDeviceApi.dosDone))dlsym(NULL, DVX_SYM("shellRunDosDone"));
|
||||
sDeviceApi.dosSplit = (__typeof__(sDeviceApi.dosSplit))dlsym(NULL, DVX_SYM("shellSplitCommand"));
|
||||
|
|
@ -5205,10 +5270,8 @@ static void deviceBind(BasFormRtT *rt) {
|
|||
cb.strig = deviceStrig;
|
||||
}
|
||||
|
||||
if (sDeviceApi.frameInit && sDeviceApi.frameNext) {
|
||||
cb.frameStart = deviceFrameStart;
|
||||
cb.frameBusy = deviceFrameBusy;
|
||||
}
|
||||
|
||||
if (sDeviceApi.dosFor && sDeviceApi.dosDone && sDeviceApi.dosSplit) {
|
||||
cb.shellStart = deviceShellStart;
|
||||
|
|
@ -5229,16 +5292,28 @@ static bool deviceFrameBusy(void *ctx) {
|
|||
|
||||
|
||||
// The frame clock starts over when the program asks for another rate.
|
||||
static void deviceFrameStart(void *ctx, int32_t hz) {
|
||||
// The game library's pacer is looked up (the library loaded) at the
|
||||
// first FRAMEWAIT; false without it (Device unavailable).
|
||||
static bool deviceFrameStart(void *ctx, int32_t hz) {
|
||||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||
uint32_t missed = 0;
|
||||
|
||||
if (!sDeviceApi.frameInit || !sDeviceApi.frameNext) {
|
||||
sDeviceApi.frameInit = (__typeof__(sDeviceApi.frameInit))dlsym(NULL, DVX_SYM("gameFrameInit"));
|
||||
sDeviceApi.frameNext = (__typeof__(sDeviceApi.frameNext))dlsym(NULL, DVX_SYM("gameFrameNext"));
|
||||
}
|
||||
|
||||
if (!sDeviceApi.frameInit || !sDeviceApi.frameNext) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rt->frameHz != hz) {
|
||||
sDeviceApi.frameInit(&rt->framePacer, (uint32_t)hz);
|
||||
rt->frameHz = hz;
|
||||
}
|
||||
|
||||
rt->frameDueUs = sDeviceApi.frameNext(&rt->framePacer, &missed);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -6065,9 +6140,9 @@ static void fireCtrlEventOut(BasFormRtT *rt, BasControlT *ctrl, const char *even
|
|||
return;
|
||||
}
|
||||
|
||||
// Re-entrancy guard: reject only same-event re-entry (runSubLoop
|
||||
// pumps events during long handlers; without this, a lingering
|
||||
// mouse-up can re-deliver Click on top of itself). But allow
|
||||
// Re-entrancy guard: reject only same-event re-entry (DoEvents or a
|
||||
// wait in a handler runs the program's events; without this, a
|
||||
// lingering mouse-up can re-deliver Click on top of itself). But allow
|
||||
// different events on the same control (e.g. LostFocus while Click
|
||||
// is still running) -- those aren't re-entrant, they're interleaved.
|
||||
if (ctrl->eventFiring) {
|
||||
|
|
@ -7010,17 +7085,6 @@ static void frmLoadQueueContextMenu(BasFrmLoadCtxT *ctx, BasControlT *ctrl, cons
|
|||
}
|
||||
|
||||
|
||||
// The game library's BASIC functions (game.bas) get the GUI context, when
|
||||
// the library is loaded.
|
||||
static void gameBind(BasFormRtT *rt) {
|
||||
__typeof__(gameBasSetContext) *setContext = (__typeof__(setContext))dlsym(NULL, DVX_SYM("gameBasSetContext"));
|
||||
|
||||
if (setContext) {
|
||||
setContext(rt->ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled) {
|
||||
const BasPropDescT *pd = basFormRtFindCommonProp(propName);
|
||||
WidgetT *w = ctrl->widget;
|
||||
|
|
@ -8458,25 +8522,10 @@ static const char *popupMenuNameFor(const BasFormT *form, const MenuT *menu) {
|
|||
}
|
||||
|
||||
|
||||
// The printing library's functions, looked up by name so the runtime
|
||||
// works without it (LPRINT and OPEN "LPT1:" then raise "Device
|
||||
// unavailable"). It also gets the GUI context for print.bas.
|
||||
// LPRINT and OPEN "LPT1:" reach the printing library through these; its
|
||||
// functions are looked up when the program first prints (printerResolve),
|
||||
// so a program that never prints does not load it.
|
||||
static void printerBind(BasFormRtT *rt) {
|
||||
__typeof__(printBasSetContext) *setContext = (__typeof__(setContext))dlsym(NULL, DVX_SYM("printBasSetContext"));
|
||||
|
||||
sPrintApi.open = (__typeof__(sPrintApi.open))dlsym(NULL, DVX_SYM("printUiOpenPort"));
|
||||
sPrintApi.write = (__typeof__(sPrintApi.write))dlsym(NULL, DVX_SYM("printJobWrite"));
|
||||
sPrintApi.close = (__typeof__(sPrintApi.close))dlsym(NULL, DVX_SYM("printJobClose"));
|
||||
sPrintApi.window = (__typeof__(sPrintApi.window))dlsym(NULL, DVX_SYM("printUiWindow"));
|
||||
|
||||
if (setContext) {
|
||||
setContext(rt->ctx);
|
||||
}
|
||||
|
||||
if (!sPrintApi.open || !sPrintApi.write || !sPrintApi.close) {
|
||||
return;
|
||||
}
|
||||
|
||||
BasPrinterCallbacksT cb;
|
||||
|
||||
cb.open = printerOpen;
|
||||
|
|
@ -8496,10 +8545,35 @@ static bool printerClose(void *ctx, void *job) {
|
|||
static void *printerOpen(void *ctx, int32_t port) {
|
||||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||
|
||||
if (!printerResolve(rt)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sPrintApi.open(rt->ctx, port == BAS_PRINTER_DEFAULT ? PRINT_PORT_DEFAULT : port);
|
||||
}
|
||||
|
||||
|
||||
// The printing library's functions, looked up by name (the library loaded)
|
||||
// the first time a program prints, so the runtime works without it (LPRINT
|
||||
// and OPEN "LPT1:" then raise "Device unavailable"). The program's
|
||||
// print.bas gets the GUI context then if it has not yet.
|
||||
static bool printerResolve(BasFormRtT *rt) {
|
||||
if (!sPrintApi.open) {
|
||||
sPrintApi.open = (__typeof__(sPrintApi.open))dlsym(NULL, DVX_SYM("printUiOpenPort"));
|
||||
sPrintApi.write = (__typeof__(sPrintApi.write))dlsym(NULL, DVX_SYM("printJobWrite"));
|
||||
sPrintApi.close = (__typeof__(sPrintApi.close))dlsym(NULL, DVX_SYM("printJobClose"));
|
||||
sPrintApi.window = (__typeof__(sPrintApi.window))dlsym(NULL, DVX_SYM("printUiWindow"));
|
||||
}
|
||||
|
||||
if (!sPrintApi.open || !sPrintApi.write || !sPrintApi.close) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ctxLibBind(rt, CTX_LIB_PRINT);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool printerWrite(void *ctx, void *job, const char *data, int32_t len) {
|
||||
(void)ctx;
|
||||
return sPrintApi.write((PrintJobT *)job, data, len);
|
||||
|
|
@ -8530,7 +8604,7 @@ static int32_t printFormWindow(const char *formName, bool askFirst) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!form || !form->window || !sPrintApi.window) {
|
||||
if (!form || !form->window || !printerResolve(rt) || !sPrintApi.window) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -8597,19 +8671,33 @@ static void queueDrain(BasFormRtT *rt) {
|
|||
|
||||
|
||||
// Keep an event for the program's own task, its arguments copied; a newer
|
||||
// state (sQueueLatest) replaces one of the same object still waiting. The
|
||||
// state (sQueueLatest) replaces the same object's newest waiting event when
|
||||
// that is the same state, so each object's events keep their order. The
|
||||
// first to wait takes the queue's eventDepth bracket. True (it will run).
|
||||
static bool queueEvent(BasFormRtT *rt, BasFormT *form, BasControlT *obj, const char *ctrlName, const char *eventName, const BasValueT *args, int32_t argCount, BasQueuedKindE kind) {
|
||||
BasQueuedEventT *slot = NULL;
|
||||
|
||||
for (int32_t i = 0; !slot && i < DVX_ARRAY_LEN(sQueueLatest); i++) {
|
||||
for (int32_t j = (int32_t)arrlen(rt->queued) - 1; j >= 0 && strcasecmp(eventName, sQueueLatest[i]) == 0; j--) {
|
||||
if (strcasecmp(eventName, sQueueLatest[i]) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The newest waiting event of the same object is replaced when it
|
||||
// is this kind; anything else of that object waits in between (a
|
||||
// drag's MouseMove goes after its MouseDown, not in place of the
|
||||
// hover move before it)
|
||||
for (int32_t j = (int32_t)arrlen(rt->queued) - 1; j >= 0; j--) {
|
||||
BasQueuedEventT *ev = &rt->queued[j];
|
||||
|
||||
if (ev->form == form && ev->obj == obj && strcasecmp(ev->eventName, eventName) == 0 && strcasecmp(ev->ctrlName, ctrlName) == 0) {
|
||||
slot = ev;
|
||||
break;
|
||||
if (ev->form != form || ev->obj != obj || strcasecmp(ev->ctrlName, ctrlName) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcasecmp(ev->eventName, eventName) == 0) {
|
||||
slot = ev;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -9126,6 +9214,21 @@ static bool rtPump(BasFormRtT *rt) {
|
|||
}
|
||||
|
||||
|
||||
// A slice end of the program's code: DVX and the other programs get their
|
||||
// turn, but none of the program's own events run (as in VB, they wait for
|
||||
// DoEvents, a wait, or the code's end). On its own task that is a yield
|
||||
// (the shell's frames go on); on another (an Unload the shell fired) a
|
||||
// frame, in which the program's events only queue.
|
||||
static bool rtSliceTurn(BasFormRtT *rt) {
|
||||
if (queueForeign(rt, NULL)) {
|
||||
return dvxUpdate(rt->ctx);
|
||||
}
|
||||
|
||||
basFormRtYield();
|
||||
return rt->ctx->running;
|
||||
}
|
||||
|
||||
|
||||
// Runs the VM in step slices, giving DVX a turn between them, until the
|
||||
// program halts or fails (an error is shown). Returns the last result.
|
||||
static BasVmResultE runSliced(BasFormRtT *rt) {
|
||||
|
|
@ -9143,7 +9246,7 @@ static BasVmResultE runSliced(BasFormRtT *rt) {
|
|||
curRtSet(prevRt);
|
||||
|
||||
if (result == BAS_VM_STEP_LIMIT) {
|
||||
if (!basFormRtUpdate(rt)) {
|
||||
if (!rtSliceTurn(rt)) {
|
||||
break;
|
||||
}
|
||||
} else if (result != BAS_VM_HALTED && result != BAS_VM_OK) {
|
||||
|
|
|
|||
|
|
@ -455,10 +455,10 @@ typedef struct BasControlT {
|
|||
BasEventOverrideT eventOverrides[BAS_MAX_EVENT_OVERRIDES];
|
||||
int32_t eventOverrideCount;
|
||||
// Re-entrancy guard: set while an event handler for this control
|
||||
// is running. runSubLoop pumps events during long-running handlers
|
||||
// (painting, draw loops), and without this guard a lingering
|
||||
// mouse-up or repaint cycle can re-fire the same Click on top of
|
||||
// itself -- an unbounded recursion. firingEventName records which
|
||||
// is running. DoEvents (or a wait that runs events) in a handler
|
||||
// runs the program's waiting events, and without this guard a
|
||||
// lingering mouse-up or repaint cycle can re-fire the same Click on
|
||||
// top of itself -- an unbounded recursion. firingEventName records which
|
||||
// event is in flight so different-event delivery (e.g. LostFocus
|
||||
// while Click is still running) is allowed through.
|
||||
bool eventFiring;
|
||||
|
|
@ -679,6 +679,9 @@ typedef struct {
|
|||
int32_t screenPointer;
|
||||
// The Printer object's pages and document (NULL until it is used).
|
||||
BasPrinterT *printer;
|
||||
// The libraries (a bit for each of formrt.c's sCtxLibs) that have
|
||||
// this program's GUI context (ctxLibBind)
|
||||
uint32_t ctxLibsBound;
|
||||
// A program's handlers run on its own task (the one that made the
|
||||
// runtime; BAS_RT_ANY_TASK: any). An event another task dispatched
|
||||
// (the shell's frame takes the input), and a form's window closed
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ The DVX BASIC IDE is modeled after Visual Basic 3.0. It consists of several floa
|
|||
.item Debug Windows -- Locals, Call Stack, Watch, and Breakpoints windows that appear automatically when debugging.
|
||||
.endlist
|
||||
|
||||
The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine. Programs execute in cooperative slices, yielding to DVX between slices so the GUI remains responsive. Call DoEvents in long-running loops to keep the event loop flowing.
|
||||
The IDE compiles BASIC source into bytecode and runs it in an integrated virtual machine. Programs execute in cooperative slices, yielding to DVX between slices so DVX remains responsive. The program's own events wait until the code that is running ends or calls DoEvents, as in Visual Basic, so a long-running loop that should answer its own buttons calls DoEvents now and then.
|
||||
|
||||
.link ide.menu.file File Menu
|
||||
.link ide.menu.edit Edit Menu
|
||||
|
|
|
|||
|
|
@ -3226,7 +3226,7 @@ UNLOAD Me
|
|||
UNLOAD formVariable
|
||||
.endcode
|
||||
|
||||
LOAD creates the form and its controls in memory. It fires the form's Load event (Form1_Load for a form named Form1) when the form is first loaded. UNLOAD destroys the form, firing its QueryUnload event (which can cancel the close) and then its Unload event. The form name here is the literal name of the form as it appears in its .frm file; UNLOAD Me unloads the form whose event handler is running. UNLOAD also takes a Form variable, or anything else that gives a form (for example a form made with CreateForm and kept in an object's property), and unloads that form.
|
||||
LOAD creates the form and its controls in memory. It fires the form's Load event (Form1_Load for a form named Form1) when the form is first loaded. UNLOAD destroys the form, firing its QueryUnload event (which can cancel the close) and then its Unload event. The form name here is the literal name of the form as it appears in its .frm file; UNLOAD Me unloads the form whose event handler is running. UNLOAD also takes a Form variable, or anything else that gives a form (for example a form made with CreateForm and kept in an object's property), and unloads that form. Once a form is unloaded, LOAD or any use of its name (a property, a control, Show) makes a new copy of it with its Load event, as in Visual Basic, even while code that ran the UNLOAD is still running; a second UNLOAD of the same form does nothing.
|
||||
|
||||
.h2 Showing and Hiding Forms
|
||||
|
||||
|
|
@ -3414,7 +3414,7 @@ End Sub
|
|||
|
||||
.h2 DoEvents
|
||||
|
||||
Yields control to DVX so the GUI can process pending events. Call this in long-running loops to keep the UI responsive (otherwise the window will appear frozen).
|
||||
Lets your program's waiting events (a click, a key, a Timer) run now. While your code runs, DVX and the other programs keep going (they get their turn many times a second), but your program's own events wait until the code that is running ends or calls DoEvents, as in Visual Basic. A long loop that should answer a Cancel button, or let a Timer run, calls DoEvents now and then; after it, check whatever an event may have changed (a Cancel flag, a form that was closed). MsgBox, InputBox, a form shown with vbModal, SLEEP, and the statements that wait for sound or a frame run your program's events too, as do the long calls described as working "as with DoEvents" (FileCopy, DIR$, long SQL statements, and others).
|
||||
|
||||
.code
|
||||
DOEVENTS
|
||||
|
|
|
|||
|
|
@ -934,6 +934,11 @@ static BasValueT arrValue(BasArrayT *arr) {
|
|||
}
|
||||
|
||||
|
||||
bool basVmAtSliceEnd(const BasVmT *vm) {
|
||||
return vm->sliceTurn;
|
||||
}
|
||||
|
||||
|
||||
bool basVmCallMember(BasVmT *vm, BasObjectT *obj, const char *name, int32_t op, const BasValueT *args, int32_t argc, BasValueT *result) {
|
||||
if (result) {
|
||||
*result = basValEmpty();
|
||||
|
|
@ -1201,6 +1206,11 @@ void basVmForgetForm(BasVmT *vm, void *formRef, BasValueT *formVars, int32_t for
|
|||
}
|
||||
|
||||
|
||||
bool basVmFormLookupForUnload(const BasVmT *vm) {
|
||||
return vm->unloadLookup;
|
||||
}
|
||||
|
||||
|
||||
int32_t basVmGetCurrentLine(const BasVmT *vm) {
|
||||
return vm->currentLine;
|
||||
}
|
||||
|
|
@ -3521,7 +3531,10 @@ static BasVmResultE basVmStepExec(BasVmT *vm) {
|
|||
|
||||
if (vm->ui.loadForm) {
|
||||
BasValueT sv = basValToString(nameVal);
|
||||
|
||||
vm->unloadLookup = operandFits(vm, (int32_t)sizeof(uint8_t)) && vm->module->code[vm->pc] == OP_UNLOAD_FORM;
|
||||
formRef = vm->ui.loadForm(vm->ui.ctx, sv.strVal->data);
|
||||
vm->unloadLookup = false;
|
||||
basValRelease(&sv);
|
||||
}
|
||||
|
||||
|
|
@ -7163,7 +7176,10 @@ static BasVmResultE execDevice(BasVmT *vm, uint8_t op) {
|
|||
return BAS_VM_ERROR;
|
||||
}
|
||||
|
||||
d->frameStart(d->ctx, hz);
|
||||
if (!d->frameStart(d->ctx, hz)) {
|
||||
runtimeError(vm, BAS_ERR_DEVICE_UNAVAILABLE, errorText(BAS_ERR_DEVICE_UNAVAILABLE));
|
||||
return BAS_VM_ERROR;
|
||||
}
|
||||
|
||||
if (!waitWhile(vm, d->frameBusy)) {
|
||||
vm->running = false;
|
||||
|
|
@ -13992,8 +14008,8 @@ fastResume:
|
|||
|
||||
// Executes instructions until callDepth drops back to savedCallDepth
|
||||
// (meaning the subroutine returned). Periodically yields via the
|
||||
// doEvents callback to keep the GUI responsive during long-running
|
||||
// event handlers.
|
||||
// doEvents callback (a slice-end turn, basVmAtSliceEnd) so DVX keeps
|
||||
// running during long event handlers.
|
||||
static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool savedRunning) {
|
||||
int32_t stepsSinceYield = 0;
|
||||
int32_t slices = 0; // a run limit's count
|
||||
|
|
@ -14050,8 +14066,10 @@ static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool
|
|||
return subLoopFail(vm, savedPc, savedCallDepth, savedRunning);
|
||||
}
|
||||
|
||||
// Yield periodically to keep the GUI responsive; code with a run
|
||||
// limit that has run too long is stopped (ON ERROR cannot trap it)
|
||||
// Yield periodically so DVX and the other programs keep running
|
||||
// (the host runs none of this program's events then: they wait for
|
||||
// DoEvents or the handler's end, as in VB); code with a run limit
|
||||
// that has run too long is stopped (ON ERROR cannot trap it)
|
||||
if (stepsSinceYield >= BAS_VM_DEFAULT_STEP_SLICE) {
|
||||
stepsSinceYield = 0;
|
||||
|
||||
|
|
@ -14059,7 +14077,13 @@ static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool
|
|||
return subLoopFail(vm, savedPc, savedCallDepth, savedRunning);
|
||||
}
|
||||
|
||||
if (!basVmDoEvents(vm)) {
|
||||
vm->sliceTurn = true;
|
||||
|
||||
bool goOn = basVmDoEvents(vm);
|
||||
|
||||
vm->sliceTurn = false;
|
||||
|
||||
if (!goOn) {
|
||||
vm->running = false;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,9 +126,13 @@ typedef void (*BasPrintFnT)(void *ctx, const char *text, bool newline);
|
|||
// success, false on cancel/error.
|
||||
typedef bool (*BasInputFnT)(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
||||
|
||||
// DoEvents callback: called for DoEvents statement.
|
||||
// The host should process pending events and return. Returns
|
||||
// true to continue execution, false to stop the program.
|
||||
// DoEvents callback: called for the DoEvents statement and the waits that
|
||||
// act like it (SLEEP, the sound and FRAMEWAIT waits, FileCopy, DIR$), when
|
||||
// the host should run the program's waiting events, and at each slice end
|
||||
// of a running handler (basVmAtSliceEnd then true), when it should give
|
||||
// DVX and the other programs their turn but run none of the program's
|
||||
// own events: as in VB, those wait for DoEvents or the code's end.
|
||||
// Returns true to continue execution, false to stop the program.
|
||||
typedef bool (*BasDoEventsFnT)(void *ctx);
|
||||
|
||||
// Breakpoint callback: called when a breakpoint fires inside a
|
||||
|
|
@ -322,14 +326,15 @@ typedef struct {
|
|||
// BEEP, SOUND and PLAY the VM keeps the host's events running while
|
||||
// soundBusy says the statement must still wait (foreground music); after
|
||||
// frameStart(hz) (the host's frame clock moves on to its next frame at hz
|
||||
// frames a second) likewise while frameBusy says that frame has not
|
||||
// begun. SHELL hands its command line to shellStart (the host runs it
|
||||
// with DVX stepped aside: false when it cannot), keeps the host's events
|
||||
// running while shellBusy, and returns shellResult (the exit code, -1 when
|
||||
// it did not run). Without beep / sound / play the statements do nothing
|
||||
// and PLAY(n) is 0, without stick / strig they read 0, FRAMEWAIT without
|
||||
// frameStart raises "Device unavailable", and SHELL without shellStart
|
||||
// runs the command with system() (BASIC outside DVX: the runner, BASCOMP).
|
||||
// frames a second; false when the host has no frame clock) likewise while
|
||||
// frameBusy says that frame has not begun. SHELL hands its command line
|
||||
// to shellStart (the host runs it with DVX stepped aside: false when it
|
||||
// cannot), keeps the host's events running while shellBusy, and returns
|
||||
// shellResult (the exit code, -1 when it did not run). Without beep /
|
||||
// sound / play the statements do nothing and PLAY(n) is 0, without stick /
|
||||
// strig they read 0, FRAMEWAIT without frameStart (or when it says false)
|
||||
// raises "Device unavailable", and SHELL without shellStart runs the
|
||||
// command with system() (BASIC outside DVX: the runner, BASCOMP).
|
||||
|
||||
// QBasic's ranges: SOUND frequency (Hz) and duration (clock ticks, 18.2
|
||||
// a second), STICK(0..3), STRIG(0..7)
|
||||
|
|
@ -347,7 +352,7 @@ typedef struct {
|
|||
bool (*soundBusy)(void *ctx);
|
||||
int32_t (*stick)(void *ctx, int32_t n);
|
||||
int32_t (*strig)(void *ctx, int32_t n);
|
||||
void (*frameStart)(void *ctx, int32_t hz);
|
||||
bool (*frameStart)(void *ctx, int32_t hz);
|
||||
bool (*frameBusy)(void *ctx);
|
||||
bool (*shellStart)(void *ctx, const char *command);
|
||||
bool (*shellBusy)(void *ctx);
|
||||
|
|
@ -883,6 +888,14 @@ typedef struct {
|
|||
struct BasScreenT *screen;
|
||||
bool stopRequested;
|
||||
|
||||
// The DoEvents callback is running for a slice end of the program's
|
||||
// code, not for DoEvents or a wait (basVmAtSliceEnd)
|
||||
bool sliceTurn;
|
||||
|
||||
// OP_LOAD_FORM is asking the host for the form an Unload follows
|
||||
// (basVmFormLookupForUnload)
|
||||
bool unloadLookup;
|
||||
|
||||
// External library callbacks (set by host)
|
||||
BasExternCallbacksT ext;
|
||||
|
||||
|
|
@ -975,6 +988,15 @@ bool basVmSetCommand(BasVmT *vm, const char *args);
|
|||
// App.User's folder is made when the program first asks for it.
|
||||
void basVmSetAppPaths(BasVmT *vm, const char *path, const char *user, const char *data);
|
||||
|
||||
// True while the DoEvents callback runs for a slice end of the program's
|
||||
// code rather than for DoEvents or a wait (see BasDoEventsFnT).
|
||||
bool basVmAtSliceEnd(const BasVmT *vm);
|
||||
|
||||
// True while the host's loadForm runs for an Unload ('Unload X' compiles to
|
||||
// OP_LOAD_FORM then OP_UNLOAD_FORM): a form whose Unload is already under
|
||||
// way is the one to give then, where Load or a reference gets a new one.
|
||||
bool basVmFormLookupForUnload(const BasVmT *vm);
|
||||
|
||||
// Give the host a turn: show what changed on the program screen, then
|
||||
// the DoEvents callback. False when the program must stop (the host said
|
||||
// so, or basVmRequestStop).
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ static BasModuleT *compileSource(const char *name, const char *source);
|
|||
static void deviceBeepStub(void *ctx);
|
||||
static bool deviceDoEventsStub(void *ctx);
|
||||
static bool deviceFrameBusyStub(void *ctx);
|
||||
static void deviceFrameStartStub(void *ctx, int32_t hz);
|
||||
static bool deviceFrameStartStub(void *ctx, int32_t hz);
|
||||
static int32_t devicePlayCountStub(void *ctx);
|
||||
static bool devicePlayStub(void *ctx, const char *macro);
|
||||
static bool deviceSoundBusyStub(void *ctx);
|
||||
|
|
@ -372,6 +372,7 @@ static int32_t sDeviceSoundWaits = 0;
|
|||
static int32_t sDeviceFrameWaits = 0;
|
||||
static int32_t sDeviceEvents = 0;
|
||||
static int32_t sDeviceEventsLeft = 0;
|
||||
static bool sDeviceNoClock = false; // frameStart says the host has no frame clock
|
||||
|
||||
static const BasDeviceCallbacksT sDeviceStubs = {
|
||||
.beep = deviceBeepStub,
|
||||
|
|
@ -463,9 +464,10 @@ static bool deviceFrameBusyStub(void *ctx) {
|
|||
}
|
||||
|
||||
|
||||
static void deviceFrameStartStub(void *ctx, int32_t hz) {
|
||||
static bool deviceFrameStartStub(void *ctx, int32_t hz) {
|
||||
(void)ctx;
|
||||
snprintf(sDeviceLog + strlen(sDeviceLog), sizeof(sDeviceLog) - strlen(sDeviceLog), "frame %ld;", (long)hz);
|
||||
return !sDeviceNoClock;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1870,6 +1872,12 @@ static void testDeviceStatements(void) {
|
|||
TEST_RUNTIME_ERROR("device-strig-range", "PRINT STRIG(-1)\n", "Illegal function call");
|
||||
TEST_RUNTIME_ERROR("device-play-bad", "PLAY \"CXD\"\n", "PLAY string");
|
||||
TEST_RUNTIME_ERROR("device-frame-zero", "FRAMEWAIT 0\n", "Illegal function call");
|
||||
|
||||
// A host whose frame clock is missing (the game library is not
|
||||
// installed) says so at the FRAMEWAIT.
|
||||
sDeviceNoClock = true;
|
||||
TEST_RUNTIME_ERROR("device-frame-noclock", "FRAMEWAIT 60\n", "Device unavailable");
|
||||
sDeviceNoClock = false;
|
||||
TEST_EQ("device-play-trapped", "ON ERROR GOTO h\nPLAY \"X\"\nEND\nh:\nPRINT ERR\nRESUME NEXT\n", "5 \n");
|
||||
|
||||
// Closing the program while it waits stops it there.
|
||||
|
|
|
|||
|
|
@ -557,6 +557,7 @@ SUB RefreshInstalled
|
|||
|
||||
ShowRows lvInstalled, rows
|
||||
lblStatus.Caption = CStr(count) & IIF(count = 1, " package installed", " packages installed")
|
||||
ShellLog "Software: " & lblStatus.Caption
|
||||
END SUB
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -498,6 +498,7 @@ SUB ShowTour(BYVAL page AS INTEGER)
|
|||
lblPage.Caption = "Page " + LTRIM$(STR$(page)) + " of " + LTRIM$(STR$(TOUR_PAGES))
|
||||
cmdPrev.Enabled = page > 1
|
||||
cmdNext.Enabled = page < TOUR_PAGES
|
||||
ShellLog "Welcome: tour page " + LTRIM$(STR$(page))
|
||||
END SUB
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -92,11 +92,22 @@ DVX_ABI_STAMP(dvxshell);
|
|||
// Random-seed file carried across runs for the cryptography library's RNG
|
||||
#define SHELL_SEED_NAME "SECLINK.RND" // in the user folder
|
||||
|
||||
// The shell task's priority: high, so the UI stays responsive even when
|
||||
// app tasks are CPU-hungry (11 credits a round against a NORMAL app's 6,
|
||||
// roughly twice the turns of any single app).
|
||||
#define SHELL_TASK_PRIORITY TS_PRIORITY_HIGH
|
||||
|
||||
// Exit DVX: how long an app may take to close a window it was asked to
|
||||
// close before it is reported as not responding, and the frames a main-loop
|
||||
// app gets to act on the request before a kept window counts as a refusal.
|
||||
// A kept window is a refusal only after QUIET frames in a row with no
|
||||
// dialog open: an app can have none open for a moment between two (a
|
||||
// BASIC program's slice may end between its save prompt and its Save As),
|
||||
// and the shell can take a whole round of its turns (its priority + 1)
|
||||
// before the app's next one.
|
||||
#define SHELL_EXIT_GRACE_MS 5000
|
||||
#define SHELL_EXIT_SETTLE_FRAMES 4
|
||||
#define SHELL_EXIT_QUIET_FRAMES (SHELL_TASK_PRIORITY + 2)
|
||||
#define SHELL_EXIT_TITLE "Exit DVX"
|
||||
#define SHELL_CLOSE_TITLE "Close Program"
|
||||
|
||||
|
|
@ -201,8 +212,8 @@ static int32_t *sTaskDepth = NULL; // stb_ds, indexed by task id
|
|||
static uint32_t sRunningTask = 0; // task whose context was last restored
|
||||
static int32_t sDepthBase = 0; // dispatchDepth when that task was switched in
|
||||
|
||||
// Serial-library RNG entry points, resolved at run time (the shell does
|
||||
// not link against the library, and the host tests have none).
|
||||
// The cryptography library's RNG calls, found at run time once the
|
||||
// library is loaded (the shell does not link against it; entropyBind).
|
||||
// secRngAddEntropy has exactly the DvxEntropyHookT signature and is
|
||||
// registered directly.
|
||||
typedef int (*RngSeedFileFnT)(const char *path);
|
||||
|
|
@ -240,7 +251,9 @@ static void compactDesktopFns(void);
|
|||
static void compactIdleHandlers(void);
|
||||
static void ctrlEscHandler(void *ctx);
|
||||
static void desktopEnded(int32_t appId);
|
||||
static bool entropyBind(RngSeedFileFnT loadSeed, DvxEntropyHookT addEntropy, RngSeedFileFnT saveSeed);
|
||||
static void entropyInit(void);
|
||||
static void entropyLibraryLoaded(void *handle);
|
||||
static void entropyShutdown(void);
|
||||
static ShellAppT *exitAppRunning(int32_t appId);
|
||||
static bool exitAskApp(AppContextT *ctx, int32_t appId, bool exiting);
|
||||
|
|
@ -392,27 +405,49 @@ static void desktopEnded(int32_t appId) {
|
|||
}
|
||||
|
||||
|
||||
// When the cryptography library is loaded: feed it input timings and restore the
|
||||
// seed saved by the previous run (secRngLoadSeed rewrites the file at once).
|
||||
static void entropyInit(void) {
|
||||
RngSeedFileFnT loadSeed = (RngSeedFileFnT)dlsym(NULL, DVX_SYM("secRngLoadSeed"));
|
||||
DvxEntropyHookT addEntropy = (DvxEntropyHookT)dlsym(NULL, DVX_SYM("secRngAddEntropy"));
|
||||
|
||||
sRngSaveSeed = (RngSeedFileFnT)dlsym(NULL, DVX_SYM("secRngSaveSeed"));
|
||||
platformUserPath(sSeedPath, sizeof(sSeedPath), SHELL_SEED_NAME);
|
||||
|
||||
if (addEntropy) {
|
||||
dvxSetInputEntropyHook(addEntropy);
|
||||
// Feed the cryptography library input timings and restore the seed saved
|
||||
// by the previous run (secRngLoadSeed rewrites the file at once). False
|
||||
// when these are not the library's calls (it is not loaded).
|
||||
static bool entropyBind(RngSeedFileFnT loadSeed, DvxEntropyHookT addEntropy, RngSeedFileFnT saveSeed) {
|
||||
if (!loadSeed || !addEntropy || !saveSeed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (loadSeed && loadSeed(sSeedPath) != SEC_SUCCESS) {
|
||||
platformSetLibraryLoadHook(NULL);
|
||||
sRngSaveSeed = saveSeed;
|
||||
dvxSetInputEntropyHook(addEntropy);
|
||||
|
||||
if (loadSeed(sSeedPath) != SEC_SUCCESS) {
|
||||
dvxLog("Shell: no usable random seed in %s (a new one is written at shutdown)", sSeedPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// The cryptography library is bound now when it is loaded already, else
|
||||
// the moment DVX loads it (a program needs it): looking it up here would
|
||||
// load it at every start.
|
||||
static void entropyInit(void) {
|
||||
platformUserPath(sSeedPath, sizeof(sSeedPath), SHELL_SEED_NAME);
|
||||
|
||||
if (!entropyBind((RngSeedFileFnT)platformLoadedSymbol(DVX_SYM("secRngLoadSeed")), (DvxEntropyHookT)platformLoadedSymbol(DVX_SYM("secRngAddEntropy")), (RngSeedFileFnT)platformLoadedSymbol(DVX_SYM("secRngSaveSeed")))) {
|
||||
platformSetLibraryLoadHook(entropyLibraryLoaded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Detach the input hook, then save a fresh seed for the next run.
|
||||
static void entropyLibraryLoaded(void *handle) {
|
||||
if (entropyBind((RngSeedFileFnT)dlsym(handle, DVX_SYM("secRngLoadSeed")), (DvxEntropyHookT)dlsym(handle, DVX_SYM("secRngAddEntropy")), (RngSeedFileFnT)dlsym(handle, DVX_SYM("secRngSaveSeed")))) {
|
||||
dvxLog("Shell: random numbers seeded as the cryptography library loaded");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Detach the input hook, then save a fresh seed for the next run (when
|
||||
// the cryptography library was loaded).
|
||||
static void entropyShutdown(void) {
|
||||
platformSetLibraryLoadHook(NULL);
|
||||
dvxSetInputEntropyHook(NULL);
|
||||
|
||||
if (sRngSaveSeed && sRngSaveSeed(sSeedPath) != SEC_SUCCESS) {
|
||||
|
|
@ -478,12 +513,13 @@ static bool exitAskApp(AppContextT *ctx, int32_t appId, bool exiting) {
|
|||
|
||||
// Pump the shell until the window asked to close is gone. A kept window
|
||||
// is a refusal once the app has shown something (its save prompt) and no
|
||||
// dialog is open any more; an app that shows nothing and keeps the window
|
||||
// for SHELL_EXIT_GRACE_MS is stuck. created is ctx->windowsCreated from
|
||||
// before the request.
|
||||
// dialog has been open for SHELL_EXIT_QUIET_FRAMES; an app that shows
|
||||
// nothing and keeps the window for SHELL_EXIT_GRACE_MS is stuck. created
|
||||
// is ctx->windowsCreated from before the request.
|
||||
static ExitCloseE exitAwaitClose(AppContextT *ctx, int32_t appId, int32_t winId, uint32_t created) {
|
||||
PlatformTicksT start = platformClock();
|
||||
PlatformTicksT grace = PLATFORM_MS_TO_TICKS(SHELL_EXIT_GRACE_MS);
|
||||
int32_t quietFrames = 0;
|
||||
|
||||
for (int32_t frame = 0; ; frame++) {
|
||||
if (!exitAppRunning(appId) || exitWindowGone(ctx, winId)) {
|
||||
|
|
@ -491,14 +527,16 @@ static ExitCloseE exitAwaitClose(AppContextT *ctx, int32_t appId, int32_t winId,
|
|||
}
|
||||
|
||||
if (frame >= SHELL_EXIT_SETTLE_FRAMES) {
|
||||
if (ctx->windowsCreated != created) {
|
||||
if (!ctx->modalWindow) {
|
||||
if (ctx->windowsCreated == created) {
|
||||
if (platformClock() - start >= grace) {
|
||||
return ExitCloseStuckE;
|
||||
}
|
||||
} else if (ctx->modalWindow) {
|
||||
quietFrames = 0;
|
||||
} else if (++quietFrames >= SHELL_EXIT_QUIET_FRAMES) {
|
||||
dvxLog("Shell: exit cancelled by '%s'", exitAppRunning(appId)->name);
|
||||
return ExitCloseRefusedE;
|
||||
}
|
||||
} else if (platformClock() - start >= grace) {
|
||||
return ExitCloseStuckE;
|
||||
}
|
||||
}
|
||||
|
||||
shellFrame(ctx);
|
||||
|
|
@ -797,11 +835,7 @@ int shellMain(int argc, char *argv[]) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// Shell task (task 0) gets high priority so the UI remains responsive
|
||||
// even when app tasks are CPU-hungry. With HIGH priority (11 credits
|
||||
// per epoch) vs app tasks at NORMAL (6 credits), the shell gets
|
||||
// roughly twice as many scheduling turns as any single app.
|
||||
tsSetPriority(TS_MAIN_TASK_ID, TS_PRIORITY_HIGH);
|
||||
tsSetPriority(TS_MAIN_TASK_ID, SHELL_TASK_PRIORITY);
|
||||
|
||||
// Initialize app slot table
|
||||
shellAppInit();
|
||||
|
|
|
|||
|
|
@ -470,7 +470,14 @@ static void saveBegin(void) {
|
|||
|
||||
if (!sSave.f) {
|
||||
saveEnd(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// A buffer of one step's chunk, flushed by each saveStep: one write to
|
||||
// the disk a step, not a default buffer's worth (16 KB, a third of a
|
||||
// second on a 486) in whichever step fills it. Unbuffered would be
|
||||
// worse: the C library writes such a file a byte at a time.
|
||||
setvbuf(sSave.f, NULL, _IOFBF, SHELL_PROG_CACHE_SAVE_CHUNK);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -505,7 +512,7 @@ static void saveStep(void) {
|
|||
n = SHELL_PROG_CACHE_SAVE_CHUNK;
|
||||
}
|
||||
|
||||
if (fwrite(sSave.data + sSave.pos, 1, n, sSave.f) != n) {
|
||||
if (fwrite(sSave.data + sSave.pos, 1, n, sSave.f) != n || fflush(sSave.f) != 0) {
|
||||
saveEnd(false);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -617,7 +617,11 @@ DVX_ABI_VERSION (dvxTypes.h) is one number for the binary interface between all
|
|||
|
|
||||
+-- Every other library loads when something first needs one of
|
||||
| its names: a module being opened (platformModuleOpen), or a
|
||||
| dlsym(NULL) (BASIC's DECLARE LIBRARY, dvxLibraryLoaded)
|
||||
| dlsym(NULL) (BASIC's DECLARE LIBRARY, dvxLibraryLoaded).
|
||||
| platformLoadedSymbol looks a name up without loading; what
|
||||
| binds to a library once it is loaded hears of each one
|
||||
| through the shell's hook (platformSetLibraryLoadHook: the
|
||||
| cryptography library's random numbers are seeded then)
|
||||
|
|
||||
+-- dvxInit() -- video mode, input, font, colors, cursors
|
||||
|
|
||||
|
|
|
|||
|
|
@ -1104,6 +1104,20 @@ typedef void *(*PlatformSymbolResolverFnT)(const char *name);
|
|||
void platformSetModuleOpen(PlatformModuleOpenFnT fn);
|
||||
void platformSetSymbolResolver(PlatformSymbolResolverFnT fn);
|
||||
|
||||
// The address of name in dvx.exe or a module already loaded, as
|
||||
// dlsym(NULL, name) finds it, but never loading a library for it
|
||||
// (dlsym(NULL) loads the library that exports a name not loaded yet).
|
||||
// For code that binds to a library only once something has loaded it.
|
||||
void *platformLoadedSymbol(const char *name);
|
||||
|
||||
// A library DVX loaded after it started (one a program needed), ready
|
||||
// for use: the loader calls platformLibraryLoaded with its handle, which
|
||||
// passes it to the hook (the shell's, for what binds to a library only
|
||||
// once it is loaded; dlsym with the handle never loads anything).
|
||||
typedef void (*PlatformLibraryLoadFnT)(void *handle);
|
||||
void platformLibraryLoaded(void *handle);
|
||||
void platformSetLibraryLoadHook(PlatformLibraryLoadFnT fn);
|
||||
|
||||
// Register platform and C runtime symbols with the dynamic module
|
||||
// loader so that DXE modules can resolve them at load time. On DOS
|
||||
// this calls dlregsym() with the full DJGPP libc/libm/libgcc/platform
|
||||
|
|
|
|||
|
|
@ -1966,6 +1966,19 @@ const char *platformLineEnding(void) {
|
|||
}
|
||||
|
||||
|
||||
// dlsym(NULL) with DJGPP's last resort (the loader's, which loads the
|
||||
// library behind a name) out of the way for the one lookup.
|
||||
void *platformLoadedSymbol(const char *name) {
|
||||
void *(*resolver)(const char *) = _dlsymresolver;
|
||||
void *sym;
|
||||
|
||||
_dlsymresolver = NULL;
|
||||
sym = dlsym(NULL, name);
|
||||
_dlsymresolver = resolver;
|
||||
return sym;
|
||||
}
|
||||
|
||||
|
||||
// Pin a code or data range into physical memory so an interrupt handler
|
||||
// can touch it without faulting under a paging DPMI host. addr is a
|
||||
// near pointer in this program's CS (isCode) or DS space.
|
||||
|
|
@ -3592,8 +3605,8 @@ static void videoError(const char *fmt, ...) {
|
|||
// loader's own start-up ones, platformRegisterDxeExports,
|
||||
// platformRegisterSymOverrides, platformUserInit, platformSetLogPath,
|
||||
// platformSetLogMemory, platformBootNoteAdd, platformSetReadOnly,
|
||||
// platformSetModuleOpen, platformSetSymbolResolver, and
|
||||
// platformDxeExports, are not exported)
|
||||
// platformSetModuleOpen, platformSetSymbolResolver,
|
||||
// platformLibraryLoaded, and platformDxeExports, are not exported)
|
||||
// 2. C runtime (libc, libm, libgcc, DJGPP internals)
|
||||
// 3. DXE3 runtime (dlsym/dlclose/dlerror/dlregsym; not dlopen: a module
|
||||
// opens one with platformModuleOpen, which loads what it needs)
|
||||
|
|
@ -3723,6 +3736,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformKindNameOf)
|
||||
DXE_EXPORT(platformKindReadDir)
|
||||
DXE_EXPORT(platformLineEnding)
|
||||
DXE_EXPORT(platformLoadedSymbol)
|
||||
DXE_EXPORT(platformLockRegion)
|
||||
DXE_EXPORT(platformLogCrashDetail)
|
||||
DXE_EXPORT(platformLogMemory)
|
||||
|
|
@ -3756,6 +3770,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformReadFile)
|
||||
DXE_EXPORT(platformReplaceFile)
|
||||
DXE_EXPORT(platformSetAppExitHook)
|
||||
DXE_EXPORT(platformSetLibraryLoadHook)
|
||||
DXE_EXPORT(platformShortPath)
|
||||
DXE_EXPORT(platformShutdownHookAdd)
|
||||
DXE_EXPORT(platformShutdownHookRemove)
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@
|
|||
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dlfcn.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -669,6 +670,12 @@ const char *platformLineEnding(void) {
|
|||
}
|
||||
|
||||
|
||||
// The host's dynamic linker never loads a library for a lookup.
|
||||
void *platformLoadedSymbol(const char *name) {
|
||||
return dlsym(RTLD_DEFAULT, name);
|
||||
}
|
||||
|
||||
|
||||
// No paging concerns in a host process: nothing to pin.
|
||||
int32_t platformLockRegion(const void *addr, uint32_t size, bool isCode) {
|
||||
(void)addr;
|
||||
|
|
|
|||
|
|
@ -250,6 +250,7 @@ bool platformKindFind(char *out, int32_t outSize, DvxKindE ki
|
|||
const char *platformKindName(DvxKindE kind);
|
||||
bool platformKindNameOf(DvxKindE kind, const char *path, char *out, int32_t outSize);
|
||||
char **platformKindReadDir(DvxKindE kind);
|
||||
void platformLibraryLoaded(void *handle);
|
||||
const char *platformLogMemory(void);
|
||||
bool platformLongNameChar(char c);
|
||||
int32_t platformMkdirRecursive(const char *path);
|
||||
|
|
@ -274,6 +275,7 @@ char *platformPathTidy(char *path);
|
|||
char *platformReadFile(const char *path, int32_t *outLen);
|
||||
bool platformReplaceFile(const char *tmpPath, const char *path, const char *bakPath);
|
||||
void platformSetAppExitHook(PlatformAppExitFnT fn);
|
||||
void platformSetLibraryLoadHook(PlatformLibraryLoadFnT fn);
|
||||
void platformSetLogMemory(void);
|
||||
void platformSetLogPath(const char *path);
|
||||
void platformSetModuleOpen(PlatformModuleOpenFnT fn);
|
||||
|
|
@ -341,6 +343,9 @@ static PlatformAppExitFnT sAppExitHook = NULL;
|
|||
// The loader's module opening (platformModuleOpen), set at its start
|
||||
static PlatformModuleOpenFnT sModuleOpen = NULL;
|
||||
|
||||
// What hears of each library loaded after the start (platformLibraryLoaded)
|
||||
static PlatformLibraryLoadFnT sLibraryLoadHook = NULL;
|
||||
|
||||
// Every modifier-specific scancode run the keyboard path folds.
|
||||
static const ScanFoldT sScanFolds[] = {
|
||||
{ FKEY_SCAN_SHIFT_F1, FKEY_LOW_COUNT, KEY_SCAN_F1, KEY_MOD_SHIFT, 0, KEY_MOD_CTRL | KEY_MOD_ALT },
|
||||
|
|
@ -1430,6 +1435,13 @@ char **platformKindReadDir(DvxKindE kind) {
|
|||
}
|
||||
|
||||
|
||||
void platformLibraryLoaded(void *handle) {
|
||||
if (sLibraryLoadHook) {
|
||||
sLibraryLoadHook(handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char *platformLogMemory(void) {
|
||||
return sLogMem ? sLogMem : "";
|
||||
}
|
||||
|
|
@ -1853,6 +1865,11 @@ void platformSetAppExitHook(PlatformAppExitFnT fn) {
|
|||
}
|
||||
|
||||
|
||||
void platformSetLibraryLoadHook(PlatformLibraryLoadFnT fn) {
|
||||
sLibraryLoadHook = fn;
|
||||
}
|
||||
|
||||
|
||||
void platformSetLogMemory(void) {
|
||||
if (!sLogMem) {
|
||||
sLogMem = (char *)calloc(1, PLATFORM_LOG_MEM_MAX);
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ The build makes mathx.lib and mathx.hlp (a preview of the help). The project hol
|
|||
|
||||
.h2 How the Library Works
|
||||
|
||||
DVX loads a library when something first needs it: a program, widget, or library that calls one of its functions (DVX loads the library just before that module), or a lookup by name (dlsym(NULL), a BASIC DECLARE LIBRARY call, dvxLibraryLoaded). Libraries with code that runs as they load (constructors) are loaded when DVX starts, so they can hook into the shell. Every function that is not static is exported: a program, widget, or library calls it directly, and DVX connects the call when it loads that module. A library that calls another library's functions lists it in its .dep file, which DVX loads first. A module that opens another module calls platformModuleOpen (dvxPlat.h), never dlopen, which dvx.exe does not export.
|
||||
DVX loads a library when something first needs it: a program, widget, or library that calls one of its functions (DVX loads the library just before that module), or a lookup by name (dlsym(NULL), a BASIC DECLARE LIBRARY call, dvxLibraryLoaded). Code that uses a library only once something else has loaded it looks its names up with platformLoadedSymbol (dvxPlat.h), which never loads one. Libraries with code that runs as they load (constructors) are loaded when DVX starts, so they can hook into the shell. Every function that is not static is exported: a program, widget, or library calls it directly, and DVX connects the call when it loads that module. A library that calls another library's functions lists it in its .dep file, which DVX loads first. A module that opens another module calls platformModuleOpen (dvxPlat.h), never dlopen, which dvx.exe does not export.
|
||||
|
||||
BASIC programs add mathx.bas to their project. DECLARE LIBRARY looks up each function by exactly the name it declares, without a trailing $ (a $ marks a function returning a string), so mathxGreet$ calls the C function mathxGreet. Keep the declarations and the C functions in step; strings are passed as const char * and returned as const char *.
|
||||
|
||||
|
|
|
|||
|
|
@ -888,8 +888,10 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
|
|||
// steal global widget focus from the active window. The widget that
|
||||
// held focus when the window last blurred gets it back (so GotFocus
|
||||
// pairs with the LostFocus the blur fired); otherwise the first
|
||||
// focusable widget does.
|
||||
if (!sFocusedWidget && win->focused) {
|
||||
// focusable widget does. A hidden window (a BASIC form during its
|
||||
// Load, focused but not shown yet) waits until it is shown, so the
|
||||
// choice is made on what its program set up, as in VB.
|
||||
if (!sFocusedWidget && win->focused && win->visible) {
|
||||
WidgetT *target = win->lastFocusWidget;
|
||||
|
||||
win->lastFocusWidget = NULL;
|
||||
|
|
|
|||
|
|
@ -885,6 +885,12 @@ static bool libLoad(ModuleT *lib) {
|
|||
loadBasicControls((CtlLoadFnT)dlsym(lib->handle, DVX_SYM(CTL_LOAD_SYMBOL)));
|
||||
}
|
||||
|
||||
// What binds to a library once it is loaded (the shell's hook), with
|
||||
// its memory DVX's too
|
||||
if (sStarted) {
|
||||
platformLibraryLoaded(lib->handle);
|
||||
}
|
||||
|
||||
dvxMemSetApp(prevApp);
|
||||
|
||||
if (!sStarted) {
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ Begin Form ParDemo
|
|||
|
||||
Begin Timer tmrCheck
|
||||
Interval = 200
|
||||
Enabled = True
|
||||
Enabled = False
|
||||
End
|
||||
End
|
||||
|
||||
|
|
@ -577,6 +577,9 @@ SUB ParDemo_Load
|
|||
ParCopy.Show
|
||||
SET copyB = NEW ParCopy
|
||||
copyB.Show
|
||||
|
||||
' The checks start once every window is up
|
||||
tmrCheck.Enabled = True
|
||||
END SUB
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ End
|
|||
' and a Data control whose record set arrives a part at a time (the first
|
||||
' DATA_FIRST rows at Refresh, the rest while the program waits), walked
|
||||
' with a Do Until EOF loop. The window title reports the outcome for the
|
||||
' script once every step has run: "SQL PASS 3 rows 3.25 long data 64/1000
|
||||
' script once every step has run: "SQL PASS 3 rows 3.25 long data 1000
|
||||
' loop 1000" or "SQL FAIL <what>".
|
||||
|
||||
OPTION EXPLICIT
|
||||
|
|
@ -216,8 +216,16 @@ SUB tmrStart_Timer
|
|||
datBig.MoveLast
|
||||
last = datBig.GetField("x")
|
||||
|
||||
ok = (first = DATA_FIRST AND datBig.RecordCount = BIG_ROWS AND loops = BIG_ROWS AND last = LTRIM$(STR$(BIG_ROWS)))
|
||||
' Refresh gave at least the first DATA_FIRST rows at once (any number
|
||||
' up to all of them by the next line: the rest come between the
|
||||
' program's turns, and one may end right after Refresh)
|
||||
ok = (first >= DATA_FIRST AND datBig.RecordCount = BIG_ROWS AND loops = BIG_ROWS AND last = LTRIM$(STR$(BIG_ROWS)))
|
||||
|
||||
IF ok THEN
|
||||
Report ok, sums + " long data " + LTRIM$(STR$(datBig.RecordCount)) + " loop " + LTRIM$(STR$(loops))
|
||||
ELSE
|
||||
Report ok, sums + " long data " + LTRIM$(STR$(first)) + "/" + LTRIM$(STR$(datBig.RecordCount)) + " loop " + LTRIM$(STR$(loops))
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@
|
|||
// GAMETEST: raw down|up <scan> ext <0|1> rep <0|1> mods <hex>
|
||||
// GAMETEST: held LShift=<0|1> RCtrl=.. Up=.. A=.. when that set changes
|
||||
// GAMETEST: key <hex> mods <hex> onKey (F1, Alt+X, ... reach it)
|
||||
// F9 starts the timing over (the script's own steps, which run in the idle
|
||||
// time a game shares, are done by then):
|
||||
// GAMETEST: timing starts over
|
||||
// F10 (which a game window receives, instead of opening a menu) ends the
|
||||
// run: the frame, timer and BIOS tick statistics are logged and checked
|
||||
// ("GAMETEST: timing PASS" or "... FAIL <what>"), and the program ends
|
||||
|
|
@ -84,6 +87,7 @@ SHELL_APP_DESCRIPTOR(
|
|||
static DxeAppContextT *sCtx = NULL;
|
||||
static volatile uint32_t sTicks = 0; // timerTick calls (interrupt time)
|
||||
static volatile bool sStop = false; // F10
|
||||
static volatile bool sRestart = false; // F9
|
||||
static volatile bool sClosed = false; // the window was closed
|
||||
|
||||
|
||||
|
|
@ -139,7 +143,9 @@ static void onKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
(void)win;
|
||||
dvxLog("GAMETEST: key %lX mods %lX", (unsigned long)key, (unsigned long)mod);
|
||||
|
||||
if (key == KEY_F10) {
|
||||
if (key == KEY_F9) {
|
||||
sRestart = true;
|
||||
} else if (key == KEY_F10) {
|
||||
sStop = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -228,6 +234,20 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
while (!sStop && !sClosed) {
|
||||
gameFrameWait(&pacer);
|
||||
|
||||
if (sRestart) {
|
||||
sRestart = false;
|
||||
dvxLog("GAMETEST: timing starts over");
|
||||
platformTimerStats(&st);
|
||||
bios0 = st.biosTicks;
|
||||
minUs = UINT64_MAX;
|
||||
maxUs = 0;
|
||||
startUs = platformMicros();
|
||||
lastUs = startUs;
|
||||
sTicks = 0;
|
||||
gameFrameInit(&pacer, GAME_FRAME_HZ);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t now = platformMicros();
|
||||
uint64_t dt = now - lastUs;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@
|
|||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame and every shot has them
|
||||
# all; the first program started (Notepad, a BASIC program) loads the BASIC
|
||||
# run time first, which DVX leaves on disk until it is needed, and the
|
||||
# icons wait while it loads
|
||||
WAIT 60
|
||||
|
||||
LAUNCH apps/kpunch/notepad/notepad.app
|
||||
WAITWIN "Untitled - Notepad"
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@
|
|||
# through the keyboard hook, not the BIOS buffer). F1 and Alt+X must
|
||||
# reach the game (no help window, no menu); F10 ends its run, when it
|
||||
# checks the timer calls and the BIOS ticks against the clock and then
|
||||
# calls exit(), which must end only that program. The DOS time must
|
||||
# calls exit(), which must end only that program. F9 first starts the
|
||||
# timing over, so the ten seconds it checks are the game's alone and not
|
||||
# the steps above (this script runs in the idle time the game shares). The DOS time must
|
||||
# keep pace with the host's while the fast timer runs (# CLOCK below).
|
||||
# Last, TEST\GAMEKEY.APP (fixtures/gamekey) shows GameKeyDown from BASIC
|
||||
# in its caption. The game loop and the event dispatch run in either
|
||||
|
|
@ -65,6 +67,8 @@ WAITLOG "GAMETEST: key 12D mods"
|
|||
WAITMS 1000
|
||||
ASSERTNOWIN "*Help*"
|
||||
ASSERTWIN "Game Test"
|
||||
KEY F9
|
||||
WAITLOG "GAMETEST: timing starts over"
|
||||
WAITMS 10000
|
||||
CLOCKMARK
|
||||
KEY F10
|
||||
|
|
|
|||
|
|
@ -43,6 +43,13 @@
|
|||
# NOFILE: SYSTEM/DVXHLPC.EXE
|
||||
# NOFILE: APPS/KPUNCH/HELPEDIT
|
||||
# FILE: APPS/KPUNCH/DVXHELP/DVXHELP.APP
|
||||
# Libraries load when first used: a BASIC program starting loads none it
|
||||
# does not call (printing, games, and serial ports), and the cryptography
|
||||
# library comes in with Software's package library, not at the start.
|
||||
# NOLOG: Loading: LIBS\kpunch\print\print.lib
|
||||
# NOLOG: Loading: LIBS\kpunch\game\game.lib
|
||||
# NOLOG: Loading: LIBS\kpunch\serial\serial.lib
|
||||
# LOG: Shell: random numbers seeded as the cryptography library loaded
|
||||
# FILE: SYSTEM/DVXPKG.EXE
|
||||
# LOG: DVX Shell ready.
|
||||
TIMEOUT 300
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@
|
|||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH TEST/LFNSMK.APP
|
||||
WAITWIN "LFN *"
|
||||
WAIT 10
|
||||
|
|
|
|||
|
|
@ -33,6 +33,10 @@
|
|||
TIMEOUT 90
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH apps/kpunch/recorder/recorder.app
|
||||
WAITWIN "New recording - Sound Recorder"
|
||||
WAIT 10
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@
|
|||
TIMEOUT 180
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH TEST/NETSMK.APP
|
||||
WAITLOG "dvxnet: packet driver"
|
||||
WAITLOG "dvxnet: address"
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
TIMEOUT 60
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH TEST/PATHSMK.APP
|
||||
WAITWIN "PATHS *"
|
||||
WAIT 10
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@
|
|||
TIMEOUT 90
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH apps/kpunch/recorder/recorder.app
|
||||
WAITWIN "New recording - Sound Recorder"
|
||||
WAIT 10
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
|
||||
LAUNCH apps/sdktest/sdkApp/sdkApp.app
|
||||
WAITWIN "SdkApp"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@
|
|||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH apps/sdktest/sdkGame/sdkGame.app
|
||||
WAITLOG "sdkGame: started"
|
||||
WAITWIN "SdkGame - Score 0 - Time *"
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@
|
|||
TIMEOUT 60
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH TEST/SHELLRUN.APP
|
||||
WAITLOG "ShellRun: ShellRunDosWait returned 0"
|
||||
WAITLOG "ShellRun: SHELL returned"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,10 @@
|
|||
|
||||
# software.scr -- Software installs a package opened with it, with the
|
||||
# package it needs from the same folder (a theme that needs another), then
|
||||
# removes the needed one, which takes the other with it.
|
||||
# removes the needed one, which takes the other with it. The shots wait
|
||||
# for Software's own lines: the install, and the installed list's count
|
||||
# (made before its window is shown, which is waited for too: the install
|
||||
# message, also "Software", has closed by then).
|
||||
# PACKAGE: swspace DOWNLOAD/SPACE.DPK
|
||||
# PACKAGE: swbase DOWNLOAD/BASE.DPK
|
||||
# LOG: Software: installed from
|
||||
|
|
@ -30,16 +33,22 @@
|
|||
TIMEOUT 180
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH apps/kpunch/software/software.app DOWNLOAD\SPACE.DPK
|
||||
WAITWIN "Install Software"
|
||||
WAIT 10
|
||||
SHOT sw-plan
|
||||
KEY Enter
|
||||
WAIT 60
|
||||
SHOT sw-done
|
||||
KEY Enter
|
||||
WAITLOG "Software: installed from"
|
||||
WAIT 20
|
||||
SHOT sw-done
|
||||
KEY Enter
|
||||
WAITLOG "Software: 2 packages installed"
|
||||
WAITWIN "Software"
|
||||
WAIT 10
|
||||
SHOT sw-list
|
||||
# Remove... with Base Theme selected (the first row)
|
||||
KEY Alt+R
|
||||
|
|
@ -47,6 +56,7 @@ WAIT 30
|
|||
SHOT sw-needs
|
||||
KEY Enter
|
||||
WAITLOG "Software: removed acme.base"
|
||||
WAIT 20
|
||||
WAITLOG "Software: 0 packages installed"
|
||||
WAIT 10
|
||||
SHOT sw-empty
|
||||
QUIT
|
||||
|
|
|
|||
|
|
@ -27,17 +27,22 @@
|
|||
# SQLFieldDbl (a DECLARE LIBRARY function returning DOUBLE). Then a long
|
||||
# statement, during which the program's Timer events run (and are refused
|
||||
# the busy database), and a Data control that reads 1000 records a part
|
||||
# at a time (64 at Refresh) and walks them with Do Until EOF. Its window
|
||||
# title reports "SQL PASS 3 rows 3.25 long data 64/1000 loop 1000" only
|
||||
# when every step worked.
|
||||
# at a time (at least 64 at Refresh, any number more by the next line when
|
||||
# the program's turn ended between them) and walks them with Do Until EOF.
|
||||
# Its window title reports "SQL PASS 3 rows 3.25 long data 1000 loop 1000" only
|
||||
# when every step worked; the shot waits for it (the steps run from a
|
||||
# Timer, so the title passes through earlier stages first).
|
||||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH TEST/SQLSMK.APP
|
||||
WAITWIN "SQL *"
|
||||
WAITWIN "SQL PASS 3 rows 3.25 long data 1000 loop 1000"
|
||||
WAIT 10
|
||||
SHOT sqlsmk
|
||||
ASSERTWIN "SQL PASS 3 rows 3.25 long data 64/1000 loop 1000"
|
||||
KEY Alt+F4
|
||||
WAITNOWIN "SQL *"
|
||||
QUIT
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@
|
|||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
# Program Manager's icons come in a few a frame (the first start has no
|
||||
# icon cache yet, and they come slower once a program runs); the shots
|
||||
# have them all, so they are in before the program starts
|
||||
WAIT 60
|
||||
LAUNCH TEST/UCDEMO.APP
|
||||
WAITWIN "UCDEMO *"
|
||||
WAITNOWIN "UCDEMO RUN"
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@
|
|||
# DVX.INI). Take the tour shows the tour's pages in place of the tiles
|
||||
# (Next, End the tour), Read the User Guide shows the DVX help, F1 the
|
||||
# Welcome help, Close (with "Don't show this again" ticked, the default)
|
||||
# records that it is not shown again.
|
||||
# records that it is not shown again. A tour page logs "Welcome: tour page
|
||||
# N" once it is drawn, which the shots wait for.
|
||||
TIMEOUT 120
|
||||
WAITWIN "Program Manager"
|
||||
MOVE 639 479
|
||||
|
|
@ -35,10 +36,12 @@ WAITWIN "Welcome to DVX"
|
|||
WAIT 20
|
||||
SHOT wl-open
|
||||
KEY Alt+T
|
||||
WAIT 20
|
||||
WAITLOG "Welcome: tour page 1"
|
||||
WAIT 10
|
||||
SHOT wl-tour
|
||||
KEY Alt+N
|
||||
WAIT 20
|
||||
WAITLOG "Welcome: tour page 2"
|
||||
WAIT 10
|
||||
SHOT wl-tour2
|
||||
KEY Alt+E
|
||||
WAIT 20
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
#define FORM_W 400
|
||||
#define FORM_H 300
|
||||
#define TIMER_INTERVAL_MS 100
|
||||
#define QUEUE_DRAG_DX 3 // caseQueuedOrder: the drag's move, in pixels
|
||||
#define TIMER_TICKS 3
|
||||
#define RESET_CYCLES 50
|
||||
#define FRM_TEXT_MAX 16384
|
||||
|
|
@ -392,6 +393,7 @@ static void caseDynamicControl(void);
|
|||
static void caseEditorMembers(void);
|
||||
static void caseEventLoopExits(void);
|
||||
static void caseEvents(void);
|
||||
static void caseEventsWaitForCode(void);
|
||||
static void caseExternCalls(void);
|
||||
static void caseExternLive(void);
|
||||
static void caseExternMarshal(void);
|
||||
|
|
@ -436,6 +438,7 @@ static void caseProgramScreenModes(void);
|
|||
static void casePropEdges(void);
|
||||
static void casePropTables(void);
|
||||
static void caseQueryUnloadCancel(void);
|
||||
static void caseQueuedOrder(void);
|
||||
static void caseRefValidation(void);
|
||||
static void caseRemoveContainerFromHandler(void);
|
||||
static void caseResetCycles(void);
|
||||
|
|
@ -457,6 +460,7 @@ static void caseTimers(void);
|
|||
static void caseToolbarClearFromHandler(void);
|
||||
static void caseTwoRuntimes(void);
|
||||
static void caseUnloadDeferred(void);
|
||||
static void caseUnloadThenLoad(void);
|
||||
static void caseValidateCancel(void);
|
||||
static void caseVisibleKeepsModal(void);
|
||||
static void caseWidgetParity(void);
|
||||
|
|
@ -489,6 +493,7 @@ static void parseOnFormProp(void *userData, const char *key, const char *
|
|||
static void parseOnMenuBegin(void *userData, const char *name, int32_t level);
|
||||
static void parseOnMenuEnd(void *userData);
|
||||
static void parseOnMenuProp(void *userData, const char *key, const char *value);
|
||||
static bool pumpRtEvents(void *ctx);
|
||||
static const char *roundTripValue(const char *basName, const WgtPropDescT *p, char *buf, int32_t bufSize);
|
||||
static int32_t screenPixel(const BasScreenT *scr, int32_t x, int32_t y);
|
||||
static const char *serReadText(int32_t com);
|
||||
|
|
@ -3964,6 +3969,95 @@ static void caseEvents(void) {
|
|||
}
|
||||
|
||||
|
||||
// A program's own events wait while its code runs, as in VB: a Timer that
|
||||
// ticked during a long handler runs after it, not at one of its slice
|
||||
// ends; DoEvents in the handler runs it there.
|
||||
static void caseEventsWaitForCode(void) {
|
||||
tCase("a program's events wait for DoEvents or the handler's end");
|
||||
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
||||
|
||||
const char *frm =
|
||||
"Begin Form Form1\n"
|
||||
" Caption = \"W\"\n"
|
||||
" Width = 400\n"
|
||||
" Height = 300\n"
|
||||
" Begin Timer Tmr1\n"
|
||||
" Interval = 100\n"
|
||||
" Enabled = False\n"
|
||||
" End\n"
|
||||
"End\n"
|
||||
"\n"
|
||||
"Sub Tmr1_Timer\n"
|
||||
" Print \"Timer\"\n"
|
||||
"End Sub\n"
|
||||
"Sub Form1_Busy\n"
|
||||
" Dim i As Long\n"
|
||||
" Dim x As Long\n"
|
||||
" Print \"Start\"\n"
|
||||
" For i = 1 To 100000\n"
|
||||
" x = x + 1\n"
|
||||
" Next\n"
|
||||
" Print \"End\"\n"
|
||||
"End Sub\n"
|
||||
"Sub Form1_BusyDo\n"
|
||||
" Dim i As Long\n"
|
||||
" Dim x As Long\n"
|
||||
" Print \"Start\"\n"
|
||||
" For i = 1 To 100000\n"
|
||||
" x = x + 1\n"
|
||||
" If i = 50000 Then DoEvents\n"
|
||||
" Next\n"
|
||||
" Print \"End\"\n"
|
||||
"End Sub\n";
|
||||
FrtAppT app;
|
||||
|
||||
if (!expectBuild(&app, "", &frm, 1)) {
|
||||
tShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
frtStart(&app, NULL);
|
||||
|
||||
BasFormT *form = frtForm(&app, "Form1");
|
||||
BasControlT *tmr = form ? frtCtrl(form, "Tmr1") : NULL;
|
||||
uint32_t own = app.rt->ownTask;
|
||||
|
||||
TEST_TRUE(tmr != NULL);
|
||||
|
||||
if (!tmr) {
|
||||
frtDestroy(&app);
|
||||
tShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
basVmSetDoEventsCallback(app.vm, pumpRtEvents, &app);
|
||||
wgtTimerStart(tmr->widget);
|
||||
|
||||
// A tick from another task waits; the long handler's slice ends do
|
||||
// not run it, its end does
|
||||
app.rt->ownTask = own + 1;
|
||||
tAdvanceClock(TIMER_INTERVAL_MS);
|
||||
app.rt->ownTask = own;
|
||||
frtOutClear(&app);
|
||||
TEST_TRUE(frtFire(&app, "Form1", "Busy"));
|
||||
TEST_STR_EQ(frtOut(&app), "Start\nEnd\n");
|
||||
TEST_TRUE(basFormRtUpdate(app.rt));
|
||||
TEST_STR_EQ(frtOut(&app), "Start\nEnd\nTimer\n");
|
||||
|
||||
// DoEvents runs it where it is
|
||||
app.rt->ownTask = own + 1;
|
||||
tAdvanceClock(TIMER_INTERVAL_MS);
|
||||
app.rt->ownTask = own;
|
||||
frtOutClear(&app);
|
||||
TEST_TRUE(frtFire(&app, "Form1", "BusyDo"));
|
||||
TEST_STR_EQ(frtOut(&app), "Start\nTimer\nEnd\n");
|
||||
|
||||
wgtTimerStop(tmr->widget);
|
||||
frtDestroy(&app);
|
||||
tShutdown();
|
||||
}
|
||||
|
||||
|
||||
static void caseExternCalls(void) {
|
||||
tCase("DECLARE LIBRARY through basExternResolve/basExternCall");
|
||||
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
||||
|
|
@ -7482,17 +7576,18 @@ static void casePendingUnloadDrain(void) {
|
|||
int32_t sx = 0;
|
||||
int32_t sy = 0;
|
||||
|
||||
// The handler unloads Form2 (deferred), re-Loads it (must get the
|
||||
// pending form back, not a resurrected copy), then unloads its own
|
||||
// form; the tail print proves the handler finished on live memory
|
||||
// and the drain then frees both forms.
|
||||
// The handler unloads Form2 (deferred), Loads it again (a new Form2,
|
||||
// as in VB; the one going away stays out of reach), then unloads its
|
||||
// own form; the tail print proves the handler finished on live memory,
|
||||
// and the drain then frees both unloaded forms, leaving the new Form2.
|
||||
tWidgetCenter(go->widget, &sx, &sy);
|
||||
tClick(sx, sy);
|
||||
tPump(1);
|
||||
outContains(&app, "tail");
|
||||
TEST_PTR_EQ(frtForm(&app, "Form1"), NULL);
|
||||
TEST_PTR_EQ(frtForm(&app, "Form2"), NULL);
|
||||
TEST_EQ((int32_t)arrlen(app.rt->forms), 0);
|
||||
TEST_TRUE(frtForm(&app, "Form2") != NULL);
|
||||
TEST_EQ((int32_t)arrlen(app.rt->forms), 1);
|
||||
TEST_EQ((int32_t)arrlen(app.rt->pendingUnload), 0);
|
||||
TEST_TRUE(!app.rt->terminated);
|
||||
|
||||
frtDestroy(&app);
|
||||
|
|
@ -8457,6 +8552,71 @@ static void caseQueryUnloadCancel(void) {
|
|||
}
|
||||
|
||||
|
||||
// Events another task dispatched wait for the program's turn in the order
|
||||
// they came: a newer MouseMove replaces only the control's newest waiting
|
||||
// event, so a drag's move runs after its MouseDown, not before it.
|
||||
static void caseQueuedOrder(void) {
|
||||
tCase("waiting events keep their order: a drag's move after its MouseDown");
|
||||
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
||||
|
||||
const char *frm =
|
||||
"Begin Form Form1\n"
|
||||
" Caption = \"Q\"\n"
|
||||
" Width = 400\n"
|
||||
" Height = 300\n"
|
||||
" Begin Label Lbl1\n"
|
||||
" Caption = \"Label\"\n"
|
||||
" End\n"
|
||||
"End\n"
|
||||
"\n"
|
||||
"Sub Lbl1_MouseDown(Button As Integer, X As Integer, Y As Integer)\n"
|
||||
" Print \"Down=\"; Button\n"
|
||||
"End Sub\n"
|
||||
"Sub Lbl1_MouseMove(Button As Integer, X As Integer, Y As Integer)\n"
|
||||
" Print \"Move=\"; Button\n"
|
||||
"End Sub\n";
|
||||
FrtAppT app;
|
||||
|
||||
if (!expectBuild(&app, "", &frm, 1)) {
|
||||
tShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
frtStart(&app, NULL);
|
||||
|
||||
BasFormT *form = frtForm(&app, "Form1");
|
||||
BasControlT *lbl = form ? frtCtrl(form, "Lbl1") : NULL;
|
||||
uint32_t own = app.rt->ownTask;
|
||||
int32_t sx = 0;
|
||||
int32_t sy = 0;
|
||||
|
||||
TEST_TRUE(lbl != NULL);
|
||||
|
||||
if (!lbl) {
|
||||
frtDestroy(&app);
|
||||
tShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hover, press, and drag while the program's task is another: all wait
|
||||
tWidgetCenter(lbl->widget, &sx, &sy);
|
||||
app.rt->ownTask = own + 1;
|
||||
tMouseMove(sx, sy);
|
||||
tMouseDown(sx, sy, MOUSE_LEFT);
|
||||
tMouseMove(sx + QUEUE_DRAG_DX, sy);
|
||||
tPump(1);
|
||||
app.rt->ownTask = own;
|
||||
frtOutClear(&app);
|
||||
TEST_TRUE(basFormRtUpdate(app.rt));
|
||||
TEST_STR_EQ(frtOut(&app), "Move=0 \nDown=1 \nMove=1 \n");
|
||||
|
||||
tMouseUp(sx + QUEUE_DRAG_DX, sy, MOUSE_LEFT);
|
||||
tPump(1);
|
||||
frtDestroy(&app);
|
||||
tShutdown();
|
||||
}
|
||||
|
||||
|
||||
static void caseRefValidation(void) {
|
||||
tCase("stale refs, CreateControl parents, menu/form overrides, size props");
|
||||
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
||||
|
|
@ -10513,6 +10673,60 @@ static void caseUnloadDeferred(void) {
|
|||
}
|
||||
|
||||
|
||||
// A form unloaded inside a handler goes when the handler is done; until
|
||||
// then another Unload of it does nothing, but Load or a reference makes a
|
||||
// new one, as in VB (it used to give the form going away, which Show
|
||||
// refused: Terminal's New Session did not open when its Session Settings
|
||||
// had just been closed).
|
||||
static void caseUnloadThenLoad(void) {
|
||||
tCase("a form unloaded in a handler: Load or Show there gets a new one");
|
||||
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
||||
|
||||
const char *frm1 =
|
||||
"Begin Form Form1\n"
|
||||
" Caption = \"One\"\n"
|
||||
"End\n"
|
||||
"\n"
|
||||
"Sub Form1_Again\n"
|
||||
" Load Form2\n"
|
||||
" Unload Form2\n"
|
||||
" Unload Form2\n"
|
||||
" Form2.Show\n"
|
||||
" Print \"shown=\"; Form2.Visible\n"
|
||||
"End Sub\n";
|
||||
const char *frm2 =
|
||||
"Begin Form Form2\n"
|
||||
" Caption = \"Two\"\n"
|
||||
"End\n"
|
||||
"\n"
|
||||
"Sub Form2_Load\n"
|
||||
" Print \"load\"\n"
|
||||
"End Sub\n"
|
||||
"Sub Form2_Unload\n"
|
||||
" Print \"unload\"\n"
|
||||
"End Sub\n";
|
||||
const char *frms[2] = { frm1, frm2 };
|
||||
FrtAppT app;
|
||||
|
||||
if (!expectBuild(&app, "", frms, 2)) {
|
||||
tShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
frtStart(&app, "Form1");
|
||||
frtOutClear(&app);
|
||||
TEST_TRUE(frtFire(&app, "Form1", "Again"));
|
||||
TEST_STR_EQ(frtOut(&app), "unload\nload\nshown=True \n");
|
||||
tPump(2);
|
||||
TEST_TRUE(frtForm(&app, "Form2") != NULL);
|
||||
TEST_TRUE(tFindWindow("Two") != NULL);
|
||||
TEST_EQ((int32_t)arrlen(app.rt->pendingUnload), 0);
|
||||
|
||||
frtDestroy(&app);
|
||||
tShutdown();
|
||||
}
|
||||
|
||||
|
||||
static void caseValidateCancel(void) {
|
||||
tCase("Validate cancel reaches the widget as a refused write");
|
||||
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
||||
|
|
@ -11331,6 +11545,13 @@ static void parseOnMenuProp(void *userData, const char *key, const char *value)
|
|||
}
|
||||
|
||||
|
||||
// The hosts' DoEvents (the stub's, the IDE's): the program's waiting
|
||||
// events, then a frame (basFormRtUpdate).
|
||||
static bool pumpRtEvents(void *ctx) {
|
||||
return basFormRtUpdate(((FrtAppT *)ctx)->rt);
|
||||
}
|
||||
|
||||
|
||||
static const char *roundTripValue(const char *basName, const WgtPropDescT *p, char *buf, int32_t bufSize) {
|
||||
// A RichEdit's selection is run-time state: an empty one at the start
|
||||
// reads the style and paragraph of plain text; its RTF is the empty
|
||||
|
|
@ -11729,6 +11950,7 @@ int main(void) {
|
|||
caseAppCommandLine();
|
||||
caseMenus();
|
||||
caseEvents();
|
||||
caseEventsWaitForCode();
|
||||
caseToolbarClearFromHandler();
|
||||
caseRemoveContainerFromHandler();
|
||||
caseVisibleKeepsModal();
|
||||
|
|
@ -11736,12 +11958,14 @@ int main(void) {
|
|||
caseLoadFormsYield();
|
||||
caseSetEvent();
|
||||
caseQueryUnloadCancel();
|
||||
caseQueuedOrder();
|
||||
caseValidateCancel();
|
||||
casePopupMenu();
|
||||
casePropTables();
|
||||
caseCommonMethods();
|
||||
caseFormShowHide();
|
||||
caseUnloadDeferred();
|
||||
caseUnloadThenLoad();
|
||||
caseFormVars();
|
||||
caseFormInstances();
|
||||
caseWithEvents();
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -156,6 +156,7 @@
|
|||
#define FIXTURE(name) DVXTEST_FIXTURES_DIR "/" name
|
||||
#define HELP_QUOTE_STR "\"" // DVX_ARG_QUOTE as a string literal
|
||||
#define FIX_SAVE FIXTURE("fixSave.so")
|
||||
#define FIX_SAVE_AS FIXTURE("fixSaveAs.so")
|
||||
#define FIX_STUCK FIXTURE("fixStuck.so")
|
||||
#define APPS_LOG "appsTest.log" // the DVX log (fixProcessInit)
|
||||
#define EXIT_NOTES_MAX 256
|
||||
|
|
@ -424,6 +425,7 @@ static void caseExitCleanApps(void);
|
|||
static void caseExitDemoPrompt(void);
|
||||
static void caseExitDirtyPrompts(void);
|
||||
static void caseExitStuckApp(void);
|
||||
static void caseExitTwoDialogs(void);
|
||||
static void caseHelpBadFiles(void);
|
||||
static void caseHelpF1Reuses(void);
|
||||
static void caseHelpKeyboard(void);
|
||||
|
|
@ -2314,6 +2316,34 @@ static void caseExitStuckApp(void) {
|
|||
}
|
||||
|
||||
|
||||
// An app that answers the close request on its own task with two dialogs
|
||||
// in a row (a BASIC program's save prompt, then its Save As) has a turn
|
||||
// with neither open between them: that gap is not a refusal, and the exit
|
||||
// completes once the second is answered.
|
||||
static void caseExitTwoDialogs(void) {
|
||||
tCase("Exit DVX waits through the gap between an app's two dialogs");
|
||||
fixShellUp(SCREEN_BPP);
|
||||
fixDialogsOn("Yes");
|
||||
|
||||
int32_t pm = loadApp(APP_PROGMAN);
|
||||
int32_t doc = loadApp(FIX_SAVE_AS);
|
||||
|
||||
fixPumpShell(TASK_FRAMES);
|
||||
TEST_TRUE(pm > 0 && doc > 0);
|
||||
TEST_EQ(fixAppWindowCount(doc), 1);
|
||||
|
||||
sExitNotes[0] = '\0';
|
||||
fixDialogs = 0;
|
||||
TEST_TRUE(shellCloseAllApps(tCtx(), pm));
|
||||
TEST_EQ(fixDialogs, 2);
|
||||
TEST_STR_EQ(sExitNotes, "saved ");
|
||||
TEST_PTR_EQ(shellGetApp(doc), NULL);
|
||||
|
||||
fixDialogsOff();
|
||||
fixShellDown();
|
||||
}
|
||||
|
||||
|
||||
static void caseHelpBadFiles(void) {
|
||||
tCase("dvxhelp rejects corrupt help files");
|
||||
fixShellUp(SCREEN_BPP);
|
||||
|
|
@ -5016,6 +5046,7 @@ int main(void) {
|
|||
RUN_CASE(caseExitDirtyPrompts);
|
||||
RUN_CASE(caseExitDemoPrompt);
|
||||
RUN_CASE(caseExitStuckApp);
|
||||
RUN_CASE(caseExitTwoDialogs);
|
||||
|
||||
return tSuiteEnd();
|
||||
}
|
||||
|
|
|
|||
109
src/test/shell/fixtures/fixSaveAs.c
Normal file
109
src/test/shell/fixtures/fixSaveAs.c
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (C) 2026 Scott Duensing
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
|
||||
// fixSaveAs.c -- shell suite fixture: a document app whose close request
|
||||
// is answered on its own task (a main loop), as a BASIC program answers
|
||||
// one: "Save changes?" (Yes/No/Cancel); on Yes a turn with no dialog open
|
||||
// (where a BASIC program's slice ends), then a second question standing in
|
||||
// for Save As, then the window closes. Each outcome is reported through
|
||||
// fixExitNote when the host defines it.
|
||||
|
||||
#include "shellApp.h"
|
||||
#include "dvxDlg.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define FIX_WIN_X 80
|
||||
#define FIX_WIN_Y 80
|
||||
#define FIX_WIN_W 200
|
||||
#define FIX_WIN_H 100
|
||||
|
||||
extern void fixExitNote(const char *event) __attribute__((weak));
|
||||
|
||||
int32_t appMain(DxeAppContextT *ctx);
|
||||
static void note(const char *event);
|
||||
static void onClose(WindowT *win);
|
||||
|
||||
static bool sCloseAsked = false;
|
||||
|
||||
SHELL_APP_DESCRIPTOR(
|
||||
.name = "Fix Save As",
|
||||
.hasMainLoop = true,
|
||||
.multiInstance = false,
|
||||
.stackSize = SHELL_STACK_DEFAULT,
|
||||
.priority = TS_PRIORITY_NORMAL
|
||||
);
|
||||
|
||||
|
||||
static void note(const char *event) {
|
||||
if (fixExitNote) {
|
||||
fixExitNote(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The request is answered on this program's task (appMain's loop).
|
||||
static void onClose(WindowT *win) {
|
||||
(void)win;
|
||||
sCloseAsked = true;
|
||||
}
|
||||
|
||||
|
||||
int32_t appMain(DxeAppContextT *ctx) {
|
||||
AppContextT *ac = ctx->shellCtx;
|
||||
WindowT *win = dvxCreateWindow(ac, "Fix Save As", FIX_WIN_X, FIX_WIN_Y, FIX_WIN_W, FIX_WIN_H, false);
|
||||
|
||||
if (!win) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
win->onClose = onClose;
|
||||
|
||||
while (true) {
|
||||
int32_t answer;
|
||||
|
||||
tsYield();
|
||||
|
||||
if (!sCloseAsked) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sCloseAsked = false;
|
||||
answer = dvxMessageBox(ac, "Fix Save As", "Save changes?", MB_YESNOCANCEL | MB_ICONQUESTION);
|
||||
|
||||
if (answer == ID_CANCEL) {
|
||||
note("cancelled");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (answer == ID_YES) {
|
||||
tsYield();
|
||||
dvxMessageBox(ac, "Fix Save As", "Save as FIX.TXT?", MB_YESNO | MB_ICONQUESTION);
|
||||
note("saved");
|
||||
} else {
|
||||
note("discarded");
|
||||
}
|
||||
|
||||
dvxDestroyWindow(ac, win);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +126,8 @@ static void testDxeWrite(const char *path, uint32_t init, int32_t cut);
|
|||
static void testFolders(void);
|
||||
static void testInfo(void);
|
||||
static void testInstalledCount(void);
|
||||
static void testLibraryHook(void *handle);
|
||||
static void testLibraryLoad(void);
|
||||
static void testMouseClamp(void);
|
||||
static void testOutOfMemory(void);
|
||||
static void testPalette(void);
|
||||
|
|
@ -142,6 +144,10 @@ static void testVideoInitFailure(void);
|
|||
static char sLastLog[PLAT_LOG_MAX];
|
||||
static int32_t sLogCalls = 0;
|
||||
|
||||
// What testLibraryHook was told (platformLibraryLoaded)
|
||||
static void *sHookHandle = NULL;
|
||||
static int32_t sHookCalls = 0;
|
||||
|
||||
|
||||
static void bankRun(uint8_t *vram, const uint8_t *src, uint32_t offset, uint32_t bytes, uint32_t gran, uint32_t *winStart, int32_t *moves);
|
||||
static void checkOneSignal(int32_t sig);
|
||||
|
|
@ -735,6 +741,35 @@ static void testInstalledCount(void) {
|
|||
}
|
||||
|
||||
|
||||
static void testLibraryHook(void *handle) {
|
||||
sHookHandle = handle;
|
||||
sHookCalls++;
|
||||
}
|
||||
|
||||
|
||||
// The loader's news of a library loaded after the start reaches the hook
|
||||
// with the library's handle (nothing when no hook is set), and a name
|
||||
// already in the process is found without loading anything.
|
||||
static void testLibraryLoad(void) {
|
||||
int32_t marker = 0;
|
||||
|
||||
sHookHandle = NULL;
|
||||
sHookCalls = 0;
|
||||
platformLibraryLoaded(&marker);
|
||||
TEST_EQ(sHookCalls, 0);
|
||||
platformSetLibraryLoadHook(testLibraryHook);
|
||||
platformLibraryLoaded(&marker);
|
||||
TEST_EQ(sHookCalls, 1);
|
||||
TEST_TRUE(sHookHandle == &marker);
|
||||
platformSetLibraryLoadHook(NULL);
|
||||
platformLibraryLoaded(&marker);
|
||||
TEST_EQ(sHookCalls, 1);
|
||||
|
||||
TEST_TRUE(platformLoadedSymbol(DVX_SYM("testPlatform")) == (void *)testPlatform);
|
||||
TEST_PTR_NULL(platformLoadedSymbol(DVX_SYM("noSuchNameAnywhere")));
|
||||
}
|
||||
|
||||
|
||||
static void testMouseClamp(void) {
|
||||
int32_t x = 0;
|
||||
int32_t y = 0;
|
||||
|
|
@ -869,6 +904,7 @@ void testPlatform(void) {
|
|||
testFolders();
|
||||
testDirEntries();
|
||||
testInstalledCount();
|
||||
testLibraryLoad();
|
||||
|
||||
for (int32_t i = 0; i < DVX_ARRAY_LEN(sBpps); i++) {
|
||||
testPng(sBpps[i]);
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ typedef struct {
|
|||
static FocusFixtureT build(const char *title);
|
||||
static void caseDisableDropsFocus(void);
|
||||
static void caseHiddenFocusedNoKeys(void);
|
||||
static void caseHiddenWindowWaits(void);
|
||||
static void caseRadioArrowFiresFocus(void);
|
||||
static void caseRestoreOnWindowFocus(void);
|
||||
static void caseSetVisibleDropsFocus(void);
|
||||
|
|
@ -154,6 +155,40 @@ static void caseHiddenFocusedNoKeys(void) {
|
|||
}
|
||||
|
||||
|
||||
// A window that holds the window manager's focus while it is hidden (a
|
||||
// BASIC form during its Load) gets no widget focus from a paint until it
|
||||
// is shown, and then the first widget that can take it then, as in VB
|
||||
// (Media Player's slider, enabled at the end of its Load).
|
||||
static void caseHiddenWindowWaits(void) {
|
||||
tCase("focus: a hidden window takes widget focus only once shown");
|
||||
|
||||
// The section's window, focused first; this one takes the focus from it
|
||||
build("Waits");
|
||||
|
||||
WindowT *win = dvxCreateWindow(tCtx(), "Hidden", OTHER_X, OTHER_Y, OTHER_W, OTHER_H, false);
|
||||
WidgetT *root = wgtInitWindow(tCtx(), win);
|
||||
WidgetT *first = wgtButton(root, "First");
|
||||
WidgetT *second = wgtButton(root, "Second");
|
||||
|
||||
TEST_TRUE(second != NULL);
|
||||
win->visible = false;
|
||||
wgtSetEnabled(first, false);
|
||||
dvxInvalidateWindow(tCtx(), win);
|
||||
tPump(WT_SETTLE);
|
||||
TEST_TRUE(win->focused);
|
||||
TEST_PTR_EQ(wgtGetFocused(), NULL);
|
||||
|
||||
wgtSetEnabled(first, true);
|
||||
dvxShowWindow(tCtx(), win);
|
||||
tPump(WT_SETTLE);
|
||||
TEST_PTR_EQ(wgtGetFocused(), first);
|
||||
|
||||
dvxDestroyWindow(tCtx(), win);
|
||||
tPump(WT_SETTLE);
|
||||
teardown();
|
||||
}
|
||||
|
||||
|
||||
// Window blur remembers the focused widget; the next paint of the
|
||||
// re-focused window hands focus back and fires onFocus.
|
||||
static void caseRadioArrowFiresFocus(void) {
|
||||
|
|
@ -321,4 +356,5 @@ void wtSectionFocus(void) {
|
|||
caseSetVisibleDropsFocus();
|
||||
caseDisableDropsFocus();
|
||||
caseRestoreOnWindowFocus();
|
||||
caseHiddenWindowWaits();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue