DVX_GUI/dvxshell/shellTaskMgr.c

360 lines
10 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 "shellApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "platform/dvxPlatform.h"
#include "thirdparty/stb_ds.h"
#include <stdio.h>
#include <string.h>
// ============================================================
// 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);
refreshTaskList();
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);
int32_t rowCount = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
// Grow the per-row string storage
TmRowStringsT row = {0};
// Column 1: Title (from first visible window owned by this app)
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;
}
}
// Column 2: Filename (basename of .app path)
char *sep = platformPathDirEnd(app->path);
const char *fname = sep ? sep + 1 : app->path;
snprintf(row.file, sizeof(row.file), "%.63s", fname);
// Column 3: Type
snprintf(row.type, sizeof(row.type), "%s", app->hasMainLoop ? "Task" : "Callback");
arrput(sRowStrs, row);
// Build cell pointers for this row
arrput(sCells, app->name);
arrput(sCells, sRowStrs[rowCount].title);
arrput(sCells, sRowStrs[rowCount].file);
arrput(sCells, sRowStrs[rowCount].type);
arrput(sCells, "Running");
rowCount++;
}
}
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 = 0;
if (count == 1) {
pos = snprintf(buf, sizeof(buf), "1 app");
} else {
pos = snprintf(buf, sizeof(buf), "%ld apps", (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();
}
}