358 lines
10 KiB
C
358 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);
|
|
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();
|
|
}
|
|
}
|