Start of dvxshell
This commit is contained in:
parent
d041f93268
commit
0db50721d9
19 changed files with 2362 additions and 9 deletions
53
apps/Makefile
Normal file
53
apps/Makefile
Normal 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
99
apps/about/about.c
Normal 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
178
apps/clock/clock.c
Normal 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
340
apps/notepad/notepad.c
Normal 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;
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ $(LIBDIR):
|
|||
$(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)/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)/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
|
||||
|
|
|
|||
52
dvx/dvxApp.c
52
dvx/dvxApp.c
|
|
@ -117,7 +117,26 @@ static void calcPopupSize(const AppContextT *ctx, const MenuT *menu, int32_t *pw
|
|||
bool hasCheck = false;
|
||||
|
||||
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) {
|
||||
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);
|
||||
drawTextAccel(d, ops, &ctx->font, px + CHROME_TITLE_PAD + 2 + checkMargin, itemY, item->label, fg, bg, true);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Draw check/radio indicator
|
||||
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) {
|
||||
win->contentDirty = true;
|
||||
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@ typedef struct {
|
|||
|
||||
typedef struct WindowT {
|
||||
int32_t id;
|
||||
int32_t appId; // shell app ID (0 = shell itself)
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t w;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "dvxVideo.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxComp.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "thirdparty/stb_image.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) {
|
||||
free(win->contentBuf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -576,7 +576,6 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
|||
if (win->onPaint) {
|
||||
RectT fullRect = {0, 0, win->contentW, win->contentH};
|
||||
win->onPaint(win, &fullRect);
|
||||
win->contentDirty = true;
|
||||
|
||||
// Dirty the window content area on screen so compositor redraws it
|
||||
if (win->widgetRoot) {
|
||||
|
|
|
|||
|
|
@ -289,7 +289,6 @@ void wgtInvalidate(WidgetT *w) {
|
|||
WindowT *win = w->window;
|
||||
RectT fullRect = {0, 0, win->contentW, win->contentH};
|
||||
win->onPaint(win, &fullRect);
|
||||
win->contentDirty = true;
|
||||
|
||||
// Dirty the window on screen
|
||||
dvxInvalidateWindow(ctx, win);
|
||||
|
|
@ -325,8 +324,6 @@ void wgtInvalidatePaint(WidgetT *w) {
|
|||
WindowT *win = w->window;
|
||||
RectT fullRect = {0, 0, win->contentW, win->contentH};
|
||||
win->onPaint(win, &fullRect);
|
||||
win->contentDirty = true;
|
||||
|
||||
dvxInvalidateWindow(ctx, win);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -222,7 +222,6 @@ void clearOtherSelections(WidgetT *except) {
|
|||
if (clearSelectionOnWidget(prev) && prevWin != except->window) {
|
||||
RectT fullRect = {0, 0, prevWin->contentW, prevWin->contentH};
|
||||
widgetOnPaint(prevWin, &fullRect);
|
||||
prevWin->contentDirty = true;
|
||||
dvxInvalidateWindow(ctx, prevWin);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
dvxshell/Makefile
Normal file
49
dvxshell/Makefile
Normal 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
281
dvxshell/shellApp.c
Normal 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
125
dvxshell/shellApp.h
Normal 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
493
dvxshell/shellDesktop.c
Normal 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
391
dvxshell/shellExport.c
Normal 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
247
dvxshell/shellMain.c
Normal 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;
|
||||
}
|
||||
|
|
@ -85,7 +85,9 @@ const char *tsGetName(uint32_t taskId);
|
|||
int32_t tsGetPriority(uint32_t taskId);
|
||||
TaskStateE tsGetState(uint32_t taskId);
|
||||
int32_t tsInit(void);
|
||||
int32_t tsKill(uint32_t taskId);
|
||||
int32_t tsPause(uint32_t taskId);
|
||||
void tsRecoverToMain(void);
|
||||
int32_t tsResume(uint32_t taskId);
|
||||
int32_t tsSetPriority(uint32_t taskId, int32_t priority);
|
||||
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) {
|
||||
if (!initialized || taskId >= (uint32_t)arrlen(tasks)) {
|
||||
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) {
|
||||
if (!initialized || taskId >= (uint32_t)arrlen(tasks)) {
|
||||
return TS_ERR_PARAM;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
#define TS_ERR_STATE (-5)
|
||||
|
||||
// Defaults
|
||||
#define TS_DEFAULT_STACK_SIZE 8192
|
||||
#define TS_DEFAULT_STACK_SIZE 32768
|
||||
#define TS_NAME_MAX 32
|
||||
|
||||
// Priority levels
|
||||
|
|
@ -79,6 +79,14 @@ const char *tsGetName(uint32_t taskId);
|
|||
// Terminate the calling task. Must not be called from the main task.
|
||||
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.
|
||||
uint32_t tsActiveCount(void);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue