298 lines
8.1 KiB
C
298 lines
8.1 KiB
C
// shellApp.c — DVX Shell application loading, lifecycle, and reaping
|
|
//
|
|
// Manages DXE app loading via dlopen/dlsym, resource tracking through
|
|
// sCurrentAppId, and clean teardown of both callback-only and main-loop apps.
|
|
|
|
#include "shellApp.h"
|
|
#include "dvxDialog.h"
|
|
|
|
#include <dlfcn.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
// ============================================================
|
|
// Module state
|
|
// ============================================================
|
|
|
|
static ShellAppT sApps[SHELL_MAX_APPS];
|
|
int32_t sCurrentAppId = 0;
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static int32_t allocSlot(void);
|
|
static void appTaskWrapper(void *arg);
|
|
static const char *baseName(const char *path);
|
|
void shellAppInit(void);
|
|
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
|
ShellAppT *shellGetApp(int32_t appId);
|
|
int32_t shellLoadApp(AppContextT *ctx, const char *path);
|
|
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
|
void shellReapApps(AppContextT *ctx);
|
|
int32_t shellRunningAppCount(void);
|
|
void shellTerminateAllApps(AppContextT *ctx);
|
|
|
|
// ============================================================
|
|
// Static helpers
|
|
// ============================================================
|
|
|
|
static int32_t allocSlot(void) {
|
|
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
|
if (sApps[i].state == AppStateFreeE) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static void appTaskWrapper(void *arg) {
|
|
ShellAppT *app = (ShellAppT *)arg;
|
|
|
|
sCurrentAppId = app->appId;
|
|
app->entryFn(&app->dxeCtx);
|
|
sCurrentAppId = 0;
|
|
|
|
// App returned from its main loop — mark for reaping
|
|
app->state = AppStateTerminatingE;
|
|
}
|
|
|
|
|
|
static const char *baseName(const char *path) {
|
|
const char *slash = strrchr(path, '/');
|
|
|
|
if (!slash) {
|
|
slash = strrchr(path, '\\');
|
|
}
|
|
|
|
return slash ? slash + 1 : path;
|
|
}
|
|
|
|
// ============================================================
|
|
// Public API (alphabetical)
|
|
// ============================================================
|
|
|
|
void shellAppInit(void) {
|
|
memset(sApps, 0, sizeof(sApps));
|
|
}
|
|
|
|
|
|
void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
|
|
if (!app || app->state == AppStateFreeE) {
|
|
return;
|
|
}
|
|
|
|
// Destroy all windows belonging to this app
|
|
for (int32_t i = ctx->stack.count - 1; i >= 0; i--) {
|
|
if (ctx->stack.windows[i]->appId == app->appId) {
|
|
dvxDestroyWindow(ctx, ctx->stack.windows[i]);
|
|
}
|
|
}
|
|
|
|
// Kill the task if it has one
|
|
if (app->hasMainLoop && app->mainTaskId > 0) {
|
|
if (tsGetState(app->mainTaskId) != TaskStateTerminated) {
|
|
tsKill(app->mainTaskId);
|
|
}
|
|
}
|
|
|
|
// Close the DXE
|
|
if (app->dxeHandle) {
|
|
dlclose(app->dxeHandle);
|
|
app->dxeHandle = NULL;
|
|
}
|
|
|
|
app->state = AppStateFreeE;
|
|
shellLog("Shell: force-killed app '%s'", app->name);
|
|
}
|
|
|
|
|
|
ShellAppT *shellGetApp(int32_t appId) {
|
|
if (appId < 1 || appId >= SHELL_MAX_APPS) {
|
|
return NULL;
|
|
}
|
|
|
|
if (sApps[appId].state == AppStateFreeE) {
|
|
return NULL;
|
|
}
|
|
|
|
return &sApps[appId];
|
|
}
|
|
|
|
|
|
int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|
// Allocate a slot
|
|
int32_t id = allocSlot();
|
|
|
|
if (id < 0) {
|
|
dvxMessageBox(ctx, "Error", "Maximum number of applications reached.", MB_OK | MB_ICONERROR);
|
|
return -1;
|
|
}
|
|
|
|
// Load the DXE
|
|
void *handle = dlopen(path, RTLD_GLOBAL);
|
|
|
|
if (!handle) {
|
|
char msg[512];
|
|
snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", baseName(path), dlerror());
|
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
|
return -1;
|
|
}
|
|
|
|
// Look up required symbols
|
|
AppDescriptorT *desc = (AppDescriptorT *)dlsym(handle, "_appDescriptor");
|
|
|
|
if (!desc) {
|
|
char msg[256];
|
|
snprintf(msg, sizeof(msg), "%s: missing appDescriptor", baseName(path));
|
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
|
dlclose(handle);
|
|
return -1;
|
|
}
|
|
|
|
int32_t (*entry)(DxeAppContextT *) = (int32_t (*)(DxeAppContextT *))dlsym(handle, "_appMain");
|
|
|
|
if (!entry) {
|
|
char msg[256];
|
|
snprintf(msg, sizeof(msg), "%s: missing appMain", baseName(path));
|
|
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
|
|
dlclose(handle);
|
|
return -1;
|
|
}
|
|
|
|
void (*shutdown)(void) = (void (*)(void))dlsym(handle, "_appShutdown");
|
|
|
|
// Fill in the app slot
|
|
ShellAppT *app = &sApps[id];
|
|
memset(app, 0, sizeof(*app));
|
|
|
|
app->appId = id;
|
|
snprintf(app->name, SHELL_APP_NAME_MAX, "%s", desc->name);
|
|
snprintf(app->path, sizeof(app->path), "%s", path);
|
|
app->dxeHandle = handle;
|
|
app->hasMainLoop = desc->hasMainLoop;
|
|
app->entryFn = entry;
|
|
app->shutdownFn = shutdown;
|
|
app->state = AppStateLoadedE;
|
|
|
|
// Set up the context passed to appMain
|
|
app->dxeCtx.shellCtx = ctx;
|
|
app->dxeCtx.appId = id;
|
|
|
|
// Derive app directory from path (everything up to last '/' or '\')
|
|
snprintf(app->dxeCtx.appDir, sizeof(app->dxeCtx.appDir), "%s", path);
|
|
|
|
char *lastSlash = strrchr(app->dxeCtx.appDir, '/');
|
|
char *lastBack = strrchr(app->dxeCtx.appDir, '\\');
|
|
|
|
if (lastBack > lastSlash) {
|
|
lastSlash = lastBack;
|
|
}
|
|
|
|
if (lastSlash) {
|
|
*lastSlash = '\0';
|
|
} else {
|
|
app->dxeCtx.appDir[0] = '.';
|
|
app->dxeCtx.appDir[1] = '\0';
|
|
}
|
|
|
|
// Launch
|
|
sCurrentAppId = id;
|
|
|
|
if (desc->hasMainLoop) {
|
|
uint32_t stackSize = desc->stackSize > 0 ? (uint32_t)desc->stackSize : TS_DEFAULT_STACK_SIZE;
|
|
int32_t priority = desc->priority;
|
|
|
|
int32_t taskId = tsCreate(desc->name, appTaskWrapper, app, stackSize, priority);
|
|
|
|
if (taskId < 0) {
|
|
sCurrentAppId = 0;
|
|
dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR);
|
|
dlclose(handle);
|
|
app->state = AppStateFreeE;
|
|
return -1;
|
|
}
|
|
|
|
app->mainTaskId = (uint32_t)taskId;
|
|
} else {
|
|
// Callback-only: call entry directly in task 0
|
|
app->entryFn(&app->dxeCtx);
|
|
}
|
|
|
|
sCurrentAppId = 0;
|
|
app->state = AppStateRunningE;
|
|
|
|
shellLog("Shell: loaded '%s' (id=%ld, mainLoop=%s, entry=0x%08lx, desc=0x%08lx)", app->name, (long)id, app->hasMainLoop ? "yes" : "no", (unsigned long)entry, (unsigned long)desc);
|
|
return id;
|
|
}
|
|
|
|
|
|
void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
|
if (!app || app->state == AppStateFreeE) {
|
|
return;
|
|
}
|
|
|
|
// Call shutdown hook if present
|
|
if (app->shutdownFn) {
|
|
sCurrentAppId = app->appId;
|
|
app->shutdownFn();
|
|
sCurrentAppId = 0;
|
|
}
|
|
|
|
// Destroy all windows belonging to this app
|
|
for (int32_t i = ctx->stack.count - 1; i >= 0; i--) {
|
|
if (ctx->stack.windows[i]->appId == app->appId) {
|
|
dvxDestroyWindow(ctx, ctx->stack.windows[i]);
|
|
}
|
|
}
|
|
|
|
// Kill the task if it has one and it's still alive
|
|
if (app->hasMainLoop && app->mainTaskId > 0) {
|
|
if (tsGetState(app->mainTaskId) != TaskStateTerminated) {
|
|
tsKill(app->mainTaskId);
|
|
}
|
|
}
|
|
|
|
// Close the DXE
|
|
if (app->dxeHandle) {
|
|
dlclose(app->dxeHandle);
|
|
app->dxeHandle = NULL;
|
|
}
|
|
|
|
shellLog("Shell: reaped app '%s'", app->name);
|
|
app->state = AppStateFreeE;
|
|
}
|
|
|
|
|
|
void shellReapApps(AppContextT *ctx) {
|
|
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
|
if (sApps[i].state == AppStateTerminatingE) {
|
|
shellReapApp(ctx, &sApps[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int32_t shellRunningAppCount(void) {
|
|
int32_t count = 0;
|
|
|
|
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
|
if (sApps[i].state == AppStateRunningE || sApps[i].state == AppStateLoadedE) {
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
void shellTerminateAllApps(AppContextT *ctx) {
|
|
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
|
if (sApps[i].state != AppStateFreeE) {
|
|
shellForceKillApp(ctx, &sApps[i]);
|
|
}
|
|
}
|
|
}
|