// shellApp.c — DV/X 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 #include #include #include // ============================================================ // 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; // 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]); } } }