// shellTaskMgr.c -- System Task Manager // // Shell-level Task Manager window. Accessible via Ctrl+Esc regardless // of which app is focused or whether the desktop app is running. Lists // all running apps with Switch To, End Task, and Run buttons. #include "shellTaskMgr.h" #include "shellApp.h" #include "dvxDialog.h" #include "dvxWidget.h" #include "dvxWm.h" #include "platform/dvxPlatform.h" #include "thirdparty/stb_ds.h" #include #include // ============================================================ // Constants // ============================================================ #define TM_COL_COUNT 5 #define TM_MAX_PATH 260 // ============================================================ // Prototypes // ============================================================ static void onTmClose(WindowT *win); static void onTmEndTask(WidgetT *w); static void onTmRun(WidgetT *w); static void onTmSwitchTo(WidgetT *w); static void refreshTaskList(void); static void updateStatusText(void); // ============================================================ // Module state // ============================================================ // Per-row string storage for the list view (must outlive each refresh cycle) typedef struct { char title[MAX_TITLE_LEN]; char file[64]; char type[12]; } TmRowStringsT; static AppContextT *sCtx = NULL; static WindowT *sTmWindow = NULL; static WidgetT *sTmListView = NULL; static WidgetT *sTmStatusLbl = NULL; static const char **sCells = NULL; // dynamic array of cell pointers static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings // ============================================================ // onTmClose // ============================================================ static void onTmClose(WindowT *win) { shellUnregisterDesktopUpdate(shellTaskMgrRefresh); arrfree(sCells); arrfree(sRowStrs); sCells = NULL; sRowStrs = NULL; sTmListView = NULL; sTmStatusLbl = NULL; sTmWindow = NULL; dvxDestroyWindow(sCtx, win); } // ============================================================ // onTmEndTask // ============================================================ static void onTmEndTask(WidgetT *w) { (void)w; if (!sTmListView) { return; } int32_t sel = wgtListViewGetSelected(sTmListView); if (sel < 0) { return; } // Re-walk the app slot table in the same order as refreshTaskList() // to map the selected row index back to the correct ShellAppT. int32_t idx = 0; for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { ShellAppT *app = shellGetApp(i); if (app && app->state == AppStateRunningE) { if (idx == sel) { shellForceKillApp(sCtx, app); shellDesktopUpdate(); return; } idx++; } } } // ============================================================ // onTmRun // ============================================================ static void onTmRun(WidgetT *w) { (void)w; FileFilterT filters[] = { { "Applications (*.app)", "*.app" }, { "All Files (*.*)", "*.*" } }; char path[TM_MAX_PATH]; if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) { shellLoadApp(sCtx, path); } } // ============================================================ // onTmSwitchTo // ============================================================ static void onTmSwitchTo(WidgetT *w) { (void)w; if (!sTmListView) { return; } int32_t sel = wgtListViewGetSelected(sTmListView); if (sel < 0) { return; } // Same index-to-appId mapping as refreshTaskList. Scan the window // stack top-down (highest Z first) to find the app's topmost window, // restore it if minimized, then raise and focus it. int32_t idx = 0; for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { ShellAppT *app = shellGetApp(i); if (app && app->state == AppStateRunningE) { if (idx == sel) { for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) { WindowT *win = sCtx->stack.windows[j]; if (win->appId == i) { if (win->minimized) { wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win); } wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j); wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1); return; } } return; } idx++; } } } // ============================================================ // refreshTaskList // ============================================================ static void refreshTaskList(void) { if (!sTmListView) { return; } // Reset dynamic arrays (keep allocations for reuse) arrsetlen(sCells, 0); arrsetlen(sRowStrs, 0); // Pass 1: collect all row data. Must finish before building cell // pointers because arrput may reallocate, invalidating earlier // pointers into the array. static int32_t *appIds = NULL; arrsetlen(appIds, 0); for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { ShellAppT *app = shellGetApp(i); if (app && app->state == AppStateRunningE) { TmRowStringsT row = {0}; for (int32_t w = 0; w < sCtx->stack.count; w++) { WindowT *win = sCtx->stack.windows[w]; if (win->appId == app->appId && win->visible) { snprintf(row.title, sizeof(row.title), "%s", win->title); break; } } char *sep = platformPathDirEnd(app->path); const char *fname = sep ? sep + 1 : app->path; snprintf(row.file, sizeof(row.file), "%.63s", fname); snprintf(row.type, sizeof(row.type), "%s", app->hasMainLoop ? "Task" : "Callback"); arrput(sRowStrs, row); arrput(appIds, i); } } // Pass 2: build cell pointer array. sRowStrs is stable now. int32_t rowCount = arrlen(sRowStrs); for (int32_t r = 0; r < rowCount; r++) { ShellAppT *app = shellGetApp(appIds[r]); arrput(sCells, app->name); arrput(sCells, sRowStrs[r].title); arrput(sCells, sRowStrs[r].file); arrput(sCells, sRowStrs[r].type); arrput(sCells, "Running"); } wgtListViewSetData(sTmListView, sCells, rowCount); } // ============================================================ // updateStatusText // ============================================================ static void updateStatusText(void) { if (!sTmStatusLbl) { return; } static char buf[128]; int32_t count = shellRunningAppCount(); uint32_t totalKb; uint32_t freeKb; bool hasMem = platformGetMemoryInfo(&totalKb, &freeKb); int32_t pos = snprintf(buf, sizeof(buf), "Applications: %ld", (long)count); if (hasMem && totalKb > 0) { uint32_t usedKb = totalKb - freeKb; snprintf(buf + pos, sizeof(buf) - pos, " Memory: %lu/%lu MB", (unsigned long)(usedKb / 1024), (unsigned long)(totalKb / 1024)); } wgtSetText(sTmStatusLbl, buf); } // ============================================================ // shellTaskMgrOpen // ============================================================ void shellTaskMgrOpen(AppContextT *ctx) { sCtx = ctx; if (sTmWindow) { // Already open -- raise and focus for (int32_t i = 0; i < ctx->stack.count; i++) { if (ctx->stack.windows[i] == sTmWindow) { wmRaiseWindow(&ctx->stack, &ctx->dirty, i); wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1); break; } } return; } int32_t winW = 520; int32_t winH = 280; int32_t winX = (ctx->display.width - winW) / 2; int32_t winY = (ctx->display.height - winH) / 3; sTmWindow = dvxCreateWindow(ctx, "Task Manager", winX, winY, winW, winH, true); if (!sTmWindow) { return; } sTmWindow->onClose = onTmClose; sTmWindow->appId = 0; // owned by the shell, not any app WidgetT *root = wgtInitWindow(ctx, sTmWindow); static ListViewColT tmCols[TM_COL_COUNT]; tmCols[0].title = "Name"; tmCols[0].width = wgtPercent(20); tmCols[0].align = ListViewAlignLeftE; tmCols[1].title = "Title"; tmCols[1].width = wgtPercent(30); tmCols[1].align = ListViewAlignLeftE; tmCols[2].title = "File"; tmCols[2].width = wgtPercent(22); tmCols[2].align = ListViewAlignLeftE; tmCols[3].title = "Type"; tmCols[3].width = wgtPercent(14); tmCols[3].align = ListViewAlignLeftE; tmCols[4].title = "Status"; tmCols[4].width = wgtPercent(14); tmCols[4].align = ListViewAlignLeftE; sTmListView = wgtListView(root); sTmListView->weight = 100; sTmListView->prefH = wgtPixels(160); wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT); WidgetT *btnRow = wgtHBox(root); btnRow->spacing = wgtPixels(8); sTmStatusLbl = wgtLabel(btnRow, ""); sTmStatusLbl->weight = 100; WidgetT *switchBtn = wgtButton(btnRow, "Switch To"); switchBtn->onClick = onTmSwitchTo; switchBtn->prefW = wgtPixels(90); WidgetT *endBtn = wgtButton(btnRow, "End Task"); endBtn->onClick = onTmEndTask; endBtn->prefW = wgtPixels(90); WidgetT *runBtn = wgtButton(btnRow, "Run..."); runBtn->onClick = onTmRun; runBtn->prefW = wgtPixels(90); shellRegisterDesktopUpdate(shellTaskMgrRefresh); refreshTaskList(); updateStatusText(); dvxFitWindow(ctx, sTmWindow); } // ============================================================ // shellTaskMgrRefresh // ============================================================ void shellTaskMgrRefresh(void) { if (sTmWindow) { refreshTaskList(); updateStatusText(); } }