DVX_GUI/shell/shellApp.h

191 lines
8.1 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 "dvxWgt.h"
#include "taskSwch.h"
#include <stdint.h>
#include <stdbool.h>
// ============================================================
// App descriptor (exported by each DXE app)
// ============================================================
#define SHELL_APP_NAME_MAX 64
// 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 appPath[DVX_MAX_PATH]; // full path to the .app file
char appDir[DVX_MAX_PATH]; // directory containing the .app file
char configDir[DVX_MAX_PATH]; // writable config directory (CONFIG/<apppath>/)
char args[1024]; // launch arguments (empty if none)
char helpFile[DVX_MAX_PATH]; // help file path (for F1 context help)
char helpTopic[128]; // current help topic ID (updated by app)
void (*onHelpQuery)(void *ctx); // called on F1 to refresh helpTopic
void *helpQueryCtx; // context for onHelpQuery
} 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[DVX_MAX_PATH];
char tempPath[DVX_MAX_PATH]; // 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; // heap-allocated; address stable across sApps realloc
} ShellAppT;
// ============================================================
// Shell global state
// ============================================================
// ============================================================
// 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);
// Load and run an app with arguments. The args string is copied into
// DxeAppContextT.args before appMain is called.
int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, const char *args);
// 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);
// Total number of app slots (for iteration bounds)
int32_t shellAppSlotCount(void);
// Count running apps (not counting the shell itself)
int32_t shellRunningAppCount(void);
// 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);
// ============================================================
// Desktop callback
// ============================================================
// Default desktop app path
#define SHELL_DESKTOP_APP "apps/kpunch/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);
// ============================================================
// Ctrl+Esc handler (set by taskmgr library)
// ============================================================
// Function pointer set by the taskmgr DXE's constructor. The shell
// calls this on Ctrl+Esc. NULL if taskmgr is not loaded.
extern void (*shellCtrlEscFn)(AppContextT *ctx);
#endif // SHELL_APP_H