diff --git a/apps/Makefile b/apps/Makefile index 7a158ae..cef0bd5 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -10,17 +10,17 @@ OBJDIR = ../obj/apps BINDIR = ../bin/apps # App definitions: each is a subdir with a single .c file -APPS = about notepad clock +APPS = progman notepad clock .PHONY: all clean $(APPS) all: $(APPS) -about: $(BINDIR)/about.app +progman: $(BINDIR)/progman.app notepad: $(BINDIR)/notepad.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 $< $(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) $(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 $@ $< $(OBJDIR)/notepad.o: notepad/notepad.c | $(OBJDIR) @@ -45,7 +45,7 @@ $(BINDIR): mkdir -p $(BINDIR) # 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)/clock.o: clock/clock.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h ../tasks/taskswitch.h diff --git a/apps/about/about.c b/apps/about/about.c deleted file mode 100644 index 3cb2a5b..0000000 --- a/apps/about/about.c +++ /dev/null @@ -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 -#include - -// ============================================================ -// 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; -} diff --git a/apps/progman/progman.c b/apps/progman/progman.c new file mode 100644 index 0000000..7f4153f --- /dev/null +++ b/apps/progman/progman.c @@ -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 +#include +#include +#include +#include +#include + +// ============================================================ +// 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; +} diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index 254eee0..c0476a5 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -528,7 +528,6 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) { for (WidgetT *c = target->parent->firstChild; c; c = c->nextSibling) { if (c == target) { wgtTabControlSetActive(target->parent, tabIdx); - wgtInvalidate(win->widgetRoot); 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 // ============================================================ @@ -1235,6 +1246,9 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) { // Dirty new position dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h); + + // Invalidate widget tree so it repaints at the new size + wgtInvalidate(win->widgetRoot); } diff --git a/dvx/dvxApp.h b/dvx/dvxApp.h index af7c758..b36f2dd 100644 --- a/dvx/dvxApp.h +++ b/dvx/dvxApp.h @@ -71,6 +71,9 @@ bool dvxUpdate(AppContextT *ctx); // Create a window 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 void dvxDestroyWindow(AppContextT *ctx, WindowT *win); diff --git a/dvx/dvxDialog.c b/dvx/dvxDialog.c index c61a712..a981a66 100644 --- a/dvx/dvxDialog.c +++ b/dvx/dvxDialog.c @@ -704,7 +704,6 @@ static void fdLoadDir(void) { // Update path display wgtSetText(sFd.pathInput, sFd.curDir); - wgtInvalidate(sFd.fileList); } @@ -817,7 +816,6 @@ static void fdOnListClick(WidgetT *w) { if (!sFd.entryIsDir[sel]) { wgtSetText(sFd.nameInput, sFd.entryNames[sel]); - wgtInvalidatePaint(sFd.nameInput); } } @@ -891,7 +889,6 @@ static void fdOnOk(WidgetT *w) { fdNavigate(dirName); wgtSetText(sFd.nameInput, ""); - wgtInvalidatePaint(sFd.nameInput); return; } @@ -910,7 +907,6 @@ static void fdOnOk(WidgetT *w) { if (stat(testPath, &st) == 0 && S_ISDIR(st.st_mode)) { fdNavigate(testPath); wgtSetText(sFd.nameInput, ""); - wgtInvalidatePaint(sFd.nameInput); return; } diff --git a/dvx/dvxWidget.h b/dvx/dvxWidget.h index 786c519..8bb6ad4 100644 --- a/dvx/dvxWidget.h +++ b/dvx/dvxWidget.h @@ -726,6 +726,9 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH); // 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 void wgtInvalidate(WidgetT *w); diff --git a/dvx/widgets/widgetAnsiTerm.c b/dvx/widgets/widgetAnsiTerm.c index a047bfd..01deee4 100644 --- a/dvx/widgets/widgetAnsiTerm.c +++ b/dvx/widgets/widgetAnsiTerm.c @@ -1377,7 +1377,7 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) { return 0; } - AppContextT *ctx = (AppContextT *)win->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(win->widgetRoot); if (!ctx) { return 0; } diff --git a/dvx/widgets/widgetComboBox.c b/dvx/widgets/widgetComboBox.c index 6c11fba..29a268d 100644 --- a/dvx/widgets/widgetComboBox.c +++ b/dvx/widgets/widgetComboBox.c @@ -72,6 +72,8 @@ void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) { if (w->as.comboBox.selectedIdx >= count) { 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.selEnd = -1; } + + wgtInvalidatePaint(w); } diff --git a/dvx/widgets/widgetDropdown.c b/dvx/widgets/widgetDropdown.c index 3c4ca36..b7cbc51 100644 --- a/dvx/widgets/widgetDropdown.c +++ b/dvx/widgets/widgetDropdown.c @@ -56,6 +56,8 @@ void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) { if (w->as.dropdown.selectedIdx >= count) { w->as.dropdown.selectedIdx = -1; } + + wgtInvalidate(w); } @@ -67,6 +69,7 @@ void wgtDropdownSetSelected(WidgetT *w, int32_t idx) { VALIDATE_WIDGET_VOID(w, WidgetDropdownE); w->as.dropdown.selectedIdx = idx; + wgtInvalidatePaint(w); } diff --git a/dvx/widgets/widgetEvent.c b/dvx/widgets/widgetEvent.c index 8be0b89..3e29c3d 100644 --- a/dvx/widgets/widgetEvent.c +++ b/dvx/widgets/widgetEvent.c @@ -579,7 +579,7 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) { // Dirty the window content area on screen so compositor redraws it if (win->widgetRoot) { - AppContextT *ctx = (AppContextT *)win->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(win->widgetRoot); if (ctx) { dvxInvalidateWindow(ctx, win); diff --git a/dvx/widgets/widgetImage.c b/dvx/widgets/widgetImage.c index 59cf3a7..40d2e2a 100644 --- a/dvx/widgets/widgetImage.c +++ b/dvx/widgets/widgetImage.c @@ -39,13 +39,7 @@ WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) { } // Find the AppContextT to get display format - WidgetT *root = parent; - - while (root->parent) { - root = root->parent; - } - - AppContextT *ctx = (AppContextT *)root->userData; + AppContextT *ctx = wgtGetContext(parent); if (!ctx) { 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.imgH = imgH; w->as.image.imgPitch = pitch; + wgtInvalidate(w); } diff --git a/dvx/widgets/widgetImageButton.c b/dvx/widgets/widgetImageButton.c index 2c7f8a3..d82019d 100644 --- a/dvx/widgets/widgetImageButton.c +++ b/dvx/widgets/widgetImageButton.c @@ -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.imgH = imgH; w->as.imageButton.imgPitch = pitch; + wgtInvalidate(w); } diff --git a/dvx/widgets/widgetListBox.c b/dvx/widgets/widgetListBox.c index 58d8246..c8fc0df 100644 --- a/dvx/widgets/widgetListBox.c +++ b/dvx/widgets/widgetListBox.c @@ -45,7 +45,7 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) { return; } - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t innerH = w->h - LISTBOX_BORDER * 2; int32_t visibleRows = innerH / font->charHeight; @@ -128,6 +128,7 @@ void wgtListBoxClearSelection(WidgetT *w) { } 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); + wgtInvalidatePaint(w); } @@ -186,6 +188,7 @@ void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) { } 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) { 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; } } + + wgtInvalidatePaint(w); } @@ -315,7 +322,6 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) { // Ctrl+A — select all (multi-select only) if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) { wgtListBoxSelectAll(w); - wgtInvalidatePaint(w); return; } @@ -334,7 +340,7 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) { return; } - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t visibleRows = (w->h - LISTBOX_BORDER * 2) / font->charHeight; diff --git a/dvx/widgets/widgetListView.c b/dvx/widgets/widgetListView.c index e3420d5..fe80dee 100644 --- a/dvx/widgets/widgetListView.c +++ b/dvx/widgets/widgetListView.c @@ -179,7 +179,7 @@ bool widgetListViewColBorderHit(const WidgetT *w, int32_t vx, int32_t vy) { return false; } - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t headerH = font->charHeight + 4; 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); + wgtInvalidatePaint(w); } @@ -305,6 +306,7 @@ void wgtListViewSelectAll(WidgetT *w) { } 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->colCount = count; 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) { 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; + wgtInvalidatePaint(w); } @@ -445,6 +451,8 @@ void wgtListViewSetSelected(WidgetT *w, int32_t idx) { 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->sortDir = dir; 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) if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) { wgtListViewSelectAll(w); - wgtInvalidatePaint(w); return; } @@ -530,7 +538,7 @@ void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod) { } // Compute visible rows for page up/down - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t headerH = font->charHeight + 4; int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH; diff --git a/dvx/widgets/widgetOps.c b/dvx/widgets/widgetOps.c index 345aec9..6f28a3e 100644 --- a/dvx/widgets/widgetOps.c +++ b/dvx/widgets/widgetOps.c @@ -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 // ============================================================ @@ -382,6 +401,8 @@ void wgtSetText(WidgetT *w, const char *text) { if (w->wclass && w->wclass->setText) { w->wclass->setText(w, text); } + + wgtInvalidate(w); } @@ -392,5 +413,6 @@ void wgtSetText(WidgetT *w, const char *text) { void wgtSetVisible(WidgetT *w, bool visible) { if (w) { w->visible = visible; + wgtInvalidate(w); } } diff --git a/dvx/widgets/widgetProgressBar.c b/dvx/widgets/widgetProgressBar.c index 714d42b..d21eb6a 100644 --- a/dvx/widgets/widgetProgressBar.c +++ b/dvx/widgets/widgetProgressBar.c @@ -64,6 +64,7 @@ void wgtProgressBarSetValue(WidgetT *w, int32_t value) { } w->as.progressBar.value = value; + wgtInvalidatePaint(w); } diff --git a/dvx/widgets/widgetScrollPane.c b/dvx/widgets/widgetScrollPane.c index b1f9b62..edb68e9 100644 --- a/dvx/widgets/widgetScrollPane.c +++ b/dvx/widgets/widgetScrollPane.c @@ -314,7 +314,7 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) { void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) { (void)mod; - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t contentMinW; diff --git a/dvx/widgets/widgetSlider.c b/dvx/widgets/widgetSlider.c index a5244df..3b31cef 100644 --- a/dvx/widgets/widgetSlider.c +++ b/dvx/widgets/widgetSlider.c @@ -49,6 +49,7 @@ void wgtSliderSetValue(WidgetT *w, int32_t value) { } w->as.slider.value = value; + wgtInvalidatePaint(w); } diff --git a/dvx/widgets/widgetSpinner.c b/dvx/widgets/widgetSpinner.c index 9d885c8..342650c 100644 --- a/dvx/widgets/widgetSpinner.c +++ b/dvx/widgets/widgetSpinner.c @@ -474,6 +474,7 @@ void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) { w->as.spinner.minValue = minVal; w->as.spinner.maxValue = maxVal; spinnerClampAndFormat(w); + wgtInvalidatePaint(w); } @@ -497,4 +498,5 @@ void wgtSpinnerSetValue(WidgetT *w, int32_t value) { w->as.spinner.value = value; spinnerClampAndFormat(w); + wgtInvalidatePaint(w); } diff --git a/dvx/widgets/widgetSplitter.c b/dvx/widgets/widgetSplitter.c index 5419afb..8e03434 100644 --- a/dvx/widgets/widgetSplitter.c +++ b/dvx/widgets/widgetSplitter.c @@ -332,4 +332,5 @@ int32_t wgtSplitterGetPos(const WidgetT *w) { void wgtSplitterSetPos(WidgetT *w, int32_t pos) { w->as.splitter.dividerPos = pos; + wgtInvalidate(w); } diff --git a/dvx/widgets/widgetTabControl.c b/dvx/widgets/widgetTabControl.c index b6dac67..d30c0d9 100644 --- a/dvx/widgets/widgetTabControl.c +++ b/dvx/widgets/widgetTabControl.c @@ -151,6 +151,7 @@ void wgtTabControlSetActive(WidgetT *w, int32_t idx) { VALIDATE_WIDGET_VOID(w, WidgetTabControlE); w->as.tabControl.activeTab = idx; + wgtInvalidate(w); } diff --git a/dvx/widgets/widgetTextInput.c b/dvx/widgets/widgetTextInput.c index c0f4f86..9efbe96 100644 --- a/dvx/widgets/widgetTextInput.c +++ b/dvx/widgets/widgetTextInput.c @@ -200,7 +200,7 @@ void clearOtherSelections(WidgetT *except) { return; } - AppContextT *ctx = (AppContextT *)except->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(except); if (!ctx) { return; @@ -753,7 +753,7 @@ static void maskedInputOnKey(WidgetT *w, int32_t key, int32_t mod) { done: // Adjust scroll { - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; 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; bool shift = (mod & KEY_MOD_SHIFT) != 0; - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; int32_t visCols = innerW / font->charWidth; @@ -2692,7 +2692,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ adjustScroll: // Adjust scroll offset to keep cursor visible { - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t fieldW = w->w; diff --git a/dvx/widgets/widgetTreeView.c b/dvx/widgets/widgetTreeView.c index 685d0fe..b4080b1 100644 --- a/dvx/widgets/widgetTreeView.c +++ b/dvx/widgets/widgetTreeView.c @@ -571,6 +571,7 @@ void wgtTreeItemSetExpanded(WidgetT *w, bool expanded) { VALIDATE_WIDGET_VOID(w, WidgetTreeItemE); w->as.treeItem.expanded = expanded; + wgtInvalidate(w); } @@ -614,6 +615,8 @@ void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item) { item->as.treeItem.selected = true; w->as.treeView.anchorItem = item; } + + wgtInvalidatePaint(w); } @@ -670,6 +673,7 @@ void wgtTreeItemSetSelected(WidgetT *w, bool selected) { VALIDATE_WIDGET_VOID(w, WidgetTreeItemE); 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; if (sel) { - AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData; + AppContextT *ctx = wgtGetContext(w); const BitmapFontT *font = &ctx->font; int32_t itemY = treeItemYPos(w, sel, font); diff --git a/dvxdemo/demo.c b/dvxdemo/demo.c index 95b8f23..9da323a 100644 --- a/dvxdemo/demo.c +++ b/dvxdemo/demo.c @@ -282,7 +282,6 @@ static void onOkClick(WidgetT *w) { if (status) { wgtSetText(status, "Button clicked!"); - wgtInvalidate(status); } } @@ -463,7 +462,6 @@ static void onToolbarClick(WidgetT *w) { if (status) { wgtSetText(status, wgtGetText(w)); - wgtInvalidate(status); } } @@ -1039,7 +1037,6 @@ static void setupTerminalWindow(AppContextT *ctx) { wgtLabel(sb, "80x25 [Local]"); dvxFitWindow(ctx, win); - wgtInvalidate(root); dvxMinimizeWindow(ctx, win); } diff --git a/dvxshell/Makefile b/dvxshell/Makefile index 7b7df35..267f01d 100644 --- a/dvxshell/Makefile +++ b/dvxshell/Makefile @@ -12,7 +12,7 @@ OBJDIR = ../obj/dvxshell BINDIR = ../bin LIBDIR = ../lib -SRCS = shellMain.c shellApp.c shellExport.c shellDesktop.c +SRCS = shellMain.c shellApp.c shellExport.c OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) 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)/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)/shellDesktop.o: shellDesktop.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h - clean: rm -rf $(OBJDIR) $(TARGET) diff --git a/dvxshell/shellApp.h b/dvxshell/shellApp.h index c0bd787..61dd66d 100644 --- a/dvxshell/shellApp.h +++ b/dvxshell/shellApp.h @@ -106,20 +106,14 @@ void shellLog(const char *fmt, ...); void shellExportInit(void); // ============================================================ -// Program Manager / Desktop +// Desktop callback // ============================================================ -// Initialize the Program Manager window -void shellDesktopInit(AppContextT *ctx); +// Default desktop app path +#define SHELL_DESKTOP_APP "apps/progman.app" -// Update status bar with running app count -void shellDesktopUpdateStatus(void); - -// ============================================================ -// Task Manager -// ============================================================ - -// Open the Task Manager dialog (Ctrl+Esc) -void shellTaskManager(AppContextT *ctx); +// Register a callback for app state changes (load, reap, crash). +// The desktop app calls this during appMain to receive notifications. +void shellRegisterDesktopUpdate(void (*updateFn)(void)); #endif // SHELL_APP_H diff --git a/dvxshell/shellDesktop.c b/dvxshell/shellDesktop.c index f703e55..61502f9 100644 --- a/dvxshell/shellDesktop.c +++ b/dvxshell/shellDesktop.c @@ -147,7 +147,6 @@ static void buildPmWindow(void) { updateStatusText(); dvxFitWindow(sCtx, sPmWindow); - wgtInvalidate(root); } @@ -202,7 +201,6 @@ static void buildTaskManager(void) { refreshTaskList(); dvxFitWindow(sCtx, sTmWindow); - wgtInvalidate(root); } @@ -385,7 +383,6 @@ static void refreshTaskList(void) { } wgtListBoxSetItems(sTmListBox, items, count); - wgtInvalidatePaint(sTmListBox); } @@ -464,7 +461,6 @@ static void updateStatusText(void) { } wgtSetText(sStatusLabel, buf); - wgtInvalidate(sStatusLabel); } // ============================================================ diff --git a/dvxshell/shellExport.c b/dvxshell/shellExport.c index cf6070a..34b3e99 100644 --- a/dvxshell/shellExport.c +++ b/dvxshell/shellExport.c @@ -13,6 +13,7 @@ #include "taskswitch.h" #include +#include #include #include #include @@ -25,6 +26,7 @@ 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 *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable); 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 // ============================================================ @@ -90,6 +107,7 @@ DXE_EXPORT_TABLE(shellExportTable) DXE_EXPORT(dvxInit) DXE_EXPORT(dvxShutdown) DXE_EXPORT(dvxUpdate) + { "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered }, DXE_EXPORT(dvxFitWindow) DXE_EXPORT(dvxInvalidateRect) DXE_EXPORT(dvxInvalidateWindow) @@ -287,6 +305,7 @@ DXE_EXPORT_TABLE(shellExportTable) DXE_EXPORT(wgtGetText) DXE_EXPORT(wgtSetEnabled) DXE_EXPORT(wgtSetVisible) + DXE_EXPORT(wgtGetContext) DXE_EXPORT(wgtSetName) DXE_EXPORT(wgtFind) DXE_EXPORT(wgtDestroy) @@ -312,8 +331,18 @@ DXE_EXPORT_TABLE(shellExportTable) DXE_EXPORT(tsCurrentId) 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(shellLoadApp) + DXE_EXPORT(shellGetApp) + DXE_EXPORT(shellForceKillApp) + DXE_EXPORT(shellRunningAppCount) + DXE_EXPORT(shellRegisterDesktopUpdate) // libc — memory DXE_EXPORT(malloc) @@ -364,6 +393,11 @@ DXE_EXPORT_TABLE(shellExportTable) DXE_EXPORT(time) DXE_EXPORT(localtime) + // libc — directory + DXE_EXPORT(opendir) + DXE_EXPORT(readdir) + DXE_EXPORT(closedir) + // libc — misc DXE_EXPORT(qsort) DXE_EXPORT(rand) diff --git a/dvxshell/shellMain.c b/dvxshell/shellMain.c index fe5e911..a72d594 100644 --- a/dvxshell/shellMain.c +++ b/dvxshell/shellMain.c @@ -1,8 +1,8 @@ // shellMain.c — DVX Shell entry point and main loop // -// Initializes the GUI, task system, DXE export table, and Program Manager. -// Runs the cooperative main loop, yielding to app tasks and reaping -// terminated apps each frame. +// Initializes the GUI, task system, DXE export table, and loads +// the desktop app. Runs the cooperative main loop, yielding to +// app tasks and reaping terminated apps each frame. #include "shellApp.h" #include "dvxDialog.h" @@ -20,14 +20,16 @@ static AppContextT sCtx; static jmp_buf sCrashJmp; -static volatile int sCrashSignal = 0; -static FILE *sLogFile = NULL; +static volatile int sCrashSignal = 0; +static FILE *sLogFile = NULL; +static void (*sDesktopUpdateFn)(void) = NULL; // ============================================================ // Prototypes // ============================================================ static void crashHandler(int sig); +static void desktopUpdate(void); static void idleYield(void *ctx); static void installCrashHandler(void); 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 // ============================================================ @@ -136,6 +149,15 @@ void shellLog(const char *fmt, ...) { } +// ============================================================ +// shellRegisterDesktopUpdate +// ============================================================ + +void shellRegisterDesktopUpdate(void (*updateFn)(void)) { + sDesktopUpdateFn = updateFn; +} + + // ============================================================ // main // ============================================================ @@ -182,8 +204,20 @@ int main(void) { sCtx.idleCallback = idleYield; sCtx.idleCtx = &sCtx; - // Create the Program Manager window - shellDesktopInit(&sCtx); + // Load the desktop app + 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 installCrashHandler(); @@ -213,7 +247,7 @@ int main(void) { sCurrentAppId = 0; sCrashSignal = 0; - shellDesktopUpdateStatus(); + desktopUpdate(); } // Main loop @@ -228,8 +262,8 @@ int main(void) { // Reap terminated apps shellReapApps(&sCtx); - // Update status display - shellDesktopUpdateStatus(); + // Notify desktop of any state changes + desktopUpdate(); } shellLog("DVX Shell shutting down..."); diff --git a/termdemo/termdemo.c b/termdemo/termdemo.c index e35c6a1..869cf91 100644 --- a/termdemo/termdemo.c +++ b/termdemo/termdemo.c @@ -265,7 +265,6 @@ int main(int argc, char *argv[]) { // Fit window to widget tree dvxFitWindow(&ctx, win); - wgtInvalidate(root); // Poll serial during idle instead of yielding CPU ctx.idleCallback = idlePoll;