DVX_GUI/taskmgr/shellTaskMgr.c

395 lines
11 KiB
C

// 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 "../shell/shellApp.h"
#include "dvxDlg.h"
#include "dvxWgt.h"
#include "box/box.h"
#include "button/button.h"
#include "label/label.h"
#include "listView/listView.h"
#include "dvxWm.h"
#include "dvxPlat.h"
#include "stb_ds_wrap.h"
#include <stdio.h>
#include <string.h>
#include "dvxMem.h"
// ============================================================
// Constants
// ============================================================
#define TM_COL_COUNT 6
#define TM_MAX_PATH 260
#define TM_WIN_W 580
#define TM_WIN_H 280
#define TM_LIST_PREF_H 160
#define TM_BTN_SPACING 8
#define TM_BTN_W 90
// ============================================================
// Prototypes
// ============================================================
static ShellAppT *getRunningAppByIndex(int32_t sel);
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];
char mem[16];
} 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
// ============================================================
// getRunningAppByIndex
// ============================================================
static ShellAppT *getRunningAppByIndex(int32_t sel) {
int32_t idx = 0;
for (int32_t i = 1; i < shellAppSlotCount(); i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
return app;
}
idx++;
}
}
return NULL;
}
// ============================================================
// 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;
}
ShellAppT *app = getRunningAppByIndex(sel);
if (app) {
shellForceKillApp(sCtx, app);
shellDesktopUpdate();
}
}
// ============================================================
// onTmRun
// ============================================================
static void onTmRun(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "Applications (*.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;
}
ShellAppT *app = getRunningAppByIndex(sel);
if (!app) {
return;
}
// 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.
for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) {
WindowT *win = sCtx->stack.windows[j];
if (win->appId == app->appId) {
if (win->minimized) {
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, &sCtx->display, win);
}
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j);
wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1);
return;
}
}
}
// ============================================================
// 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 < shellAppSlotCount(); 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");
uint32_t memKb = dvxMemGetAppUsage(app->appId) / 1024;
if (memKb >= 1024) {
snprintf(row.mem, sizeof(row.mem), "%lu MB", (unsigned long)(memKb / 1024));
} else {
snprintf(row.mem, sizeof(row.mem), "%lu KB", (unsigned long)memKb);
}
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, sRowStrs[r].mem);
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 = TM_WIN_W;
int32_t winH = TM_WIN_H;
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(18);
tmCols[0].align = ListViewAlignLeftE;
tmCols[1].title = "Title";
tmCols[1].width = wgtPercent(26);
tmCols[1].align = ListViewAlignLeftE;
tmCols[2].title = "File";
tmCols[2].width = wgtPercent(18);
tmCols[2].align = ListViewAlignLeftE;
tmCols[3].title = "Type";
tmCols[3].width = wgtPercent(12);
tmCols[3].align = ListViewAlignLeftE;
tmCols[4].title = "Memory";
tmCols[4].width = wgtPercent(12);
tmCols[4].align = ListViewAlignRightE;
tmCols[5].title = "Status";
tmCols[5].width = wgtPercent(14);
tmCols[5].align = ListViewAlignLeftE;
sTmListView = wgtListView(root);
sTmListView->weight = 100;
sTmListView->prefH = wgtPixels(TM_LIST_PREF_H);
sTmListView->onDblClick = onTmSwitchTo;
wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT);
WidgetT *btnRow = wgtHBox(root);
btnRow->spacing = wgtPixels(TM_BTN_SPACING);
sTmStatusLbl = wgtLabel(btnRow, "");
sTmStatusLbl->weight = 100;
WidgetT *switchBtn = wgtButton(btnRow, "&Switch To");
switchBtn->onClick = onTmSwitchTo;
switchBtn->prefW = wgtPixels(TM_BTN_W);
WidgetT *endBtn = wgtButton(btnRow, "&End Task");
endBtn->onClick = onTmEndTask;
endBtn->prefW = wgtPixels(TM_BTN_W);
WidgetT *runBtn = wgtButton(btnRow, "&Run...");
runBtn->onClick = onTmRun;
runBtn->prefW = wgtPixels(TM_BTN_W);
shellRegisterDesktopUpdate(shellTaskMgrRefresh);
refreshTaskList();
updateStatusText();
dvxFitWindow(ctx, sTmWindow);
}
// ============================================================
// shellTaskMgrRefresh
// ============================================================
void shellTaskMgrRefresh(void) {
if (sTmWindow) {
refreshTaskList();
updateStatusText();
}
}
// ============================================================
// DXE constructor -- register Ctrl+Esc handler with the shell
// ============================================================
static void taskmgrInit(void) __attribute__((constructor));
static void taskmgrInit(void) {
shellCtrlEscFn = shellTaskMgrOpen;
}