195 lines
8.2 KiB
C
195 lines
8.2 KiB
C
// shellApp.h -- DVX Shell application lifecycle types and API
|
|
//
|
|
// The shell supports two kinds of DXE apps:
|
|
//
|
|
// 1. Callback-only apps (hasMainLoop = false):
|
|
// appMain() is called directly in the shell's task 0. It creates
|
|
// windows, registers event callbacks, and returns immediately. The
|
|
// app then lives entirely through GUI callbacks driven by the shell's
|
|
// main loop. This is simpler and cheaper (no extra stack/task) and
|
|
// is the right choice for apps that are purely event-driven (dialogs,
|
|
// simple tools). Lifecycle ends when the last window is closed.
|
|
//
|
|
// 2. Main-loop apps (hasMainLoop = true):
|
|
// A dedicated cooperative task is created for the app. appMain() runs
|
|
// in that task and can do its own polling/processing loop, calling
|
|
// tsYield() to share CPU. This is needed for apps that have continuous
|
|
// work (terminal emulators, games, long computations). Lifecycle ends
|
|
// when appMain() returns or the task is killed.
|
|
//
|
|
// Both types use the same DXE interface (appDescriptor + appMain), so
|
|
// app authors choose by setting hasMainLoop in their descriptor.
|
|
#ifndef SHELL_APP_H
|
|
#define SHELL_APP_H
|
|
|
|
#include "dvxApp.h"
|
|
#include "dvxWidget.h"
|
|
#include "taskswitch.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
// ============================================================
|
|
// App descriptor (exported by each DXE app)
|
|
// ============================================================
|
|
|
|
#define SHELL_APP_NAME_MAX 64
|
|
// Fixed array size for app slots. A small fixed limit avoids dynamic
|
|
// allocation complexity and matches the practical limit of a DOS desktop.
|
|
// Slot 0 is reserved for the shell itself, so 31 app slots are usable.
|
|
#define SHELL_MAX_APPS 32
|
|
|
|
// Every DXE app exports a global AppDescriptorT named "appDescriptor".
|
|
// The shell reads it at load time to determine how to launch the app.
|
|
|
|
// Use in AppDescriptorT.stackSize to get the default task stack size
|
|
#define SHELL_STACK_DEFAULT 0
|
|
|
|
typedef struct {
|
|
char name[SHELL_APP_NAME_MAX];
|
|
bool hasMainLoop;
|
|
bool multiInstance; // true = allow multiple instances via temp copy
|
|
int32_t stackSize; // SHELL_STACK_DEFAULT or explicit byte count
|
|
int32_t priority; // TS_PRIORITY_* or custom
|
|
} AppDescriptorT;
|
|
|
|
// ============================================================
|
|
// App context (passed to appMain)
|
|
// ============================================================
|
|
|
|
// Passed as the sole argument to appMain(). Gives the app access to
|
|
// the shell's GUI context (for creating windows, drawing, etc.) and
|
|
// its own identity. appDir is derived from the .app file path at load
|
|
// time so the app can find its own resources (icons, data files) via
|
|
// relative paths -- important because the working directory is shared
|
|
// by all apps and can't be changed per-app in DOS.
|
|
typedef struct {
|
|
AppContextT *shellCtx; // the shell's GUI context
|
|
int32_t appId; // this app's ID
|
|
char appDir[260]; // directory containing the .app file
|
|
char configDir[260]; // writable config directory (CONFIG/<apppath>/)
|
|
} DxeAppContextT;
|
|
|
|
// ============================================================
|
|
// Per-app state
|
|
// ============================================================
|
|
|
|
// State machine: Free -> Loaded -> Running -> Terminating -> Free
|
|
// LoadedE is transient (only during shellLoadApp before entry is called).
|
|
// TerminatingE means the app's task has exited but cleanup (window
|
|
// destruction, DXE unload) hasn't happened yet -- the shell's main loop
|
|
// reaps these each frame via shellReapApps().
|
|
typedef enum {
|
|
AppStateFreeE, // slot available
|
|
AppStateLoadedE, // DXE loaded, not yet started
|
|
AppStateRunningE, // entry point called, active
|
|
AppStateTerminatingE // shutdown in progress
|
|
} AppStateE;
|
|
|
|
typedef struct {
|
|
int32_t appId; // unique ID = slot index (1-based; 0 = shell)
|
|
char name[SHELL_APP_NAME_MAX];
|
|
char path[260];
|
|
char tempPath[260]; // temp copy path for multi-instance (empty = not a copy)
|
|
void *dxeHandle; // dlopen() handle
|
|
AppStateE state;
|
|
bool hasMainLoop;
|
|
uint32_t mainTaskId; // task ID if hasMainLoop, else 0
|
|
int32_t (*entryFn)(DxeAppContextT *);
|
|
void (*shutdownFn)(void); // may be NULL
|
|
DxeAppContextT dxeCtx; // context passed to appMain
|
|
} ShellAppT;
|
|
|
|
// ============================================================
|
|
// Shell global state
|
|
// ============================================================
|
|
|
|
// Current app ID for resource tracking (0 = shell).
|
|
// Set before calling any app code (entry, shutdown, callbacks) so that
|
|
// wrapper functions (shellWrapCreateWindow, etc.) can stamp newly created
|
|
// resources with the correct owner. This is the mechanism that lets the
|
|
// shell know which windows belong to which app, enabling per-app cleanup
|
|
// on crash or termination. It's a global rather than a thread-local
|
|
// because cooperative multitasking means only one task runs at a time.
|
|
extern int32_t sCurrentAppId;
|
|
|
|
// ============================================================
|
|
// App lifecycle API
|
|
// ============================================================
|
|
|
|
// Initialize the app slot table
|
|
void shellAppInit(void);
|
|
|
|
// Load and start an app from a DXE file. Returns app ID (>= 1) or -1 on error.
|
|
// For callback-only apps, appMain runs synchronously and returns before
|
|
// shellLoadApp returns. For main-loop apps, a task is created and the
|
|
// app starts running on the next tsYield.
|
|
int32_t shellLoadApp(AppContextT *ctx, const char *path);
|
|
|
|
// Reap finished apps (call each frame from main loop). Returns true if
|
|
// any apps were reaped, so the caller can notify the desktop to refresh.
|
|
// For callback-only apps, termination is triggered by shellWrapDestroyWindow
|
|
// when the last window closes. For main-loop apps, termination happens
|
|
// when appMain returns (via appTaskWrapper marking AppStateTerminatingE).
|
|
bool shellReapApps(AppContextT *ctx);
|
|
|
|
// Gracefully shut down a single app -- calls shutdownFn if present,
|
|
// destroys windows, kills task, closes DXE handle.
|
|
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
|
|
|
// Forcibly kill an app (Task Manager "End Task"). Skips shutdownFn --
|
|
// used when the app is hung or has crashed and cannot be trusted to
|
|
// run its own cleanup code.
|
|
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
|
|
|
// Terminate all running apps (shell shutdown)
|
|
void shellTerminateAllApps(AppContextT *ctx);
|
|
|
|
// Get app slot by ID (returns NULL if invalid/free)
|
|
ShellAppT *shellGetApp(int32_t appId);
|
|
|
|
// Count running apps (not counting the shell itself)
|
|
int32_t shellRunningAppCount(void);
|
|
|
|
// ============================================================
|
|
// Logging
|
|
// ============================================================
|
|
|
|
// Write a printf-style message to DVX.LOG
|
|
void shellLog(const char *fmt, ...);
|
|
|
|
// Ensure an app's config directory exists (creates all parent dirs).
|
|
// Returns 0 on success, -1 on failure.
|
|
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx);
|
|
|
|
// Build a full path to a file in the app's config directory.
|
|
// e.g. shellConfigPath(ctx, "settings.ini", buf, sizeof(buf))
|
|
// -> "CONFIG/PROGMAN/settings.ini"
|
|
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);
|
|
|
|
// ============================================================
|
|
// DXE export table
|
|
// ============================================================
|
|
|
|
// Register the DXE symbol export table (call before any dlopen)
|
|
void shellExportInit(void);
|
|
|
|
// ============================================================
|
|
// Desktop callback
|
|
// ============================================================
|
|
|
|
// Default desktop app path
|
|
#define SHELL_DESKTOP_APP "apps/progman/progman.app"
|
|
|
|
// Register a callback for app state changes (load, reap, crash).
|
|
// Apps call this during appMain to receive notifications when app state
|
|
// changes (load, reap, crash). Multiple callbacks are supported.
|
|
void shellRegisterDesktopUpdate(void (*updateFn)(void));
|
|
|
|
// Remove a previously registered callback (call before app shutdown).
|
|
void shellUnregisterDesktopUpdate(void (*updateFn)(void));
|
|
|
|
// Notify the desktop app that app state has changed (load, reap, crash).
|
|
void shellDesktopUpdate(void);
|
|
|
|
#endif // SHELL_APP_H
|