// shellDesktop.c — Program Manager window for DVX Shell // // Displays a grid of available DXE apps from the apps/ directory. // Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc). #include "shellApp.h" #include "dvxDialog.h" #include #include #include #include // ============================================================ // Constants // ============================================================ #define MAX_DXE_FILES 64 #define MAX_PATH_LEN 260 #define PM_GRID_COLS 4 #define PM_BTN_W 100 #define PM_BTN_H 24 // Menu command IDs #define CMD_RUN 100 #define CMD_EXIT 101 #define CMD_CASCADE 200 #define CMD_TILE 201 #define CMD_TILE_H 202 #define CMD_TILE_V 203 #define CMD_ABOUT 300 #define CMD_TASK_MGR 301 // ============================================================ // Module state // ============================================================ typedef struct { char name[SHELL_APP_NAME_MAX]; // display name (filename without .app) char path[MAX_PATH_LEN]; // full path } DxeEntryT; static AppContextT *sCtx = NULL; static WindowT *sPmWindow = NULL; static WidgetT *sStatusLabel = NULL; static DxeEntryT sDxeFiles[MAX_DXE_FILES]; static int32_t sDxeCount = 0; // ============================================================ // Prototypes // ============================================================ static void buildPmWindow(void); static void onAppButtonClick(WidgetT *w); static void onPmClose(WindowT *win); static void onPmMenu(WindowT *win, int32_t menuId); static void scanAppsDir(void); static void showAboutDialog(void); static void updateStatusText(void); // Task Manager static WindowT *sTmWindow = NULL; static WidgetT *sTmListBox = NULL; static void buildTaskManager(void); static void onTmClose(WindowT *win); static void onTmEndTask(WidgetT *w); static void onTmSwitchTo(WidgetT *w); static void refreshTaskList(void); // ============================================================ // Static functions (alphabetical) // ============================================================ static void buildPmWindow(void) { int32_t screenW = sCtx->display.width; int32_t screenH = sCtx->display.height; int32_t winW = 440; int32_t winH = 340; int32_t winX = (screenW - winW) / 2; int32_t winY = (screenH - winH) / 4; sPmWindow = dvxCreateWindow(sCtx, "Program Manager", winX, winY, winW, winH, true); if (!sPmWindow) { return; } sPmWindow->appId = 0; sPmWindow->onClose = onPmClose; sPmWindow->onMenu = onPmMenu; // Menu bar MenuBarT *menuBar = wmAddMenuBar(sPmWindow); MenuT *fileMenu = wmAddMenu(menuBar, "&File"); wmAddMenuItem(fileMenu, "&Run...", CMD_RUN); wmAddMenuSeparator(fileMenu); wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT); MenuT *windowMenu = wmAddMenu(menuBar, "&Window"); wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE); wmAddMenuItem(windowMenu, "&Tile", CMD_TILE); wmAddMenuItem(windowMenu, "Tile &Horizontally", CMD_TILE_H); wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V); MenuT *helpMenu = wmAddMenu(menuBar, "&Help"); wmAddMenuItem(helpMenu, "&About DVX Shell...", CMD_ABOUT); wmAddMenuSeparator(helpMenu); wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR); // Widget tree WidgetT *root = wgtInitWindow(sCtx, sPmWindow); // App button grid in a frame WidgetT *appFrame = wgtFrame(root, "Applications"); appFrame->weight = 100; if (sDxeCount == 0) { WidgetT *lbl = wgtLabel(appFrame, "(No applications found in apps/ directory)"); (void)lbl; } else { // Build rows of buttons int32_t row = 0; WidgetT *hbox = NULL; for (int32_t i = 0; i < sDxeCount; i++) { if (i % PM_GRID_COLS == 0) { hbox = wgtHBox(appFrame); hbox->spacing = wgtPixels(8); row++; } WidgetT *btn = wgtButton(hbox, sDxeFiles[i].name); btn->prefW = wgtPixels(PM_BTN_W); btn->prefH = wgtPixels(PM_BTN_H); btn->userData = &sDxeFiles[i]; btn->onDblClick = onAppButtonClick; btn->onClick = onAppButtonClick; } (void)row; } // Status bar WidgetT *statusBar = wgtStatusBar(root); sStatusLabel = wgtLabel(statusBar, ""); sStatusLabel->weight = 100; updateStatusText(); dvxFitWindow(sCtx, sPmWindow); wgtInvalidate(root); } static void buildTaskManager(void) { if (sTmWindow) { // Already open — just raise it for (int32_t i = 0; i < sCtx->stack.count; i++) { if (sCtx->stack.windows[i] == sTmWindow) { wmRaiseWindow(&sCtx->stack, &sCtx->dirty, i); wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1); break; } } return; } int32_t screenW = sCtx->display.width; int32_t screenH = sCtx->display.height; int32_t winW = 300; int32_t winH = 280; int32_t winX = (screenW - winW) / 2; int32_t winY = (screenH - winH) / 3; sTmWindow = dvxCreateWindow(sCtx, "Task Manager", winX, winY, winW, winH, true); if (!sTmWindow) { return; } sTmWindow->appId = 0; sTmWindow->onClose = onTmClose; WidgetT *root = wgtInitWindow(sCtx, sTmWindow); // List box of running apps sTmListBox = wgtListBox(root); sTmListBox->weight = 100; sTmListBox->prefH = wgtPixels(160); // Button row WidgetT *btnRow = wgtHBox(root); btnRow->align = AlignEndE; btnRow->spacing = wgtPixels(8); 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); refreshTaskList(); dvxFitWindow(sCtx, sTmWindow); wgtInvalidate(root); } static void onAppButtonClick(WidgetT *w) { DxeEntryT *entry = (DxeEntryT *)w->userData; if (!entry) { return; } shellLoadApp(sCtx, entry->path); updateStatusText(); } static void onPmClose(WindowT *win) { (void)win; // Confirm exit int32_t result = dvxMessageBox(sCtx, "Exit Shell", "Are you sure you want to exit DVX Shell?", MB_YESNO | MB_ICONQUESTION); if (result == ID_YES) { sCtx->running = false; } } static void onPmMenu(WindowT *win, int32_t menuId) { (void)win; switch (menuId) { case CMD_RUN: { FileFilterT filters[] = { { "Applications (*.app)", "*.app" }, { "All Files (*.*)", "*.*" } }; char path[MAX_PATH_LEN]; if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) { shellLoadApp(sCtx, path); updateStatusText(); } } break; case CMD_EXIT: onPmClose(sPmWindow); break; case CMD_CASCADE: dvxCascadeWindows(sCtx); break; case CMD_TILE: dvxTileWindows(sCtx); break; case CMD_TILE_H: dvxTileWindowsH(sCtx); break; case CMD_TILE_V: dvxTileWindowsV(sCtx); break; case CMD_ABOUT: showAboutDialog(); break; case CMD_TASK_MGR: buildTaskManager(); break; } } static void onTmClose(WindowT *win) { sTmListBox = NULL; sTmWindow = NULL; dvxDestroyWindow(sCtx, win); } static void onTmEndTask(WidgetT *w) { (void)w; if (!sTmListBox) { return; } int32_t sel = wgtListBoxGetSelected(sTmListBox); if (sel < 0) { return; } // Map list index to app ID 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(); updateStatusText(); return; } idx++; } } } static void onTmSwitchTo(WidgetT *w) { (void)w; if (!sTmListBox) { return; } int32_t sel = wgtListBoxGetSelected(sTmListBox); if (sel < 0) { return; } // Map list index to app ID, find topmost window for that app 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) { // Find the topmost window for this app 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++; } } } static void refreshTaskList(void) { if (!sTmListBox) { return; } static const char *items[SHELL_MAX_APPS]; static char names[SHELL_MAX_APPS][SHELL_APP_NAME_MAX + 16]; int32_t count = 0; for (int32_t i = 1; i < SHELL_MAX_APPS; i++) { ShellAppT *app = shellGetApp(i); if (app && app->state == AppStateRunningE) { const char *state = app->hasMainLoop ? "task" : "callback"; snprintf(names[count], sizeof(names[count]), "%s [%s]", app->name, state); items[count] = names[count]; count++; } } wgtListBoxSetItems(sTmListBox, items, count); wgtInvalidatePaint(sTmListBox); } static void scanAppsDir(void) { DIR *dir = opendir("apps"); if (!dir) { shellLog("Shell: apps/ directory not found"); return; } sDxeCount = 0; struct dirent *ent; while ((ent = readdir(dir)) != NULL && sDxeCount < MAX_DXE_FILES) { int32_t len = strlen(ent->d_name); if (len < 5) { continue; } // Check for .app extension (case-insensitive) const char *ext = ent->d_name + len - 4; if (strcmp(ext, ".app") != 0 && strcmp(ext, ".APP") != 0) { continue; } DxeEntryT *entry = &sDxeFiles[sDxeCount]; // Name = filename without extension int32_t nameLen = len - 4; if (nameLen >= SHELL_APP_NAME_MAX) { nameLen = SHELL_APP_NAME_MAX - 1; } memcpy(entry->name, ent->d_name, nameLen); entry->name[nameLen] = '\0'; // Capitalize first letter for display if (entry->name[0] >= 'a' && entry->name[0] <= 'z') { entry->name[0] -= 32; } snprintf(entry->path, sizeof(entry->path), "apps/%s", ent->d_name); sDxeCount++; } closedir(dir); shellLog("Shell: found %ld app(s)", (long)sDxeCount); } static void showAboutDialog(void) { dvxMessageBox(sCtx, "About DVX Shell", "DVX Shell 1.0\n\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.", MB_OK | MB_ICONINFO); } static void updateStatusText(void) { if (!sStatusLabel) { return; } static char buf[64]; int32_t count = shellRunningAppCount(); if (count == 0) { snprintf(buf, sizeof(buf), "No applications running"); } else if (count == 1) { snprintf(buf, sizeof(buf), "1 application running"); } else { snprintf(buf, sizeof(buf), "%ld applications running", (long)count); } wgtSetText(sStatusLabel, buf); wgtInvalidate(sStatusLabel); } // ============================================================ // Public API // ============================================================ void shellDesktopInit(AppContextT *ctx) { sCtx = ctx; scanAppsDir(); buildPmWindow(); } void shellDesktopUpdateStatus(void) { updateStatusText(); // Also refresh task manager if open if (sTmWindow) { refreshTaskList(); } } void shellTaskManager(AppContextT *ctx) { sCtx = ctx; buildTaskManager(); }