Start of dvxshell

This commit is contained in:
Scott Duensing 2026-03-16 23:19:49 -05:00
parent d041f93268
commit 0db50721d9
19 changed files with 2362 additions and 9 deletions

53
apps/Makefile Normal file
View file

@ -0,0 +1,53 @@
# DV/X Shell Applications Makefile — builds DXE3 modules
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../dvx -I../tasks -I../dvxshell
OBJDIR = ../obj/apps
BINDIR = ../bin/apps
# App definitions: each is a subdir with a single .c file
APPS = about notepad clock
.PHONY: all clean $(APPS)
all: $(APPS)
about: $(BINDIR)/about.dxe
notepad: $(BINDIR)/notepad.dxe
clock: $(BINDIR)/clock.dxe
$(BINDIR)/about.dxe: $(OBJDIR)/about.o | $(BINDIR)
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(BINDIR)/notepad.dxe: $(OBJDIR)/notepad.o | $(BINDIR)
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(BINDIR)/clock.dxe: $(OBJDIR)/clock.o | $(BINDIR)
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $<
$(OBJDIR)/about.o: about/about.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/notepad.o: notepad/notepad.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/clock.o: clock/clock.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
# Dependencies
$(OBJDIR)/about.o: about/about.c ../dvx/dvxApp.h ../dvx/dvxWidget.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
clean:
rm -rf $(OBJDIR) $(BINDIR)

99
apps/about/about.c Normal file
View file

@ -0,0 +1,99 @@
// about.c — "About DV/X 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 DV/X Shell", winX, winY, winW, winH, false);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
WidgetT *root = wgtInitWindow(ac, sWin);
wgtLabel(root, "DV/X Shell 1.0");
wgtHSeparator(root);
wgtLabel(root, "A DESQview/X-style 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;
}

178
apps/clock/clock.c Normal file
View file

@ -0,0 +1,178 @@
// clock.c — Clock DXE application (main-loop with tsYield)
//
// Demonstrates a main-loop app: runs its own loop calling tsYield(),
// updates a clock display every second, and invalidates its window.
#include "dvxApp.h"
#include "dvxWidget.h"
#include "dvxDraw.h"
#include "dvxVideo.h"
#include "shellApp.h"
#include "taskswitch.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
// ============================================================
// Module state
// ============================================================
typedef struct {
bool quit;
char timeStr[32];
char dateStr[32];
time_t lastUpdate;
} ClockStateT;
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
static ClockStateT sState;
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
void appShutdown(void);
static void onClose(WindowT *win);
static void onPaint(WindowT *win, RectT *dirty);
static void updateTime(void);
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Clock",
.hasMainLoop = true,
.stackSize = 0,
.priority = TS_PRIORITY_LOW
};
// ============================================================
// Callbacks (fire in task 0 during dvxUpdate)
// ============================================================
static void onClose(WindowT *win) {
(void)win;
sState.quit = true;
}
static void onPaint(WindowT *win, RectT *dirty) {
(void)dirty;
AppContextT *ac = sCtx->shellCtx;
const BlitOpsT *ops = dvxGetBlitOps(ac);
const BitmapFontT *font = dvxGetFont(ac);
const ColorSchemeT *colors = dvxGetColors(ac);
// Local display copy pointing at content buffer
DisplayT cd = *dvxGetDisplay(ac);
cd.backBuf = win->contentBuf;
cd.width = win->contentW;
cd.height = win->contentH;
cd.pitch = win->contentPitch;
cd.clipX = 0;
cd.clipY = 0;
cd.clipW = win->contentW;
cd.clipH = win->contentH;
// Background
rectFill(&cd, ops, 0, 0, win->contentW, win->contentH, colors->contentBg);
// Time string (large, centered)
int32_t timeW = textWidth(font, sState.timeStr);
int32_t timeX = (win->contentW - timeW) / 2;
int32_t timeY = win->contentH / 3;
drawText(&cd, ops, font, timeX, timeY, sState.timeStr, colors->contentFg, colors->contentBg, true);
// Date string (centered below)
int32_t dateW = textWidth(font, sState.dateStr);
int32_t dateX = (win->contentW - dateW) / 2;
int32_t dateY = timeY + font->charHeight + 8;
drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
}
// ============================================================
// Time update
// ============================================================
static void updateTime(void) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
if (!tm) {
snprintf(sState.timeStr, sizeof(sState.timeStr), "--:--:--");
snprintf(sState.dateStr, sizeof(sState.dateStr), "----:--:--");
sState.lastUpdate = now;
return;
}
snprintf(sState.timeStr, sizeof(sState.timeStr), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
sState.lastUpdate = now;
}
// ============================================================
// Shutdown hook (optional DXE export)
// ============================================================
void appShutdown(void) {
sState.quit = true;
}
// ============================================================
// Entry point (runs in its own task)
// ============================================================
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
AppContextT *ac = ctx->shellCtx;
memset(&sState, 0, sizeof(sState));
updateTime();
int32_t winW = 200;
int32_t winH = 100;
int32_t winX = ac->display.width - winW - 40;
int32_t winY = 40;
sWin = dvxCreateWindow(ac, "Clock", winX, winY, winW, winH, false);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
sWin->onPaint = onPaint;
// Initial paint into content buffer
RectT full = {0, 0, sWin->contentW, sWin->contentH};
onPaint(sWin, &full);
dvxInvalidateWindow(ac, sWin);
// Main loop: update time, invalidate, yield
while (!sState.quit) {
time_t now = time(NULL);
if (now != sState.lastUpdate) {
updateTime();
onPaint(sWin, &full);
dvxInvalidateWindow(ac, sWin);
}
tsYield();
}
// Cleanup
if (sWin) {
dvxDestroyWindow(ac, sWin);
sWin = NULL;
}
return 0;
}

340
apps/notepad/notepad.c Normal file
View file

@ -0,0 +1,340 @@
// notepad.c — Simple text editor DXE application (callback-only)
//
// Demonstrates a callback-only app with menus, text editing, and file I/O.
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "shellApp.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// Constants
// ============================================================
#define TEXT_BUF_SIZE 32768
#define CMD_NEW 100
#define CMD_OPEN 101
#define CMD_SAVE 102
#define CMD_SAVEAS 103
#define CMD_EXIT 104
#define CMD_CUT 200
#define CMD_COPY 201
#define CMD_PASTE 202
#define CMD_SELALL 203
// ============================================================
// Module state
// ============================================================
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
static WidgetT *sTextArea = NULL;
static char sFilePath[260] = "";
static uint32_t sCleanHash = 0;
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
static bool askSaveChanges(void);
static void doNew(void);
static void doOpen(void);
static void doSave(void);
static void doSaveAs(void);
static uint32_t hashText(const char *text);
static bool isDirty(void);
static void markClean(void);
static void onClose(WindowT *win);
static void onMenu(WindowT *win, int32_t menuId);
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Notepad",
.hasMainLoop = false,
.stackSize = 0,
.priority = TS_PRIORITY_NORMAL
};
// ============================================================
// Dirty tracking
// ============================================================
static uint32_t hashText(const char *text) {
if (!text) {
return 0;
}
uint32_t h = 5381;
while (*text) {
h = ((h << 5) + h) ^ (uint8_t)*text;
text++;
}
return h;
}
static bool isDirty(void) {
const char *text = wgtGetText(sTextArea);
return hashText(text) != sCleanHash;
}
static void markClean(void) {
const char *text = wgtGetText(sTextArea);
sCleanHash = hashText(text);
}
// Returns true if it's OK to proceed (saved, discarded, or not dirty).
// Returns false if the user cancelled.
static bool askSaveChanges(void) {
if (!isDirty()) {
return true;
}
int32_t result = dvxMessageBox(sCtx->shellCtx, "Notepad", "Save changes?", MB_YESNOCANCEL | MB_ICONQUESTION);
if (result == ID_YES) {
doSave();
return true;
}
if (result == ID_NO) {
return true;
}
return false;
}
// ============================================================
// File operations
// ============================================================
static void doNew(void) {
if (!askSaveChanges()) {
return;
}
wgtSetText(sTextArea, "");
sFilePath[0] = '\0';
markClean();
dvxSetTitle(sCtx->shellCtx, sWin, "Untitled - Notepad");
}
static void doOpen(void) {
if (!askSaveChanges()) {
return;
}
FileFilterT filters[] = {
{ "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
if (!dvxFileDialog(sCtx->shellCtx, "Open", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
return;
}
FILE *f = fopen(path, "rb");
if (!f) {
dvxMessageBox(sCtx->shellCtx, "Error", "Could not open file.", MB_OK | MB_ICONERROR);
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size >= TEXT_BUF_SIZE - 1) {
size = TEXT_BUF_SIZE - 2;
}
char *buf = (char *)malloc(size + 1);
if (buf) {
fread(buf, 1, size, f);
buf[size] = '\0';
wgtSetText(sTextArea, buf);
free(buf);
}
fclose(f);
snprintf(sFilePath, sizeof(sFilePath), "%s", path);
markClean();
char title[300];
snprintf(title, sizeof(title), "%s - Notepad", sFilePath);
dvxSetTitle(sCtx->shellCtx, sWin, title);
}
static void doSave(void) {
if (sFilePath[0] == '\0') {
doSaveAs();
return;
}
const char *text = wgtGetText(sTextArea);
if (!text) {
return;
}
FILE *f = fopen(sFilePath, "wb");
if (!f) {
dvxMessageBox(sCtx->shellCtx, "Error", "Could not save file.", MB_OK | MB_ICONERROR);
return;
}
fwrite(text, 1, strlen(text), f);
fclose(f);
markClean();
}
static void doSaveAs(void) {
FileFilterT filters[] = {
{ "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
if (!dvxFileDialog(sCtx->shellCtx, "Save As", FD_SAVE, NULL, filters, 2, path, sizeof(path))) {
return;
}
snprintf(sFilePath, sizeof(sFilePath), "%s", path);
doSave();
char title[300];
snprintf(title, sizeof(title), "%s - Notepad", sFilePath);
dvxSetTitle(sCtx->shellCtx, sWin, title);
}
// ============================================================
// Callbacks
// ============================================================
static void onClose(WindowT *win) {
if (!askSaveChanges()) {
return;
}
dvxDestroyWindow(sCtx->shellCtx, win);
sWin = NULL;
sTextArea = NULL;
}
static void onMenu(WindowT *win, int32_t menuId) {
(void)win;
switch (menuId) {
case CMD_NEW:
doNew();
break;
case CMD_OPEN:
doOpen();
break;
case CMD_SAVE:
doSave();
break;
case CMD_SAVEAS:
doSaveAs();
break;
case CMD_EXIT:
onClose(sWin);
break;
case CMD_CUT:
break;
case CMD_COPY:
break;
case CMD_PASTE:
break;
case CMD_SELALL:
break;
}
}
// ============================================================
// 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 = 480;
int32_t winH = 360;
int32_t winX = (screenW - winW) / 2 + 20;
int32_t winY = (screenH - winH) / 3 + 20;
sWin = dvxCreateWindow(ac, "Untitled - Notepad", winX, winY, winW, winH, true);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
sWin->onMenu = onMenu;
// Menu bar
MenuBarT *menuBar = wmAddMenuBar(sWin);
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
wmAddMenuItem(fileMenu, "&New", CMD_NEW);
wmAddMenuItem(fileMenu, "&Open...", CMD_OPEN);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "&Save", CMD_SAVE);
wmAddMenuItem(fileMenu, "Save &As...", CMD_SAVEAS);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);
MenuT *editMenu = wmAddMenu(menuBar, "&Edit");
wmAddMenuItem(editMenu, "Cu&t\tCtrl+X", CMD_CUT);
wmAddMenuItem(editMenu, "&Copy\tCtrl+C", CMD_COPY);
wmAddMenuItem(editMenu, "&Paste\tCtrl+V", CMD_PASTE);
wmAddMenuSeparator(editMenu);
wmAddMenuItem(editMenu, "Select &All\tCtrl+A", CMD_SELALL);
// Widget tree
WidgetT *root = wgtInitWindow(ac, sWin);
sTextArea = wgtTextArea(root, TEXT_BUF_SIZE);
sTextArea->weight = 100;
sFilePath[0] = '\0';
markClean();
wgtInvalidate(root);
return 0;
}

View file

@ -86,7 +86,7 @@ $(LIBDIR):
$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h $(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h $(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h $(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h thirdparty/stb_image.h $(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h dvxWidget.h thirdparty/stb_image.h
$(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h $(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h
$(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h $(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h $(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h

View file

@ -117,7 +117,26 @@ static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw
bool hasCheck = false; bool hasCheck = false;
for (int32_t k = 0; k < menu->itemCount; k++) { for (int32_t k = 0; k < menu->itemCount; k++) {
int32_t itemW = textWidthAccel(&ctx->font, menu->items[k].label); const char *label = menu->items[k].label;
const char *tab = strchr(label, '\t');
int32_t itemW;
if (tab) {
// Left part (with accel underline) + gap + right part (shortcut text)
char leftBuf[MAX_MENU_LABEL];
int32_t leftLen = (int32_t)(tab - label);
if (leftLen >= MAX_MENU_LABEL) {
leftLen = MAX_MENU_LABEL - 1;
}
memcpy(leftBuf, label, leftLen);
leftBuf[leftLen] = '\0';
itemW = textWidthAccel(&ctx->font, leftBuf) + ctx->font.charWidth * 3 + textWidth(&ctx->font, tab + 1);
} else {
itemW = textWidthAccel(&ctx->font, label);
}
if (itemW > maxW) { if (itemW > maxW) {
maxW = itemW; maxW = itemW;
@ -976,7 +995,35 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c
} }
rectFill(d, ops, px + 2, itemY, pw - 4, ctx->font.charHeight, bg); rectFill(d, ops, px + 2, itemY, pw - 4, ctx->font.charHeight, bg);
// Split label at tab: left part is the menu text, right part is the shortcut
const char *tab = strchr(item->label, '\t');
if (tab) {
char leftBuf[MAX_MENU_LABEL];
int32_t leftLen = (int32_t)(tab - item->label);
if (leftLen >= MAX_MENU_LABEL) {
leftLen = MAX_MENU_LABEL - 1;
}
memcpy(leftBuf, item->label, leftLen);
leftBuf[leftLen] = '\0';
drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, leftBuf, fg, bg, true);
const char *right = tab + 1;
int32_t rightW = textWidth(&ctx->font, right);
int32_t rightX = px + pw - rightW - CHROME_TITLE_PAD - 4;
if (item->subMenu) {
rightX -= SUBMENU_ARROW_WIDTH;
}
drawText(d, ops, &ctx->font, rightX, itemY, right, fg, bg, true);
} else {
drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, item->label, fg, bg, true); drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, item->label, fg, bg, true);
}
// Draw check/radio indicator // Draw check/radio indicator
if (item->checked) { if (item->checked) {
@ -1313,6 +1360,7 @@ void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int
// ============================================================ // ============================================================
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win) { void dvxInvalidateWindow(AppContextT *ctx, WindowT *win) {
win->contentDirty = true;
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h); dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
} }

View file

@ -276,6 +276,7 @@ typedef struct {
typedef struct WindowT { typedef struct WindowT {
int32_t id; int32_t id;
int32_t appId; // shell app ID (0 = shell itself)
int32_t x; int32_t x;
int32_t y; int32_t y;
int32_t w; int32_t w;

View file

@ -4,6 +4,7 @@
#include "dvxVideo.h" #include "dvxVideo.h"
#include "dvxDraw.h" #include "dvxDraw.h"
#include "dvxComp.h" #include "dvxComp.h"
#include "dvxWidget.h"
#include "thirdparty/stb_image.h" #include "thirdparty/stb_image.h"
#include <stdlib.h> #include <stdlib.h>
@ -859,6 +860,12 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) {
} }
} }
// Destroy widget tree before freeing window
if (win->widgetRoot) {
wgtDestroy(win->widgetRoot);
win->widgetRoot = NULL;
}
if (win->contentBuf) { if (win->contentBuf) {
free(win->contentBuf); free(win->contentBuf);
} }

View file

@ -576,7 +576,6 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
if (win->onPaint) { if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH}; RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect); win->onPaint(win, &fullRect);
win->contentDirty = true;
// 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) {

View file

@ -289,7 +289,6 @@ void wgtInvalidate(WidgetT *w) {
WindowT *win = w->window; WindowT *win = w->window;
RectT fullRect = {0, 0, win->contentW, win->contentH}; RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect); win->onPaint(win, &fullRect);
win->contentDirty = true;
// Dirty the window on screen // Dirty the window on screen
dvxInvalidateWindow(ctx, win); dvxInvalidateWindow(ctx, win);
@ -325,8 +324,6 @@ void wgtInvalidatePaint(WidgetT *w) {
WindowT *win = w->window; WindowT *win = w->window;
RectT fullRect = {0, 0, win->contentW, win->contentH}; RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect); win->onPaint(win, &fullRect);
win->contentDirty = true;
dvxInvalidateWindow(ctx, win); dvxInvalidateWindow(ctx, win);
} }

View file

@ -222,7 +222,6 @@ void clearOtherSelections(WidgetT *except) {
if (clearSelectionOnWidget(prev) && prevWin != except->window) { if (clearSelectionOnWidget(prev) && prevWin != except->window) {
RectT fullRect = {0, 0, prevWin->contentW, prevWin->contentH}; RectT fullRect = {0, 0, prevWin->contentW, prevWin->contentH};
widgetOnPaint(prevWin, &fullRect); widgetOnPaint(prevWin, &fullRect);
prevWin->contentDirty = true;
dvxInvalidateWindow(ctx, prevWin); dvxInvalidateWindow(ctx, prevWin);
} }
} }

49
dvxshell/Makefile Normal file
View file

@ -0,0 +1,49 @@
# DV/X Shell Makefile for DJGPP cross-compilation
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff
CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../dvx -I../tasks
LDFLAGS = -L../lib -ldvx -ltasks -lm
OBJDIR = ../obj/dvxshell
BINDIR = ../bin
LIBDIR = ../lib
SRCS = shellMain.c shellApp.c shellExport.c shellDesktop.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(BINDIR)/dvxshell.exe
.PHONY: all clean libs
all: libs $(TARGET)
libs:
$(MAKE) -C ../dvx
$(MAKE) -C ../tasks
$(TARGET): $(OBJS) $(LIBDIR)/libdvx.a $(LIBDIR)/libtasks.a | $(BINDIR)
$(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) -Wl,-Map=$(BINDIR)/dvxshell.map
$(EXE2COFF) $@
cat $(CWSDSTUB) $(BINDIR)/dvxshell > $@
rm -f $(BINDIR)/dvxshell
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
# Dependencies
$(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)

281
dvxshell/shellApp.c Normal file
View file

@ -0,0 +1,281 @@
// shellApp.c — DV/X Shell application loading, lifecycle, and reaping
//
// Manages DXE app loading via dlopen/dlsym, resource tracking through
// sCurrentAppId, and clean teardown of both callback-only and main-loop apps.
#include "shellApp.h"
#include "dvxDialog.h"
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// ============================================================
// Module state
// ============================================================
static ShellAppT sApps[SHELL_MAX_APPS];
int32_t sCurrentAppId = 0;
// ============================================================
// Prototypes
// ============================================================
static int32_t allocSlot(void);
static void appTaskWrapper(void *arg);
static const char *baseName(const char *path);
void shellAppInit(void);
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);
int32_t shellRunningAppCount(void);
void shellTerminateAllApps(AppContextT *ctx);
// ============================================================
// Static helpers
// ============================================================
static int32_t allocSlot(void) {
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (sApps[i].state == AppStateFreeE) {
return i;
}
}
return -1;
}
static void appTaskWrapper(void *arg) {
ShellAppT *app = (ShellAppT *)arg;
sCurrentAppId = app->appId;
app->entryFn(&app->dxeCtx);
sCurrentAppId = 0;
// App returned from its main loop — mark for reaping
app->state = AppStateTerminatingE;
}
static const char *baseName(const char *path) {
const char *slash = strrchr(path, '/');
if (!slash) {
slash = strrchr(path, '\\');
}
return slash ? slash + 1 : path;
}
// ============================================================
// Public API (alphabetical)
// ============================================================
void shellAppInit(void) {
memset(sApps, 0, sizeof(sApps));
}
void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
if (!app || app->state == AppStateFreeE) {
return;
}
// Destroy all windows belonging to this app
for (int32_t i = ctx->stack.count - 1; i >= 0; i--) {
if (ctx->stack.windows[i]->appId == app->appId) {
dvxDestroyWindow(ctx, ctx->stack.windows[i]);
}
}
// Kill the task if it has one
if (app->hasMainLoop && app->mainTaskId > 0) {
if (tsGetState(app->mainTaskId) != TaskStateTerminated) {
tsKill(app->mainTaskId);
}
}
// Close the DXE
if (app->dxeHandle) {
dlclose(app->dxeHandle);
app->dxeHandle = NULL;
}
app->state = AppStateFreeE;
shellLog("Shell: force-killed app '%s'", app->name);
}
ShellAppT *shellGetApp(int32_t appId) {
if (appId < 1 || appId >= SHELL_MAX_APPS) {
return NULL;
}
if (sApps[appId].state == AppStateFreeE) {
return NULL;
}
return &sApps[appId];
}
int32_t shellLoadApp(AppContextT *ctx, const char *path) {
// Allocate a slot
int32_t id = allocSlot();
if (id < 0) {
dvxMessageBox(ctx, "Error", "Maximum number of applications reached.", MB_OK | MB_ICONERROR);
return -1;
}
// Load the DXE
void *handle = dlopen(path, RTLD_GLOBAL);
if (!handle) {
char msg[512];
snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", baseName(path), dlerror());
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
return -1;
}
// Look up required symbols
AppDescriptorT *desc = (AppDescriptorT *)dlsym(handle, "_appDescriptor");
if (!desc) {
char msg[256];
snprintf(msg, sizeof(msg), "%s: missing appDescriptor", baseName(path));
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
dlclose(handle);
return -1;
}
int32_t (*entry)(DxeAppContextT *) = (int32_t (*)(DxeAppContextT *))dlsym(handle, "_appMain");
if (!entry) {
char msg[256];
snprintf(msg, sizeof(msg), "%s: missing appMain", baseName(path));
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR);
dlclose(handle);
return -1;
}
void (*shutdown)(void) = (void (*)(void))dlsym(handle, "_appShutdown");
// Fill in the app slot
ShellAppT *app = &sApps[id];
memset(app, 0, sizeof(*app));
app->appId = id;
snprintf(app->name, SHELL_APP_NAME_MAX, "%s", desc->name);
snprintf(app->path, sizeof(app->path), "%s", path);
app->dxeHandle = handle;
app->hasMainLoop = desc->hasMainLoop;
app->entryFn = entry;
app->shutdownFn = shutdown;
app->state = AppStateLoadedE;
// Set up the context passed to appMain
app->dxeCtx.shellCtx = ctx;
app->dxeCtx.appId = id;
// Launch
sCurrentAppId = id;
if (desc->hasMainLoop) {
uint32_t stackSize = desc->stackSize > 0 ? (uint32_t)desc->stackSize : TS_DEFAULT_STACK_SIZE;
int32_t priority = desc->priority;
int32_t taskId = tsCreate(desc->name, appTaskWrapper, app, stackSize, priority);
if (taskId < 0) {
sCurrentAppId = 0;
dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR);
dlclose(handle);
app->state = AppStateFreeE;
return -1;
}
app->mainTaskId = (uint32_t)taskId;
} else {
// Callback-only: call entry directly in task 0
app->entryFn(&app->dxeCtx);
}
sCurrentAppId = 0;
app->state = AppStateRunningE;
shellLog("Shell: loaded '%s' (id=%ld, mainLoop=%s, entry=0x%08lx, desc=0x%08lx)", app->name, (long)id, app->hasMainLoop ? "yes" : "no", (unsigned long)entry, (unsigned long)desc);
return id;
}
void shellReapApp(AppContextT *ctx, ShellAppT *app) {
if (!app || app->state == AppStateFreeE) {
return;
}
// Call shutdown hook if present
if (app->shutdownFn) {
sCurrentAppId = app->appId;
app->shutdownFn();
sCurrentAppId = 0;
}
// Destroy all windows belonging to this app
for (int32_t i = ctx->stack.count - 1; i >= 0; i--) {
if (ctx->stack.windows[i]->appId == app->appId) {
dvxDestroyWindow(ctx, ctx->stack.windows[i]);
}
}
// Kill the task if it has one and it's still alive
if (app->hasMainLoop && app->mainTaskId > 0) {
if (tsGetState(app->mainTaskId) != TaskStateTerminated) {
tsKill(app->mainTaskId);
}
}
// Close the DXE
if (app->dxeHandle) {
dlclose(app->dxeHandle);
app->dxeHandle = NULL;
}
shellLog("Shell: reaped app '%s'", app->name);
app->state = AppStateFreeE;
}
void shellReapApps(AppContextT *ctx) {
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (sApps[i].state == AppStateTerminatingE) {
shellReapApp(ctx, &sApps[i]);
}
}
}
int32_t shellRunningAppCount(void) {
int32_t count = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (sApps[i].state == AppStateRunningE || sApps[i].state == AppStateLoadedE) {
count++;
}
}
return count;
}
void shellTerminateAllApps(AppContextT *ctx) {
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (sApps[i].state != AppStateFreeE) {
shellForceKillApp(ctx, &sApps[i]);
}
}
}

125
dvxshell/shellApp.h Normal file
View file

@ -0,0 +1,125 @@
// shellApp.h — DV/X Shell application lifecycle types and API
#ifndef SHELL_APP_H
#define SHELL_APP_H
#include "dvxApp.h"
#include "dvxWidget.h"
#include "taskswitch.h"
#include <stdint.h>
#include <stdbool.h>
// ============================================================
// App descriptor (exported by each DXE app)
// ============================================================
#define SHELL_APP_NAME_MAX 64
#define SHELL_MAX_APPS 32
typedef struct {
char name[SHELL_APP_NAME_MAX];
bool hasMainLoop;
int32_t stackSize; // 0 = TS_DEFAULT_STACK_SIZE
int32_t priority; // TS_PRIORITY_* or custom
} AppDescriptorT;
// ============================================================
// App context (passed to appMain)
// ============================================================
typedef struct {
AppContextT *shellCtx; // the shell's GUI context
int32_t appId; // this app's ID
} DxeAppContextT;
// ============================================================
// Per-app state
// ============================================================
typedef enum {
AppStateFreeE, // slot available
AppStateLoadedE, // DXE loaded, not yet started
AppStateRunningE, // entry point called, active
AppStateTerminatingE // shutdown in progress
} AppStateE;
typedef struct {
int32_t appId; // unique ID = slot index (1-based; 0 = shell)
char name[SHELL_APP_NAME_MAX];
char path[260];
void *dxeHandle; // dlopen() handle
AppStateE state;
bool hasMainLoop;
uint32_t mainTaskId; // task ID if hasMainLoop, else 0
int32_t (*entryFn)(DxeAppContextT *);
void (*shutdownFn)(void); // may be NULL
DxeAppContextT dxeCtx; // context passed to appMain
} ShellAppT;
// ============================================================
// Shell global state
// ============================================================
// Current app ID for resource tracking (0 = shell)
extern int32_t sCurrentAppId;
// ============================================================
// App lifecycle API
// ============================================================
// Initialize the app slot table
void shellAppInit(void);
// Load and start an app from a DXE file. Returns app ID (>= 1) or -1 on error.
int32_t shellLoadApp(AppContextT *ctx, const char *path);
// Reap finished callback-only apps (call each frame from main loop)
void shellReapApps(AppContextT *ctx);
// Gracefully shut down a single app
void shellReapApp(AppContextT *ctx, ShellAppT *app);
// Forcibly kill an app (Task Manager "End Task")
void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
// Terminate all running apps (shell shutdown)
void shellTerminateAllApps(AppContextT *ctx);
// Get app slot by ID (returns NULL if invalid/free)
ShellAppT *shellGetApp(int32_t appId);
// Count running apps (not counting the shell itself)
int32_t shellRunningAppCount(void);
// ============================================================
// Logging
// ============================================================
// Write a printf-style message to SHELL.LOG
void shellLog(const char *fmt, ...);
// ============================================================
// DXE export table
// ============================================================
// Register the DXE symbol export table (call before any dlopen)
void shellExportInit(void);
// ============================================================
// Program Manager / Desktop
// ============================================================
// Initialize the Program Manager window
void shellDesktopInit(AppContextT *ctx);
// Update status bar with running app count
void shellDesktopUpdateStatus(void);
// ============================================================
// Task Manager
// ============================================================
// Open the Task Manager dialog (Ctrl+Esc)
void shellTaskManager(AppContextT *ctx);
#endif // SHELL_APP_H

493
dvxshell/shellDesktop.c Normal file
View file

@ -0,0 +1,493 @@
// shellDesktop.c — Program Manager window for DV/X 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>
// ============================================================
// 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 .dxe)
char path[MAX_PATH_LEN]; // full path
} DxeEntryT;
static AppContextT *sCtx = NULL;
static WindowT *sPmWindow = NULL;
static WidgetT *sStatusLabel = NULL;
static DxeEntryT sDxeFiles[MAX_DXE_FILES];
static int32_t sDxeCount = 0;
// ============================================================
// Prototypes
// ============================================================
static void buildPmWindow(void);
static void onAppButtonClick(WidgetT *w);
static void onPmClose(WindowT *win);
static void onPmMenu(WindowT *win, int32_t menuId);
static void scanAppsDir(void);
static void showAboutDialog(void);
static void updateStatusText(void);
// Task Manager
static WindowT *sTmWindow = NULL;
static WidgetT *sTmListBox = NULL;
static void buildTaskManager(void);
static void onTmClose(WindowT *win);
static void onTmEndTask(WidgetT *w);
static void onTmSwitchTo(WidgetT *w);
static void refreshTaskList(void);
// ============================================================
// Static functions (alphabetical)
// ============================================================
static void buildPmWindow(void) {
int32_t screenW = sCtx->display.width;
int32_t screenH = sCtx->display.height;
int32_t winW = 440;
int32_t winH = 340;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 4;
sPmWindow = dvxCreateWindow(sCtx, "Program Manager", winX, winY, winW, winH, true);
if (!sPmWindow) {
return;
}
sPmWindow->appId = 0;
sPmWindow->onClose = onPmClose;
sPmWindow->onMenu = onPmMenu;
// Menu bar
MenuBarT *menuBar = wmAddMenuBar(sPmWindow);
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
wmAddMenuItem(windowMenu, "&Tile", CMD_TILE);
wmAddMenuItem(windowMenu, "Tile &Horizontally", CMD_TILE_H);
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
wmAddMenuItem(helpMenu, "&About DV/X 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, "");
updateStatusText();
dvxFitWindow(sCtx, sPmWindow);
wgtInvalidate(root);
}
static void buildTaskManager(void) {
if (sTmWindow) {
// Already open — just raise it
for (int32_t i = 0; i < sCtx->stack.count; i++) {
if (sCtx->stack.windows[i] == sTmWindow) {
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, i);
wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1);
break;
}
}
return;
}
int32_t screenW = sCtx->display.width;
int32_t screenH = sCtx->display.height;
int32_t winW = 300;
int32_t winH = 280;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 3;
sTmWindow = dvxCreateWindow(sCtx, "Task Manager", winX, winY, winW, winH, true);
if (!sTmWindow) {
return;
}
sTmWindow->appId = 0;
sTmWindow->onClose = onTmClose;
WidgetT *root = wgtInitWindow(sCtx, sTmWindow);
// List box of running apps
sTmListBox = wgtListBox(root);
sTmListBox->weight = 100;
sTmListBox->prefH = wgtPixels(160);
// Button row
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
btnRow->spacing = wgtPixels(8);
WidgetT *switchBtn = wgtButton(btnRow, "Switch To");
switchBtn->onClick = onTmSwitchTo;
switchBtn->prefW = wgtPixels(90);
WidgetT *endBtn = wgtButton(btnRow, "End Task");
endBtn->onClick = onTmEndTask;
endBtn->prefW = wgtPixels(90);
refreshTaskList();
dvxFitWindow(sCtx, sTmWindow);
wgtInvalidate(root);
}
static void onAppButtonClick(WidgetT *w) {
DxeEntryT *entry = (DxeEntryT *)w->userData;
if (!entry) {
return;
}
shellLoadApp(sCtx, entry->path);
updateStatusText();
}
static void onPmClose(WindowT *win) {
(void)win;
// Confirm exit
int32_t result = dvxMessageBox(sCtx, "Exit Shell", "Are you sure you want to exit DV/X 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[] = {
{ "DXE Applications (*.dxe)", "*.dxe" },
{ "All Files (*.*)", "*.*" }
};
char path[MAX_PATH_LEN];
if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
shellLoadApp(sCtx, path);
updateStatusText();
}
}
break;
case CMD_EXIT:
onPmClose(sPmWindow);
break;
case CMD_CASCADE:
dvxCascadeWindows(sCtx);
break;
case CMD_TILE:
dvxTileWindows(sCtx);
break;
case CMD_TILE_H:
dvxTileWindowsH(sCtx);
break;
case CMD_TILE_V:
dvxTileWindowsV(sCtx);
break;
case CMD_ABOUT:
showAboutDialog();
break;
case CMD_TASK_MGR:
buildTaskManager();
break;
}
}
static void onTmClose(WindowT *win) {
sTmListBox = NULL;
sTmWindow = NULL;
dvxDestroyWindow(sCtx, win);
}
static void onTmEndTask(WidgetT *w) {
(void)w;
if (!sTmListBox) {
return;
}
int32_t sel = wgtListBoxGetSelected(sTmListBox);
if (sel < 0) {
return;
}
// Map list index to app ID
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
shellForceKillApp(sCtx, app);
refreshTaskList();
updateStatusText();
return;
}
idx++;
}
}
}
static void onTmSwitchTo(WidgetT *w) {
(void)w;
if (!sTmListBox) {
return;
}
int32_t sel = wgtListBoxGetSelected(sTmListBox);
if (sel < 0) {
return;
}
// Map list index to app ID, find topmost window for that app
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
// Find the topmost window for this app
for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) {
WindowT *win = sCtx->stack.windows[j];
if (win->appId == i) {
if (win->minimized) {
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win);
}
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j);
wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1);
return;
}
}
return;
}
idx++;
}
}
}
static void refreshTaskList(void) {
if (!sTmListBox) {
return;
}
static const char *items[SHELL_MAX_APPS];
static char names[SHELL_MAX_APPS][SHELL_APP_NAME_MAX + 16];
int32_t count = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
const char *state = app->hasMainLoop ? "task" : "callback";
snprintf(names[count], sizeof(names[count]), "%s [%s]", app->name, state);
items[count] = names[count];
count++;
}
}
wgtListBoxSetItems(sTmListBox, items, count);
wgtInvalidatePaint(sTmListBox);
}
static void scanAppsDir(void) {
DIR *dir = opendir("apps");
if (!dir) {
shellLog("Shell: apps/ directory not found");
return;
}
sDxeCount = 0;
struct dirent *ent;
while ((ent = readdir(dir)) != NULL && sDxeCount < MAX_DXE_FILES) {
int32_t len = strlen(ent->d_name);
if (len < 5) {
continue;
}
// Check for .dxe extension (case-insensitive)
const char *ext = ent->d_name + len - 4;
if (strcmp(ext, ".dxe") != 0 && strcmp(ext, ".DXE") != 0) {
continue;
}
DxeEntryT *entry = &sDxeFiles[sDxeCount];
// Name = filename without extension
int32_t nameLen = len - 4;
if (nameLen >= SHELL_APP_NAME_MAX) {
nameLen = SHELL_APP_NAME_MAX - 1;
}
memcpy(entry->name, ent->d_name, nameLen);
entry->name[nameLen] = '\0';
// Capitalize first letter for display
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
entry->name[0] -= 32;
}
snprintf(entry->path, sizeof(entry->path), "apps/%s", ent->d_name);
sDxeCount++;
}
closedir(dir);
shellLog("Shell: found %ld DXE app(s)", (long)sDxeCount);
}
static void showAboutDialog(void) {
dvxMessageBox(sCtx, "About DV/X Shell",
"DV/X Shell 1.0\n\nA DESQview/X-style 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);
wgtInvalidatePaint(sStatusLabel);
}
// ============================================================
// Public API
// ============================================================
void shellDesktopInit(AppContextT *ctx) {
sCtx = ctx;
scanAppsDir();
buildPmWindow();
}
void shellDesktopUpdateStatus(void) {
updateStatusText();
// Also refresh task manager if open
if (sTmWindow) {
refreshTaskList();
}
}
void shellTaskManager(AppContextT *ctx) {
sCtx = ctx;
buildTaskManager();
}

391
dvxshell/shellExport.c Normal file
View file

@ -0,0 +1,391 @@
// shellExport.c — DXE export table and wrapper functions for DV/X Shell
//
// Exports all dvx*/wgt*/ts* symbols that DXE apps need. A few functions
// are wrapped for resource tracking (window ownership via appId).
#include "shellApp.h"
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxDraw.h"
#include "dvxVideo.h"
#include "dvxWm.h"
#include "taskswitch.h"
#include <sys/dxe.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
// ============================================================
// Prototypes
// ============================================================
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 void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
// ============================================================
// Wrapper: dvxCreateWindow — stamps win->appId
// ============================================================
static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
WindowT *win = dvxCreateWindow(ctx, title, x, y, w, h, resizable);
if (win) {
win->appId = sCurrentAppId;
}
return win;
}
// ============================================================
// Wrapper: dvxDestroyWindow — checks for last-window reap
// ============================================================
static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) {
int32_t appId = win->appId;
dvxDestroyWindow(ctx, win);
// If this was a callback-only app's last window, mark for reaping
if (appId > 0) {
ShellAppT *app = shellGetApp(appId);
if (app && !app->hasMainLoop && app->state == AppStateRunningE) {
// Check if app still has any windows
bool hasWindows = false;
for (int32_t i = 0; i < ctx->stack.count; i++) {
if (ctx->stack.windows[i]->appId == appId) {
hasWindows = true;
break;
}
}
if (!hasWindows) {
app->state = AppStateTerminatingE;
}
}
}
}
// ============================================================
// Export table
// ============================================================
// We use extern_asm to get the actual addresses for wrapped functions
// since we export under the original names but point to our wrappers.
DXE_EXPORT_TABLE(shellExportTable)
// Wrapped functions (exported under original names)
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
// dvxApp.h — direct exports
DXE_EXPORT(dvxInit)
DXE_EXPORT(dvxShutdown)
DXE_EXPORT(dvxUpdate)
DXE_EXPORT(dvxFitWindow)
DXE_EXPORT(dvxInvalidateRect)
DXE_EXPORT(dvxInvalidateWindow)
DXE_EXPORT(dvxMinimizeWindow)
DXE_EXPORT(dvxMaximizeWindow)
DXE_EXPORT(dvxQuit)
DXE_EXPORT(dvxSetTitle)
DXE_EXPORT(dvxGetFont)
DXE_EXPORT(dvxGetColors)
DXE_EXPORT(dvxGetDisplay)
DXE_EXPORT(dvxGetBlitOps)
DXE_EXPORT(dvxSetWindowIcon)
DXE_EXPORT(dvxScreenshot)
DXE_EXPORT(dvxWindowScreenshot)
DXE_EXPORT(dvxCreateAccelTable)
DXE_EXPORT(dvxFreeAccelTable)
DXE_EXPORT(dvxAddAccel)
DXE_EXPORT(dvxCascadeWindows)
DXE_EXPORT(dvxTileWindows)
DXE_EXPORT(dvxTileWindowsH)
DXE_EXPORT(dvxTileWindowsV)
DXE_EXPORT(dvxClipboardCopy)
DXE_EXPORT(dvxClipboardGet)
// dvxDialog.h
DXE_EXPORT(dvxMessageBox)
DXE_EXPORT(dvxFileDialog)
// dvxDraw.h
DXE_EXPORT(rectFill)
DXE_EXPORT(rectCopy)
DXE_EXPORT(drawBevel)
DXE_EXPORT(drawChar)
DXE_EXPORT(drawText)
DXE_EXPORT(drawTextN)
DXE_EXPORT(textWidth)
DXE_EXPORT(drawTextAccel)
DXE_EXPORT(textWidthAccel)
DXE_EXPORT(drawFocusRect)
DXE_EXPORT(drawHLine)
DXE_EXPORT(drawVLine)
// dvxVideo.h
DXE_EXPORT(packColor)
// dvxWm.h
DXE_EXPORT(wmAddMenuBar)
DXE_EXPORT(wmAddMenu)
DXE_EXPORT(wmAddMenuItem)
DXE_EXPORT(wmAddMenuCheckItem)
DXE_EXPORT(wmAddMenuRadioItem)
DXE_EXPORT(wmAddMenuSeparator)
DXE_EXPORT(wmAddSubMenu)
DXE_EXPORT(wmAddVScrollbar)
DXE_EXPORT(wmAddHScrollbar)
DXE_EXPORT(wmSetTitle)
DXE_EXPORT(wmSetIcon)
DXE_EXPORT(wmCreateMenu)
DXE_EXPORT(wmFreeMenu)
// dvxWidget.h — window integration
DXE_EXPORT(wgtInitWindow)
// dvxWidget.h — containers
DXE_EXPORT(wgtVBox)
DXE_EXPORT(wgtHBox)
DXE_EXPORT(wgtFrame)
// dvxWidget.h — basic widgets
DXE_EXPORT(wgtLabel)
DXE_EXPORT(wgtButton)
DXE_EXPORT(wgtCheckbox)
DXE_EXPORT(wgtTextInput)
DXE_EXPORT(wgtPasswordInput)
DXE_EXPORT(wgtMaskedInput)
// dvxWidget.h — radio buttons
DXE_EXPORT(wgtRadioGroup)
DXE_EXPORT(wgtRadio)
// dvxWidget.h — spacing
DXE_EXPORT(wgtSpacer)
DXE_EXPORT(wgtHSeparator)
DXE_EXPORT(wgtVSeparator)
// dvxWidget.h — complex widgets
DXE_EXPORT(wgtListBox)
DXE_EXPORT(wgtTextArea)
// dvxWidget.h — dropdown/combo
DXE_EXPORT(wgtDropdown)
DXE_EXPORT(wgtDropdownSetItems)
DXE_EXPORT(wgtDropdownGetSelected)
DXE_EXPORT(wgtDropdownSetSelected)
DXE_EXPORT(wgtComboBox)
DXE_EXPORT(wgtComboBoxSetItems)
DXE_EXPORT(wgtComboBoxGetSelected)
DXE_EXPORT(wgtComboBoxSetSelected)
// dvxWidget.h — progress bar
DXE_EXPORT(wgtProgressBar)
DXE_EXPORT(wgtProgressBarV)
DXE_EXPORT(wgtProgressBarSetValue)
DXE_EXPORT(wgtProgressBarGetValue)
// dvxWidget.h — slider
DXE_EXPORT(wgtSlider)
DXE_EXPORT(wgtSliderSetValue)
DXE_EXPORT(wgtSliderGetValue)
// dvxWidget.h — spinner
DXE_EXPORT(wgtSpinner)
DXE_EXPORT(wgtSpinnerSetValue)
DXE_EXPORT(wgtSpinnerGetValue)
DXE_EXPORT(wgtSpinnerSetRange)
DXE_EXPORT(wgtSpinnerSetStep)
// dvxWidget.h — tab control
DXE_EXPORT(wgtTabControl)
DXE_EXPORT(wgtTabPage)
DXE_EXPORT(wgtTabControlSetActive)
DXE_EXPORT(wgtTabControlGetActive)
// dvxWidget.h — status bar / toolbar
DXE_EXPORT(wgtStatusBar)
DXE_EXPORT(wgtToolbar)
// dvxWidget.h — tree view
DXE_EXPORT(wgtTreeView)
DXE_EXPORT(wgtTreeViewGetSelected)
DXE_EXPORT(wgtTreeViewSetSelected)
DXE_EXPORT(wgtTreeViewSetMultiSelect)
DXE_EXPORT(wgtTreeViewSetReorderable)
DXE_EXPORT(wgtTreeItem)
DXE_EXPORT(wgtTreeItemSetExpanded)
DXE_EXPORT(wgtTreeItemIsExpanded)
DXE_EXPORT(wgtTreeItemIsSelected)
DXE_EXPORT(wgtTreeItemSetSelected)
// dvxWidget.h — list view
DXE_EXPORT(wgtListView)
DXE_EXPORT(wgtListViewSetColumns)
DXE_EXPORT(wgtListViewSetData)
DXE_EXPORT(wgtListViewGetSelected)
DXE_EXPORT(wgtListViewSetSelected)
DXE_EXPORT(wgtListViewSetSort)
DXE_EXPORT(wgtListViewSetHeaderClickCallback)
DXE_EXPORT(wgtListViewSetMultiSelect)
DXE_EXPORT(wgtListViewIsItemSelected)
DXE_EXPORT(wgtListViewSetItemSelected)
DXE_EXPORT(wgtListViewSelectAll)
DXE_EXPORT(wgtListViewClearSelection)
DXE_EXPORT(wgtListViewSetReorderable)
// dvxWidget.h — scroll pane / splitter
DXE_EXPORT(wgtScrollPane)
DXE_EXPORT(wgtSplitter)
DXE_EXPORT(wgtSplitterSetPos)
DXE_EXPORT(wgtSplitterGetPos)
// dvxWidget.h — image / image button
DXE_EXPORT(wgtImageButton)
DXE_EXPORT(wgtImageButtonSetData)
DXE_EXPORT(wgtImage)
DXE_EXPORT(wgtImageFromFile)
DXE_EXPORT(wgtImageSetData)
// dvxWidget.h — canvas
DXE_EXPORT(wgtCanvas)
DXE_EXPORT(wgtCanvasClear)
DXE_EXPORT(wgtCanvasSetPenColor)
DXE_EXPORT(wgtCanvasSetPenSize)
DXE_EXPORT(wgtCanvasSave)
DXE_EXPORT(wgtCanvasLoad)
DXE_EXPORT(wgtCanvasDrawLine)
DXE_EXPORT(wgtCanvasDrawRect)
DXE_EXPORT(wgtCanvasFillRect)
DXE_EXPORT(wgtCanvasFillCircle)
DXE_EXPORT(wgtCanvasSetPixel)
DXE_EXPORT(wgtCanvasGetPixel)
// dvxWidget.h — ANSI terminal
DXE_EXPORT(wgtAnsiTerm)
DXE_EXPORT(wgtAnsiTermWrite)
DXE_EXPORT(wgtAnsiTermClear)
DXE_EXPORT(wgtAnsiTermSetComm)
DXE_EXPORT(wgtAnsiTermSetScrollback)
DXE_EXPORT(wgtAnsiTermPoll)
DXE_EXPORT(wgtAnsiTermRepaint)
// dvxWidget.h — operations
DXE_EXPORT(wgtInvalidate)
DXE_EXPORT(wgtInvalidatePaint)
DXE_EXPORT(wgtSetText)
DXE_EXPORT(wgtGetText)
DXE_EXPORT(wgtSetEnabled)
DXE_EXPORT(wgtSetVisible)
DXE_EXPORT(wgtSetName)
DXE_EXPORT(wgtFind)
DXE_EXPORT(wgtDestroy)
// dvxWidget.h — list box ops
DXE_EXPORT(wgtListBoxSetItems)
DXE_EXPORT(wgtListBoxGetSelected)
DXE_EXPORT(wgtListBoxSetSelected)
DXE_EXPORT(wgtListBoxSetMultiSelect)
DXE_EXPORT(wgtListBoxIsItemSelected)
DXE_EXPORT(wgtListBoxSetItemSelected)
DXE_EXPORT(wgtListBoxSelectAll)
DXE_EXPORT(wgtListBoxClearSelection)
DXE_EXPORT(wgtListBoxSetReorderable)
// dvxWidget.h — layout
DXE_EXPORT(wgtResolveSize)
DXE_EXPORT(wgtLayout)
DXE_EXPORT(wgtPaint)
// taskswitch.h
DXE_EXPORT(tsYield)
DXE_EXPORT(tsCurrentId)
DXE_EXPORT(tsActiveCount)
// Shell logging
DXE_EXPORT(shellLog)
// libc — memory
DXE_EXPORT(malloc)
DXE_EXPORT(free)
DXE_EXPORT(calloc)
DXE_EXPORT(realloc)
// libc — string
DXE_EXPORT(memcpy)
DXE_EXPORT(memset)
DXE_EXPORT(memmove)
DXE_EXPORT(memcmp)
DXE_EXPORT(strlen)
DXE_EXPORT(strcmp)
DXE_EXPORT(strncmp)
DXE_EXPORT(strcpy)
DXE_EXPORT(strncpy)
DXE_EXPORT(strcat)
DXE_EXPORT(strncat)
DXE_EXPORT(strchr)
DXE_EXPORT(strrchr)
DXE_EXPORT(strstr)
DXE_EXPORT(strtol)
// libc — I/O
DXE_EXPORT(printf)
DXE_EXPORT(fprintf)
DXE_EXPORT(sprintf)
DXE_EXPORT(snprintf)
DXE_EXPORT(puts)
DXE_EXPORT(fopen)
DXE_EXPORT(fclose)
DXE_EXPORT(fread)
DXE_EXPORT(fwrite)
DXE_EXPORT(fgets)
DXE_EXPORT(fseek)
DXE_EXPORT(ftell)
DXE_EXPORT(feof)
DXE_EXPORT(ferror)
// libc — math
DXE_EXPORT(sin)
DXE_EXPORT(cos)
DXE_EXPORT(sqrt)
// libc — time
DXE_EXPORT(clock)
DXE_EXPORT(time)
DXE_EXPORT(localtime)
// libc — misc
DXE_EXPORT(qsort)
DXE_EXPORT(rand)
DXE_EXPORT(srand)
DXE_EXPORT(abs)
DXE_EXPORT(atoi)
DXE_EXPORT_END
// ============================================================
// shellRegisterExports
// ============================================================
static void shellRegisterExports(void) {
dlregsym(shellExportTable);
}
// ============================================================
// Public init function
// ============================================================
void shellExportInit(void) {
shellRegisterExports();
}

247
dvxshell/shellMain.c Normal file
View file

@ -0,0 +1,247 @@
// shellMain.c — DV/X 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.
#include "shellApp.h"
#include "dvxDialog.h"
#include <stdarg.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/exceptn.h>
// ============================================================
// Module state
// ============================================================
static AppContextT sCtx;
static jmp_buf sCrashJmp;
static volatile int sCrashSignal = 0;
static FILE *sLogFile = NULL;
// ============================================================
// Prototypes
// ============================================================
static void crashHandler(int sig);
static void idleYield(void *ctx);
static void installCrashHandler(void);
static void logCrash(int sig);
// ============================================================
// crashHandler — catch page faults and other fatal signals
// ============================================================
static void crashHandler(int sig) {
logCrash(sig);
// Re-install handler (DJGPP resets to SIG_DFL after delivery)
signal(sig, crashHandler);
sCrashSignal = sig;
longjmp(sCrashJmp, 1);
}
// ============================================================
// idleYield — called when no dirty rects need compositing
// ============================================================
static void idleYield(void *ctx) {
(void)ctx;
if (tsActiveCount() > 1) {
tsYield();
}
}
// ============================================================
// installCrashHandler
// ============================================================
static void installCrashHandler(void) {
signal(SIGSEGV, crashHandler);
signal(SIGFPE, crashHandler);
signal(SIGILL, crashHandler);
}
// ============================================================
// logCrash — write exception details to the log
// ============================================================
static void logCrash(int sig) {
const char *sigName = "UNKNOWN";
if (sig == SIGSEGV) {
sigName = "SIGSEGV (page fault)";
} else if (sig == SIGFPE) {
sigName = "SIGFPE (floating point exception)";
} else if (sig == SIGILL) {
sigName = "SIGILL (illegal instruction)";
}
shellLog("=== CRASH ===");
shellLog("Signal: %d (%s)", sig, sigName);
shellLog("Current app ID: %ld", (long)sCurrentAppId);
if (sCurrentAppId > 0) {
ShellAppT *app = shellGetApp(sCurrentAppId);
if (app) {
shellLog("App name: %s", app->name);
shellLog("App path: %s", app->path);
shellLog("Has main loop: %s", app->hasMainLoop ? "yes" : "no");
shellLog("Task ID: %lu", (unsigned long)app->mainTaskId);
}
} else {
shellLog("Crashed in shell (task 0)");
}
// Dump CPU registers from exception state
jmp_buf *estate = __djgpp_exception_state_ptr;
if (estate) {
struct __jmp_buf *regs = &(*estate)[0];
shellLog("EIP: 0x%08lx CS: 0x%04x", regs->__eip, regs->__cs);
shellLog("EAX: 0x%08lx EBX: 0x%08lx ECX: 0x%08lx EDX: 0x%08lx", regs->__eax, regs->__ebx, regs->__ecx, regs->__edx);
shellLog("ESI: 0x%08lx EDI: 0x%08lx EBP: 0x%08lx ESP: 0x%08lx", regs->__esi, regs->__edi, regs->__ebp, regs->__esp);
shellLog("DS: 0x%04x ES: 0x%04x FS: 0x%04x GS: 0x%04x SS: 0x%04x", regs->__ds, regs->__es, regs->__fs, regs->__gs, regs->__ss);
shellLog("EFLAGS: 0x%08lx", regs->__eflags);
}
}
// ============================================================
// shellLog — append a line to SHELL.LOG
// ============================================================
void shellLog(const char *fmt, ...) {
if (!sLogFile) {
return;
}
va_list ap;
va_start(ap, fmt);
vfprintf(sLogFile, fmt, ap);
va_end(ap);
fprintf(sLogFile, "\n");
fflush(sLogFile);
}
// ============================================================
// main
// ============================================================
int main(void) {
sLogFile = fopen("shell.log", "w");
shellLog("DV/X Shell starting...");
// Initialize GUI
int32_t result = dvxInit(&sCtx, 640, 480, 32);
if (result != 0) {
shellLog("Failed to initialize DV/X GUI (error %ld)", (long)result);
if (sLogFile) {
fclose(sLogFile);
}
return 1;
}
// Initialize task system
if (tsInit() != TS_OK) {
shellLog("Failed to initialize task system");
dvxShutdown(&sCtx);
if (sLogFile) {
fclose(sLogFile);
}
return 1;
}
// Shell task (task 0) gets high priority for responsive UI
tsSetPriority(0, TS_PRIORITY_HIGH);
// Register DXE export table
shellExportInit();
// Initialize app slot table
shellAppInit();
// Set up idle callback for cooperative yielding
sCtx.idleCallback = idleYield;
sCtx.idleCtx = &sCtx;
// Create the Program Manager window
shellDesktopInit(&sCtx);
// Install crash handler after everything is initialized
installCrashHandler();
shellLog("DV/X Shell ready.");
// Set recovery point for crash handler
if (setjmp(sCrashJmp) != 0) {
// Returned here from crash handler via longjmp.
// If the crash was in a non-main task, the task switcher still
// thinks that task is running. Fix it before doing anything else.
tsRecoverToMain();
shellLog("Recovering from crash, killing app %ld", (long)sCurrentAppId);
if (sCurrentAppId > 0) {
ShellAppT *app = shellGetApp(sCurrentAppId);
if (app) {
shellForceKillApp(&sCtx, app);
}
}
sCurrentAppId = 0;
sCrashSignal = 0;
shellDesktopUpdateStatus();
}
// Main loop
while (sCtx.running) {
dvxUpdate(&sCtx);
// Give app tasks CPU time even during active frames
if (tsActiveCount() > 1) {
tsYield();
}
// Reap terminated apps
shellReapApps(&sCtx);
// Update status display
shellDesktopUpdateStatus();
}
shellLog("DV/X Shell shutting down...");
// Clean shutdown: terminate all apps
shellTerminateAllApps(&sCtx);
tsShutdown();
dvxShutdown(&sCtx);
shellLog("DV/X Shell exited.");
if (sLogFile) {
fclose(sLogFile);
sLogFile = NULL;
}
return 0;
}

View file

@ -85,7 +85,9 @@ const char *tsGetName(uint32_t taskId);
int32_t tsGetPriority(uint32_t taskId); int32_t tsGetPriority(uint32_t taskId);
TaskStateE tsGetState(uint32_t taskId); TaskStateE tsGetState(uint32_t taskId);
int32_t tsInit(void); int32_t tsInit(void);
int32_t tsKill(uint32_t taskId);
int32_t tsPause(uint32_t taskId); int32_t tsPause(uint32_t taskId);
void tsRecoverToMain(void);
int32_t tsResume(uint32_t taskId); int32_t tsResume(uint32_t taskId);
int32_t tsSetPriority(uint32_t taskId, int32_t priority); int32_t tsSetPriority(uint32_t taskId, int32_t priority);
void tsShutdown(void); void tsShutdown(void);
@ -393,6 +395,32 @@ int32_t tsInit(void) {
} }
int32_t tsKill(uint32_t taskId) {
if (!initialized || taskId >= (uint32_t)arrlen(tasks)) {
return TS_ERR_PARAM;
}
if (!tasks[taskId].allocated) {
return TS_ERR_PARAM;
}
if (tasks[taskId].isMain) {
return TS_ERR_STATE;
}
if (taskId == currentIdx) {
return TS_ERR_STATE;
}
if (tasks[taskId].state == TaskStateTerminated) {
return TS_ERR_STATE;
}
tasks[taskId].state = TaskStateTerminated;
free(tasks[taskId].stack);
tasks[taskId].stack = NULL;
tasks[taskId].allocated = false;
return TS_OK;
}
int32_t tsPause(uint32_t taskId) { int32_t tsPause(uint32_t taskId) {
if (!initialized || taskId >= (uint32_t)arrlen(tasks)) { if (!initialized || taskId >= (uint32_t)arrlen(tasks)) {
return TS_ERR_PARAM; return TS_ERR_PARAM;
@ -426,6 +454,16 @@ int32_t tsPause(uint32_t taskId) {
} }
void tsRecoverToMain(void) {
if (!initialized) {
return;
}
currentIdx = 0;
tasks[0].state = TaskStateRunning;
}
int32_t tsResume(uint32_t taskId) { int32_t tsResume(uint32_t taskId) {
if (!initialized || taskId >= (uint32_t)arrlen(tasks)) { if (!initialized || taskId >= (uint32_t)arrlen(tasks)) {
return TS_ERR_PARAM; return TS_ERR_PARAM;

View file

@ -21,7 +21,7 @@
#define TS_ERR_STATE (-5) #define TS_ERR_STATE (-5)
// Defaults // Defaults
#define TS_DEFAULT_STACK_SIZE 8192 #define TS_DEFAULT_STACK_SIZE 32768
#define TS_NAME_MAX 32 #define TS_NAME_MAX 32
// Priority levels // Priority levels
@ -79,6 +79,14 @@ const char *tsGetName(uint32_t taskId);
// Terminate the calling task. Must not be called from the main task. // Terminate the calling task. Must not be called from the main task.
void tsExit(void); void tsExit(void);
// Forcibly terminate another task. Cannot kill main task (id 0) or self.
int32_t tsKill(uint32_t taskId);
// Crash recovery: force scheduler back to main task (id 0).
// Call after longjmp from a signal handler that fired in a non-main task.
// The crashed task is NOT cleaned up — call tsKill() afterward.
void tsRecoverToMain(void);
// Get the number of non-terminated tasks. // Get the number of non-terminated tasks.
uint32_t tsActiveCount(void); uint32_t tsActiveCount(void);