DVX_GUI/src/libs/kpunch/dvxshell/dvxshell.dhs
2026-09-29 17:03:51 -05:00

768 lines
40 KiB
Text

# 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.
.section Technical Reference (for Developers)|Libraries
.topic shell.overview
.title DVX Shell Library
.toc 0 DVX Shell Library
.default
.index DVX Shell
.index shellApp.h
.index DXE Application Loading
.h1 DVX Shell Library
The DVX shell library manages the lifecycle of DXE applications: loading, launching, tracking, and reaping. It provides the bridge between the DVX GUI compositor and dynamically loaded DXE3 application modules.
Header: dvxshell/shellApp.h
Loaded as: bin/libs/dvxshell.lib
.h2 App Model
The shell supports two kinds of DXE apps:
.list
.item Callback-only (hasMainLoop = false) -- appMain() runs in the shell's task 0, creates windows, registers event callbacks, and returns immediately. The app lives through GUI callbacks. Lifecycle ends when the last window is closed. Simpler and cheaper (no extra stack/task).
.item Main-loop (hasMainLoop = true) -- A dedicated cooperative task is created. appMain() runs in that task and can do its own polling loop, calling tsYield() to share CPU. Lifecycle ends when appMain() returns or the task is killed. Needed for terminal emulators, games, or long computations.
.endlist
Both types use the same DXE interface: an exported appDescriptor and appMain function.
.h2 DXE Interface
Every .app DXE module must export these symbols (COFF convention uses leading underscore):
.table
Symbol Type Required
------ ---- --------
_appDescriptor AppDescriptorT Yes
_appMain int32_t (*)(DxeAppContextT *) Yes
_appShutdown void (*)(void) No
.endtable
The shell resolves appDescriptor at dlopen time; if missing, the load fails with an error dialog. If its abiVersion is not the shell's DVX_ABI_VERSION the app was built against other headers: the load fails with "This program was built for a different version of DVX and cannot run." and both numbers go to DVX.LOG. appMain is called immediately for callback-only apps or from the new task for main-loop apps. appShutdown, if present, is called during graceful reap (but not during force kill from crashed apps).
.h2 Callback-Only vs Main-Loop Apps
The app descriptor's hasMainLoop flag selects between two very different lifecycles:
.list
.item hasMainLoop = false (callback-only). The shell calls appMain directly on task 0 at dlopen time. The function creates windows, registers event callbacks, and returns. After that the app has no executing thread of its own -- it exists purely through GUI callbacks dispatched by dvxUpdate. The shell reaps the app automatically when its last window closes. Best for modal tools, dialogs, and event-driven utilities.
.item hasMainLoop = true (main-loop). The shell creates a dedicated cooperative task (via tsCreate) with the descriptor's stackSize (or TS_DEFAULT_STACK_SIZE) and priority. appMain runs in that task and can do its own work loop, calling tsYield or any GUI function that yields. The app terminates when appMain returns (the wrapper sets AppStateTerminatingE) or when forced via shellForceKillApp. Best for terminal emulators, games, and any app with continuous background work.
.endlist
Both app types use the same export interface; only the descriptor's flags differ. Apps cannot switch modes at runtime.
.h2 Icon Conventions
Shell-level UI (Program Manager, Task Manager) displays app icons at 16x16 and 32x32. Icons are not handled by the shell itself; each app embeds its own icons via the DVX resource system (DVX_RES_ICON):
.list
.item 16x16 BMP for toolbar entries and list rows
.item 32x32 BMP for desktop shortcuts and Program Manager tiles
.endlist
The Program Manager reads the app's 32x32 icon resource when building shortcut tiles. Apps without an icon resource fall back to a default shell-provided bitmap.
.h2 State Machine
App slots progress through four states:
.code
Free -> Loaded -> Running -> Terminating -> Free
.endcode
LoadedE is transient (only during shellLoadApp before the entry point is called). TerminatingE means the app's task has exited, or a force kill was deferred (killPending) because the app's code was still running, but cleanup has not yet occurred. The shell's main loop reaps terminating apps each frame via shellReapApps().
.h2 Contents
.link shell.types Types and Constants
.link shell.lifecycle Lifecycle API
.link shell.query Query API
.link shell.startup Starting Up
.link shell.config Configuration API
.link shell.desktop Desktop Callbacks
.link shell.info System Information
.link shell.programs Installed Programs
.link shell.run Running Programs and Documents
.link shell.assoc File Associations
.topic shell.types
.title Shell Types and Constants
.toc 1 Types and Constants
.index AppDescriptorT
.index DxeAppContextT
.index ShellAppT
.index AppStateE
.index SHELL_APP_NAME_MAX
.index SHELL_STACK_DEFAULT
.index SHELL_DESKTOP_APP
.index SHELL_APP_DESCRIPTOR
.h1 Types and Constants
.h2 Constants
.table
Constant Value Description
-------- ----- -----------
SHELL_APP_NAME_MAX 64 Maximum length of an app name string.
SHELL_STACK_DEFAULT 0 Use in AppDescriptorT.stackSize to get the default task stack size.
SHELL_DESKTOP_APP "apps/kpunch/progman/progman.app" Default desktop application path.
SHELL_INI_SECTION "shell" DVX.INI section of the shell's own keys.
SHELL_INI_DESKTOP "desktop" [shell] key: the desktop app (SHELL_DESKTOP_APP when missing).
SHELL_INI_WELCOME "welcome" [shell] key: a program started after the desktop while the key exists (see Starting Up).
SHELL_HELP_TOPIC_MAX 128 Capacity of DxeAppContextT.helpTopic.
DVX_HELP_VIEWER_APP_LITERAL "APPS\KPUNCH\DVXHELP\DVXHELP.APP" Path of the help viewer app.
DVX_SYSTEM_HELP_FILE_LITERAL "APPS\KPUNCH\PROGMAN\DVXHELP.HLP" Path of the system help file (F1 fallback, Program Manager's Help menu).
.endtable
.h3 SHELL_APP_CALL
.code
SHELL_APP_CALL(app, call);
.endcode
Brackets a direct shell call into an app's code (appMain of a callback-only app, appShutdown, onHelpQuery, onNewArgs). It counts the call on the calling task (shellAppCallEnter / shellAppCallLeave), so a force kill issued from inside that call is deferred until the call has returned.
.h2 AppDescriptorT
Exported by every DXE app as a global named appDescriptor. The shell reads it at load time to determine how to launch the app. Declare it with SHELL_APP_DESCRIPTOR, which names the global and fills abiVersion:
.code
SHELL_APP_DESCRIPTOR(
.name = "Clock",
.hasMainLoop = true,
.multiInstance = true,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_LOW
);
.endcode
.table
Field Type Description
----- ---- -----------
abiVersion uint32_t DVX_ABI_VERSION the app was built with (always the first field). The shell refuses the app when it differs from its own.
name[SHELL_APP_NAME_MAX] char[] Display name of the application.
hasMainLoop bool false = callback-only, true = dedicated task.
multiInstance bool true = allow multiple simultaneous instances via temp copy.
stackSize int32_t SHELL_STACK_DEFAULT or explicit byte count for the task stack.
priority int32_t Task priority, TS_PRIORITY_LOW..TS_PRIORITY_HIGH (0..10). Any other value makes the launch fail.
.endtable
.h2 DxeAppContextT
Passed as the sole argument to appMain(). Gives the app access to the shell's GUI context and its own identity. Heap-allocated by shellLoadApp so its address is stable across reallocations of the internal app slot table (apps may safely cache the pointer in their static globals).
.table
Field Type Description
----- ---- -----------
shellCtx AppContextT * The shell's GUI context (for creating windows, drawing, etc.).
appId int32_t This app's unique ID (slot index, 1-based).
appPath[DVX_MAX_PATH] char[] Full path to the .app DXE file.
appDir[DVX_MAX_PATH] char[] Directory containing the .app file (for loading resources).
configDir[DVX_MAX_PATH] char[] Writable config directory (e.g. CONFIG/APPS/KPUNCH/DVXBASIC/).
args[1024] char[] Launch arguments (empty string if none): blank-separated, one with blanks in double quotes. Walk them with dvxNextArg. A document opened through its file type arrives here (see File Associations); BASIC programs read these arguments with COMMAND$.
helpFile[DVX_MAX_PATH] char[] Help file path (for F1 context help). Before appMain the shell sets it to <app>.hlp in the .app's directory when that file exists (else empty); appMain may change it.
helpTopic[128] char[] Current help topic ID (updated by the app at runtime). Size is SHELL_HELP_TOPIC_MAX.
onHelpQuery void (*)(void *ctx) Optional callback the shell fires on F1 so the app can refresh helpTopic from context.
helpQueryCtx void * Opaque context pointer passed to onHelpQuery.
onNewArgs void (*)(void *ctx, const char *args) Optional. Set by an app that can take new launch arguments while running. F1 hands its "helpfile [topic]" arguments to a running help viewer that sets this, and raises it, instead of loading another copy.
newArgsCtx void * Opaque context pointer passed to onNewArgs.
.endtable
The appDir field is derived from the .app file path at load time so apps can find their own resources via relative paths. This is necessary because the working directory is shared by all apps in DOS. The config directory is mirrored from the app path: an app at APPS/KPUNCH/DVXBASIC/dvxbasic.app gets CONFIG/APPS/KPUNCH/DVXBASIC/. A full path inside the DVX directory (for example one picked with the Task Manager's Run... dialog) is mirrored the same way. An app outside the DVX directory, or loaded by a bare file name, gets CONFIG/APPS/<file name without extension>/.
.h2 AppStateE
.table
Value Description
----- -----------
AppStateFreeE Slot is available for reuse.
AppStateLoadedE DXE loaded, entry point not yet called (transient).
AppStateRunningE Entry point called, app is active.
AppStateTerminatingE Shutdown in progress, awaiting reap.
.endtable
.h2 ShellAppT
Per-app slot in the shell's app table. Slot 0 is reserved for the shell itself; apps use slots 1 and above.
.table
Field Type Description
----- ---- -----------
appId int32_t Unique ID (slot index, 1-based; 0 = shell).
name[SHELL_APP_NAME_MAX] char[] Display name from AppDescriptorT.
path[DVX_MAX_PATH] char[] Original DXE file path.
tempPath[DVX_MAX_PATH] char[] Temp copy path for multi-instance (empty if not a copy).
dxeHandle void * dlopen() handle for the DXE module.
state AppStateE Current lifecycle state.
hasMainLoop bool Whether this app has a dedicated task.
mainTaskId uint32_t Task ID if hasMainLoop, else 0.
entryFn int32_t (*)(DxeAppContextT *) Pointer to appMain.
shutdownFn void (*)(void) Pointer to appShutdown (may be NULL).
dxeCtx DxeAppContextT * Heap-allocated context (address stable across realloc).
taskCodeDepth int32_t * Per task id: SHELL_APP_CALL calls into this app still open on that task's stack.
killPending bool A force kill was deferred; shellReapApps finishes it.
.endtable
.topic shell.lifecycle
.title App Lifecycle API
.toc 1 Lifecycle API
.index shellAppInit
.index shellLoadApp
.index shellLoadAppWithArgs
.index shellReapApps
.index shellReapApp
.index shellForceKillApp
.index shellTerminateAllApps
.index shellCloseAllApps
.index shellRequestExit
.index shellRunExitRequest
.index Exit DVX
.index shellOpenHelp
.index shellAppCodeForgetTask
.index shellTaskDepthForget
.index shellTaskDepthShare
.h1 App Lifecycle API
.h2 shellAppInit
.code
void shellAppInit(void);
.endcode
Initialize the app slot table. Seeds slot 0 (reserved for the shell). Must be called once at startup before any other shell API function.
.h2 shellLoadApp
.code
int32_t shellLoadApp(AppContextT *ctx, const char *path);
.endcode
Load and start an app from a DXE file. Returns the app ID (>= 1) on success, or -1 on error.
For callback-only apps, appMain() runs synchronously and returns before shellLoadApp returns. For main-loop apps, a cooperative task is created and the app begins running on the next tsYield().
If multiInstance is false in the app's descriptor and the same DXE is already loaded, the call fails with an error dialog. If multiInstance is true, the DXE is copied to a temp file so dlopen gets an independent code and data image.
.h2 shellLoadAppWithArgs
.code
int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, const char *args);
.endcode
Load and run an app with arguments. The args string is copied into DxeAppContextT.args before appMain() is called. Otherwise identical to shellLoadApp().
.h2 shellReapApps
.code
bool shellReapApps(AppContextT *ctx);
.endcode
Scan for and reap finished apps. Call once per frame from the main loop.
Returns true if any apps were reaped, so the caller can trigger a desktop refresh. For main-loop apps, termination is detected by the AppStateTerminatingE state (set when appMain returns). For callback-only apps, termination is detected when no windows remain for that app.
.h2 shellReapApp
.code
void shellReapApp(AppContextT *ctx, ShellAppT *app);
.endcode
Gracefully shut down a single app. Calls the app's shutdownFn (if present), destroys all windows belonging to the app, kills its task (if any), releases any widget classes and system-wide hotkeys the app registered and did not unregister (wgtUnregisterAppClasses, dvxUnregisterAppHotkeys), closes the DXE handle, and frees the context. The slot returns to AppStateFreeE.
.h2 shellForceKillApp
.code
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
.endcode
Forcibly kill an app. Used by the Task Manager "End Task" or as part of crash recovery. The app's shutdownFn is NOT called: a force kill targets a hung or faulting app whose own code cannot be trusted to run. Instead the shell drops every idle and desktop-update callback the app registered, destroys all of the app's windows, kills its task (for main-loop apps), and finally closes the DXE handle.
Cleanup order: callbacks and windows first (removes them from the compositor), then the task (frees its stack; any dispatch frames still open on it are dropped from the context's dispatch depth), then the widget classes and system-wide hotkeys the app registered (its shutdownFn never got the chance to unregister them), then the DXE handle (unmaps the code). Closing the DXE before destroying windows would cause callbacks into unmapped code.
If the app's code may still be running underneath the call -- End Task clicked in a dialog the app has open, a kill issued from the app's own task, or from inside a shell call into the app -- the unload is deferred: the app's windows are hidden and queued for destruction, the slot is marked Terminating, and the shell finishes the kill (still without shutdownFn) at the next safe point of its main loop, once that code has returned. Event-handler frames on the app's own task do not hold the kill back, since they end with the task: an app stuck in an endless event handler is killed at once (or at the next main-loop pass).
.h2 shellOpenHelp
.code
void shellOpenHelp(AppContextT *ctx, const char *args);
.endcode
Show help. args is "helpfile [topicId]"; empty or NULL args open the system help. A running help viewer that accepts new arguments (onNewArgs) is given them and raised; otherwise a new viewer is loaded. F1 and application Help menus both use it, so a Help > Contents command can pass ctx->helpFile as is.
.h2 shellTaskDepthForget / shellTaskDepthShare / shellAppCodeForgetTask
.code
void shellTaskDepthForget(uint32_t taskId);
int32_t shellTaskDepthShare(uint32_t taskId);
void shellAppCodeForgetTask(uint32_t taskId);
.endcode
The context's dispatch depth counts event-dispatch frames on every task's stack. shellTaskDepthShare returns one task's share. When a task is killed its frames can never unwind: shellTaskDepthForget removes its share, and (through shellAppCodeForgetTask) the SHELL_APP_CALL calls it held. Crash recovery does the same for the crashed task and the shell's own task.
.h2 shellTerminateAllApps
.code
void shellTerminateAllApps(AppContextT *ctx);
.endcode
Force-kill all running apps. Called during shell shutdown, after the main loop has exited, so every active app is released immediately (never deferred) and no shutdownFn runs. By then Exit DVX (below) has already given every app except the desktop the chance to close itself; what is left is the desktop app and apps that had no visible window to close.
.h2 Exit DVX: shellRequestExit / shellRunExitRequest / shellCloseAllApps
.code
void shellRequestExit(void);
void shellRunExitRequest(AppContextT *ctx, int32_t keepAppId);
bool shellCloseAllApps(AppContextT *ctx, int32_t keepAppId);
.endcode
Exiting DVX never closes an app without first asking it to close, the way the user would. The desktop app's Exit command (Program Manager's File > Exit DVX and its window's close box, after its own "Are you sure?" confirmation) calls shellRequestExit, which only records the request. The shell's main loop calls shellRunExitRequest after every frame; when a request is pending it clears it, runs shellCloseAllApps for every app except the desktop, and calls dvxQuit only if that returns true. Otherwise DVX keeps running.
shellCloseAllApps asks the running apps to close one at a time, in app-slot order, exactly as if the user had closed their windows: for each app, its oldest visible window is raised and dvxRequestWindowClose is called on it inside a dispatch frame, so the window's onClose runs just as it does for the close box or Alt+F4. A C app's onClose shows its own save prompt; a BASIC app's form runs QueryUnload and Unload. This repeats with the app's next visible window until it has none left or the app has ended. Windows that hide on close (tool palettes) count as closed, and hidden windows are left alone. Apps that close are reaped normally (their shutdownFn runs). Between requests the shell keeps pumping frames (dvxUpdate, a task yield, shellReapApps), so main-loop apps can react.
A window that is still open after a request is judged as follows:
.list
.item If the app created a window while handling the request (ctx->windowsCreated changed -- its save prompt) and no dialog box is still open, the user chose to keep it (Cancel). shellCloseAllApps returns false at once: that app and every app not yet asked keep running untouched. Apps already closed stay closed.
.item If the app showed nothing and still has the window after 5 seconds, it is not responding. The shell asks "'name' is not responding to the request to close. End it and continue exiting DVX?". Yes ends it with shellForceKillApp (no shutdownFn) and the exit continues; No returns false.
.endlist
shellCloseAllApps also returns false, without asking any app, when a dialog box is already open (ctx->modalWindow): the dialog is raised so it can be answered first. It pumps the event loop, so call it only from the shell's top level, never from inside a callback. An app ended from the Task Manager while the sequence runs simply counts as closed.
.topic shell.query
.title Query API
.toc 1 Query API
.index shellGetApp
.index shellAppSlotCount
.index shellRunningAppCount
.index shellAppFindByPath
.index shellPathsMatch
.h1 Query API
.h2 shellGetApp
.code
ShellAppT *shellGetApp(int32_t appId);
.endcode
Look up an app slot by ID. Returns a pointer to the ShellAppT, or NULL if the ID is out of range or the slot is free. Valid app IDs are 1 through shellAppSlotCount() - 1.
.h2 shellAppSlotCount
.code
int32_t shellAppSlotCount(void);
.endcode
Return the total number of app slots (including slot 0). Use as the iteration bound when scanning all slots. Slot 0 is the shell itself; app slots start at 1.
.h2 shellRunningAppCount
.code
int32_t shellRunningAppCount(void);
.endcode
Count running apps, not counting the shell itself. Includes apps in both AppStateLoadedE and AppStateRunningE states.
.h2 shellAppFindByPath
.code
ShellAppT *shellAppFindByPath(const char *path);
.endcode
Return an active app slot loaded from path, or NULL. Paths are compared with shellPathsMatch.
.h2 shellPathsMatch
.code
bool shellPathsMatch(const char *a, const char *b);
.endcode
True when two paths name the same file under DOS rules: case-insensitive, with / and \ the same separator.
.topic shell.startup
.title Starting Up
.toc 1 Starting Up
.index Safe Video Mode
.index /SAFE
.index /RUN=
.index SHELL_INI_WELCOME
.index Welcome Program
.h1 Starting Up
shellMain brings DVX up in this order: preferences (DVX.INI), the task system, the GUI in the saved display mode, the desktop app, then the start-up programs.
.h2 Safe Video Mode
The GUI starts in the [video] mode of DVX.INI (prefsGetVideoMode). If that mode cannot be set (dvxInit fails), or a Shift key is held down while DVX starts (read with platformKeyboardGetModifiers just before the mode is set), or DVX was started with /SAFE, the shell starts in the built-in default mode instead (DVX_DEFAULT_VIDEO_W/H/BPP: 640x480, 16 bpp or the closest mode the card has). Before the desktop loads, a message box says so and asks whether to use that mode from now on: Yes writes it to DVX.INI (prefsSetVideoMode, prefsSave); No leaves DVX.INI alone, so the saved mode is tried again next time. If the default mode fails too, DVX does not start.
.h2 Start-up Programs
After the desktop app has loaded:
.list
.item When the DVX folder cannot be written to (platformIsReadOnly: the loader found it read-only, as on a CD-ROM), a message says once that changed settings last until DVX exits and that the help is used as it is.
.item The program named by DVX.INI [shell] welcome (SHELL_INI_WELCOME), if the key is there. The shipped DVX.INI names the Welcome window, which removes the key when the user ticks "Don't show this again", so the key itself is the "first start" flag.
.item The program given with /RUN=path on the DVX command line (for example DVX /RUN=APPS\KPUNCH\NOTEPAD\NOTEPAD.APP).
.endlist
Both are started with shellRunCommand, so a missing program is reported to the user.
.topic shell.config
.title Configuration API
.toc 1 Configuration API
.index shellEnsureConfigDir
.index shellConfigPath
.h1 Configuration API
Each app has a per-app configuration directory derived from its DXE path, mirrored under CONFIG/. For example, an app at APPS/KPUNCH/DVXBASIC/dvxbasic.app gets the config directory CONFIG/APPS/KPUNCH/DVXBASIC/.
.h2 shellEnsureConfigDir
.code
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx);
.endcode
Ensure the app's config directory exists, creating all parent directories as needed. Returns 0 on success, -1 on failure.
Call this before writing any config files. The directory path comes from ctx->configDir.
.h2 shellConfigPath
.code
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);
.endcode
Build a full path to a file in the app's config directory by joining ctx->configDir with the given filename.
.code
// Example:
char path[DVX_MAX_PATH];
shellConfigPath(ctx, "settings.ini", path, sizeof(path));
// -> "CONFIG/APPS/KPUNCH/PROGMAN/settings.ini"
.endcode
.h2 Random Seed File
When the serial library is loaded, the shell feeds keyboard and mouse event timings to its random-number generator (dvxSetInputEntropyHook) and carries generator state across runs in CONFIG/SECLINK.RND: the file is read and immediately rewritten at startup, and rewritten again at shutdown. The library is found at run time; without it no seed file is written.
.topic shell.desktop
.title Desktop Callbacks
.toc 1 Desktop Callbacks
.index shellRegisterDesktopUpdate
.index shellUnregisterDesktopUpdate
.index shellRegisterIdle
.index shellUnregisterIdle
.index shellRegisterAppCleanup
.index shellUnregisterAppCleanup
.index ShellAppCleanupFnT
.index shellDesktopUpdate
.index shellCtrlEscFn
.h1 Desktop Callbacks
The shell wires several AppContextT event hooks (onCtrlEsc, onF1, onTitleChange, idleCallback) during startup and exposes them to other DXEs via function-pointer extern and registration API. Desktop managers register a callback to refresh their display when apps come and go. The Task Manager DXE registers its Ctrl+Esc handler via the shellCtrlEscFn extern pointer (see Task Manager documentation).
.h2 Shell Event Hooks
.table
AppContextT field Handler Purpose
----------------- ------- -------
idleCallback shellIdleDispatch Runs every registered idle handler (see shellRegisterIdle), then yields to cooperative tasks, when dvxUpdate has no input events and no dirty rects.
onCtrlEsc ctrlEscHandler Invokes shellCtrlEscFn if the Task Manager DXE is loaded.
onF1 f1HelpHandler Opens the focused app's helpFile/helpTopic (or the system help) in the help viewer, reusing a running viewer that sets onNewArgs.
onTitleChange titleChangeHandler Runs shellDesktopUpdate so Program Manager window lists refresh.
.endtable
.h2 shellRegisterDesktopUpdate
.code
void shellRegisterDesktopUpdate(void (*updateFn)(void));
.endcode
Register a callback for app state change notifications. Multiple callbacks are supported; registering the same callback again has no effect. Apps typically call this during appMain() to receive notifications. A callback may register or unregister callbacks (including itself) while notifications are being delivered.
.h2 shellUnregisterDesktopUpdate
.code
void shellUnregisterDesktopUpdate(void (*updateFn)(void));
.endcode
Remove a previously registered callback. Call this before app shutdown to avoid dangling function pointers.
.h2 shellRegisterIdle
.code
void shellRegisterIdle(void (*fn)(void *ctx), void *ctx);
.endcode
Register an idle handler invoked every idle frame by the shell main loop, regardless of which app is foreground. Background pollers (serial and secLink) register here so their connections keep pumping while another app is in front. Registration is idempotent on the (fn, ctx) pair. The registering module MUST unregister before it is unloaded -- app DXEs do this from their _appShutdown hook on a graceful exit. On a force kill (which does not run _appShutdown) the shell drops the app's registrations itself.
.h2 shellUnregisterIdle
.code
void shellUnregisterIdle(void (*fn)(void *ctx), void *ctx);
.endcode
Remove a previously registered idle handler. No-op if the (fn, ctx) pair is absent, so a double-unregister is harmless.
.h2 shellRegisterAppCleanup
.code
typedef void (*ShellAppCleanupFnT)(int32_t appId);
void shellRegisterAppCleanup(ShellAppCleanupFnT fn);
void shellUnregisterAppCleanup(ShellAppCleanupFnT fn);
.endcode
Register a function the shell calls with an app's id whenever that app ends -- normally, force-killed or after a crash -- once its windows are gone, next to the shell's own release of the app's widget classes and hotkeys. A library uses it to release what the app left open with it: the networking library closes the app's sockets this way. For library code that stays loaded; registering the same function again has no effect, and unregistering one that is not registered does nothing.
.h2 shellDesktopUpdate
.code
void shellDesktopUpdate(void);
.endcode
Notify all registered desktop callbacks that app state has changed. Called internally by the shell after loading or reaping an app. Can also be called by apps that need to trigger a desktop refresh.
.h2 shellCtrlEscFn
.code
extern void (*shellCtrlEscFn)(AppContextT *ctx);
.endcode
Function pointer set by the taskmgr DXE's constructor. The shell calls this when Ctrl+Esc is pressed. NULL if the task manager is not loaded.
.topic shell.info
.title System Information
.toc 1 System Information
.index shellInfoInit
.index shellGetSystemInfo
.h1 System Information
Header: dvxshell/shellInf.h
Thin wrapper around the platform layer's hardware detection. Gathers system information at startup, logs it, and caches the result for display in dialogs.
.h2 shellInfoInit
.code
void shellInfoInit(AppContextT *ctx);
.endcode
Gather all hardware information via the platform layer, log each line to DVX.LOG, and store the result for later retrieval. Call once after dvxInit().
.h2 shellGetSystemInfo
.code
const char *shellGetSystemInfo(void);
.endcode
Return the formatted system information text. The returned pointer is valid for the lifetime of the process (static buffer in the platform layer).
.topic shell.programs
.title Installed Programs
.toc 1 Installed Programs
.index shellProgramScan
.index shellProgramInfo
.index shellProgramIcon
.index shellProgramListFree
.index shellIsProgram
.index ShellProgramT
.h1 Installed Programs
Header: dvxshell/shellProg.h
Finds the installed programs (every .app file under APPS, SHELL_PROG_DIR) and reads what they are called and what they look like, for launchers, file managers and setup programs. A program's display name and description are its "name" and "description" text resources (the capitalized file name when it has no name resource); its picture is its "icon32" resource.
.table
Constant Value Meaning
-------- ----- -------
SHELL_PROG_DIR "apps" Folder, relative to the DVX root, holding every program.
SHELL_PROG_EXT ".app" A program's file extension.
SHELL_PROG_DESC_MAX 128 ShellProgramT.description capacity.
SHELL_PROG_ICON 32 Large icon edge in pixels.
SHELL_PROG_ICON_SMALL 16 Small icon edge in pixels.
.endtable
.code
typedef struct {
char path[DVX_MAX_PATH];
char name[SHELL_APP_NAME_MAX];
char description[SHELL_PROG_DESC_MAX];
} ShellProgramT;
.endcode
.h2 shellProgramScan
.code
int32_t shellProgramScan(const char *dir, const char *skipPath, bool readInfo, ShellProgressFnT progress, void *progressCtx, ShellProgramT **outList);
.endcode
Every program under dir (NULL for SHELL_PROG_DIR) and its subfolders, except skipPath (compared with shellPathsMatch; NULL for none), in the order found. With readInfo each entry is filled by shellProgramInfo; without it only path is set, which is fast (no program file is opened) and enough to notice programs being added or removed. progress, when not NULL, is called with progressCtx after each program (for example to keep the screen responsive). Returns the count; *outList is an array of that many entries, NULL when there are none. Free it with shellProgramListFree.
.h2 shellProgramInfo
.code
void shellProgramInfo(const char *path, ShellProgramT *out);
.endcode
Fill out with the program's path, display name and description. Always succeeds: without resources (or for a file that is not a program) the name is the file name without its extension, first letter capitalized, and the description is empty.
.h2 shellProgramIcon
.code
uint8_t *shellProgramIcon(AppContextT *ctx, const char *path, int32_t edge, int32_t *outPitch);
.endcode
A picture edge x edge pixels in display format, for drawing with rectCopy: a program's icon32 resource, or any image file dvxLoadImage reads (BMP, PNG, JPEG, GIF). Other sizes are scaled (nearest pixel). Returns NULL when there is no picture. Free with dvxFreeImage.
.h2 shellProgramListFree / shellIsProgram
.code
void shellProgramListFree(ShellProgramT *list);
bool shellIsProgram(const char *path);
.endcode
shellProgramListFree frees a list from shellProgramScan (NULL is fine). shellIsProgram is true when path has the .app extension (any case).
.topic shell.run
.title Running Programs and Documents
.toc 1 Running Programs and Documents
.index shellRunProgram
.index shellRunCommand
.index shellRunDialog
.index shellSplitCommand
.index shellResolveArgs
.index Run dialog
.h1 Running Programs and Documents
Header: dvxshell/shellRun.h
One way for every program to start another (Program Manager's items and Run..., the Task Manager's Run..., File Manager, setup programs). A command line is a program, in quotes if it contains blanks, followed by its arguments; it is split with dvxNextArg, the same rule a program uses to read its own arguments. A document in place of a program opens in the program associated with its file type (see File Associations).
.h2 shellRunProgram
.code
int32_t shellRunProgram(AppContextT *ctx, const char *program, const char *args, const char *folder);
.endcode
Start program with args. A .app is loaded with shellLoadAppWithArgs, its arguments first passed through shellResolveArgs(folder, ...). Anything else is a document: it is looked for in folder first (when relative), must exist, and is opened with shellAssocCommand's program and arguments. Problems are shown to the user in an error box (a missing document, a document type with no association; load errors come from shellLoadApp as usual). Returns the new application id, or -1.
.h2 shellRunCommand
.code
int32_t shellRunCommand(AppContextT *ctx, const char *command);
.endcode
shellSplitCommand, then shellRunProgram with no working folder. Returns the application id or -1 (also for an empty command).
.h2 shellRunDialog
.code
bool shellRunDialog(AppContextT *ctx, const char *const *history, int32_t historyCount, char *outCommand, int32_t outSize);
.endcode
The standard Run dialog: an Open box listing history (newest first, the first one preselected) with a Browse... button. Returns true with the trimmed, non-empty command in outCommand when the user chose OK; OK with an empty box does nothing. The caller runs the command (shellRunCommand) and keeps its own history. SHELL_RUN_CMD_MAX (512) is a suitable outCommand size.
.h2 shellSplitCommand
.code
bool shellSplitCommand(const char *command, char *program, int32_t programSize, char *args, int32_t argsSize);
.endcode
Split a command line into the program (quotes removed) and the rest, trimmed. False, with both outputs empty, when there is no program.
.h2 shellResolveArgs
.code
void shellResolveArgs(const char *folder, const char *args, char *out, int32_t outSize);
.endcode
Copy args to out with blanks normalized, replacing every relative argument that names an existing file inside folder by folder\name, so a program finds the files an item's arguments name no matter where it runs. Quoted arguments keep their quotes. With an empty or NULL folder the arguments are only normalized. DVX programs share one current folder, so this is how a program item's working folder takes effect.
.topic shell.assoc
.title File Associations
.toc 1 File Associations
.index shellAssocLookup
.index shellAssocOpenDocument
.index shellAssocCommand
.index shellAssocSet
.index shellAssocRemove
.index shellAssocCount
.index shellAssocGet
.index shellAssocNormalize
.index ASSOC.INI
.index File Associations
.h1 File Associations
Header: dvxshell/shellAssoc.h
Which program opens each kind of document. One store serves every program: CONFIG\ASSOC.INI (SHELL_ASSOC_INI_PATH), section [associations]. Each key is an extension without the dot, upper case; each value is a program path relative to the DVX root, optionally followed by ,arguments where %1 stands for the document:
.code
[associations]
TXT = apps/kpunch/notepad/notepad.app
FRM = apps/kpunch/dvxbasic/dvxbasic.app,%1
.endcode
DVX ships a default ASSOC.INI; users change it on the Control Panel's File Types page, and a setup program registers its own types with shellAssocSet. The store is read once through dvxPrefs and kept; every change is saved at once with the crash-safe rewrite, and a failed save drops the cached copy so the next call rereads the file. Extension arguments may have a leading dot and any case.
.h2 Looking Up
.code
bool shellAssocLookup(const char *extension, char *programPath, int32_t programPathSize);
int32_t shellAssocCount(void);
bool shellAssocGet(int32_t index, char *extension, int32_t extensionSize, char *programPath, int32_t programPathSize);
.endcode
shellAssocLookup gives the stored value (program plus any ,arguments) for an extension; false when there is none (or its value is empty). shellAssocCount and shellAssocGet walk every association in file order (index 0 to count-1); the extension comes back as written in the file.
.h2 Opening a Document
.code
bool shellAssocCommand(const char *documentPath, char *program, int32_t programSize, char *args, int32_t argsSize);
bool shellAssocOpenDocument(AppContextT *ctx, const char *documentPath);
.endcode
shellAssocCommand builds the program and argument string that open a document: the value is split at the first comma, every %1 in the arguments becomes documentPath, and without arguments documentPath alone is passed (in double quotes when it contains a blank, so dvxNextArg and COMMAND$(1) still return it whole). A program that opens documents takes the first argument of its DxeAppContextT.args (dvxNextArg) as the file to open when it starts; a BASIC program uses COMMAND$(1). shellAssocOpenDocument does that and starts the program; false when there is no association or the program does not start. It shows nothing to the user (shellRunProgram does, for a document it cannot open).
.h2 Changing
.code
bool shellAssocSet(const char *extension, const char *programPath);
bool shellAssocRemove(const char *extension);
bool shellAssocNormalize(const char *extension, char *out);
.endcode
shellAssocSet adds or replaces an association (programPath may carry ,arguments) and saves; false for a bad extension, an empty program, or a failed save. shellAssocRemove removes one and saves; false when it did not exist or the save failed. shellAssocNormalize writes the extension as the store keys it (SHELL_ASSOC_EXT_MAX bytes: no dot, upper case) and is false for an empty extension, one longer than eight characters, or anything but letters and digits.