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)/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
|
||||||
|
|
|
||||||
50
dvx/dvxApp.c
50
dvx/dvxApp.c
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
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);
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue