// 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 #include // ============================================================ // 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 } 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, ...); // ============================================================ // 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). // The desktop app (Program Manager) calls this during appMain to receive // notifications so it can refresh its task list / window list display. // Only one callback is supported — the desktop is always loaded first // and is the only consumer. void shellRegisterDesktopUpdate(void (*updateFn)(void)); #endif // SHELL_APP_H