Working on bugs found via real hardware.
This commit is contained in:
parent
1556ba889c
commit
14bc2027df
9 changed files with 163 additions and 561 deletions
|
|
@ -186,7 +186,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
|
|||
|
||||
case CMD_HELP_ABOUT:
|
||||
dvxMessageBox(sAc, "About DVX Demo",
|
||||
"DVX GUI Demonstration\n\n"
|
||||
"DVX GUI Demonstration\n"
|
||||
"A DOS Visual eXecutive windowing system for DOS.",
|
||||
MB_OK | MB_ICONINFO);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
|
|
@ -68,12 +69,13 @@ static void onAppButtonClick(WidgetT *w);
|
|||
static void onPmClose(WindowT *win);
|
||||
static void onPmMenu(WindowT *win, int32_t menuId);
|
||||
static void scanAppsDir(void);
|
||||
static void scanAppsDirRecurse(const char *dirPath);
|
||||
static void showAboutDialog(void);
|
||||
static void updateStatusText(void);
|
||||
|
||||
// Task Manager
|
||||
static WindowT *sTmWindow = NULL;
|
||||
static WidgetT *sTmListBox = NULL;
|
||||
static WidgetT *sTmListView = NULL;
|
||||
static void buildTaskManager(void);
|
||||
static void onTmClose(WindowT *win);
|
||||
static void onTmEndTask(WidgetT *w);
|
||||
|
|
@ -206,10 +208,22 @@ static void buildTaskManager(void) {
|
|||
|
||||
WidgetT *root = wgtInitWindow(sAc, sTmWindow);
|
||||
|
||||
// List box of running apps
|
||||
sTmListBox = wgtListBox(root);
|
||||
sTmListBox->weight = 100;
|
||||
sTmListBox->prefH = wgtPixels(160);
|
||||
// List view of running apps
|
||||
ListViewColT tmCols[3];
|
||||
tmCols[0].title = "Name";
|
||||
tmCols[0].width = wgtPercent(50);
|
||||
tmCols[0].align = ListViewAlignLeftE;
|
||||
tmCols[1].title = "Type";
|
||||
tmCols[1].width = wgtPercent(25);
|
||||
tmCols[1].align = ListViewAlignLeftE;
|
||||
tmCols[2].title = "Status";
|
||||
tmCols[2].width = wgtPercent(25);
|
||||
tmCols[2].align = ListViewAlignLeftE;
|
||||
|
||||
sTmListView = wgtListView(root);
|
||||
sTmListView->weight = 100;
|
||||
sTmListView->prefH = wgtPixels(160);
|
||||
wgtListViewSetColumns(sTmListView, tmCols, 3);
|
||||
|
||||
// Button row
|
||||
WidgetT *btnRow = wgtHBox(root);
|
||||
|
|
@ -324,8 +338,8 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
|
||||
|
||||
static void onTmClose(WindowT *win) {
|
||||
sTmListBox = NULL;
|
||||
sTmWindow = NULL;
|
||||
sTmListView = NULL;
|
||||
sTmWindow = NULL;
|
||||
dvxDestroyWindow(sAc, win);
|
||||
}
|
||||
|
||||
|
|
@ -333,11 +347,11 @@ static void onTmClose(WindowT *win) {
|
|||
static void onTmEndTask(WidgetT *w) {
|
||||
(void)w;
|
||||
|
||||
if (!sTmListBox) {
|
||||
if (!sTmListView) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t sel = wgtListBoxGetSelected(sTmListBox);
|
||||
int32_t sel = wgtListViewGetSelected(sTmListView);
|
||||
|
||||
if (sel < 0) {
|
||||
return;
|
||||
|
|
@ -370,11 +384,11 @@ static void onTmEndTask(WidgetT *w) {
|
|||
static void onTmSwitchTo(WidgetT *w) {
|
||||
(void)w;
|
||||
|
||||
if (!sTmListBox) {
|
||||
if (!sTmListView) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t sel = wgtListBoxGetSelected(sTmListBox);
|
||||
int32_t sel = wgtListViewGetSelected(sTmListView);
|
||||
|
||||
if (sel < 0) {
|
||||
return;
|
||||
|
|
@ -417,13 +431,14 @@ static void onTmSwitchTo(WidgetT *w) {
|
|||
|
||||
|
||||
static void refreshTaskList(void) {
|
||||
if (!sTmListBox) {
|
||||
if (!sTmListView) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const char *items[SHELL_MAX_APPS];
|
||||
static char names[SHELL_MAX_APPS][SHELL_APP_NAME_MAX + 16];
|
||||
int32_t count = 0;
|
||||
// 3 columns per row: Name, Type, Status
|
||||
static const char *cells[SHELL_MAX_APPS * 3];
|
||||
static char typeStrs[SHELL_MAX_APPS][12];
|
||||
int32_t rowCount = 0;
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
if (i == sMyAppId) {
|
||||
|
|
@ -433,29 +448,56 @@ static void refreshTaskList(void) {
|
|||
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++;
|
||||
int32_t base = rowCount * 3;
|
||||
cells[base] = app->name;
|
||||
snprintf(typeStrs[rowCount], sizeof(typeStrs[rowCount]), "%s", app->hasMainLoop ? "Task" : "Callback");
|
||||
cells[base + 1] = typeStrs[rowCount];
|
||||
cells[base + 2] = "Running";
|
||||
rowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
wgtListBoxSetItems(sTmListBox, items, count);
|
||||
wgtListViewSetData(sTmListView, cells, rowCount);
|
||||
}
|
||||
|
||||
|
||||
static void scanAppsDir(void) {
|
||||
DIR *dir = opendir("apps");
|
||||
sAppCount = 0;
|
||||
scanAppsDirRecurse("apps");
|
||||
shellLog("Progman: found %ld app(s)", (long)sAppCount);
|
||||
}
|
||||
|
||||
|
||||
static void scanAppsDirRecurse(const char *dirPath) {
|
||||
DIR *dir = opendir(dirPath);
|
||||
|
||||
if (!dir) {
|
||||
shellLog("Progman: apps/ directory not found");
|
||||
if (sAppCount == 0) {
|
||||
shellLog("Progman: %s directory not found", dirPath);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sAppCount = 0;
|
||||
struct dirent *ent;
|
||||
|
||||
while ((ent = readdir(dir)) != NULL && sAppCount < MAX_APP_FILES) {
|
||||
// Skip . and ..
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char fullPath[MAX_PATH_LEN];
|
||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name);
|
||||
|
||||
// Check if this is a directory — recurse into it
|
||||
struct stat st;
|
||||
|
||||
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
scanAppsDirRecurse(fullPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t len = strlen(ent->d_name);
|
||||
|
||||
if (len < 5) {
|
||||
|
|
@ -491,12 +533,11 @@ static void scanAppsDir(void) {
|
|||
entry->name[0] -= 32;
|
||||
}
|
||||
|
||||
snprintf(entry->path, sizeof(entry->path), "apps/%s", ent->d_name);
|
||||
snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
|
||||
sAppCount++;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
shellLog("Progman: found %ld app(s)", (long)sAppCount);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, i
|
|||
// Shut down video — restore text mode, unmap framebuffer, free backbuffer.
|
||||
void platformVideoShutdown(DisplayT *d);
|
||||
|
||||
// Enumerate available graphics modes. Calls cb(w, h, bpp, userData) for
|
||||
// each LFB-capable graphics mode found.
|
||||
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
|
||||
|
||||
// Set palette entries (8-bit mode). pal is R,G,B triplets.
|
||||
void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
|
||||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
|
||||
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
|
||||
static int32_t setVesaMode(uint16_t mode);
|
||||
|
||||
// Alt+key scan code to ASCII lookup table (indexed by BIOS scan code).
|
||||
|
|
@ -135,6 +136,68 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pref
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformVideoEnumModes
|
||||
// ============================================================
|
||||
|
||||
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F00;
|
||||
r.x.es = __tb >> 4;
|
||||
r.x.di = __tb & 0x0F;
|
||||
|
||||
// Write "VBE2" signature to request VBE 2.0+ info
|
||||
_farpokeb(_dos_ds, __tb + 0, 'V');
|
||||
_farpokeb(_dos_ds, __tb + 1, 'B');
|
||||
_farpokeb(_dos_ds, __tb + 2, 'E');
|
||||
_farpokeb(_dos_ds, __tb + 3, '2');
|
||||
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
|
||||
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
|
||||
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
|
||||
|
||||
for (int32_t i = 0; i < 256; i++) {
|
||||
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
|
||||
|
||||
if (mode == 0xFFFF) {
|
||||
break;
|
||||
}
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x4F01;
|
||||
r.x.cx = mode;
|
||||
r.x.es = __tb >> 4;
|
||||
r.x.di = __tb & 0x0F;
|
||||
__dpmi_int(0x10, &r);
|
||||
|
||||
if (r.x.ax != 0x004F) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
|
||||
|
||||
// Only report LFB-capable graphics modes
|
||||
if (!(attr & 0x0080) || !(attr & 0x0010)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t w = _farpeekw(_dos_ds, __tb + 18);
|
||||
int32_t h = _farpeekw(_dos_ds, __tb + 20);
|
||||
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
|
||||
|
||||
cb(w, h, bpp, userData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// getModeInfo
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
|
|||
ShellAppT *shellGetApp(int32_t appId);
|
||||
int32_t shellLoadApp(AppContextT *ctx, const char *path);
|
||||
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
||||
void shellReapApps(AppContextT *ctx);
|
||||
bool shellReapApps(AppContextT *ctx);
|
||||
int32_t shellRunningAppCount(void);
|
||||
void shellTerminateAllApps(AppContextT *ctx);
|
||||
|
||||
|
|
@ -267,12 +267,17 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
|||
}
|
||||
|
||||
|
||||
void shellReapApps(AppContextT *ctx) {
|
||||
bool shellReapApps(AppContextT *ctx) {
|
||||
bool reaped = false;
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
if (sApps[i].state == AppStateTerminatingE) {
|
||||
shellReapApp(ctx, &sApps[i]);
|
||||
reaped = true;
|
||||
}
|
||||
}
|
||||
|
||||
return reaped;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ void shellAppInit(void);
|
|||
int32_t shellLoadApp(AppContextT *ctx, const char *path);
|
||||
|
||||
// Reap finished callback-only apps (call each frame from main loop)
|
||||
void shellReapApps(AppContextT *ctx);
|
||||
bool shellReapApps(AppContextT *ctx);
|
||||
|
||||
// Gracefully shut down a single app
|
||||
void shellReapApp(AppContextT *ctx, ShellAppT *app);
|
||||
|
|
@ -111,7 +111,7 @@ void shellExportInit(void);
|
|||
// ============================================================
|
||||
|
||||
// Default desktop app path
|
||||
#define SHELL_DESKTOP_APP "apps/progman.app"
|
||||
#define SHELL_DESKTOP_APP "apps/progman/progman.app"
|
||||
|
||||
// Register a callback for app state changes (load, reap, crash).
|
||||
// The desktop app calls this during appMain to receive notifications.
|
||||
|
|
|
|||
|
|
@ -1,526 +0,0 @@
|
|||
// 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// ============================================================
|
||||
// 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 scanAppsDirRecurse(const char *dirPath);
|
||||
static void showAboutDialog(void);
|
||||
static void updateStatusText(void);
|
||||
|
||||
// Task Manager
|
||||
static WindowT *sTmWindow = NULL;
|
||||
static WidgetT *sTmListView = 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);
|
||||
}
|
||||
|
||||
|
||||
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 view of running apps
|
||||
static const ListViewColT tmCols[] = {
|
||||
{ "Name", wgtPercent(50), ListViewAlignLeftE },
|
||||
{ "Type", wgtPercent(25), ListViewAlignLeftE },
|
||||
{ "Status", wgtPercent(25), ListViewAlignLeftE },
|
||||
};
|
||||
|
||||
sTmListView = wgtListView(root);
|
||||
sTmListView->weight = 100;
|
||||
sTmListView->prefH = wgtPixels(160);
|
||||
wgtListViewSetColumns(sTmListView, tmCols, 3);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
sTmListView = NULL;
|
||||
sTmWindow = NULL;
|
||||
dvxDestroyWindow(sCtx, win);
|
||||
}
|
||||
|
||||
|
||||
static void onTmEndTask(WidgetT *w) {
|
||||
(void)w;
|
||||
|
||||
if (!sTmListView) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t sel = wgtListViewGetSelected(sTmListView);
|
||||
|
||||
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 (!sTmListView) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t sel = wgtListViewGetSelected(sTmListView);
|
||||
|
||||
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 (!sTmListView) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3 columns per row: Name, Type, Status
|
||||
static const char *cells[SHELL_MAX_APPS * 3];
|
||||
static char typeStrs[SHELL_MAX_APPS][12];
|
||||
int32_t rowCount = 0;
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
ShellAppT *app = shellGetApp(i);
|
||||
|
||||
if (app && app->state == AppStateRunningE) {
|
||||
int32_t base = rowCount * 3;
|
||||
cells[base] = app->name;
|
||||
snprintf(typeStrs[rowCount], sizeof(typeStrs[rowCount]), "%s", app->hasMainLoop ? "Task" : "Callback");
|
||||
cells[base + 1] = typeStrs[rowCount];
|
||||
cells[base + 2] = "Running";
|
||||
rowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
wgtListViewSetData(sTmListView, cells, rowCount);
|
||||
}
|
||||
|
||||
|
||||
static void scanAppsDir(void) {
|
||||
sDxeCount = 0;
|
||||
scanAppsDirRecurse("apps");
|
||||
shellLog("Shell: found %ld app(s)", (long)sDxeCount);
|
||||
}
|
||||
|
||||
|
||||
static void scanAppsDirRecurse(const char *dirPath) {
|
||||
DIR *dir = opendir(dirPath);
|
||||
|
||||
if (!dir) {
|
||||
if (sDxeCount == 0) {
|
||||
shellLog("Shell: %s directory not found", dirPath);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
struct dirent *ent;
|
||||
|
||||
while ((ent = readdir(dir)) != NULL && sDxeCount < MAX_DXE_FILES) {
|
||||
// Skip . and ..
|
||||
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char fullPath[MAX_PATH_LEN];
|
||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, ent->d_name);
|
||||
|
||||
// Check if this is a directory — recurse into it
|
||||
struct stat st;
|
||||
|
||||
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
scanAppsDirRecurse(fullPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
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), "%s", fullPath);
|
||||
sDxeCount++;
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 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();
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
#include "taskswitch.h"
|
||||
|
||||
#include <sys/dxe.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -402,6 +403,9 @@ DXE_EXPORT_TABLE(shellExportTable)
|
|||
DXE_EXPORT(readdir)
|
||||
DXE_EXPORT(closedir)
|
||||
|
||||
// libc — filesystem
|
||||
DXE_EXPORT(stat)
|
||||
|
||||
// libc — misc
|
||||
DXE_EXPORT(qsort)
|
||||
DXE_EXPORT(rand)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "shellApp.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "platform/dvxPlatform.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
|
|
@ -33,6 +34,7 @@ static void desktopUpdate(void);
|
|||
static void idleYield(void *ctx);
|
||||
static void installCrashHandler(void);
|
||||
static void logCrash(int sig);
|
||||
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
|
||||
|
||||
// ============================================================
|
||||
// crashHandler — catch page faults and other fatal signals
|
||||
|
|
@ -130,6 +132,12 @@ static void logCrash(int sig) {
|
|||
}
|
||||
|
||||
|
||||
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData) {
|
||||
(void)userData;
|
||||
shellLog(" %ldx%ld %ldbpp", (long)w, (long)h, (long)bpp);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// shellLog — append a line to DVX.LOG
|
||||
// ============================================================
|
||||
|
|
@ -179,6 +187,10 @@ int main(void) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
shellLog("Available video modes:");
|
||||
platformVideoEnumModes(logVideoMode, NULL);
|
||||
shellLog("Selected: %ldx%ld %ldbpp (pitch %ld)", (long)sCtx.display.width, (long)sCtx.display.height, (long)sCtx.display.format.bitsPerPixel, (long)sCtx.display.pitch);
|
||||
|
||||
// Initialize task system
|
||||
if (tsInit() != TS_OK) {
|
||||
shellLog("Failed to initialize task system");
|
||||
|
|
@ -259,11 +271,10 @@ int main(void) {
|
|||
tsYield();
|
||||
}
|
||||
|
||||
// Reap terminated apps
|
||||
shellReapApps(&sCtx);
|
||||
|
||||
// Notify desktop of any state changes
|
||||
desktopUpdate();
|
||||
// Reap terminated apps and notify desktop if anything changed
|
||||
if (shellReapApps(&sCtx)) {
|
||||
desktopUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
shellLog("DVX Shell shutting down...");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue