DVX_GUI/dvxshell/shellApp.h

184 lines
7.7 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
} 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));
// Notify the desktop app that app state has changed (load, reap, crash).
void shellDesktopUpdate(void);
#endif // SHELL_APP_H