ProgMan is now it's own app. About app moved into ProgMan. Minor API improvements.

This commit is contained in:
Scott Duensing 2026-03-17 16:11:31 -05:00
parent 76030270f9
commit 78302c26d2
31 changed files with 716 additions and 164 deletions

View file

@ -10,17 +10,17 @@ OBJDIR = ../obj/apps
BINDIR = ../bin/apps BINDIR = ../bin/apps
# App definitions: each is a subdir with a single .c file # App definitions: each is a subdir with a single .c file
APPS = about notepad clock APPS = progman notepad clock
.PHONY: all clean $(APPS) .PHONY: all clean $(APPS)
all: $(APPS) all: $(APPS)
about: $(BINDIR)/about.app progman: $(BINDIR)/progman.app
notepad: $(BINDIR)/notepad.app notepad: $(BINDIR)/notepad.app
clock: $(BINDIR)/clock.app clock: $(BINDIR)/clock.app
$(BINDIR)/about.app: $(OBJDIR)/about.o | $(BINDIR) $(BINDIR)/progman.app: $(OBJDIR)/progman.o | $(BINDIR)
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(BINDIR)/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR) $(BINDIR)/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR)
@ -29,7 +29,7 @@ $(BINDIR)/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR)
$(BINDIR)/clock.app: $(OBJDIR)/clock.o | $(BINDIR) $(BINDIR)/clock.app: $(OBJDIR)/clock.o | $(BINDIR)
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $<
$(OBJDIR)/about.o: about/about.c | $(OBJDIR) $(OBJDIR)/progman.o: progman/progman.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/notepad.o: notepad/notepad.c | $(OBJDIR) $(OBJDIR)/notepad.o: notepad/notepad.c | $(OBJDIR)
@ -45,7 +45,7 @@ $(BINDIR):
mkdir -p $(BINDIR) mkdir -p $(BINDIR)
# Dependencies # Dependencies
$(OBJDIR)/about.o: about/about.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvxshell/shellApp.h $(OBJDIR)/progman.o: progman/progman.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h
$(OBJDIR)/notepad.o: notepad/notepad.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h $(OBJDIR)/notepad.o: notepad/notepad.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h
$(OBJDIR)/clock.o: clock/clock.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h ../tasks/taskswitch.h $(OBJDIR)/clock.o: clock/clock.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h ../tasks/taskswitch.h

View file

@ -1,99 +0,0 @@
// about.c — "About DVX Shell" sample DXE application (callback-only)
//
// Demonstrates a simple callback-only app: creates a window with widgets,
// registers callbacks, and returns. The shell event loop handles the rest.
#include "dvxApp.h"
#include "dvxWidget.h"
#include "shellApp.h"
#include <stdint.h>
#include <stdbool.h>
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
static void onClose(WindowT *win);
static void onOkClick(WidgetT *w);
// ============================================================
// App state
// ============================================================
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
// ============================================================
// App descriptor (required DXE export)
// ============================================================
AppDescriptorT appDescriptor = {
.name = "About",
.hasMainLoop = false,
.stackSize = 0,
.priority = TS_PRIORITY_NORMAL
};
// ============================================================
// Callbacks
// ============================================================
static void onClose(WindowT *win) {
dvxDestroyWindow(sCtx->shellCtx, win);
sWin = NULL;
}
static void onOkClick(WidgetT *w) {
(void)w;
if (sWin) {
dvxDestroyWindow(sCtx->shellCtx, sWin);
sWin = NULL;
}
}
// ============================================================
// Entry point
// ============================================================
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
AppContextT *ac = ctx->shellCtx;
int32_t screenW = ac->display.width;
int32_t screenH = ac->display.height;
int32_t winW = 280;
int32_t winH = 200;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 3;
sWin = dvxCreateWindow(ac, "About DVX Shell", winX, winY, winW, winH, false);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
WidgetT *root = wgtInitWindow(ac, sWin);
wgtLabel(root, "DVX Shell 1.0");
wgtHSeparator(root);
wgtLabel(root, "A DOS Visual eXecutive desktop shell for DJGPP/DPMI.");
wgtLabel(root, "Using DXE3 dynamic loading for application modules.");
wgtSpacer(root);
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignCenterE;
WidgetT *okBtn = wgtButton(btnRow, "OK");
okBtn->onClick = onOkClick;
okBtn->prefW = wgtPixels(80);
dvxFitWindow(ac, sWin);
wgtInvalidate(root);
return 0;
}

534
apps/progman/progman.c Normal file
View file

@ -0,0 +1,534 @@
// progman.c — Program Manager application for DVX Shell
//
// Displays a grid of available apps from the apps/ directory.
// Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc).
// This is a callback-only DXE app: creates windows, registers callbacks,
// and returns.
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "shellApp.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
// ============================================================
// Constants
// ============================================================
#define MAX_APP_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
} AppEntryT;
static DxeAppContextT *sCtx = NULL;
static AppContextT *sAc = NULL;
static int32_t sMyAppId = 0;
static WindowT *sPmWindow = NULL;
static WidgetT *sStatusLabel = NULL;
static AppEntryT sAppFiles[MAX_APP_FILES];
static int32_t sAppCount = 0;
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
static void buildPmWindow(void);
static void desktopUpdate(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);
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Program Manager",
.hasMainLoop = false,
.stackSize = 0,
.priority = TS_PRIORITY_NORMAL
};
// ============================================================
// Static functions (alphabetical)
// ============================================================
static void buildPmWindow(void) {
int32_t screenW = sAc->display.width;
int32_t screenH = sAc->display.height;
int32_t winW = 440;
int32_t winH = 340;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 4;
sPmWindow = dvxCreateWindow(sAc, "Program Manager", winX, winY, winW, winH, true);
if (!sPmWindow) {
return;
}
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(sAc, sPmWindow);
// App button grid in a frame
WidgetT *appFrame = wgtFrame(root, "Applications");
appFrame->weight = 100;
if (sAppCount == 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 < sAppCount; i++) {
if (i % PM_GRID_COLS == 0) {
hbox = wgtHBox(appFrame);
hbox->spacing = wgtPixels(8);
row++;
}
WidgetT *btn = wgtButton(hbox, sAppFiles[i].name);
btn->prefW = wgtPixels(PM_BTN_W);
btn->prefH = wgtPixels(PM_BTN_H);
btn->userData = &sAppFiles[i];
btn->onDblClick = onAppButtonClick;
btn->onClick = onAppButtonClick;
}
(void)row;
}
// Status bar
WidgetT *statusBar = wgtStatusBar(root);
sStatusLabel = wgtLabel(statusBar, "");
sStatusLabel->weight = 100;
updateStatusText();
dvxFitWindow(sAc, sPmWindow);
}
static void buildTaskManager(void) {
if (sTmWindow) {
// Already open — just raise it
for (int32_t i = 0; i < sAc->stack.count; i++) {
if (sAc->stack.windows[i] == sTmWindow) {
wmRaiseWindow(&sAc->stack, &sAc->dirty, i);
wmSetFocus(&sAc->stack, &sAc->dirty, sAc->stack.count - 1);
break;
}
}
return;
}
int32_t screenW = sAc->display.width;
int32_t screenH = sAc->display.height;
int32_t winW = 300;
int32_t winH = 280;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 3;
sTmWindow = dvxCreateWindow(sAc, "Task Manager", winX, winY, winW, winH, true);
if (!sTmWindow) {
return;
}
sTmWindow->onClose = onTmClose;
WidgetT *root = wgtInitWindow(sAc, 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(sAc, sTmWindow);
}
static void desktopUpdate(void) {
updateStatusText();
if (sTmWindow) {
refreshTaskList();
}
}
static void onAppButtonClick(WidgetT *w) {
AppEntryT *entry = (AppEntryT *)w->userData;
if (!entry) {
return;
}
shellLoadApp(sAc, entry->path);
updateStatusText();
}
static void onPmClose(WindowT *win) {
(void)win;
// Confirm exit
int32_t result = dvxMessageBox(sAc, "Exit Shell", "Are you sure you want to exit DVX Shell?", MB_YESNO | MB_ICONQUESTION);
if (result == ID_YES) {
dvxQuit(sAc);
}
}
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(sAc, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
shellLoadApp(sAc, path);
updateStatusText();
}
}
break;
case CMD_EXIT:
onPmClose(sPmWindow);
break;
case CMD_CASCADE:
dvxCascadeWindows(sAc);
break;
case CMD_TILE:
dvxTileWindows(sAc);
break;
case CMD_TILE_H:
dvxTileWindowsH(sAc);
break;
case CMD_TILE_V:
dvxTileWindowsV(sAc);
break;
case CMD_ABOUT:
showAboutDialog();
break;
case CMD_TASK_MGR:
buildTaskManager();
break;
}
}
static void onTmClose(WindowT *win) {
sTmListBox = NULL;
sTmWindow = NULL;
dvxDestroyWindow(sAc, 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 (skip our own appId)
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (i == sMyAppId) {
continue;
}
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
shellForceKillApp(sAc, 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 (skip our own appId), find topmost window
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (i == sMyAppId) {
continue;
}
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
// Find the topmost window for this app
for (int32_t j = sAc->stack.count - 1; j >= 0; j--) {
WindowT *win = sAc->stack.windows[j];
if (win->appId == i) {
if (win->minimized) {
wmRestoreMinimized(&sAc->stack, &sAc->dirty, win);
}
wmRaiseWindow(&sAc->stack, &sAc->dirty, j);
wmSetFocus(&sAc->stack, &sAc->dirty, sAc->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++) {
if (i == sMyAppId) {
continue;
}
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);
}
static void scanAppsDir(void) {
DIR *dir = opendir("apps");
if (!dir) {
shellLog("Progman: apps/ directory not found");
return;
}
sAppCount = 0;
struct dirent *ent;
while ((ent = readdir(dir)) != NULL && sAppCount < MAX_APP_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;
}
// Skip ourselves
if (strcmp(ent->d_name, "progman.app") == 0 || strcmp(ent->d_name, "PROGMAN.APP") == 0) {
continue;
}
AppEntryT *entry = &sAppFiles[sAppCount];
// 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);
sAppCount++;
}
closedir(dir);
shellLog("Progman: found %ld app(s)", (long)sAppCount);
}
static void showAboutDialog(void) {
dvxMessageBox(sAc, "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];
// Subtract 1 to exclude ourselves from the count
int32_t count = shellRunningAppCount() - 1;
if (count < 0) {
count = 0;
}
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);
}
// ============================================================
// Entry point
// ============================================================
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
sAc = ctx->shellCtx;
sMyAppId = ctx->appId;
scanAppsDir();
buildPmWindow();
// Register for state change notifications from the shell
shellRegisterDesktopUpdate(desktopUpdate);
return 0;
}

View file

@ -528,7 +528,6 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
for (WidgetT *c = target->parent->firstChild; c; c = c->nextSibling) { for (WidgetT *c = target->parent->firstChild; c; c = c->nextSibling) {
if (c == target) { if (c == target) {
wgtTabControlSetActive(target->parent, tabIdx); wgtTabControlSetActive(target->parent, tabIdx);
wgtInvalidate(win->widgetRoot);
break; break;
} }
@ -1085,6 +1084,18 @@ WindowT *dvxCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t
} }
// ============================================================
// dvxCreateWindowCentered
// ============================================================
WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) {
int32_t x = (ctx->display.width - w) / 2;
int32_t y = (ctx->display.height - h) / 2;
return dvxCreateWindow(ctx, title, x, y, w, h, resizable);
}
// ============================================================ // ============================================================
// dvxAddAccel // dvxAddAccel
// ============================================================ // ============================================================
@ -1235,6 +1246,9 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) {
// Dirty new position // Dirty new position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h); dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Invalidate widget tree so it repaints at the new size
wgtInvalidate(win->widgetRoot);
} }

View file

@ -71,6 +71,9 @@ bool dvxUpdate(AppContextT *ctx);
// Create a window // Create a window
WindowT *dvxCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable); WindowT *dvxCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);
// Create a centered window (position computed from screen size)
WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable);
// Destroy a window // Destroy a window
void dvxDestroyWindow(AppContextT *ctx, WindowT *win); void dvxDestroyWindow(AppContextT *ctx, WindowT *win);

View file

@ -704,7 +704,6 @@ static void fdLoadDir(void) {
// Update path display // Update path display
wgtSetText(sFd.pathInput, sFd.curDir); wgtSetText(sFd.pathInput, sFd.curDir);
wgtInvalidate(sFd.fileList);
} }
@ -817,7 +816,6 @@ static void fdOnListClick(WidgetT *w) {
if (!sFd.entryIsDir[sel]) { if (!sFd.entryIsDir[sel]) {
wgtSetText(sFd.nameInput, sFd.entryNames[sel]); wgtSetText(sFd.nameInput, sFd.entryNames[sel]);
wgtInvalidatePaint(sFd.nameInput);
} }
} }
@ -891,7 +889,6 @@ static void fdOnOk(WidgetT *w) {
fdNavigate(dirName); fdNavigate(dirName);
wgtSetText(sFd.nameInput, ""); wgtSetText(sFd.nameInput, "");
wgtInvalidatePaint(sFd.nameInput);
return; return;
} }
@ -910,7 +907,6 @@ static void fdOnOk(WidgetT *w) {
if (stat(testPath, &st) == 0 && S_ISDIR(st.st_mode)) { if (stat(testPath, &st) == 0 && S_ISDIR(st.st_mode)) {
fdNavigate(testPath); fdNavigate(testPath);
wgtSetText(sFd.nameInput, ""); wgtSetText(sFd.nameInput, "");
wgtInvalidatePaint(sFd.nameInput);
return; return;
} }

View file

@ -726,6 +726,9 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
// Operations // Operations
// ============================================================ // ============================================================
// Get the AppContextT from any widget (walks to root)
struct AppContextT *wgtGetContext(const WidgetT *w);
// Mark a widget (and ancestors) for relayout and repaint // Mark a widget (and ancestors) for relayout and repaint
void wgtInvalidate(WidgetT *w); void wgtInvalidate(WidgetT *w);

View file

@ -1377,7 +1377,7 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
return 0; return 0;
} }
AppContextT *ctx = (AppContextT *)win->widgetRoot->userData; AppContextT *ctx = wgtGetContext(win->widgetRoot);
if (!ctx) { if (!ctx) {
return 0; return 0;
} }

View file

@ -72,6 +72,8 @@ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
if (w->as.comboBox.selectedIdx >= count) { if (w->as.comboBox.selectedIdx >= count) {
w->as.comboBox.selectedIdx = -1; w->as.comboBox.selectedIdx = -1;
} }
wgtInvalidate(w);
} }
@ -94,6 +96,8 @@ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
w->as.comboBox.selStart = -1; w->as.comboBox.selStart = -1;
w->as.comboBox.selEnd = -1; w->as.comboBox.selEnd = -1;
} }
wgtInvalidatePaint(w);
} }

View file

@ -56,6 +56,8 @@ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
if (w->as.dropdown.selectedIdx >= count) { if (w->as.dropdown.selectedIdx >= count) {
w->as.dropdown.selectedIdx = -1; w->as.dropdown.selectedIdx = -1;
} }
wgtInvalidate(w);
} }
@ -67,6 +69,7 @@ void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, WidgetDropdownE); VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
w->as.dropdown.selectedIdx = idx; w->as.dropdown.selectedIdx = idx;
wgtInvalidatePaint(w);
} }

View file

@ -579,7 +579,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
// Dirty the window content area on screen so compositor redraws it // Dirty the window content area on screen so compositor redraws it
if (win->widgetRoot) { if (win->widgetRoot) {
AppContextT *ctx = (AppContextT *)win->widgetRoot->userData; AppContextT *ctx = wgtGetContext(win->widgetRoot);
if (ctx) { if (ctx) {
dvxInvalidateWindow(ctx, win); dvxInvalidateWindow(ctx, win);

View file

@ -39,13 +39,7 @@ WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) {
} }
// Find the AppContextT to get display format // Find the AppContextT to get display format
WidgetT *root = parent; AppContextT *ctx = wgtGetContext(parent);
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) { if (!ctx) {
return NULL; return NULL;
@ -107,6 +101,7 @@ void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int3
w->as.image.imgW = imgW; w->as.image.imgW = imgW;
w->as.image.imgH = imgH; w->as.image.imgH = imgH;
w->as.image.imgPitch = pitch; w->as.image.imgPitch = pitch;
wgtInvalidate(w);
} }

View file

@ -40,6 +40,7 @@ void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH
w->as.imageButton.imgW = imgW; w->as.imageButton.imgW = imgW;
w->as.imageButton.imgH = imgH; w->as.imageButton.imgH = imgH;
w->as.imageButton.imgPitch = pitch; w->as.imageButton.imgPitch = pitch;
wgtInvalidate(w);
} }

View file

@ -45,7 +45,7 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
return; return;
} }
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t innerH = w->h - LISTBOX_BORDER * 2; int32_t innerH = w->h - LISTBOX_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight; int32_t visibleRows = innerH / font->charHeight;
@ -128,6 +128,7 @@ void wgtListBoxClearSelection(WidgetT *w) {
} }
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount); memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
wgtInvalidatePaint(w);
} }
@ -171,6 +172,7 @@ void wgtListBoxSelectAll(WidgetT *w) {
} }
memset(w->as.listBox.selBits, 1, w->as.listBox.itemCount); memset(w->as.listBox.selBits, 1, w->as.listBox.itemCount);
wgtInvalidatePaint(w);
} }
@ -186,6 +188,7 @@ void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
} }
w->as.listBox.selBits[idx] = selected ? 1 : 0; w->as.listBox.selBits[idx] = selected ? 1 : 0;
wgtInvalidatePaint(w);
} }
@ -229,6 +232,8 @@ void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) { if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1; w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
} }
wgtInvalidate(w);
} }
@ -278,6 +283,8 @@ void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
w->as.listBox.selBits[idx] = 1; w->as.listBox.selBits[idx] = 1;
} }
} }
wgtInvalidatePaint(w);
} }
@ -315,7 +322,6 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Ctrl+A — select all (multi-select only) // Ctrl+A — select all (multi-select only)
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) { if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
wgtListBoxSelectAll(w); wgtListBoxSelectAll(w);
wgtInvalidatePaint(w);
return; return;
} }
@ -334,7 +340,7 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
return; return;
} }
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight; int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight;

View file

@ -179,7 +179,7 @@ bool widgetListViewColBorderHit(const WidgetT *w, int32_t vx, int32_t vy) {
return false; return false;
} }
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t headerH = font->charHeight + 4; int32_t headerH = font->charHeight + 4;
int32_t headerTop = w->y + LISTVIEW_BORDER; int32_t headerTop = w->y + LISTVIEW_BORDER;
@ -273,6 +273,7 @@ void wgtListViewClearSelection(WidgetT *w) {
} }
memset(w->as.listView->selBits, 0, w->as.listView->rowCount); memset(w->as.listView->selBits, 0, w->as.listView->rowCount);
wgtInvalidatePaint(w);
} }
@ -305,6 +306,7 @@ void wgtListViewSelectAll(WidgetT *w) {
} }
memset(w->as.listView->selBits, 1, w->as.listView->rowCount); memset(w->as.listView->selBits, 1, w->as.listView->rowCount);
wgtInvalidatePaint(w);
} }
@ -322,6 +324,7 @@ void wgtListViewSetColumns(WidgetT *w, const ListViewColT *cols, int32_t count)
w->as.listView->cols = cols; w->as.listView->cols = cols;
w->as.listView->colCount = count; w->as.listView->colCount = count;
w->as.listView->totalColW = 0; w->as.listView->totalColW = 0;
wgtInvalidate(w);
} }
@ -361,6 +364,8 @@ void wgtListViewSetData(WidgetT *w, const char **cellData, int32_t rowCount) {
if (w->as.listView->selBits && w->as.listView->selectedIdx >= 0) { if (w->as.listView->selBits && w->as.listView->selectedIdx >= 0) {
w->as.listView->selBits[w->as.listView->selectedIdx] = 1; w->as.listView->selBits[w->as.listView->selectedIdx] = 1;
} }
wgtInvalidate(w);
} }
@ -387,6 +392,7 @@ void wgtListViewSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
} }
w->as.listView->selBits[idx] = selected ? 1 : 0; w->as.listView->selBits[idx] = selected ? 1 : 0;
wgtInvalidatePaint(w);
} }
@ -445,6 +451,8 @@ void wgtListViewSetSelected(WidgetT *w, int32_t idx) {
w->as.listView->selBits[idx] = 1; w->as.listView->selBits[idx] = 1;
} }
} }
wgtInvalidatePaint(w);
} }
@ -458,6 +466,7 @@ void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir) {
w->as.listView->sortCol = col; w->as.listView->sortCol = col;
w->as.listView->sortDir = dir; w->as.listView->sortDir = dir;
listViewBuildSortIndex(w); listViewBuildSortIndex(w);
wgtInvalidatePaint(w);
} }
@ -492,7 +501,6 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Ctrl+A — select all (multi-select only) // Ctrl+A — select all (multi-select only)
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) { if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
wgtListViewSelectAll(w); wgtListViewSelectAll(w);
wgtInvalidatePaint(w);
return; return;
} }
@ -530,7 +538,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
} }
// Compute visible rows for page up/down // Compute visible rows for page up/down
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t headerH = font->charHeight + 4; int32_t headerH = font->charHeight + 4;
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH; int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;

View file

@ -216,6 +216,25 @@ void wgtSetName(WidgetT *w, const char *name) {
} }
// ============================================================
// wgtGetContext
// ============================================================
AppContextT *wgtGetContext(const WidgetT *w) {
if (!w) {
return NULL;
}
const WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
return (AppContextT *)root->userData;
}
// ============================================================ // ============================================================
// wgtGetText // wgtGetText
// ============================================================ // ============================================================
@ -382,6 +401,8 @@ void wgtSetText(WidgetT *w, const char *text) {
if (w->wclass && w->wclass->setText) { if (w->wclass && w->wclass->setText) {
w->wclass->setText(w, text); w->wclass->setText(w, text);
} }
wgtInvalidate(w);
} }
@ -392,5 +413,6 @@ void wgtSetText(WidgetT *w, const char *text) {
void wgtSetVisible(WidgetT *w, bool visible) { void wgtSetVisible(WidgetT *w, bool visible) {
if (w) { if (w) {
w->visible = visible; w->visible = visible;
wgtInvalidate(w);
} }
} }

View file

@ -64,6 +64,7 @@ void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
} }
w->as.progressBar.value = value; w->as.progressBar.value = value;
wgtInvalidatePaint(w);
} }

View file

@ -314,7 +314,7 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) { void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod; (void)mod;
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t contentMinW; int32_t contentMinW;

View file

@ -49,6 +49,7 @@ void wgtSliderSetValue(WidgetT *w, int32_t value) {
} }
w->as.slider.value = value; w->as.slider.value = value;
wgtInvalidatePaint(w);
} }

View file

@ -474,6 +474,7 @@ void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
w->as.spinner.minValue = minVal; w->as.spinner.minValue = minVal;
w->as.spinner.maxValue = maxVal; w->as.spinner.maxValue = maxVal;
spinnerClampAndFormat(w); spinnerClampAndFormat(w);
wgtInvalidatePaint(w);
} }
@ -497,4 +498,5 @@ void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
w->as.spinner.value = value; w->as.spinner.value = value;
spinnerClampAndFormat(w); spinnerClampAndFormat(w);
wgtInvalidatePaint(w);
} }

View file

@ -332,4 +332,5 @@ int32_t wgtSplitterGetPos(const WidgetT *w) {
void wgtSplitterSetPos(WidgetT *w, int32_t pos) { void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
w->as.splitter.dividerPos = pos; w->as.splitter.dividerPos = pos;
wgtInvalidate(w);
} }

View file

@ -151,6 +151,7 @@ void wgtTabControlSetActive(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, WidgetTabControlE); VALIDATE_WIDGET_VOID(w, WidgetTabControlE);
w->as.tabControl.activeTab = idx; w->as.tabControl.activeTab = idx;
wgtInvalidate(w);
} }

View file

@ -200,7 +200,7 @@ void clearOtherSelections(WidgetT *except) {
return; return;
} }
AppContextT *ctx = (AppContextT *)except->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(except);
if (!ctx) { if (!ctx) {
return; return;
@ -753,7 +753,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) {
done: done:
// Adjust scroll // Adjust scroll
{ {
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t visibleChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth; int32_t visibleChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth;
@ -962,7 +962,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
int32_t *pSC = &w->as.textArea.selCursor; int32_t *pSC = &w->as.textArea.selCursor;
bool shift = (mod & KEY_MOD_SHIFT) != 0; bool shift = (mod & KEY_MOD_SHIFT) != 0;
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
int32_t visCols = innerW / font->charWidth; int32_t visCols = innerW / font->charWidth;
@ -2692,7 +2692,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
adjustScroll: adjustScroll:
// Adjust scroll offset to keep cursor visible // Adjust scroll offset to keep cursor visible
{ {
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t fieldW = w->w; int32_t fieldW = w->w;

View file

@ -571,6 +571,7 @@ void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) {
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE); VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
w->as.treeItem.expanded = expanded; w->as.treeItem.expanded = expanded;
wgtInvalidate(w);
} }
@ -614,6 +615,8 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) {
item->as.treeItem.selected = true; item->as.treeItem.selected = true;
w->as.treeView.anchorItem = item; w->as.treeView.anchorItem = item;
} }
wgtInvalidatePaint(w);
} }
@ -670,6 +673,7 @@ void wgtTreeItemSetSelected(WidgetT *w, bool selected) {
VALIDATE_WIDGET_VOID(w, WidgetTreeItemE); VALIDATE_WIDGET_VOID(w, WidgetTreeItemE);
w->as.treeItem.selected = selected; w->as.treeItem.selected = selected;
wgtInvalidatePaint(w);
} }
@ -814,7 +818,7 @@ void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
sel = w->as.treeView.selectedItem; sel = w->as.treeView.selectedItem;
if (sel) { if (sel) {
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t itemY = treeItemYPos(w, sel, font); int32_t itemY = treeItemYPos(w, sel, font);

View file

@ -282,7 +282,6 @@ static void onOkClick(WidgetT *w) {
if (status) { if (status) {
wgtSetText(status, "Button clicked!"); wgtSetText(status, "Button clicked!");
wgtInvalidate(status);
} }
} }
@ -463,7 +462,6 @@ static void onToolbarClick(WidgetT *w) {
if (status) { if (status) {
wgtSetText(status, wgtGetText(w)); wgtSetText(status, wgtGetText(w));
wgtInvalidate(status);
} }
} }
@ -1039,7 +1037,6 @@ static void setupTerminalWindow(AppContextT *ctx) {
wgtLabel(sb, "80x25 [Local]"); wgtLabel(sb, "80x25 [Local]");
dvxFitWindow(ctx, win); dvxFitWindow(ctx, win);
wgtInvalidate(root);
dvxMinimizeWindow(ctx, win); dvxMinimizeWindow(ctx, win);
} }

View file

@ -12,7 +12,7 @@ OBJDIR = ../obj/dvxshell
BINDIR = ../bin BINDIR = ../bin
LIBDIR = ../lib LIBDIR = ../lib
SRCS = shellMain.c shellApp.c shellExport.c shellDesktop.c SRCS = shellMain.c shellApp.c shellExport.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(BINDIR)/dvxshell.exe TARGET = $(BINDIR)/dvxshell.exe
@ -43,7 +43,5 @@ $(BINDIR):
$(OBJDIR)/shellMain.o: shellMain.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h $(OBJDIR)/shellMain.o: shellMain.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h
$(OBJDIR)/shellApp.o: shellApp.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h $(OBJDIR)/shellApp.o: shellApp.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h
$(OBJDIR)/shellExport.o: shellExport.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvx/dvxWm.h ../tasks/taskswitch.h $(OBJDIR)/shellExport.o: shellExport.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvx/dvxWm.h ../tasks/taskswitch.h
$(OBJDIR)/shellDesktop.o: shellDesktop.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h
clean: clean:
rm -rf $(OBJDIR) $(TARGET) rm -rf $(OBJDIR) $(TARGET)

View file

@ -106,20 +106,14 @@ void shellLog(const char *fmt, ...);
void shellExportInit(void); void shellExportInit(void);
// ============================================================ // ============================================================
// Program Manager / Desktop // Desktop callback
// ============================================================ // ============================================================
// Initialize the Program Manager window // Default desktop app path
void shellDesktopInit(AppContextT *ctx); #define SHELL_DESKTOP_APP "apps/progman.app"
// Update status bar with running app count // Register a callback for app state changes (load, reap, crash).
void shellDesktopUpdateStatus(void); // The desktop app calls this during appMain to receive notifications.
void shellRegisterDesktopUpdate(void (*updateFn)(void));
// ============================================================
// Task Manager
// ============================================================
// Open the Task Manager dialog (Ctrl+Esc)
void shellTaskManager(AppContextT *ctx);
#endif // SHELL_APP_H #endif // SHELL_APP_H

View file

@ -147,7 +147,6 @@ static void buildPmWindow(void) {
updateStatusText(); updateStatusText();
dvxFitWindow(sCtx, sPmWindow); dvxFitWindow(sCtx, sPmWindow);
wgtInvalidate(root);
} }
@ -202,7 +201,6 @@ static void buildTaskManager(void) {
refreshTaskList(); refreshTaskList();
dvxFitWindow(sCtx, sTmWindow); dvxFitWindow(sCtx, sTmWindow);
wgtInvalidate(root);
} }
@ -385,7 +383,6 @@ static void refreshTaskList(void) {
} }
wgtListBoxSetItems(sTmListBox, items, count); wgtListBoxSetItems(sTmListBox, items, count);
wgtInvalidatePaint(sTmListBox);
} }
@ -464,7 +461,6 @@ static void updateStatusText(void) {
} }
wgtSetText(sStatusLabel, buf); wgtSetText(sStatusLabel, buf);
wgtInvalidate(sStatusLabel);
} }
// ============================================================ // ============================================================

View file

@ -13,6 +13,7 @@
#include "taskswitch.h" #include "taskswitch.h"
#include <sys/dxe.h> #include <sys/dxe.h>
#include <dirent.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
@ -25,6 +26,7 @@
static void shellRegisterExports(void); static void shellRegisterExports(void);
static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable); static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);
static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable);
static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win); static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
// ============================================================ // ============================================================
@ -42,6 +44,21 @@ static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32
} }
// ============================================================
// Wrapper: dvxCreateWindowCentered — stamps win->appId
// ============================================================
static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) {
WindowT *win = dvxCreateWindowCentered(ctx, title, w, h, resizable);
if (win) {
win->appId = sCurrentAppId;
}
return win;
}
// ============================================================ // ============================================================
// Wrapper: dvxDestroyWindow — checks for last-window reap // Wrapper: dvxDestroyWindow — checks for last-window reap
// ============================================================ // ============================================================
@ -90,6 +107,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(dvxInit) DXE_EXPORT(dvxInit)
DXE_EXPORT(dvxShutdown) DXE_EXPORT(dvxShutdown)
DXE_EXPORT(dvxUpdate) DXE_EXPORT(dvxUpdate)
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
DXE_EXPORT(dvxFitWindow) DXE_EXPORT(dvxFitWindow)
DXE_EXPORT(dvxInvalidateRect) DXE_EXPORT(dvxInvalidateRect)
DXE_EXPORT(dvxInvalidateWindow) DXE_EXPORT(dvxInvalidateWindow)
@ -287,6 +305,7 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wgtGetText) DXE_EXPORT(wgtGetText)
DXE_EXPORT(wgtSetEnabled) DXE_EXPORT(wgtSetEnabled)
DXE_EXPORT(wgtSetVisible) DXE_EXPORT(wgtSetVisible)
DXE_EXPORT(wgtGetContext)
DXE_EXPORT(wgtSetName) DXE_EXPORT(wgtSetName)
DXE_EXPORT(wgtFind) DXE_EXPORT(wgtFind)
DXE_EXPORT(wgtDestroy) DXE_EXPORT(wgtDestroy)
@ -312,8 +331,18 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(tsCurrentId) DXE_EXPORT(tsCurrentId)
DXE_EXPORT(tsActiveCount) DXE_EXPORT(tsActiveCount)
// Shell logging // dvxWm.h — direct window management
DXE_EXPORT(wmRaiseWindow)
DXE_EXPORT(wmSetFocus)
DXE_EXPORT(wmRestoreMinimized)
// Shell API
DXE_EXPORT(shellLog) DXE_EXPORT(shellLog)
DXE_EXPORT(shellLoadApp)
DXE_EXPORT(shellGetApp)
DXE_EXPORT(shellForceKillApp)
DXE_EXPORT(shellRunningAppCount)
DXE_EXPORT(shellRegisterDesktopUpdate)
// libc — memory // libc — memory
DXE_EXPORT(malloc) DXE_EXPORT(malloc)
@ -364,6 +393,11 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(time) DXE_EXPORT(time)
DXE_EXPORT(localtime) DXE_EXPORT(localtime)
// libc — directory
DXE_EXPORT(opendir)
DXE_EXPORT(readdir)
DXE_EXPORT(closedir)
// libc — misc // libc — misc
DXE_EXPORT(qsort) DXE_EXPORT(qsort)
DXE_EXPORT(rand) DXE_EXPORT(rand)

View file

@ -1,8 +1,8 @@
// shellMain.c — DVX Shell entry point and main loop // shellMain.c — DVX Shell entry point and main loop
// //
// Initializes the GUI, task system, DXE export table, and Program Manager. // Initializes the GUI, task system, DXE export table, and loads
// Runs the cooperative main loop, yielding to app tasks and reaping // the desktop app. Runs the cooperative main loop, yielding to
// terminated apps each frame. // app tasks and reaping terminated apps each frame.
#include "shellApp.h" #include "shellApp.h"
#include "dvxDialog.h" #include "dvxDialog.h"
@ -22,12 +22,14 @@ static AppContextT sCtx;
static jmp_buf sCrashJmp; static jmp_buf sCrashJmp;
static volatile int sCrashSignal = 0; static volatile int sCrashSignal = 0;
static FILE *sLogFile = NULL; static FILE *sLogFile = NULL;
static void (*sDesktopUpdateFn)(void) = NULL;
// ============================================================ // ============================================================
// Prototypes // Prototypes
// ============================================================ // ============================================================
static void crashHandler(int sig); static void crashHandler(int sig);
static void desktopUpdate(void);
static void idleYield(void *ctx); static void idleYield(void *ctx);
static void installCrashHandler(void); static void installCrashHandler(void);
static void logCrash(int sig); static void logCrash(int sig);
@ -47,6 +49,17 @@ static void crashHandler(int sig) {
} }
// ============================================================
// desktopUpdate — notify desktop app of state change
// ============================================================
static void desktopUpdate(void) {
if (sDesktopUpdateFn) {
sDesktopUpdateFn();
}
}
// ============================================================ // ============================================================
// idleYield — called when no dirty rects need compositing // idleYield — called when no dirty rects need compositing
// ============================================================ // ============================================================
@ -136,6 +149,15 @@ void shellLog(const char *fmt, ...) {
} }
// ============================================================
// shellRegisterDesktopUpdate
// ============================================================
void shellRegisterDesktopUpdate(void (*updateFn)(void)) {
sDesktopUpdateFn = updateFn;
}
// ============================================================ // ============================================================
// main // main
// ============================================================ // ============================================================
@ -182,8 +204,20 @@ int main(void) {
sCtx.idleCallback = idleYield; sCtx.idleCallback = idleYield;
sCtx.idleCtx = &sCtx; sCtx.idleCtx = &sCtx;
// Create the Program Manager window // Load the desktop app
shellDesktopInit(&sCtx); int32_t desktopId = shellLoadApp(&sCtx, SHELL_DESKTOP_APP);
if (desktopId < 0) {
shellLog("Failed to load desktop app '%s'", SHELL_DESKTOP_APP);
tsShutdown();
dvxShutdown(&sCtx);
if (sLogFile) {
fclose(sLogFile);
}
return 1;
}
// Install crash handler after everything is initialized // Install crash handler after everything is initialized
installCrashHandler(); installCrashHandler();
@ -213,7 +247,7 @@ int main(void) {
sCurrentAppId = 0; sCurrentAppId = 0;
sCrashSignal = 0; sCrashSignal = 0;
shellDesktopUpdateStatus(); desktopUpdate();
} }
// Main loop // Main loop
@ -228,8 +262,8 @@ int main(void) {
// Reap terminated apps // Reap terminated apps
shellReapApps(&sCtx); shellReapApps(&sCtx);
// Update status display // Notify desktop of any state changes
shellDesktopUpdateStatus(); desktopUpdate();
} }
shellLog("DVX Shell shutting down..."); shellLog("DVX Shell shutting down...");

View file

@ -265,7 +265,6 @@ int main(int argc, char *argv[]) {
// Fit window to widget tree // Fit window to widget tree
dvxFitWindow(&ctx, win); dvxFitWindow(&ctx, win);
wgtInvalidate(root);
// Poll serial during idle instead of yielding CPU // Poll serial during idle instead of yielding CPU
ctx.idleCallback = idlePoll; ctx.idleCallback = idlePoll;