Compare commits

..

2 commits

30 changed files with 2899 additions and 868 deletions

View file

@ -24,4 +24,5 @@ clean:
$(MAKE) -C dvxshell clean
$(MAKE) -C apps clean
-rmdir obj/dvx/widgets obj/dvx/platform obj/dvx/thirdparty obj/dvx obj/tasks obj/dvxshell obj/apps obj 2>/dev/null
-rmdir bin/config bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin lib 2>/dev/null
-rm -rf bin/config
-rmdir bin/apps/ctrlpanel bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin lib 2>/dev/null

View file

@ -10,17 +10,21 @@ OBJDIR = ../obj/apps
BINDIR = ../bin/apps
# App definitions: each is a subdir with a single .c file
APPS = progman notepad clock dvxdemo
APPS = progman notepad clock dvxdemo ctrlpanel
.PHONY: all clean $(APPS)
all: $(APPS)
ctrlpanel: $(BINDIR)/ctrlpanel/ctrlpanel.app
progman: $(BINDIR)/progman/progman.app
notepad: $(BINDIR)/notepad/notepad.app
clock: $(BINDIR)/clock/clock.app
dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app
$(BINDIR)/ctrlpanel/ctrlpanel.app: $(OBJDIR)/ctrlpanel.o | $(BINDIR)/ctrlpanel
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
@ -36,6 +40,9 @@ $(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEM
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/
$(OBJDIR)/ctrlpanel.o: ctrlpanel/ctrlpanel.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/progman.o: progman/progman.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
@ -51,6 +58,9 @@ $(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c | $(OBJDIR)
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR)/ctrlpanel:
mkdir -p $(BINDIR)/ctrlpanel
$(BINDIR)/progman:
mkdir -p $(BINDIR)/progman
@ -64,13 +74,15 @@ $(BINDIR)/dvxdemo:
mkdir -p $(BINDIR)/dvxdemo
# Dependencies
$(OBJDIR)/ctrlpanel.o: ctrlpanel/ctrlpanel.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxPrefs.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/platform/dvxPlatform.h ../dvxshell/shellApp.h
$(OBJDIR)/progman.o: progman/progman.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h ../dvxshell/shellInfo.h
$(OBJDIR)/notepad.o: notepad/notepad.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h
$(OBJDIR)/clock.o: clock/clock.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h ../tasks/taskswitch.h
$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h
clean:
rm -f $(OBJDIR)/progman.o $(OBJDIR)/notepad.o $(OBJDIR)/clock.o $(OBJDIR)/dvxdemo.o
rm -f $(OBJDIR)/ctrlpanel.o $(OBJDIR)/progman.o $(OBJDIR)/notepad.o $(OBJDIR)/clock.o $(OBJDIR)/dvxdemo.o
rm -f $(BINDIR)/ctrlpanel/ctrlpanel.app
rm -f $(BINDIR)/progman/progman.app
rm -f $(BINDIR)/notepad/notepad.app
rm -f $(BINDIR)/clock/clock.app

View file

@ -144,7 +144,14 @@ static void updateTime(void) {
return;
}
snprintf(sState.timeStr, sizeof(sState.timeStr), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
int32_t hour12 = tm->tm_hour % 12;
const char *ampm = (tm->tm_hour < 12) ? "AM" : "PM";
if (hour12 == 0) {
hour12 = 12;
}
snprintf(sState.timeStr, sizeof(sState.timeStr), "%d:%02d:%02d %s", hour12, tm->tm_min, tm->tm_sec, ampm);
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
sState.lastUpdate = now;
}

806
apps/ctrlpanel/ctrlpanel.c Normal file
View file

@ -0,0 +1,806 @@
// ctrlpanel.c — DVX Control Panel
//
// System configuration app with four tabs:
// Mouse — scroll direction, double-click speed, acceleration
// Colors — all 18 system colors, theme load/save
// Desktop — wallpaper image (stretch mode)
// Video — resolution and color depth
//
// Changes preview live. OK saves to DVX.INI, Cancel reverts to the
// state captured when the control panel was opened.
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "platform/dvxPlatform.h"
#include "shellApp.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
// ============================================================
// Constants
// ============================================================
#define CP_WIN_W 460
#define CP_WIN_H 340
#define MAX_VIDEO_MODES 64
#define MAX_THEME_FILES 32
#define THEME_DIR "CONFIG/THEMES"
#define THEME_EXT ".THM"
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Control Panel",
.hasMainLoop = false,
.multiInstance = false,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
// ============================================================
// Video mode entry
// ============================================================
typedef struct {
int32_t w;
int32_t h;
int32_t bpp;
char label[32];
} VideoModeEntryT;
// ============================================================
// Module state
// ============================================================
static DxeAppContextT *sCtx = NULL;
static AppContextT *sAc = NULL;
static WindowT *sWin = NULL;
// Saved state for Cancel
static uint8_t sSavedColorRgb[ColorCountE][3];
static int32_t sSavedWheelDir;
static int32_t sSavedDblClick;
static int32_t sSavedAccel;
static int32_t sSavedVideoW;
static int32_t sSavedVideoH;
static int32_t sSavedVideoBpp;
static bool sSavedHadWallpaper;
// Mouse tab widgets
static WidgetT *sWheelDrop = NULL;
static WidgetT *sDblClickSldr = NULL;
static WidgetT *sDblClickLbl = NULL;
static WidgetT *sAccelDrop = NULL;
// Colors tab widgets
static WidgetT *sColorList = NULL;
static WidgetT *sRedSldr = NULL;
static WidgetT *sGreenSldr = NULL;
static WidgetT *sBlueSldr = NULL;
static WidgetT *sRedLbl = NULL;
static WidgetT *sGreenLbl = NULL;
static WidgetT *sBlueLbl = NULL;
// Desktop tab widgets
static WidgetT *sWallpaperLbl = NULL;
static char sWallpaperPath[260];
// Video tab
static VideoModeEntryT sVideoModes[MAX_VIDEO_MODES];
static const char *sVideoLabels[MAX_VIDEO_MODES];
static int32_t sVideoModeCount = 0;
static WidgetT *sVideoList = NULL;
// Theme list
static char sThemeNames[MAX_THEME_FILES][64];
static const char *sThemeLabels[MAX_THEME_FILES];
static char sThemePaths[MAX_THEME_FILES][260];
static int32_t sThemeCount = 0;
static WidgetT *sThemeList = NULL;
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
static void buildMouseTab(WidgetT *page);
static void buildColorsTab(WidgetT *page);
static void buildDesktopTab(WidgetT *page);
static void buildVideoTab(WidgetT *page);
static void enumVideoModesCb(int32_t w, int32_t h, int32_t bpp, void *userData);
static int32_t mapAccelName(const char *name);
static const char *mapAccelValue(int32_t val);
static void onAccelChange(WidgetT *w);
static void onApplyTheme(WidgetT *w);
static void onResetColors(WidgetT *w);
static void onCancel(WidgetT *w);
static void onChooseWallpaper(WidgetT *w);
static void onClearWallpaper(WidgetT *w);
static void onClose(WindowT *win);
static void onColorSelect(WidgetT *w);
static void onColorSlider(WidgetT *w);
static void onDblClickSlider(WidgetT *w);
static void onOk(WidgetT *w);
static void onSaveTheme(WidgetT *w);
static void onVideoApply(WidgetT *w);
static void onWheelChange(WidgetT *w);
static void saveSnapshot(void);
static void restoreSnapshot(void);
static void scanThemes(void);
static void updateColorSliders(void);
static void updateDblClickLabel(void);
// ============================================================
// buildColorsTab
// ============================================================
static void buildColorsTab(WidgetT *page) {
// Color list on the left, sliders on the right
WidgetT *hbox = wgtHBox(page);
hbox->weight = 100;
hbox->spacing = wgtPixels(8);
// Left side: color name list + theme controls
WidgetT *leftVbox = wgtVBox(hbox);
leftVbox->weight = 50;
leftVbox->spacing = wgtPixels(4);
wgtLabel(leftVbox, "System Colors:");
// Build color name list
static const char *colorNames[ColorCountE];
for (int32_t i = 0; i < ColorCountE; i++) {
colorNames[i] = dvxColorName((ColorIdE)i);
}
sColorList = wgtListBox(leftVbox);
sColorList->weight = 100;
sColorList->onChange = onColorSelect;
wgtListBoxSetItems(sColorList, colorNames, ColorCountE);
wgtListBoxSetSelected(sColorList, 0);
// Theme row
WidgetT *themeRow = wgtHBox(leftVbox);
themeRow->spacing = wgtPixels(4);
wgtLabel(themeRow, "Theme:");
scanThemes();
sThemeList = wgtDropdown(themeRow);
sThemeList->weight = 100;
wgtDropdownSetItems(sThemeList, sThemeLabels, sThemeCount);
WidgetT *loadBtn = wgtButton(themeRow, "Apply");
loadBtn->onClick = onApplyTheme;
loadBtn->prefW = wgtPixels(60);
WidgetT *saveBtn = wgtButton(themeRow, "Save...");
saveBtn->onClick = onSaveTheme;
saveBtn->prefW = wgtPixels(60);
WidgetT *resetBtn = wgtButton(themeRow, "Reset");
resetBtn->onClick = onResetColors;
resetBtn->prefW = wgtPixels(60);
// Right side: RGB sliders
WidgetT *rightVbox = wgtVBox(hbox);
rightVbox->weight = 50;
rightVbox->spacing = wgtPixels(4);
wgtLabel(rightVbox, "Red:");
sRedSldr = wgtSlider(rightVbox, 0, 255);
sRedSldr->onChange = onColorSlider;
sRedLbl = wgtLabel(rightVbox, "0");
wgtLabel(rightVbox, "Green:");
sGreenSldr = wgtSlider(rightVbox, 0, 255);
sGreenSldr->onChange = onColorSlider;
sGreenLbl = wgtLabel(rightVbox, "0");
wgtLabel(rightVbox, "Blue:");
sBlueSldr = wgtSlider(rightVbox, 0, 255);
sBlueSldr->onChange = onColorSlider;
sBlueLbl = wgtLabel(rightVbox, "0");
updateColorSliders();
}
// ============================================================
// buildDesktopTab
// ============================================================
static void buildDesktopTab(WidgetT *page) {
wgtLabel(page, "Wallpaper:");
sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)");
WidgetT *btnRow = wgtHBox(page);
btnRow->spacing = wgtPixels(8);
WidgetT *chooseBtn = wgtButton(btnRow, "Browse...");
chooseBtn->onClick = onChooseWallpaper;
chooseBtn->prefW = wgtPixels(90);
WidgetT *clearBtn = wgtButton(btnRow, "Clear");
clearBtn->onClick = onClearWallpaper;
clearBtn->prefW = wgtPixels(90);
}
// ============================================================
// buildMouseTab
// ============================================================
static void buildMouseTab(WidgetT *page) {
// Scroll direction
WidgetT *wheelRow = wgtHBox(page);
wheelRow->spacing = wgtPixels(8);
wgtLabel(wheelRow, "Scroll Wheel:");
static const char *wheelItems[] = {"Normal", "Reversed"};
sWheelDrop = wgtDropdown(wheelRow);
sWheelDrop->weight = 100;
sWheelDrop->onChange = onWheelChange;
wgtDropdownSetItems(sWheelDrop, wheelItems, 2);
wgtDropdownSetSelected(sWheelDrop, sAc->wheelDirection < 0 ? 1 : 0);
// Double-click speed
wgtLabel(page, "Double-Click Speed:");
WidgetT *dblRow = wgtHBox(page);
dblRow->spacing = wgtPixels(8);
wgtLabel(dblRow, "Fast");
sDblClickSldr = wgtSlider(dblRow, 200, 900);
sDblClickSldr->weight = 100;
sDblClickSldr->onChange = onDblClickSlider;
int32_t dblMs = prefsGetInt("mouse", "doubleclick", 500);
wgtSliderSetValue(sDblClickSldr, dblMs);
wgtLabel(dblRow, "Slow");
sDblClickLbl = wgtLabel(page, "");
updateDblClickLabel();
// Acceleration
WidgetT *accelRow = wgtHBox(page);
accelRow->spacing = wgtPixels(8);
wgtLabel(accelRow, "Acceleration:");
static const char *accelItems[] = {"Off", "Low", "Medium", "High"};
sAccelDrop = wgtDropdown(accelRow);
sAccelDrop->weight = 100;
sAccelDrop->onChange = onAccelChange;
wgtDropdownSetItems(sAccelDrop, accelItems, 4);
const char *accelStr = prefsGetString("mouse", "acceleration", "medium");
if (strcmp(accelStr, "off") == 0) {
wgtDropdownSetSelected(sAccelDrop, 0);
} else if (strcmp(accelStr, "low") == 0) {
wgtDropdownSetSelected(sAccelDrop, 1);
} else if (strcmp(accelStr, "high") == 0) {
wgtDropdownSetSelected(sAccelDrop, 3);
} else {
wgtDropdownSetSelected(sAccelDrop, 2);
}
}
// ============================================================
// buildVideoTab
// ============================================================
static void buildVideoTab(WidgetT *page) {
wgtLabel(page, "Available Video Modes:");
sVideoModeCount = 0;
platformVideoEnumModes(enumVideoModesCb, NULL);
sVideoList = wgtListBox(page);
sVideoList->weight = 100;
wgtListBoxSetItems(sVideoList, sVideoLabels, sVideoModeCount);
// Select the current mode
for (int32_t i = 0; i < sVideoModeCount; i++) {
if (sVideoModes[i].w == sAc->display.width &&
sVideoModes[i].h == sAc->display.height &&
sVideoModes[i].bpp == sAc->display.format.bitsPerPixel) {
wgtListBoxSetSelected(sVideoList, i);
break;
}
}
WidgetT *applyBtn = wgtButton(page, "Apply Mode");
applyBtn->onClick = onVideoApply;
applyBtn->prefW = wgtPixels(100);
}
// ============================================================
// enumVideoModesCb
// ============================================================
static void enumVideoModesCb(int32_t w, int32_t h, int32_t bpp, void *userData) {
(void)userData;
if (sVideoModeCount >= MAX_VIDEO_MODES) {
return;
}
VideoModeEntryT *m = &sVideoModes[sVideoModeCount];
m->w = w;
m->h = h;
m->bpp = bpp;
snprintf(m->label, sizeof(m->label), "%ldx%ld %ldbpp", (long)w, (long)h, (long)bpp);
sVideoLabels[sVideoModeCount] = m->label;
sVideoModeCount++;
}
// ============================================================
// mapAccelName / mapAccelValue
// ============================================================
static int32_t mapAccelName(const char *name) {
if (strcmp(name, "off") == 0) return 10000;
if (strcmp(name, "low") == 0) return 100;
if (strcmp(name, "medium") == 0) return 64;
if (strcmp(name, "high") == 0) return 32;
return 64;
}
static const char *mapAccelValue(int32_t idx) {
switch (idx) {
case 0: return "off";
case 1: return "low";
case 2: return "medium";
case 3: return "high";
default: return "medium";
}
}
// ============================================================
// Callbacks — Mouse tab
// ============================================================
static void onWheelChange(WidgetT *w) {
int32_t sel = wgtDropdownGetSelected(w);
int32_t dir = (sel == 1) ? -1 : 1;
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
int32_t accelVal = mapAccelName(accelName);
dvxSetMouseConfig(sAc, dir, dbl, accelVal);
}
static void onDblClickSlider(WidgetT *w) {
(void)w;
updateDblClickLabel();
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
int32_t accelVal = mapAccelName(accelName);
dvxSetMouseConfig(sAc, dir, dbl, accelVal);
}
static void onAccelChange(WidgetT *w) {
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
const char *accelName = mapAccelValue(wgtDropdownGetSelected(w));
int32_t accelVal = mapAccelName(accelName);
dvxSetMouseConfig(sAc, dir, dbl, accelVal);
}
// ============================================================
// Callbacks — Colors tab
// ============================================================
static void onColorSelect(WidgetT *w) {
(void)w;
updateColorSliders();
}
static void onColorSlider(WidgetT *w) {
(void)w;
int32_t sel = wgtListBoxGetSelected(sColorList);
if (sel < 0 || sel >= ColorCountE) {
return;
}
uint8_t r = (uint8_t)wgtSliderGetValue(sRedSldr);
uint8_t g = (uint8_t)wgtSliderGetValue(sGreenSldr);
uint8_t b = (uint8_t)wgtSliderGetValue(sBlueSldr);
// Update label text
static char rBuf[8];
static char gBuf[8];
static char bBuf[8];
snprintf(rBuf, sizeof(rBuf), "%d", r);
snprintf(gBuf, sizeof(gBuf), "%d", g);
snprintf(bBuf, sizeof(bBuf), "%d", b);
wgtSetText(sRedLbl, rBuf);
wgtSetText(sGreenLbl, gBuf);
wgtSetText(sBlueLbl, bBuf);
dvxSetColor(sAc, (ColorIdE)sel, r, g, b);
}
static void onApplyTheme(WidgetT *w) {
(void)w;
int32_t sel = wgtDropdownGetSelected(sThemeList);
if (sel < 0 || sel >= sThemeCount) {
return;
}
dvxLoadTheme(sAc, sThemePaths[sel]);
updateColorSliders();
}
static void onSaveTheme(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "Theme Files (*.thm)", "*.thm" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
if (dvxFileDialog(sAc, "Save Theme", FD_SAVE, THEME_DIR, filters, 2, path, sizeof(path))) {
dvxSaveTheme(sAc, path);
scanThemes();
wgtDropdownSetItems(sThemeList, sThemeLabels, sThemeCount);
}
}
static void onResetColors(WidgetT *w) {
(void)w;
dvxResetColorScheme(sAc);
updateColorSliders();
}
// ============================================================
// Callbacks — Desktop tab
// ============================================================
static void onChooseWallpaper(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "Images (*.bmp;*.png;*.jpg)", "*.bmp" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
if (dvxSetWallpaper(sAc, path)) {
strncpy(sWallpaperPath, path, sizeof(sWallpaperPath) - 1);
sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
wgtSetText(sWallpaperLbl, sWallpaperPath);
} else {
dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR);
}
}
}
static void onClearWallpaper(WidgetT *w) {
(void)w;
dvxSetWallpaper(sAc, NULL);
sWallpaperPath[0] = '\0';
wgtSetText(sWallpaperLbl, "(none)");
}
// ============================================================
// Callbacks — Video tab
// ============================================================
static void onVideoApply(WidgetT *w) {
(void)w;
int32_t sel = wgtListBoxGetSelected(sVideoList);
if (sel < 0 || sel >= sVideoModeCount) {
return;
}
VideoModeEntryT *m = &sVideoModes[sel];
if (m->w == sAc->display.width &&
m->h == sAc->display.height &&
m->bpp == sAc->display.format.bitsPerPixel) {
return;
}
if (dvxChangeVideoMode(sAc, m->w, m->h, m->bpp) != 0) {
dvxMessageBox(sAc, "Error", "Failed to change video mode.", MB_OK | MB_ICONERROR);
}
}
// ============================================================
// Callbacks — OK / Cancel / Close
// ============================================================
static void onOk(WidgetT *w) {
(void)w;
// Save mouse settings
int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
prefsSetString("mouse", "wheel", wheelSel == 1 ? "reversed" : "normal");
prefsSetInt("mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
prefsSetString("mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
// Save colors to INI
for (int32_t i = 0; i < ColorCountE; i++) {
uint8_t r;
uint8_t g;
uint8_t b;
dvxGetColor(sAc, (ColorIdE)i, &r, &g, &b);
char val[16];
snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
prefsSetString("colors", dvxColorName((ColorIdE)i), val);
}
// Save desktop settings
if (sWallpaperPath[0]) {
prefsSetString("desktop", "wallpaper", sWallpaperPath);
} else {
prefsRemove("desktop", "wallpaper");
}
// Save video settings
prefsSetInt("video", "width", sAc->display.width);
prefsSetInt("video", "height", sAc->display.height);
prefsSetInt("video", "bpp", sAc->display.format.bitsPerPixel);
prefsSave();
dvxDestroyWindow(sAc, sWin);
sWin = NULL;
}
static void onCancel(WidgetT *w) {
(void)w;
restoreSnapshot();
dvxDestroyWindow(sAc, sWin);
sWin = NULL;
}
static void onClose(WindowT *win) {
restoreSnapshot();
dvxDestroyWindow(sAc, win);
sWin = NULL;
}
// ============================================================
// saveSnapshot / restoreSnapshot
// ============================================================
static void saveSnapshot(void) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
sSavedWheelDir = sAc->wheelDirection;
sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC);
sSavedAccel = 0;
sSavedVideoW = sAc->display.width;
sSavedVideoH = sAc->display.height;
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
sSavedHadWallpaper = (sAc->wallpaperBuf != NULL);
const char *wp = prefsGetString("desktop", "wallpaper", NULL);
if (wp) {
strncpy(sWallpaperPath, wp, sizeof(sWallpaperPath) - 1);
sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
} else {
sWallpaperPath[0] = '\0';
}
}
static void restoreSnapshot(void) {
// Restore colors
memcpy(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb));
dvxApplyColorScheme(sAc);
// Restore mouse
const char *accelStr = prefsGetString("mouse", "acceleration", "medium");
int32_t accelVal = mapAccelName(accelStr);
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal);
// Restore video mode if changed
if (sAc->display.width != sSavedVideoW ||
sAc->display.height != sSavedVideoH ||
sAc->display.format.bitsPerPixel != sSavedVideoBpp) {
dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp);
}
// Restore wallpaper
if (sSavedHadWallpaper && sWallpaperPath[0]) {
dvxSetWallpaper(sAc, sWallpaperPath);
} else {
dvxSetWallpaper(sAc, NULL);
}
}
// ============================================================
// scanThemes
// ============================================================
static void scanThemes(void) {
sThemeCount = 0;
// Scan THEME_DIR for .THM files
DIR *dir = opendir(THEME_DIR);
if (!dir) {
return;
}
struct dirent *ent;
while ((ent = readdir(dir)) != NULL && sThemeCount < MAX_THEME_FILES) {
char *dot = strrchr(ent->d_name, '.');
if (!dot) {
continue;
}
// Case-insensitive extension check
if (strcasecmp(dot, THEME_EXT) != 0) {
continue;
}
// Use filename without extension as display name
int32_t nameLen = (int32_t)(dot - ent->d_name);
if (nameLen >= 64) {
nameLen = 63;
}
memcpy(sThemeNames[sThemeCount], ent->d_name, nameLen);
sThemeNames[sThemeCount][nameLen] = '\0';
sThemeLabels[sThemeCount] = sThemeNames[sThemeCount];
snprintf(sThemePaths[sThemeCount], sizeof(sThemePaths[sThemeCount]), "%s/%s", THEME_DIR, ent->d_name);
sThemeCount++;
}
closedir(dir);
}
// ============================================================
// updateColorSliders
// ============================================================
static void updateColorSliders(void) {
int32_t sel = wgtListBoxGetSelected(sColorList);
if (sel < 0 || sel >= ColorCountE) {
return;
}
uint8_t r;
uint8_t g;
uint8_t b;
dvxGetColor(sAc, (ColorIdE)sel, &r, &g, &b);
wgtSliderSetValue(sRedSldr, r);
wgtSliderSetValue(sGreenSldr, g);
wgtSliderSetValue(sBlueSldr, b);
static char rBuf[8];
static char gBuf[8];
static char bBuf[8];
snprintf(rBuf, sizeof(rBuf), "%d", r);
snprintf(gBuf, sizeof(gBuf), "%d", g);
snprintf(bBuf, sizeof(bBuf), "%d", b);
wgtSetText(sRedLbl, rBuf);
wgtSetText(sGreenLbl, gBuf);
wgtSetText(sBlueLbl, bBuf);
}
// ============================================================
// updateDblClickLabel
// ============================================================
static void updateDblClickLabel(void) {
static char buf[32];
snprintf(buf, sizeof(buf), "%ld ms", (long)wgtSliderGetValue(sDblClickSldr));
wgtSetText(sDblClickLbl, buf);
}
// ============================================================
// appMain
// ============================================================
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
sAc = ctx->shellCtx;
int32_t winX = (sAc->display.width - CP_WIN_W) / 2;
int32_t winY = (sAc->display.height - CP_WIN_H) / 2;
sWin = dvxCreateWindow(sAc, "Control Panel", winX, winY, CP_WIN_W, CP_WIN_H, true);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
saveSnapshot();
WidgetT *root = wgtInitWindow(sAc, sWin);
// Tab control fills the window
WidgetT *tabs = wgtTabControl(root);
tabs->weight = 100;
WidgetT *mouseTab = wgtTabPage(tabs, "Mouse");
WidgetT *colorsTab = wgtTabPage(tabs, "Colors");
WidgetT *desktopTab = wgtTabPage(tabs, "Desktop");
WidgetT *videoTab = wgtTabPage(tabs, "Video");
buildMouseTab(mouseTab);
buildColorsTab(colorsTab);
buildDesktopTab(desktopTab);
buildVideoTab(videoTab);
// OK / Cancel buttons at bottom
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
btnRow->spacing = wgtPixels(8);
WidgetT *okBtn = wgtButton(btnRow, "OK");
okBtn->onClick = onOk;
okBtn->prefW = wgtPixels(80);
WidgetT *cancelBtn = wgtButton(btnRow, "Cancel");
cancelBtn->onClick = onCancel;
cancelBtn->prefW = wgtPixels(80);
dvxFitWindow(sAc, sWin);
return 0;
}

View file

@ -14,6 +14,7 @@
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "platform/dvxPlatform.h"
#include "shellApp.h"
#include <stdint.h>
@ -77,6 +78,7 @@ static void onMenu(WindowT *win, int32_t menuId);
AppDescriptorT appDescriptor = {
.name = "Notepad",
.hasMainLoop = false,
.multiInstance = true,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
@ -194,6 +196,7 @@ static void doOpen(void) {
if (buf) {
fread(buf, 1, size, f);
buf[size] = '\0';
platformStripLineEndings(buf, (int32_t)size);
wgtSetText(sTextArea, buf);
free(buf);
}
@ -228,7 +231,16 @@ static void doSave(void) {
return;
}
fwrite(text, 1, strlen(text), f);
const char *eol = platformLineEnding();
for (const char *p = text; *p; p++) {
if (*p == '\n') {
fputs(eol, f);
} else {
fputc(*p, f);
}
}
fclose(f);
markClean();
}

View file

@ -23,6 +23,7 @@
#include "dvxWidget.h"
#include "dvxWm.h"
#include "shellApp.h"
#include "shellTaskMgr.h"
#include "shellInfo.h"
#include <stdint.h>
@ -58,9 +59,6 @@
#define CMD_TASK_MGR 301
#define CMD_SYSINFO 302
// Task Manager column count
#define TM_COL_COUNT 4
// ============================================================
// Module state
// ============================================================
@ -76,7 +74,6 @@ typedef struct {
// of collision between apps even though the names look global.
static DxeAppContextT *sCtx = NULL;
static AppContextT *sAc = NULL;
static int32_t sMyAppId = 0;
static WindowT *sPmWindow = NULL;
static WidgetT *sStatusLabel = NULL;
static bool sMinOnRun = false;
@ -99,14 +96,6 @@ static void showAboutDialog(void);
static void showSystemInfo(void);
static void updateStatusText(void);
// Task Manager
static WindowT *sTmWindow = NULL;
static WidgetT *sTmListView = NULL;
static void buildTaskManager(void);
static void onTmClose(WindowT *win);
static void onTmEndTask(WidgetT *w);
static void onTmSwitchTo(WidgetT *w);
static void refreshTaskList(void);
// ============================================================
// App descriptor
@ -220,86 +209,11 @@ static void buildPmWindow(void) {
}
// Build or raise the Task Manager window. Singleton pattern: if sTmWindow is
// already live, we just raise it to the top of the Z-order instead of
// creating a duplicate, mimicking Windows 3.x Task Manager behavior.
static void buildTaskManager(void) {
if (sTmWindow) {
// Already open — find it in the window stack and bring to front
for (int32_t i = 0; i < sAc->stack.count; i++) {
if (sAc->stack.windows[i] == sTmWindow) {
wmRaiseWindow(&sAc->stack, &sAc->dirty, i);
wmSetFocus(&sAc->stack, &sAc->dirty, sAc->stack.count - 1);
break;
}
}
return;
}
int32_t screenW = sAc->display.width;
int32_t screenH = sAc->display.height;
int32_t winW = 420;
int32_t winH = 280;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 3;
sTmWindow = dvxCreateWindow(sAc, "Task Manager", winX, winY, winW, winH, true);
if (!sTmWindow) {
return;
}
sTmWindow->onClose = onTmClose;
WidgetT *root = wgtInitWindow(sAc, sTmWindow);
// ListView with Name (descriptor), File (basename), Type, Status columns.
// Static: wgtListViewSetColumns stores a pointer, not a copy.
static ListViewColT tmCols[TM_COL_COUNT];
tmCols[0].title = "Name";
tmCols[0].width = wgtPercent(35);
tmCols[0].align = ListViewAlignLeftE;
tmCols[1].title = "File";
tmCols[1].width = wgtPercent(30);
tmCols[1].align = ListViewAlignLeftE;
tmCols[2].title = "Type";
tmCols[2].width = wgtPercent(17);
tmCols[2].align = ListViewAlignLeftE;
tmCols[3].title = "Status";
tmCols[3].width = wgtPercent(18);
tmCols[3].align = ListViewAlignLeftE;
sTmListView = wgtListView(root);
sTmListView->weight = 100;
sTmListView->prefH = wgtPixels(160);
wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT);
// Button row right-aligned (AlignEndE) to follow Windows UI convention
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
btnRow->spacing = wgtPixels(8);
WidgetT *switchBtn = wgtButton(btnRow, "Switch To");
switchBtn->onClick = onTmSwitchTo;
switchBtn->prefW = wgtPixels(90);
WidgetT *endBtn = wgtButton(btnRow, "End Task");
endBtn->onClick = onTmEndTask;
endBtn->prefW = wgtPixels(90);
refreshTaskList();
dvxFitWindow(sAc, sTmWindow);
}
// Shell calls this via shellRegisterDesktopUpdate whenever an app is loaded,
// reaped, or crashes. We refresh the running count and Task Manager list.
// reaped, or crashes. We refresh the running count in the status bar.
// (Task Manager refresh is handled by the shell's shellDesktopUpdate.)
static void desktopUpdate(void) {
updateStatusText();
if (sTmWindow) {
refreshTaskList();
}
}
@ -390,165 +304,12 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
break;
case CMD_TASK_MGR:
buildTaskManager();
shellTaskMgrOpen(sAc);
break;
}
}
// Null the static pointers before destroying so buildTaskManager() knows
// the window is gone and will create a fresh one next time.
static void onTmClose(WindowT *win) {
sTmListView = NULL;
sTmWindow = NULL;
dvxDestroyWindow(sAc, win);
}
static void onTmEndTask(WidgetT *w) {
(void)w;
if (!sTmListView) {
return;
}
int32_t sel = wgtListViewGetSelected(sTmListView);
if (sel < 0) {
return;
}
// The list view rows don't carry app IDs directly, so we re-walk the
// app slot table in the same order as refreshTaskList() to map the
// selected row index back to the correct ShellAppT. We skip our own
// appId so progman can't kill itself.
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (i == sMyAppId) {
continue;
}
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
shellForceKillApp(sAc, app);
refreshTaskList();
updateStatusText();
return;
}
idx++;
}
}
}
static void onTmSwitchTo(WidgetT *w) {
(void)w;
if (!sTmListView) {
return;
}
int32_t sel = wgtListViewGetSelected(sTmListView);
if (sel < 0) {
return;
}
// Same index-to-appId mapping as onTmEndTask. We scan the window
// stack top-down (highest Z first) to find the app's topmost window,
// restore it if minimized, then raise and focus it.
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (i == sMyAppId) {
continue;
}
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
// Find the topmost window for this app
for (int32_t j = sAc->stack.count - 1; j >= 0; j--) {
WindowT *win = sAc->stack.windows[j];
if (win->appId == i) {
if (win->minimized) {
wmRestoreMinimized(&sAc->stack, &sAc->dirty, win);
}
wmRaiseWindow(&sAc->stack, &sAc->dirty, j);
wmSetFocus(&sAc->stack, &sAc->dirty, sAc->stack.count - 1);
return;
}
}
return;
}
idx++;
}
}
}
// Rebuild the Task Manager list view from the shell's app slot table.
// Uses static arrays because the list view data pointers must remain valid
// until the next call to wgtListViewSetData (the widget doesn't copy strings).
static void refreshTaskList(void) {
if (!sTmListView) {
return;
}
// Flat array of cell strings: [row0_col0..col3, row1_col0..col3, ...]
static const char *cells[SHELL_MAX_APPS * TM_COL_COUNT];
static char typeStrs[SHELL_MAX_APPS][12];
static char fileStrs[SHELL_MAX_APPS][64];
int32_t rowCount = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
if (i == sMyAppId) {
continue;
}
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
int32_t base = rowCount * TM_COL_COUNT;
// Column 0: Name (from appDescriptor)
cells[base] = app->name;
// Column 1: Filename (basename of .app path)
const char *slash = strrchr(app->path, '/');
const char *back = strrchr(app->path, '\\');
if (back > slash) {
slash = back;
}
const char *fname = slash ? slash + 1 : app->path;
snprintf(fileStrs[rowCount], sizeof(fileStrs[rowCount]), "%.63s", fname);
cells[base + 1] = fileStrs[rowCount];
// Column 2: Type (main-loop task vs callback-only)
snprintf(typeStrs[rowCount], sizeof(typeStrs[rowCount]), "%s", app->hasMainLoop ? "Task" : "Callback");
cells[base + 2] = typeStrs[rowCount];
// Column 3: Status
cells[base + 3] = "Running";
rowCount++;
}
}
wgtListViewSetData(sTmListView, cells, rowCount);
}
// Top-level scan entry point. Recursively walks apps/ looking for .app files.
// The apps/ path is relative to the working directory, which the shell sets
// to the root of the DVX install before loading any apps.
@ -681,14 +442,7 @@ static void updateStatusText(void) {
}
static char buf[64];
// shellRunningAppCount() includes us. Subtract 1 so the user sees
// only the apps they launched, not the Program Manager itself.
int32_t count = shellRunningAppCount() - 1;
if (count < 0) {
count = 0;
}
int32_t count = shellRunningAppCount();
if (count == 0) {
snprintf(buf, sizeof(buf), "No applications running");
@ -712,7 +466,6 @@ static void updateStatusText(void) {
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
sAc = ctx->shellCtx;
sMyAppId = ctx->appId;
scanAppsDir();
buildPmWindow();
@ -721,5 +474,7 @@ int32_t appMain(DxeAppContextT *ctx) {
// bar and Task Manager stay current without polling
shellRegisterDesktopUpdate(desktopUpdate);
return 0;
}

View file

@ -5,19 +5,17 @@ CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar
RANLIB = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ranlib
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../tasks
OBJDIR = ../obj/dvx
WOBJDIR = ../obj/dvx/widgets
POBJDIR = ../obj/dvx/platform
TOBJDIR = ../obj/dvx/thirdparty
LIBDIR = ../lib
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c dvxPrefs.c
PSRCS = platform/dvxPlatformDos.c
TSRCS = thirdparty/ini/src/ini.c
WSRCS = widgets/widgetAnsiTerm.c \
widgets/widgetClass.c \
@ -54,15 +52,14 @@ WSRCS = widgets/widgetAnsiTerm.c \
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS))
WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS))
TOBJS = $(TOBJDIR)/ini.o
TARGET = $(LIBDIR)/libdvx.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) $(TOBJS) | $(LIBDIR)
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS) $(TOBJS)
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) | $(LIBDIR)
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS)
$(RANLIB) $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
@ -74,9 +71,6 @@ $(POBJDIR)/%.o: platform/%.c | $(POBJDIR)
$(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(TOBJDIR)/ini.o: thirdparty/ini/src/ini.c | $(TOBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
@ -86,9 +80,6 @@ $(POBJDIR):
$(WOBJDIR):
mkdir -p $(WOBJDIR)
$(TOBJDIR):
mkdir -p $(TOBJDIR)
$(LIBDIR):
mkdir -p $(LIBDIR)
@ -102,13 +93,12 @@ $(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)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h widgets/widgetInternal.h dvxTypes.h dvxDraw.h
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h thirdparty/ini/src/ini.h
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h
# Platform file dependencies
$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h
# Thirdparty file dependencies
$(TOBJDIR)/ini.o: thirdparty/ini/src/ini.c thirdparty/ini/src/ini.h
# Widget file dependencies
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
@ -145,4 +135,4 @@ $(WOBJDIR)/widgetScrollbar.o: widgets/widgetScrollbar.c $(WIDGET_DEPS)
$(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS)
clean:
rm -f $(OBJS) $(POBJS) $(WOBJS) $(TOBJS) $(TARGET)
rm -f $(OBJS) $(POBJS) $(WOBJS) $(TARGET)

View file

@ -461,8 +461,18 @@ static void compositeAndFlush(AppContextT *ctx) {
// Set clip rect to this dirty rect
setClipRect(d, dr->x, dr->y, dr->w, dr->h);
// 1. Draw desktop background
// 1. Draw desktop background (wallpaper or solid color)
if (ctx->wallpaperBuf) {
int32_t bytesPerPx = d->format.bitsPerPixel / 8;
for (int32_t row = dr->y; row < dr->y + dr->h; row++) {
uint8_t *src = ctx->wallpaperBuf + row * ctx->wallpaperPitch + dr->x * bytesPerPx;
uint8_t *dst = d->backBuf + row * d->pitch + dr->x * bytesPerPx;
memcpy(dst, src, dr->w * bytesPerPx);
}
} else {
rectFill(d, ops, dr->x, dr->y, dr->w, dr->h, ctx->colors.desktop);
}
// 2. Draw minimized window icons (under all windows)
wmDrawMinimizedIcons(d, ops, &ctx->colors, ws, dr);
@ -1373,6 +1383,86 @@ const char *dvxClipboardGet(int32_t *outLen) {
}
// ============================================================
// Color scheme — name table and indexed access
// ============================================================
static const char *sColorNames[ColorCountE] = {
"desktop", "windowFace", "windowHighlight",
"windowShadow", "activeTitleBg", "activeTitleFg",
"inactiveTitleBg", "inactiveTitleFg", "contentBg",
"contentFg", "menuBg", "menuFg",
"menuHighlightBg", "menuHighlightFg", "buttonFace",
"scrollbarBg", "scrollbarFg", "scrollbarTrough"
};
// Default GEOS Ensemble Motif-style colors (RGB triplets)
static const uint8_t sDefaultColors[ColorCountE][3] = {
{ 0, 128, 128}, // desktop — GEOS teal
{192, 192, 192}, // windowFace
{255, 255, 255}, // windowHighlight
{128, 128, 128}, // windowShadow
{ 48, 48, 48}, // activeTitleBg — dark charcoal
{255, 255, 255}, // activeTitleFg
{160, 160, 160}, // inactiveTitleBg
{ 64, 64, 64}, // inactiveTitleFg
{192, 192, 192}, // contentBg
{ 0, 0, 0}, // contentFg
{192, 192, 192}, // menuBg
{ 0, 0, 0}, // menuFg
{ 48, 48, 48}, // menuHighlightBg
{255, 255, 255}, // menuHighlightFg
{192, 192, 192}, // buttonFace
{192, 192, 192}, // scrollbarBg
{128, 128, 128}, // scrollbarFg
{160, 160, 160}, // scrollbarTrough
};
// Access the packed color value in ColorSchemeT by index.
static uint32_t *colorSlot(ColorSchemeT *cs, ColorIdE id) {
return ((uint32_t *)cs) + (int32_t)id;
}
// ============================================================
// dvxColorName
// ============================================================
const char *dvxColorName(ColorIdE id) {
if (id < 0 || id >= ColorCountE) {
return "unknown";
}
return sColorNames[id];
}
// ============================================================
// dvxApplyColorScheme
// ============================================================
void dvxApplyColorScheme(AppContextT *ctx) {
DisplayT *d = &ctx->display;
for (int32_t i = 0; i < ColorCountE; i++) {
*colorSlot(&ctx->colors, (ColorIdE)i) = packColor(d, ctx->colorRgb[i][0], ctx->colorRgb[i][1], ctx->colorRgb[i][2]);
}
// Repaint everything
dirtyListAdd(&ctx->dirty, 0, 0, d->width, d->height);
}
// ============================================================
// dvxResetColorScheme
// ============================================================
void dvxResetColorScheme(AppContextT *ctx) {
memcpy(ctx->colorRgb, sDefaultColors, sizeof(sDefaultColors));
dvxApplyColorScheme(ctx);
}
// ============================================================
// dvxCascadeWindows
// ============================================================
@ -1423,6 +1513,124 @@ void dvxCascadeWindows(AppContextT *ctx) {
}
// ============================================================
// dvxChangeVideoMode
// ============================================================
//
// Live video mode switch. Saves the old display state, attempts to
// set the new mode, and if successful, reinitializes all dependent
// subsystems: blit ops, colors, cursors, mouse range, wallpaper,
// and all window content buffers. Windows larger than the new
// screen are clamped. On failure, the old mode is restored.
int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
// Save old state for rollback
DisplayT oldDisplay = ctx->display;
// Stash old wallpaper (don't free — we may need it for rollback)
uint8_t *oldWpBuf = ctx->wallpaperBuf;
int32_t oldWpPitch = ctx->wallpaperPitch;
ctx->wallpaperBuf = NULL;
ctx->wallpaperPitch = 0;
// Free old video buffers (no text mode restore)
platformVideoFreeBuffers(&ctx->display);
// Try the new mode
if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) {
// Restore old mode
ctx->display = oldDisplay;
ctx->display.backBuf = NULL;
ctx->display.palette = NULL;
if (videoInit(&ctx->display, oldDisplay.width, oldDisplay.height, oldDisplay.format.bitsPerPixel) != 0) {
// Both failed — catastrophic
return -1;
}
// Restore wallpaper
ctx->wallpaperBuf = oldWpBuf;
ctx->wallpaperPitch = oldWpPitch;
drawInit(&ctx->blitOps, &ctx->display);
dvxApplyColorScheme(ctx);
return -1;
}
// New mode succeeded — free old wallpaper
free(oldWpBuf);
// Reinit blit ops for new pixel format
drawInit(&ctx->blitOps, &ctx->display);
// Repack all colors for new pixel format
dvxApplyColorScheme(ctx);
// Repack cursor colors
ctx->cursorFg = packColor(&ctx->display, 255, 255, 255);
ctx->cursorBg = packColor(&ctx->display, 0, 0, 0);
// Reinit mouse range
platformMouseInit(ctx->display.width, ctx->display.height);
ctx->hasMouseWheel = platformMouseWheelInit();
// Clamp mouse position to new screen
if (ctx->mouseX >= ctx->display.width) {
ctx->mouseX = ctx->display.width - 1;
}
if (ctx->mouseY >= ctx->display.height) {
ctx->mouseY = ctx->display.height - 1;
}
// Clamp and reallocate all window content buffers
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
// Clamp window position to new screen bounds
if (win->x + win->w > ctx->display.width) {
win->x = ctx->display.width - win->w;
}
if (win->x < 0) {
win->x = 0;
win->w = ctx->display.width;
}
if (win->y + win->h > ctx->display.height) {
win->y = ctx->display.height - win->h;
}
if (win->y < 0) {
win->y = 0;
win->h = ctx->display.height;
}
// Clear maximized flag since screen size changed
win->maximized = false;
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
if (win->onResize) {
win->onResize(win, win->contentW, win->contentH);
}
if (win->onPaint) {
RectT fullRect = {0, 0, win->contentW, win->contentH};
win->onPaint(win, &fullRect);
win->contentDirty = true;
}
}
// Reset clip and dirty the full screen
resetClipRect(&ctx->display);
dirtyListInit(&ctx->dirty);
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
return 0;
}
// ============================================================
// dvxCreateAccelTable
// ============================================================
@ -1548,6 +1756,244 @@ const BitmapFontT *dvxGetFont(const AppContextT *ctx) {
}
// ============================================================
// dvxGetColor
// ============================================================
void dvxGetColor(const AppContextT *ctx, ColorIdE id, uint8_t *r, uint8_t *g, uint8_t *b) {
if (id < 0 || id >= ColorCountE) {
*r = *g = *b = 0;
return;
}
*r = ctx->colorRgb[id][0];
*g = ctx->colorRgb[id][1];
*b = ctx->colorRgb[id][2];
}
// ============================================================
// dvxLoadTheme
// ============================================================
bool dvxLoadTheme(AppContextT *ctx, const char *filename) {
FILE *fp = fopen(filename, "rb");
if (!fp) {
return false;
}
char line[256];
while (fgets(line, sizeof(line), fp)) {
// Strip trailing whitespace
char *end = line + strlen(line) - 1;
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ')) {
*end-- = '\0';
}
char *p = line;
while (*p == ' ' || *p == '\t') {
p++;
}
// Skip comments, blank lines, section headers
if (*p == '\0' || *p == ';' || *p == '#' || *p == '[') {
continue;
}
// Parse key = r,g,b
char *eq = strchr(p, '=');
if (!eq) {
continue;
}
*eq = '\0';
// Trim key
char *key = p;
char *keyEnd = eq - 1;
while (keyEnd >= key && (*keyEnd == ' ' || *keyEnd == '\t')) {
*keyEnd-- = '\0';
}
// Parse r,g,b
char *val = eq + 1;
while (*val == ' ' || *val == '\t') {
val++;
}
int32_t r;
int32_t g;
int32_t b;
if (sscanf(val, "%d,%d,%d", &r, &g, &b) != 3) {
continue;
}
// Find matching color name
for (int32_t i = 0; i < ColorCountE; i++) {
if (strcmp(key, sColorNames[i]) == 0) {
ctx->colorRgb[i][0] = (uint8_t)r;
ctx->colorRgb[i][1] = (uint8_t)g;
ctx->colorRgb[i][2] = (uint8_t)b;
break;
}
}
}
fclose(fp);
dvxApplyColorScheme(ctx);
return true;
}
// ============================================================
// dvxSaveTheme
// ============================================================
bool dvxSaveTheme(const AppContextT *ctx, const char *filename) {
FILE *fp = fopen(filename, "wb");
if (!fp) {
return false;
}
fprintf(fp, "; DVX Color Theme\r\n\r\n[colors]\r\n");
for (int32_t i = 0; i < ColorCountE; i++) {
fprintf(fp, "%-20s = %d,%d,%d\r\n", sColorNames[i], ctx->colorRgb[i][0], ctx->colorRgb[i][1], ctx->colorRgb[i][2]);
}
fclose(fp);
return true;
}
// ============================================================
// dvxSetColor
// ============================================================
void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b) {
if (id < 0 || id >= ColorCountE) {
return;
}
ctx->colorRgb[id][0] = r;
ctx->colorRgb[id][1] = g;
ctx->colorRgb[id][2] = b;
*colorSlot(&ctx->colors, id) = packColor(&ctx->display, r, g, b);
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
}
// ============================================================
// dvxSetWallpaper
// ============================================================
bool dvxSetWallpaper(AppContextT *ctx, const char *path) {
// Free existing wallpaper
free(ctx->wallpaperBuf);
ctx->wallpaperBuf = NULL;
ctx->wallpaperPitch = 0;
if (!path) {
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
return true;
}
int32_t imgW;
int32_t imgH;
int32_t channels;
uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3);
if (!rgb) {
return false;
}
// Pre-scale to screen dimensions using bilinear interpolation and
// convert to native pixel format. Bilinear samples the 4 nearest
// source pixels and blends by fractional distance, producing smooth
// gradients instead of blocky nearest-neighbor artifacts. Uses
// 8-bit fixed-point weights (256 = 1.0) to avoid floating point.
int32_t screenW = ctx->display.width;
int32_t screenH = ctx->display.height;
int32_t bpp = ctx->display.format.bitsPerPixel;
int32_t pitch = screenW * (bpp / 8);
uint8_t *buf = (uint8_t *)malloc(pitch * screenH);
if (!buf) {
stbi_image_free(rgb);
return false;
}
int32_t srcStride = imgW * 3;
for (int32_t y = 0; y < screenH; y++) {
// Fixed-point source Y: 16.16
int32_t srcYfp = (int32_t)((int64_t)y * imgH * 65536 / screenH);
int32_t sy0 = srcYfp >> 16;
int32_t sy1 = sy0 + 1;
int32_t fy = (srcYfp >> 8) & 0xFF; // fractional Y (0-255)
int32_t ify = 256 - fy;
uint8_t *dst = buf + y * pitch;
if (sy1 >= imgH) {
sy1 = imgH - 1;
}
uint8_t *row0 = rgb + sy0 * srcStride;
uint8_t *row1 = rgb + sy1 * srcStride;
for (int32_t x = 0; x < screenW; x++) {
int32_t srcXfp = (int32_t)((int64_t)x * imgW * 65536 / screenW);
int32_t sx0 = srcXfp >> 16;
int32_t sx1 = sx0 + 1;
int32_t fx = (srcXfp >> 8) & 0xFF;
int32_t ifx = 256 - fx;
if (sx1 >= imgW) {
sx1 = imgW - 1;
}
// Sample 4 source pixels
uint8_t *p00 = row0 + sx0 * 3;
uint8_t *p10 = row0 + sx1 * 3;
uint8_t *p01 = row1 + sx0 * 3;
uint8_t *p11 = row1 + sx1 * 3;
// Bilinear blend (8-bit fixed-point, 256 = 1.0)
int32_t r = (p00[0] * ifx * ify + p10[0] * fx * ify + p01[0] * ifx * fy + p11[0] * fx * fy) >> 16;
int32_t g = (p00[1] * ifx * ify + p10[1] * fx * ify + p01[1] * ifx * fy + p11[1] * fx * fy) >> 16;
int32_t b = (p00[2] * ifx * ify + p10[2] * fx * ify + p01[2] * ifx * fy + p11[2] * fx * fy) >> 16;
uint32_t px = packColor(&ctx->display, (uint8_t)r, (uint8_t)g, (uint8_t)b);
if (bpp == 8) {
dst[x] = (uint8_t)px;
} else if (bpp == 15 || bpp == 16) {
((uint16_t *)dst)[x] = (uint16_t)px;
} else {
((uint32_t *)dst)[x] = px;
}
}
}
stbi_image_free(rgb);
ctx->wallpaperBuf = buf;
ctx->wallpaperPitch = pitch;
dirtyListAdd(&ctx->dirty, 0, 0, screenW, screenH);
return true;
}
// ============================================================
// dvxInit
// ============================================================
@ -1912,6 +2358,8 @@ void dvxShutdown(AppContextT *ctx) {
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
}
free(ctx->wallpaperBuf);
ctx->wallpaperBuf = NULL;
videoShutdown(&ctx->display);
}
@ -1922,6 +2370,10 @@ void dvxShutdown(AppContextT *ctx) {
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title) {
wmSetTitle(win, &ctx->dirty, title);
if (ctx->onTitleChange) {
ctx->onTitleChange(ctx->titleChangeCtx);
}
}
@ -2182,7 +2634,11 @@ static void executeSysMenuCmd(AppContextT *ctx, int32_t cmd) {
case SysMenuMinimizeE:
if (ctx->modalWindow != win) {
wmMinimize(&ctx->stack, &ctx->dirty, win);
dirtyListAdd(&ctx->dirty, 0, ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING, ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
int32_t iconY;
int32_t iconH;
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
}
break;
@ -2259,10 +2715,11 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
if (ctx->lastIconClickId == iconWin->id &&
(now - ctx->lastIconClickTime) < ctx->dblClickTicks) {
// Double-click: restore minimized window
// Dirty the entire icon strip area
dirtyListAdd(&ctx->dirty, 0,
ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING,
ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
// Dirty the entire icon area (may span multiple rows)
int32_t iconY;
int32_t iconH;
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin);
ctx->lastIconClickId = -1;
} else {
@ -2382,10 +2839,11 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
case HIT_MINIMIZE:
if (ctx->modalWindow != win) {
wmMinimize(&ctx->stack, &ctx->dirty, win);
// Dirty the icon strip area so the new icon gets drawn
dirtyListAdd(&ctx->dirty, 0,
ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING,
ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
int32_t iconY;
int32_t iconH;
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
}
break;
@ -2412,27 +2870,9 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
// GEOS's blue, giving DV/X its own identity.
static void initColorScheme(AppContextT *ctx) {
DisplayT *d = &ctx->display;
// GEOS Ensemble Motif-style color scheme
ctx->colors.desktop = packColor(d, 0, 128, 128); // GEOS teal desktop
ctx->colors.windowFace = packColor(d, 192, 192, 192); // standard Motif grey
ctx->colors.windowHighlight = packColor(d, 255, 255, 255);
ctx->colors.windowShadow = packColor(d, 128, 128, 128);
ctx->colors.activeTitleBg = packColor(d, 48, 48, 48); // GEOS dark charcoal
ctx->colors.activeTitleFg = packColor(d, 255, 255, 255);
ctx->colors.inactiveTitleBg = packColor(d, 160, 160, 160); // lighter grey
ctx->colors.inactiveTitleFg = packColor(d, 64, 64, 64);
ctx->colors.contentBg = packColor(d, 192, 192, 192);
ctx->colors.contentFg = packColor(d, 0, 0, 0);
ctx->colors.menuBg = packColor(d, 192, 192, 192);
ctx->colors.menuFg = packColor(d, 0, 0, 0);
ctx->colors.menuHighlightBg = packColor(d, 48, 48, 48);
ctx->colors.menuHighlightFg = packColor(d, 255, 255, 255);
ctx->colors.buttonFace = packColor(d, 192, 192, 192);
ctx->colors.scrollbarBg = packColor(d, 192, 192, 192);
ctx->colors.scrollbarFg = packColor(d, 128, 128, 128);
ctx->colors.scrollbarTrough = packColor(d, 160, 160, 160); // GEOS lighter trough
// Load defaults into the RGB source array, then pack all at once
memcpy(ctx->colorRgb, sDefaultColors, sizeof(sDefaultColors));
dvxApplyColorScheme(ctx);
}
@ -2748,7 +3188,8 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win)
//
// 1. Alt+Tab / Shift+Alt+Tab — window cycling (always works)
// 2. Alt+F4 — close focused window
// 3. F10 — activate/toggle menu bar
// 3. Ctrl+Esc — system-wide hotkey (task manager)
// 4. F10 — activate/toggle menu bar
// 4. Keyboard move/resize mode (arrow keys captured exclusively)
// 5. Alt+Space — system menu toggle
// 6. System menu keyboard navigation (arrows, enter, esc, accel)
@ -2822,6 +3263,15 @@ static void pollKeyboard(AppContextT *ctx) {
continue;
}
// Ctrl+Esc — system-wide hotkey (e.g. task manager)
if (scancode == 0x01 && ascii == 0x1B && (shiftFlags & KEY_MOD_CTRL)) {
if (ctx->onCtrlEsc) {
ctx->onCtrlEsc(ctx->ctrlEscCtx);
}
continue;
}
// F10 — activate menu bar
if (ascii == 0 && scancode == 0x44) {
if (ctx->stack.focusedIdx >= 0) {
@ -3481,8 +3931,9 @@ static void refreshMinimizedIcons(AppContextT *ctx) {
if (!win->iconData && win->contentDirty) {
if (count >= ctx->iconRefreshIdx) {
int32_t ix = ICON_SPACING + iconIdx * (ICON_TOTAL_SIZE + ICON_SPACING);
int32_t iy = d->height - ICON_TOTAL_SIZE - ICON_SPACING;
int32_t ix;
int32_t iy;
wmMinimizedIconPos(d, iconIdx, &ix, &iy);
dirtyListAdd(&ctx->dirty, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE);
win->contentDirty = false;
ctx->iconRefreshIdx = count + 1;
@ -3721,7 +4172,16 @@ static void updateTooltip(AppContextT *ctx) {
return;
}
// Find the widget under the cursor
// Check minimized icons first (they sit outside any window)
const char *tipText = NULL;
int32_t iconIdx = wmMinimizedIconHit(&ctx->stack, &ctx->display, mx, my);
if (iconIdx >= 0) {
tipText = ctx->stack.windows[iconIdx]->title;
}
// Otherwise check widgets in the content area of a window
if (!tipText) {
int32_t hitPart;
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
@ -3758,7 +4218,6 @@ static void updateTooltip(AppContextT *ctx) {
break;
}
// If the inner child has a tooltip, use it; otherwise check if it's another container
if (inner->tooltip) {
hit = inner;
break;
@ -3777,10 +4236,13 @@ static void updateTooltip(AppContextT *ctx) {
return;
}
// Show the tooltip
ctx->tooltipText = hit->tooltip;
tipText = hit->tooltip;
}
int32_t tw = textWidth(&ctx->font, hit->tooltip) + TOOLTIP_PAD * 2;
// Show the tooltip
ctx->tooltipText = tipText;
int32_t tw = textWidth(&ctx->font, tipText) + TOOLTIP_PAD * 2;
int32_t th = ctx->font.charHeight + TOOLTIP_PAD * 2;
// Position below and right of cursor

View file

@ -78,6 +78,10 @@ typedef struct AppContextT {
void (*idleCallback)(void *ctx); // called instead of yield when non-NULL
void *idleCtx;
WindowT *modalWindow; // if non-NULL, only this window receives input
void (*onCtrlEsc)(void *ctx); // system-wide Ctrl+Esc handler (e.g. task manager)
void *ctrlEscCtx;
void (*onTitleChange)(void *ctx); // called when any window title changes
void *titleChangeCtx;
// Tooltip state — tooltip appears after the mouse hovers over a widget
// with a tooltip string for a brief delay. Pre-computing W/H avoids
// re-measuring on every paint frame.
@ -95,6 +99,12 @@ typedef struct AppContextT {
// Mouse configuration (loaded from preferences)
int32_t wheelDirection; // 1 = normal, -1 = reversed
clock_t dblClickTicks; // double-click speed in clock() ticks
// Color scheme source RGB values (unpacked, for theme save/get)
uint8_t colorRgb[ColorCountE][3];
// Wallpaper — pre-scaled to screen dimensions in native pixel format.
// NULL means no wallpaper (solid desktop color).
uint8_t *wallpaperBuf; // pixel data (screen width * height * bpp/8)
int32_t wallpaperPitch; // bytes per row
} AppContextT;
// Initialize the entire GUI stack: video mode, input devices, font,
@ -102,11 +112,51 @@ typedef struct AppContextT {
// entry point for starting a DVX application. Returns 0 on success.
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Switch to a new video mode live. Reallocates the backbuffer, all
// window content buffers, repacks colors, rescales wallpaper, and
// repositions windows that would be off-screen. Returns 0 on success
// or -1 on failure (old mode is restored on failure).
int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Configure mouse behaviour. wheelDir: 1 = normal, -1 = reversed.
// dblClickMs: double-click speed in milliseconds (e.g. 500).
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold);
// ============================================================
// Color scheme
// ============================================================
// Set a single color by ID. Repacks to native pixel format and
// invalidates the entire screen.
void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b);
// Get a color's RGB values by ID.
void dvxGetColor(const AppContextT *ctx, ColorIdE id, uint8_t *r, uint8_t *g, uint8_t *b);
// Apply all colors from ctx->colorRgb[] at once (repack + full repaint).
void dvxApplyColorScheme(AppContextT *ctx);
// Reset all colors to the built-in defaults and repaint.
void dvxResetColorScheme(AppContextT *ctx);
// Load a theme file (INI format with [colors] section) and apply it.
bool dvxLoadTheme(AppContextT *ctx, const char *filename);
// Save the current color scheme to a theme file.
bool dvxSaveTheme(const AppContextT *ctx, const char *filename);
// Return the INI key name for a color ID (e.g. "desktop", "windowFace").
const char *dvxColorName(ColorIdE id);
// ============================================================
// Wallpaper
// ============================================================
// Load and apply a wallpaper image (stretched to screen). Pass NULL
// to clear the wallpaper and revert to the solid desktop color.
bool dvxSetWallpaper(AppContextT *ctx, const char *path);
// Tear down the GUI stack in reverse order: destroy all windows, restore
// text mode, release input devices. Safe to call after a failed dvxInit().
void dvxShutdown(AppContextT *ctx);

View file

@ -1,16 +1,128 @@
// dvxPrefs.c — INI-based preferences system
// dvxPrefs.c — INI-based preferences system (read/write)
//
// Thin wrapper around rxi/ini that adds typed accessors with defaults.
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
// Custom INI parser and writer. Stores entries as a dynamic array of
// section/key/value triples using stb_ds. Preserves insertion order
// on save so the file remains human-readable.
#include "dvxPrefs.h"
#include "thirdparty/ini/src/ini.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// stb_ds dynamic arrays (implementation lives in libtasks.a)
#include "thirdparty/stb_ds.h"
static ini_t *sIni = NULL;
// ============================================================
// Internal types
// ============================================================
typedef struct {
char *section;
char *key;
char *value;
} PrefsEntryT;
// Comment lines are stored to preserve them on save. A comment has
// key=NULL and value=the full line text (including the ; prefix).
// Section headers have key=NULL and value=NULL.
static PrefsEntryT *sEntries = NULL; // stb_ds dynamic array
static char *sFilePath = NULL; // path used by prefsLoad (for prefsSave)
// ============================================================
// Helpers
// ============================================================
static char *dupStr(const char *s) {
if (!s) {
return NULL;
}
size_t len = strlen(s);
char *d = (char *)malloc(len + 1);
if (d) {
memcpy(d, s, len + 1);
}
return d;
}
static void freeEntry(PrefsEntryT *e) {
free(e->section);
free(e->key);
free(e->value);
e->section = NULL;
e->key = NULL;
e->value = NULL;
}
// Case-insensitive string compare
static int strcmpci(const char *a, const char *b) {
for (;;) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a) {
return d;
}
a++;
b++;
}
}
// Find an entry by section+key (case-insensitive). Returns index or -1.
static int32_t findEntry(const char *section, const char *key) {
for (int32_t i = 0; i < arrlen(sEntries); i++) {
PrefsEntryT *e = &sEntries[i];
if (e->key && e->section &&
strcmpci(e->section, section) == 0 &&
strcmpci(e->key, key) == 0) {
return i;
}
}
return -1;
}
// Find the index of a section header entry. Returns -1 if not found.
static int32_t findSection(const char *section) {
for (int32_t i = 0; i < arrlen(sEntries); i++) {
PrefsEntryT *e = &sEntries[i];
if (!e->key && !e->value && e->section &&
strcmpci(e->section, section) == 0) {
return i;
}
}
return -1;
}
// Trim leading/trailing whitespace in place. Returns pointer into buf.
static char *trimInPlace(char *buf) {
while (*buf == ' ' || *buf == '\t') {
buf++;
}
char *end = buf + strlen(buf) - 1;
while (end >= buf && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) {
*end-- = '\0';
}
return buf;
}
// ============================================================
@ -18,10 +130,14 @@ static ini_t *sIni = NULL;
// ============================================================
void prefsFree(void) {
if (sIni) {
ini_free(sIni);
sIni = NULL;
for (int32_t i = 0; i < arrlen(sEntries); i++) {
freeEntry(&sEntries[i]);
}
arrfree(sEntries);
sEntries = NULL;
free(sFilePath);
sFilePath = NULL;
}
@ -36,7 +152,6 @@ bool prefsGetBool(const char *section, const char *key, bool defaultVal) {
return defaultVal;
}
// Case-insensitive first character check covers true/yes/1 and false/no/0
char c = (char)tolower((unsigned char)val[0]);
if (c == 't' || c == 'y' || c == '1') {
@ -78,13 +193,13 @@ int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal) {
// ============================================================
const char *prefsGetString(const char *section, const char *key, const char *defaultVal) {
if (!sIni) {
int32_t idx = findEntry(section, key);
if (idx < 0) {
return defaultVal;
}
const char *val = ini_get(sIni, section, key);
return val ? val : defaultVal;
return sEntries[idx].value;
}
@ -95,7 +210,231 @@ const char *prefsGetString(const char *section, const char *key, const char *def
bool prefsLoad(const char *filename) {
prefsFree();
sIni = ini_load(filename);
FILE *fp = fopen(filename, "rb");
return sIni != NULL;
if (!fp) {
return false;
}
sFilePath = dupStr(filename);
char line[512];
char *currentSection = dupStr("");
while (fgets(line, sizeof(line), fp)) {
// Strip trailing whitespace/newline
char *end = line + strlen(line) - 1;
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) {
*end-- = '\0';
}
char *p = line;
// Skip leading whitespace
while (*p == ' ' || *p == '\t') {
p++;
}
// Blank line — store as comment to preserve formatting
if (*p == '\0') {
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
e.value = dupStr("");
arrput(sEntries, e);
continue;
}
// Comment line
if (*p == ';' || *p == '#') {
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
e.value = dupStr(line);
arrput(sEntries, e);
continue;
}
// Section header
if (*p == '[') {
char *close = strchr(p, ']');
if (close) {
*close = '\0';
free(currentSection);
currentSection = dupStr(trimInPlace(p + 1));
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
arrput(sEntries, e);
}
continue;
}
// Key=value
char *eq = strchr(p, '=');
if (eq) {
*eq = '\0';
PrefsEntryT e = {0};
e.section = dupStr(currentSection);
e.key = dupStr(trimInPlace(p));
e.value = dupStr(trimInPlace(eq + 1));
arrput(sEntries, e);
}
}
free(currentSection);
fclose(fp);
return true;
}
// ============================================================
// prefsRemove
// ============================================================
void prefsRemove(const char *section, const char *key) {
int32_t idx = findEntry(section, key);
if (idx >= 0) {
freeEntry(&sEntries[idx]);
arrdel(sEntries, idx);
}
}
// ============================================================
// prefsSave
// ============================================================
bool prefsSave(void) {
if (!sFilePath) {
return false;
}
return prefsSaveAs(sFilePath);
}
// ============================================================
// prefsSaveAs
// ============================================================
bool prefsSaveAs(const char *filename) {
FILE *fp = fopen(filename, "wb");
if (!fp) {
return false;
}
const char *lastSection = "";
for (int32_t i = 0; i < arrlen(sEntries); i++) {
PrefsEntryT *e = &sEntries[i];
// Comment or blank line (key=NULL, value=text or empty)
if (!e->key && e->value) {
fprintf(fp, "%s\r\n", e->value);
continue;
}
// Section header (key=NULL, value=NULL)
if (!e->key && !e->value) {
fprintf(fp, "[%s]\r\n", e->section);
lastSection = e->section;
continue;
}
// Key=value
if (e->key && e->value) {
fprintf(fp, "%s = %s\r\n", e->key, e->value);
}
}
fclose(fp);
return true;
}
// ============================================================
// prefsSetBool
// ============================================================
void prefsSetBool(const char *section, const char *key, bool value) {
prefsSetString(section, key, value ? "true" : "false");
}
// ============================================================
// prefsSetInt
// ============================================================
void prefsSetInt(const char *section, const char *key, int32_t value) {
char buf[32];
snprintf(buf, sizeof(buf), "%ld", (long)value);
prefsSetString(section, key, buf);
}
// ============================================================
// prefsSetString
// ============================================================
void prefsSetString(const char *section, const char *key, const char *value) {
int32_t idx = findEntry(section, key);
if (idx >= 0) {
// Update existing entry
free(sEntries[idx].value);
sEntries[idx].value = dupStr(value);
return;
}
// Find or create section header
int32_t secIdx = findSection(section);
if (secIdx < 0) {
// Add blank line before new section (unless file is empty)
if (arrlen(sEntries) > 0) {
PrefsEntryT blank = {0};
blank.section = dupStr(section);
blank.value = dupStr("");
arrput(sEntries, blank);
}
// Add section header
PrefsEntryT secEntry = {0};
secEntry.section = dupStr(section);
arrput(sEntries, secEntry);
secIdx = arrlen(sEntries) - 1;
}
// Find insertion point: after last entry in this section
int32_t insertAt = secIdx + 1;
while (insertAt < arrlen(sEntries)) {
PrefsEntryT *e = &sEntries[insertAt];
// Stop if we've hit a different section header
if (!e->key && !e->value && e->section &&
strcmpci(e->section, section) != 0) {
break;
}
// Stop if we've hit an entry from a different section
if (e->section && strcmpci(e->section, section) != 0) {
break;
}
insertAt++;
}
// Insert new entry
PrefsEntryT newEntry = {0};
newEntry.section = dupStr(section);
newEntry.key = dupStr(key);
newEntry.value = dupStr(value);
arrins(sEntries, insertAt, newEntry);
}

View file

@ -1,11 +1,9 @@
// dvxPrefs.h — INI-based preferences system
// dvxPrefs.h — INI-based preferences system (read/write)
//
// Loads a configuration file at startup and provides typed accessors
// with caller-supplied defaults. The INI file is read-only at runtime;
// values are queried by section + key. If the file is missing or a key
// is absent, the default is returned silently.
//
// The underlying parser is rxi/ini (thirdparty/ini/src).
// with caller-supplied defaults. Values can be modified at runtime
// and saved back to disk. If the file is missing or a key is absent,
// getters return the default silently.
#ifndef DVX_PREFS_H
#define DVX_PREFS_H
@ -18,20 +16,36 @@
// Only one file may be loaded at a time; calling again frees the previous.
bool prefsLoad(const char *filename);
// Release all memory held by the loaded INI file.
// Save the current in-memory state back to the file that was loaded.
// Returns true on success.
bool prefsSave(void);
// Save the current in-memory state to a specific file.
bool prefsSaveAs(const char *filename);
// Release all memory held by the preferences.
void prefsFree(void);
// Retrieve a string value. Returns defaultVal if the section/key pair
// is not present. The returned pointer is valid until prefsFree().
// Retrieve a string value. Returns defaultVal if the key is not present.
// The returned pointer is valid until the key is modified or prefsFree().
const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
// Retrieve an integer value. Returns defaultVal if the section/key pair
// is not present or cannot be parsed.
// Retrieve an integer value.
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal);
// Retrieve a boolean value. Recognises "true", "yes", "1" as true and
// "false", "no", "0" as false (case-insensitive). Returns defaultVal
// for anything else or if the key is missing.
// Retrieve a boolean value. Recognises "true"/"yes"/"1" and "false"/"no"/"0".
bool prefsGetBool(const char *section, const char *key, bool defaultVal);
// Set a string value. Creates the section and key if they don't exist.
void prefsSetString(const char *section, const char *key, const char *value);
// Set an integer value.
void prefsSetInt(const char *section, const char *key, int32_t value);
// Set a boolean value (stored as "true"/"false").
void prefsSetBool(const char *section, const char *key, bool value);
// Remove a key from a section. No-op if not found.
void prefsRemove(const char *section, const char *key);
#endif

View file

@ -206,6 +206,30 @@ typedef struct {
uint32_t scrollbarTrough;
} ColorSchemeT;
// Color IDs for addressing individual colors in ColorSchemeT.
// Order matches the struct field order.
typedef enum {
ColorDesktopE,
ColorWindowFaceE,
ColorWindowHighlightE,
ColorWindowShadowE,
ColorActiveTitleBgE,
ColorActiveTitleFgE,
ColorInactiveTitleBgE,
ColorInactiveTitleFgE,
ColorContentBgE,
ColorContentFgE,
ColorMenuBgE,
ColorMenuFgE,
ColorMenuHighlightBgE,
ColorMenuHighlightFgE,
ColorButtonFaceE,
ColorScrollbarBgE,
ColorScrollbarFgE,
ColorScrollbarTroughE,
ColorCountE
} ColorIdE;
// ============================================================
// Dirty rectangle list
// ============================================================

View file

@ -97,7 +97,7 @@ static void drawScrollbar(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
static void drawScrollbarArrow(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size, int32_t dir);
static void drawTitleBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, WindowT *win);
static void drawTitleGadget(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t x, int32_t y, int32_t size);
static void minimizedIconPos(const DisplayT *d, int32_t index, int32_t *x, int32_t *y);
// wmMinimizedIconPos declared in dvxWm.h
static int32_t scrollbarThumbInfo(const ScrollbarT *sb, int32_t *thumbPos, int32_t *thumbSize);
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
@ -720,17 +720,61 @@ static void freeMenuRecursive(MenuT *menu) {
// ============================================================
//
// Computes the screen position of a minimized window icon. Icons are laid
// out in a horizontal strip along the bottom of the screen, left to right.
// This mirrors the DESQview/X and Windows 3.x convention of showing minimized
// windows as icons at the bottom of the desktop.
// out left to right along the bottom of the screen, wrapping to the next
// row upward when the current row is full. This mirrors the Windows 3.x
// convention. If rows wrap past the top of the screen the icons are simply
// clipped by the compositor.
//
// The index is the ordinal among minimized windows (not the stack index),
// so icon positions stay packed when non-minimized windows exist between
// minimized ones in the stack. This avoids gaps in the icon strip.
static void minimizedIconPos(const DisplayT *d, int32_t index, int32_t *x, int32_t *y) {
*x = ICON_SPACING + index * (ICON_TOTAL_SIZE + ICON_SPACING);
*y = d->height - ICON_TOTAL_SIZE - ICON_SPACING;
void wmMinimizedIconPos(const DisplayT *d, int32_t index, int32_t *x, int32_t *y) {
int32_t cellW = ICON_TOTAL_SIZE + ICON_SPACING;
int32_t perRow = (d->width - ICON_SPACING) / cellW;
if (perRow < 1) {
perRow = 1;
}
int32_t col = index % perRow;
int32_t row = index / perRow;
*x = ICON_SPACING + col * cellW;
*y = d->height - (row + 1) * (ICON_TOTAL_SIZE + ICON_SPACING);
}
// ============================================================
// wmMinimizedIconRect
// ============================================================
void wmMinimizedIconRect(const WindowStackT *stack, const DisplayT *d, int32_t *ry, int32_t *rh) {
int32_t count = 0;
for (int32_t i = 0; i < stack->count; i++) {
if (stack->windows[i]->visible && stack->windows[i]->minimized) {
count++;
}
}
if (count == 0) {
*ry = d->height;
*rh = 0;
return;
}
// Find the topmost icon position (last icon has highest row index)
int32_t topX;
int32_t topY;
wmMinimizedIconPos(d, count - 1, &topX, &topY);
if (topY < 0) {
topY = 0;
}
*ry = topY;
*rh = d->height - topY;
}
@ -1384,7 +1428,7 @@ void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
int32_t ix;
int32_t iy;
minimizedIconPos(d, iconIdx, &ix, &iy);
wmMinimizedIconPos(d, iconIdx, &ix, &iy);
iconIdx++;
// Check if icon intersects clip rect
@ -1713,7 +1757,7 @@ int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t
int32_t ix;
int32_t iy;
minimizedIconPos(d, iconIdx, &ix, &iy);
wmMinimizedIconPos(d, iconIdx, &ix, &iy);
iconIdx++;
if (mx >= ix && mx < ix + ICON_TOTAL_SIZE &&

View file

@ -179,6 +179,14 @@ void wmMinimize(WindowStackT *stack, DirtyListT *dl, WindowT *win);
// Returns stack index of the minimized window, or -1
int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t mx, int32_t my);
// Compute screen position of a minimized icon by ordinal index.
// Icons wrap into rows from bottom to top when the screen is full.
void wmMinimizedIconPos(const DisplayT *d, int32_t index, int32_t *x, int32_t *y);
// Compute the screen rect covering all minimized icon rows.
// Used to dirty the icon area when windows are minimized or restored.
void wmMinimizedIconRect(const WindowStackT *stack, const DisplayT *d, int32_t *y, int32_t *h);
// Restore a maximized window to its pre-maximize geometry
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);

View file

@ -200,14 +200,30 @@ const char *platformGetSystemInfo(const DisplayT *display);
// describing why it's invalid. Used by the file dialog's save-as validation.
const char *platformValidateFilename(const char *name);
// Query current system memory. Sets totalKb and freeKb to the total
// and free physical memory in kilobytes. Returns false if unavailable.
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb);
// Change the working directory, including drive letter on DOS. Standard
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
// first when the path contains a drive prefix (e.g. "A:\DVX").
void platformChdir(const char *path);
// Free the backbuffer and palette without restoring text mode. Used
// when switching between graphics modes live.
void platformVideoFreeBuffers(DisplayT *d);
// Return a pointer to the last directory separator in path, or NULL if
// none is found. On DOS this checks both '/' and '\\' since DJGPP
// accepts either. On other platforms only '/' is recognised.
char *platformPathDirEnd(const char *path);
// Return the platform's native line ending string.
// "\r\n" on DOS/Windows, "\n" on Unix/Linux.
const char *platformLineEnding(void);
// Strip platform-specific line ending characters from a buffer in place.
// On DOS this removes '\r' from CR+LF pairs. Returns the new length.
int32_t platformStripLineEndings(char *buf, int32_t len);
#endif // DVX_PLATFORM_H

View file

@ -68,6 +68,17 @@ static void sysInfoAppend(const char *fmt, ...);
static bool sHasMouseWheel = false;
static int32_t sLastWheelDelta = 0;
// Software cursor tracking. Many real-hardware mouse drivers fail to
// honour INT 33h functions 07h/08h (set coordinate range) in VESA modes
// because they don't recognise non-standard video modes. We bypass the
// driver's position entirely: platformMousePoll reads raw mickey deltas
// via function 0Bh, accumulates them into sCurX/sCurY, and clamps to
// the screen bounds. Function 03h is still used for button state.
static int32_t sMouseRangeW = 0;
static int32_t sMouseRangeH = 0;
static int32_t sCurX = 0;
static int32_t sCurY = 0;
// Alt+key scan code to ASCII lookup table (indexed by BIOS scan code).
// INT 16h returns these scan codes with ascii=0 for Alt+key combos.
// Using a 256-byte lookup table instead of a switch or if-chain because
@ -1021,6 +1032,27 @@ const char *platformGetSystemInfo(const DisplayT *display) {
}
// ============================================================
// platformGetMemoryInfo
// ============================================================
bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
__dpmi_free_mem_info memInfo;
if (__dpmi_get_free_memory_information(&memInfo) != 0) {
*totalKb = 0;
*freeKb = 0;
return false;
}
*totalKb = (memInfo.total_number_of_physical_pages != 0xFFFFFFFFUL)
? memInfo.total_number_of_physical_pages * 4 : 0;
*freeKb = (memInfo.total_number_of_free_pages != 0xFFFFFFFFUL)
? memInfo.total_number_of_free_pages * 4 : 0;
return true;
}
// ============================================================
// platformInit
// ============================================================
@ -1103,6 +1135,33 @@ bool platformKeyboardRead(PlatformKeyEventT *evt) {
}
// ============================================================
// platformLineEnding
// ============================================================
const char *platformLineEnding(void) {
return "\r\n";
}
// ============================================================
// platformStripLineEndings
// ============================================================
int32_t platformStripLineEndings(char *buf, int32_t len) {
int32_t dst = 0;
for (int32_t src = 0; src < len; src++) {
if (buf[src] != '\r') {
buf[dst++] = buf[src];
}
}
buf[dst] = '\0';
return dst;
}
// ============================================================
// platformMouseInit
// ============================================================
@ -1122,30 +1181,20 @@ bool platformKeyboardRead(PlatformKeyEventT *evt) {
void platformMouseInit(int32_t screenW, int32_t screenH) {
__dpmi_regs r;
sMouseRangeW = screenW;
sMouseRangeH = screenH;
sCurX = screenW / 2;
sCurY = screenH / 2;
// Function 00h: reset driver, detect mouse hardware
memset(&r, 0, sizeof(r));
r.x.ax = 0x0000;
__dpmi_int(0x33, &r);
// Function 07h: set horizontal min/max range
// Flush any stale mickey counters so the first poll starts clean.
// Function 0Bh returns and resets the accumulated motion counters.
memset(&r, 0, sizeof(r));
r.x.ax = 0x0007;
r.x.cx = 0;
r.x.dx = screenW - 1;
__dpmi_int(0x33, &r);
// Function 08h: set vertical min/max range
memset(&r, 0, sizeof(r));
r.x.ax = 0x0008;
r.x.cx = 0;
r.x.dx = screenH - 1;
__dpmi_int(0x33, &r);
// Function 04h: warp cursor to center of screen
memset(&r, 0, sizeof(r));
r.x.ax = 0x0004;
r.x.cx = screenW / 2;
r.x.dx = screenH / 2;
r.x.ax = 0x000B;
__dpmi_int(0x33, &r);
}
@ -1173,32 +1222,55 @@ void platformMouseSetAccel(int32_t threshold) {
// platformMousePoll
// ============================================================
//
// Reads current mouse state via INT 33h function 03h.
// Returns: CX=X position, DX=Y position, BX=button state
// (bit 0 = left, bit 1 = right, bit 2 = middle).
//
// Polling is used instead of a callback/event model because the
// DVX event loop already runs at frame rate. Installing a real-mode
// callback for mouse events would add DPMI mode-switch overhead
// on every mickeyed movement, which is wasteful when we only sample
// once per frame anyway.
// Reads button state via function 03h and raw mickey deltas via
// function 0Bh. Position is tracked in software (sCurX/sCurY)
// rather than using the driver's coordinates, because many real-
// hardware mouse drivers cannot handle VESA mode coordinate ranges.
// Function 0Bh returns the accumulated mickey motion since the last
// call and is reliable across all drivers.
void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons) {
__dpmi_regs r;
// Function 03h: read button state only
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x33, &r);
*mx = r.x.cx;
*my = r.x.dx;
*buttons = r.x.bx & 0x07; // BL only: bits 0-2 = left/right/middle
*buttons = r.x.bx & 0x07;
// BH = signed 8-bit wheel counter (cleared on read by the driver).
// Only meaningful if the wheel API was activated via platformMouseWheelInit.
if (sHasMouseWheel) {
sLastWheelDelta = (int32_t)(int8_t)(r.x.bx >> 8);
}
// Function 0Bh: read mickey motion counters (signed 16-bit deltas,
// cleared on read). Accumulate into software cursor position.
memset(&r, 0, sizeof(r));
r.x.ax = 0x000B;
__dpmi_int(0x33, &r);
sCurX += (int16_t)r.x.cx;
sCurY += (int16_t)r.x.dx;
if (sCurX < 0) {
sCurX = 0;
}
if (sCurX >= sMouseRangeW) {
sCurX = sMouseRangeW - 1;
}
if (sCurY < 0) {
sCurY = 0;
}
if (sCurY >= sMouseRangeH) {
sCurY = sMouseRangeH - 1;
}
*mx = sCurX;
*my = sCurY;
}
@ -1247,12 +1319,13 @@ int32_t platformMouseWheelPoll(void) {
// so the pointer visually sticks to the border.
void platformMouseWarp(int32_t x, int32_t y) {
__dpmi_regs r;
sCurX = x;
sCurY = y;
// Flush any pending mickeys so the next poll doesn't undo the warp
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0004;
r.x.cx = x;
r.x.dx = y;
r.x.ax = 0x000B;
__dpmi_int(0x33, &r);
}
@ -1680,6 +1753,21 @@ void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t cou
// Also frees the backbuffer, palette, and disables near pointers
// (re-enables DJGPP's segment limit checking for safety).
void platformVideoFreeBuffers(DisplayT *d) {
if (d->backBuf) {
free(d->backBuf);
d->backBuf = NULL;
}
if (d->palette) {
free(d->palette);
d->palette = NULL;
}
d->lfb = NULL;
}
void platformVideoShutdown(DisplayT *d) {
// INT 10h function 00h, mode 03h = 80x25 color text
__dpmi_regs r;

View file

@ -1,20 +0,0 @@
Copyright (c) 2016 rxi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,63 +0,0 @@
# ini
A *tiny* ANSI C library for loading .ini config files
## Usage
The files **[ini.c](src/ini.c?raw=1)** and **[ini.h](src/ini.h?raw=1)** should
be dropped into an existing project.
The library has support for sections, comment lines and quoted string values
(with escapes). Unquoted values and keys are trimmed of whitespace when loaded.
```ini
; last modified 1 April 2001 by John Doe
[owner]
name = John Doe
organization = Acme Widgets Inc.
[database]
; use IP address in case network name resolution is not working
server = 192.0.2.62
port = 143
file = "payroll.dat"
```
An ini file can be loaded into memory by using the `ini_load()` function.
`NULL` is returned if the file cannot be loaded.
```c
ini_t *config = ini_load("config.ini");
```
The library provides two functions for retrieving values: the first is
`ini_get()`. Given a section and a key the corresponding value is returned if
it exists. If the `section` argument is `NULL` then all sections are searched.
```c
const char *name = ini_get(config, "owner", "name");
if (name) {
printf("name: %s\n", name);
}
```
The second, `ini_sget()`, takes the same arguments as `ini_get()` with the
addition of a scanf format string and a pointer for where to store the value.
```c
const char *server = "default";
int port = 80;
ini_sget(config, "database", "server", NULL, &server);
ini_sget(config, "database", "port", "%d", &port);
printf("server: %s:%d\n", server, port);
```
The `ini_free()` function is used to free the memory used by the `ini_t*`
object when we are done with it. Calling this function invalidates all string
pointers returned by the library.
```c
ini_free(config);
```
## License
This library is free software; you can redistribute it and/or modify it under
the terms of the MIT license. See [LICENSE](LICENSE) for details.

View file

@ -1,274 +0,0 @@
/**
* Copyright (c) 2016 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "ini.h"
struct ini_t {
char *data;
char *end;
};
/* Case insensitive string compare */
static int strcmpci(const char *a, const char *b) {
for (;;) {
int d = tolower(*a) - tolower(*b);
if (d != 0 || !*a) {
return d;
}
a++, b++;
}
}
/* Returns the next string in the split data */
static char* next(ini_t *ini, char *p) {
p += strlen(p);
while (p < ini->end && *p == '\0') {
p++;
}
return p;
}
static void trim_back(ini_t *ini, char *p) {
while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
*p-- = '\0';
}
}
static char* discard_line(ini_t *ini, char *p) {
while (p < ini->end && *p != '\n') {
*p++ = '\0';
}
return p;
}
static char *unescape_quoted_value(ini_t *ini, char *p) {
/* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
* as escape sequences are always larger than their resultant data */
char *q = p;
p++;
while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
if (*p == '\\') {
/* Handle escaped char */
p++;
switch (*p) {
default : *q = *p; break;
case 'r' : *q = '\r'; break;
case 'n' : *q = '\n'; break;
case 't' : *q = '\t'; break;
case '\r' :
case '\n' :
case '\0' : goto end;
}
} else {
/* Handle normal char */
*q = *p;
}
q++, p++;
}
end:
return q;
}
/* Splits data in place into strings containing section-headers, keys and
* values using one or more '\0' as a delimiter. Unescapes quoted values */
static void split_data(ini_t *ini) {
char *value_start, *line_start;
char *p = ini->data;
while (p < ini->end) {
switch (*p) {
case '\r':
case '\n':
case '\t':
case ' ':
*p = '\0';
/* Fall through */
case '\0':
p++;
break;
case '[':
p += strcspn(p, "]\n");
*p = '\0';
break;
case ';':
p = discard_line(ini, p);
break;
default:
line_start = p;
p += strcspn(p, "=\n");
/* Is line missing a '='? */
if (*p != '=') {
p = discard_line(ini, line_start);
break;
}
trim_back(ini, p - 1);
/* Replace '=' and whitespace after it with '\0' */
do {
*p++ = '\0';
} while (*p == ' ' || *p == '\r' || *p == '\t');
/* Is a value after '=' missing? */
if (*p == '\n' || *p == '\0') {
p = discard_line(ini, line_start);
break;
}
if (*p == '"') {
/* Handle quoted string value */
value_start = p;
p = unescape_quoted_value(ini, p);
/* Was the string empty? */
if (p == value_start) {
p = discard_line(ini, line_start);
break;
}
/* Discard the rest of the line after the string value */
p = discard_line(ini, p);
} else {
/* Handle normal value */
p += strcspn(p, "\n");
trim_back(ini, p - 1);
}
break;
}
}
}
ini_t* ini_load(const char *filename) {
ini_t *ini = NULL;
FILE *fp = NULL;
int n, sz;
/* Init ini struct */
ini = malloc(sizeof(*ini));
if (!ini) {
goto fail;
}
memset(ini, 0, sizeof(*ini));
/* Open file */
fp = fopen(filename, "rb");
if (!fp) {
goto fail;
}
/* Get file size */
fseek(fp, 0, SEEK_END);
sz = ftell(fp);
rewind(fp);
/* Load file content into memory, null terminate, init end var */
ini->data = malloc(sz + 1);
ini->data[sz] = '\0';
ini->end = ini->data + sz;
n = fread(ini->data, 1, sz, fp);
if (n != sz) {
goto fail;
}
/* Prepare data */
split_data(ini);
/* Clean up and return */
fclose(fp);
return ini;
fail:
if (fp) fclose(fp);
if (ini) ini_free(ini);
return NULL;
}
void ini_free(ini_t *ini) {
free(ini->data);
free(ini);
}
const char* ini_get(ini_t *ini, const char *section, const char *key) {
char *current_section = "";
char *val;
char *p = ini->data;
if (*p == '\0') {
p = next(ini, p);
}
while (p < ini->end) {
if (*p == '[') {
/* Handle section */
current_section = p + 1;
} else {
/* Handle key */
val = next(ini, p);
if (!section || !strcmpci(section, current_section)) {
if (!strcmpci(p, key)) {
return val;
}
}
p = val;
}
p = next(ini, p);
}
return NULL;
}
int ini_sget(
ini_t *ini, const char *section, const char *key,
const char *scanfmt, void *dst
) {
const char *val = ini_get(ini, section, key);
if (!val) {
return 0;
}
if (scanfmt) {
sscanf(val, scanfmt, dst);
} else {
*((const char**) dst) = val;
}
return 1;
}

View file

@ -1,20 +0,0 @@
/**
* Copyright (c) 2016 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `ini.c` for details.
*/
#ifndef INI_H
#define INI_H
#define INI_VERSION "0.1.1"
typedef struct ini_t ini_t;
ini_t* ini_load(const char *filename);
void ini_free(ini_t *ini);
const char* ini_get(ini_t *ini, const char *section, const char *key);
int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst);
#endif

View file

@ -11,15 +11,18 @@ LDFLAGS = -L../lib -ldvx -ltasks -lm
OBJDIR = ../obj/dvxshell
BINDIR = ../bin
CONFIGDIR = ../bin/config
THEMEDIR = ../bin/config/themes
LIBDIR = ../lib
SRCS = shellMain.c shellApp.c shellExport.c shellInfo.c
SRCS = shellMain.c shellApp.c shellExport.c shellInfo.c shellTaskMgr.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(BINDIR)/dvx.exe
.PHONY: all clean libs
all: libs $(TARGET) $(CONFIGDIR)/dvx.ini
THEMES = $(THEMEDIR)/geos.thm $(THEMEDIR)/win31.thm $(THEMEDIR)/cde.thm
all: libs $(TARGET) $(CONFIGDIR)/dvx.ini $(THEMES)
libs:
$(MAKE) -C ../dvx
@ -32,7 +35,7 @@ $(TARGET): $(OBJS) $(LIBDIR)/libdvx.a $(LIBDIR)/libtasks.a | $(BINDIR)
rm -f $(BINDIR)/dvx
$(CONFIGDIR)/dvx.ini: ../dvx.ini | $(CONFIGDIR)
cp $< $@
sed 's/$$/\r/' $< > $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
@ -46,11 +49,18 @@ $(BINDIR):
$(CONFIGDIR):
mkdir -p $(CONFIGDIR)
$(THEMEDIR):
mkdir -p $(THEMEDIR)
$(THEMEDIR)/%.thm: ../themes/%.thm | $(THEMEDIR)
sed 's/$$/\r/' $< > $@
# 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 shellInfo.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvx/dvxWm.h ../tasks/taskswitch.h
$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h ../dvx/dvxApp.h ../dvx/platform/dvxPlatform.h
$(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/platform/dvxPlatform.h
clean:
rm -f $(OBJS) $(TARGET) $(BINDIR)/dvx.map $(BINDIR)/dvx.log
rm -rf $(CONFIGDIR)
rm -rf $(THEMEDIR) $(CONFIGDIR)

View file

@ -172,12 +172,13 @@ void shellExportInit(void);
#define SHELL_DESKTOP_APP "apps/progman/progman.app"
// Register a callback for app state changes (load, reap, crash).
// The desktop app (Program Manager) calls this during appMain to receive
// notifications so it can refresh its task list / window list display.
// Only one callback is supported — the desktop is always loaded first
// and is the only consumer.
// Apps call this during appMain to receive notifications when app state
// changes (load, reap, crash). Multiple callbacks are supported.
void shellRegisterDesktopUpdate(void (*updateFn)(void));
// Remove a previously registered callback (call before app shutdown).
void shellUnregisterDesktopUpdate(void (*updateFn)(void));
// Notify the desktop app that app state has changed (load, reap, crash).
void shellDesktopUpdate(void);

View file

@ -29,23 +29,43 @@
#include "shellApp.h"
#include "shellInfo.h"
#include "shellTaskMgr.h"
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxDraw.h"
#include "dvxPrefs.h"
#include "platform/dvxPlatform.h"
#include "dvxVideo.h"
#include "dvxWm.h"
#include "taskswitch.h"
#include <sys/dxe.h>
#include <sys/stat.h>
#include <ctype.h>
#include <dirent.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
// stb headers (no IMPLEMENTATION — symbols are in libdvx.a / libtasks.a)
#include "thirdparty/stb_image.h"
#include "thirdparty/stb_image_write.h"
#include "thirdparty/stb_ds.h"
// DJGPP stdio internals — the stdin/stdout/stderr macros dereference
// these pointers. Without exporting them, any DXE that uses printf
// or fprintf gets an unresolved symbol.
extern FILE __dj_stdin;
extern FILE __dj_stdout;
extern FILE __dj_stderr;
// ============================================================
// Prototypes
@ -144,14 +164,41 @@ DXE_EXPORT_TABLE(shellExportTable)
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
// dvxPlatform.h — platform abstraction
DXE_EXPORT(platformLineEnding)
DXE_EXPORT(platformChdir)
DXE_EXPORT(platformGetMemoryInfo)
DXE_EXPORT(platformMouseSetAccel)
DXE_EXPORT(platformMouseWarp)
DXE_EXPORT(platformPathDirEnd)
DXE_EXPORT(platformStripLineEndings)
DXE_EXPORT(platformValidateFilename)
DXE_EXPORT(platformVideoEnumModes)
// dvxPrefs.h — preferences
DXE_EXPORT(prefsGetBool)
DXE_EXPORT(prefsGetInt)
DXE_EXPORT(prefsGetString)
DXE_EXPORT(prefsLoad)
DXE_EXPORT(prefsRemove)
DXE_EXPORT(prefsSave)
DXE_EXPORT(prefsSaveAs)
DXE_EXPORT(prefsSetBool)
DXE_EXPORT(prefsSetInt)
DXE_EXPORT(prefsSetString)
// dvxApp.h — direct exports
DXE_EXPORT(dvxApplyColorScheme)
DXE_EXPORT(dvxChangeVideoMode)
DXE_EXPORT(dvxColorName)
DXE_EXPORT(dvxGetColor)
DXE_EXPORT(dvxInit)
DXE_EXPORT(dvxLoadTheme)
DXE_EXPORT(dvxResetColorScheme)
DXE_EXPORT(dvxSaveTheme)
DXE_EXPORT(dvxSetColor)
DXE_EXPORT(dvxSetMouseConfig)
DXE_EXPORT(dvxSetWallpaper)
DXE_EXPORT(dvxShutdown)
DXE_EXPORT(dvxUpdate)
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
@ -202,6 +249,8 @@ DXE_EXPORT_TABLE(shellExportTable)
// dvxVideo.h
DXE_EXPORT(packColor)
DXE_EXPORT(resetClipRect)
DXE_EXPORT(setClipRect)
// dvxWm.h
DXE_EXPORT(wmAddMenuBar)
@ -213,6 +262,8 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(wmAddSubMenu)
DXE_EXPORT(wmAddVScrollbar)
DXE_EXPORT(wmAddHScrollbar)
DXE_EXPORT(wmMinimizedIconPos)
DXE_EXPORT(wmMinimizedIconRect)
DXE_EXPORT(wmSetTitle)
DXE_EXPORT(wmSetIcon)
DXE_EXPORT(wmCreateMenu)
@ -389,9 +440,17 @@ DXE_EXPORT_TABLE(shellExportTable)
// tsCreate/tsKill/etc. are NOT exported because apps should not
// manipulate the task system directly — the shell manages task
// lifecycle through shellLoadApp/shellForceKillApp.
DXE_EXPORT(tsYield)
DXE_EXPORT(tsCurrentId)
DXE_EXPORT(tsActiveCount)
DXE_EXPORT(tsCreate)
DXE_EXPORT(tsCurrentId)
DXE_EXPORT(tsGetName)
DXE_EXPORT(tsGetPriority)
DXE_EXPORT(tsGetState)
DXE_EXPORT(tsKill)
DXE_EXPORT(tsPause)
DXE_EXPORT(tsResume)
DXE_EXPORT(tsSetPriority)
DXE_EXPORT(tsYield)
// dvxWm.h — direct window management
DXE_EXPORT(wmRaiseWindow)
@ -402,80 +461,222 @@ DXE_EXPORT_TABLE(shellExportTable)
DXE_EXPORT(shellLog)
DXE_EXPORT(shellLoadApp)
DXE_EXPORT(shellGetApp)
DXE_EXPORT(shellTaskMgrOpen)
DXE_EXPORT(shellForceKillApp)
DXE_EXPORT(shellRunningAppCount)
DXE_EXPORT(shellRegisterDesktopUpdate)
DXE_EXPORT(shellUnregisterDesktopUpdate)
DXE_EXPORT(shellGetSystemInfo)
// libc exports below. DXE3 modules are compiled as relocatable objects,
// not fully linked executables. Any libc function the DXE calls must be
// re-exported here so the DXE3 loader can resolve the reference at
// dlopen time. Forgetting an entry produces a cryptic "unresolved
// symbol" error at load time — no lazy binding fallback exists.
// ================================================================
// libc / libm exports. DXE3 modules are relocatable objects, not
// fully linked executables. Every C library function a DXE calls
// must appear here so the loader can resolve it at dlopen time.
// This is intentionally comprehensive to avoid "unresolved symbol"
// surprises when apps use standard functions.
// ================================================================
// libc — memory
DXE_EXPORT(malloc)
DXE_EXPORT(free)
// --- memory ---
DXE_EXPORT(calloc)
DXE_EXPORT(free)
DXE_EXPORT(malloc)
DXE_EXPORT(realloc)
// libc — string
DXE_EXPORT(memcpy)
DXE_EXPORT(memset)
DXE_EXPORT(memmove)
// --- string / memory ops ---
DXE_EXPORT(memchr)
DXE_EXPORT(memcmp)
DXE_EXPORT(strlen)
DXE_EXPORT(strcmp)
DXE_EXPORT(strncmp)
DXE_EXPORT(strcpy)
DXE_EXPORT(strncpy)
DXE_EXPORT(memcpy)
DXE_EXPORT(memmove)
DXE_EXPORT(memset)
DXE_EXPORT(strcasecmp)
DXE_EXPORT(strcat)
DXE_EXPORT(strncat)
DXE_EXPORT(strchr)
DXE_EXPORT(strcmp)
DXE_EXPORT(strcpy)
DXE_EXPORT(strcspn)
DXE_EXPORT(strdup)
DXE_EXPORT(strerror)
DXE_EXPORT(strlen)
DXE_EXPORT(strncasecmp)
DXE_EXPORT(strncat)
DXE_EXPORT(strncmp)
DXE_EXPORT(strncpy)
DXE_EXPORT(strpbrk)
DXE_EXPORT(strrchr)
DXE_EXPORT(strspn)
DXE_EXPORT(strstr)
DXE_EXPORT(strtol)
DXE_EXPORT(strtok)
// libc — I/O
DXE_EXPORT(printf)
// --- ctype ---
DXE_EXPORT(isalnum)
DXE_EXPORT(isalpha)
DXE_EXPORT(isdigit)
DXE_EXPORT(islower)
DXE_EXPORT(isprint)
DXE_EXPORT(ispunct)
DXE_EXPORT(isspace)
DXE_EXPORT(isupper)
DXE_EXPORT(isxdigit)
DXE_EXPORT(tolower)
DXE_EXPORT(toupper)
// --- conversion ---
DXE_EXPORT(abs)
DXE_EXPORT(atof)
DXE_EXPORT(atoi)
DXE_EXPORT(atol)
DXE_EXPORT(labs)
DXE_EXPORT(strtod)
DXE_EXPORT(strtol)
DXE_EXPORT(strtoul)
// --- formatted I/O ---
DXE_EXPORT(fprintf)
DXE_EXPORT(sprintf)
DXE_EXPORT(snprintf)
DXE_EXPORT(fputs)
DXE_EXPORT(fscanf)
DXE_EXPORT(printf)
DXE_EXPORT(puts)
DXE_EXPORT(fopen)
DXE_EXPORT(fclose)
DXE_EXPORT(fread)
DXE_EXPORT(fwrite)
DXE_EXPORT(snprintf)
DXE_EXPORT(sprintf)
DXE_EXPORT(sscanf)
DXE_EXPORT(vfprintf)
DXE_EXPORT(vprintf)
DXE_EXPORT(vsnprintf)
DXE_EXPORT(vsprintf)
// --- character I/O ---
DXE_EXPORT(fgetc)
DXE_EXPORT(fgets)
DXE_EXPORT(fseek)
DXE_EXPORT(ftell)
DXE_EXPORT(fputc)
DXE_EXPORT(getc)
DXE_EXPORT(putc)
DXE_EXPORT(putchar)
DXE_EXPORT(ungetc)
// --- file I/O ---
DXE_EXPORT(fclose)
DXE_EXPORT(feof)
DXE_EXPORT(ferror)
DXE_EXPORT(fflush)
DXE_EXPORT(fopen)
DXE_EXPORT(fread)
DXE_EXPORT(freopen)
DXE_EXPORT(fseek)
DXE_EXPORT(ftell)
DXE_EXPORT(fwrite)
DXE_EXPORT(remove)
DXE_EXPORT(rename)
DXE_EXPORT(rewind)
DXE_EXPORT(tmpfile)
DXE_EXPORT(tmpnam)
// libc — math
DXE_EXPORT(sin)
DXE_EXPORT(cos)
DXE_EXPORT(sqrt)
// libc — time
DXE_EXPORT(clock)
DXE_EXPORT(time)
DXE_EXPORT(localtime)
// libc — directory
// --- directory ---
DXE_EXPORT(closedir)
DXE_EXPORT(mkdir)
DXE_EXPORT(opendir)
DXE_EXPORT(readdir)
DXE_EXPORT(closedir)
DXE_EXPORT(rmdir)
// libc — filesystem
// --- filesystem ---
DXE_EXPORT(access)
DXE_EXPORT(chdir)
DXE_EXPORT(getcwd)
DXE_EXPORT(stat)
DXE_EXPORT(unlink)
// libc — misc
// --- time ---
DXE_EXPORT(clock)
DXE_EXPORT(difftime)
DXE_EXPORT(gmtime)
DXE_EXPORT(localtime)
DXE_EXPORT(mktime)
DXE_EXPORT(strftime)
DXE_EXPORT(time)
// --- process / environment ---
DXE_EXPORT(abort)
DXE_EXPORT(atexit)
DXE_EXPORT(exit)
DXE_EXPORT(getenv)
DXE_EXPORT(system)
// --- sorting / searching ---
DXE_EXPORT(bsearch)
DXE_EXPORT(qsort)
// --- random ---
DXE_EXPORT(rand)
DXE_EXPORT(srand)
DXE_EXPORT(abs)
DXE_EXPORT(atoi)
// --- setjmp / signal ---
DXE_EXPORT(longjmp)
DXE_EXPORT(setjmp)
DXE_EXPORT(signal)
// --- libm ---
DXE_EXPORT(acos)
DXE_EXPORT(asin)
DXE_EXPORT(atan)
DXE_EXPORT(atan2)
DXE_EXPORT(ceil)
DXE_EXPORT(cos)
DXE_EXPORT(exp)
DXE_EXPORT(fabs)
DXE_EXPORT(floor)
DXE_EXPORT(fmod)
DXE_EXPORT(frexp)
DXE_EXPORT(ldexp)
DXE_EXPORT(log)
DXE_EXPORT(log10)
DXE_EXPORT(modf)
DXE_EXPORT(pow)
DXE_EXPORT(sin)
DXE_EXPORT(sqrt)
DXE_EXPORT(tan)
// --- errno ---
DXE_EXPORT(errno)
// --- DJGPP stdio internals ---
// The stdin/stdout/stderr macros in DJGPP expand to pointers to
// these FILE structs. Without them, any DXE that does printf()
// or fprintf(stderr, ...) gets an unresolved symbol at load time.
DXE_EXPORT(__dj_stdin)
DXE_EXPORT(__dj_stdout)
DXE_EXPORT(__dj_stderr)
// --- stb_ds (dynamic arrays / hashmaps) ---
// Internal functions called by the arrput/arrfree/hm* macros.
// Implementation lives in libtasks.a.
DXE_EXPORT(stbds_arrfreef)
DXE_EXPORT(stbds_arrgrowf)
DXE_EXPORT(stbds_hash_bytes)
DXE_EXPORT(stbds_hash_string)
DXE_EXPORT(stbds_hmdel_key)
DXE_EXPORT(stbds_hmfree_func)
DXE_EXPORT(stbds_hmget_key)
DXE_EXPORT(stbds_hmget_key_ts)
DXE_EXPORT(stbds_hmput_default)
DXE_EXPORT(stbds_hmput_key)
DXE_EXPORT(stbds_rand_seed)
DXE_EXPORT(stbds_shmode_func)
DXE_EXPORT(stbds_stralloc)
DXE_EXPORT(stbds_strreset)
// --- stb_image (image loading) ---
DXE_EXPORT(stbi_failure_reason)
DXE_EXPORT(stbi_image_free)
DXE_EXPORT(stbi_info)
DXE_EXPORT(stbi_info_from_memory)
DXE_EXPORT(stbi_load)
DXE_EXPORT(stbi_load_from_memory)
DXE_EXPORT(stbi_set_flip_vertically_on_load)
// --- stb_image_write ---
DXE_EXPORT(stbi_write_bmp)
DXE_EXPORT(stbi_write_png)
DXE_EXPORT(stbi_write_jpg)
DXE_EXPORT(stbi_write_tga)
DXE_EXPORT_END

View file

@ -26,9 +26,11 @@
#include "shellApp.h"
#include "shellInfo.h"
#include "shellTaskMgr.h"
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "platform/dvxPlatform.h"
#include "thirdparty/stb_ds.h"
#include <stdarg.h>
#include <setjmp.h>
@ -51,7 +53,9 @@ static jmp_buf sCrashJmp;
// the recovery code which signal fired (for logging/diagnostics).
static volatile int sCrashSignal = 0;
static FILE *sLogFile = NULL;
static void (*sDesktopUpdateFn)(void) = NULL;
// Desktop update callback list (dynamic, managed via stb_ds arrput/arrdel)
typedef void (*DesktopUpdateFnT)(void);
static DesktopUpdateFnT *sDesktopUpdateFns = NULL;
// ============================================================
// Prototypes
@ -93,12 +97,31 @@ static void crashHandler(int sig) {
// ============================================================
void shellDesktopUpdate(void) {
if (sDesktopUpdateFn) {
sDesktopUpdateFn();
for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) {
sDesktopUpdateFns[i]();
}
}
// ============================================================
// ctrlEscHandler — system-wide Ctrl+Esc callback
// ============================================================
static void ctrlEscHandler(void *ctx) {
shellTaskMgrOpen((AppContextT *)ctx);
}
// ============================================================
// titleChangeHandler — refresh listeners when a window title changes
// ============================================================
static void titleChangeHandler(void *ctx) {
(void)ctx;
shellDesktopUpdate();
}
// ============================================================
// idleYield — called when no dirty rects need compositing
// ============================================================
@ -213,7 +236,21 @@ void shellLog(const char *fmt, ...) {
// ============================================================
void shellRegisterDesktopUpdate(void (*updateFn)(void)) {
sDesktopUpdateFn = updateFn;
arrput(sDesktopUpdateFns, updateFn);
}
// ============================================================
// shellUnregisterDesktopUpdate
// ============================================================
void shellUnregisterDesktopUpdate(void (*updateFn)(void)) {
for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) {
if (sDesktopUpdateFns[i] == updateFn) {
arrdel(sDesktopUpdateFns, i);
return;
}
}
}
@ -275,6 +312,42 @@ int main(int argc, char *argv[]) {
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal);
shellLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s", wheelStr, (long)dblClick, accelStr);
// Apply saved color scheme from INI
bool colorsLoaded = false;
for (int32_t i = 0; i < ColorCountE; i++) {
const char *val = prefsGetString("colors", dvxColorName((ColorIdE)i), NULL);
if (val) {
int32_t r;
int32_t g;
int32_t b;
if (sscanf(val, "%d,%d,%d", &r, &g, &b) == 3) {
sCtx.colorRgb[i][0] = (uint8_t)r;
sCtx.colorRgb[i][1] = (uint8_t)g;
sCtx.colorRgb[i][2] = (uint8_t)b;
colorsLoaded = true;
}
}
}
if (colorsLoaded) {
dvxApplyColorScheme(&sCtx);
shellLog("Preferences: loaded custom color scheme");
}
// Apply saved wallpaper
const char *wpPath = prefsGetString("desktop", "wallpaper", NULL);
if (wpPath) {
if (dvxSetWallpaper(&sCtx, wpPath)) {
shellLog("Preferences: loaded wallpaper %s", wpPath);
} else {
shellLog("Preferences: failed to load wallpaper %s", wpPath);
}
}
}
if (result != 0) {
@ -324,6 +397,10 @@ int main(int argc, char *argv[]) {
// app tasks CPU time during quiet periods.
sCtx.idleCallback = idleYield;
sCtx.idleCtx = &sCtx;
sCtx.onCtrlEsc = ctrlEscHandler;
sCtx.ctrlEscCtx = &sCtx;
sCtx.onTitleChange = titleChangeHandler;
sCtx.titleChangeCtx = &sCtx;
// Load the desktop app
int32_t desktopId = shellLoadApp(&sCtx, SHELL_DESKTOP_APP);

360
dvxshell/shellTaskMgr.c Normal file
View file

@ -0,0 +1,360 @@
// shellTaskMgr.c — System Task Manager
//
// Shell-level Task Manager window. Accessible via Ctrl+Esc regardless
// of which app is focused or whether the desktop app is running. Lists
// all running apps with Switch To, End Task, and Run buttons.
#include "shellTaskMgr.h"
#include "shellApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "platform/dvxPlatform.h"
#include "thirdparty/stb_ds.h"
#include <stdio.h>
#include <string.h>
// ============================================================
// Constants
// ============================================================
#define TM_COL_COUNT 5
#define TM_MAX_PATH 260
// ============================================================
// Prototypes
// ============================================================
static void onTmClose(WindowT *win);
static void onTmEndTask(WidgetT *w);
static void onTmRun(WidgetT *w);
static void onTmSwitchTo(WidgetT *w);
static void refreshTaskList(void);
static void updateStatusText(void);
// ============================================================
// Module state
// ============================================================
// Per-row string storage for the list view (must outlive each refresh cycle)
typedef struct {
char title[MAX_TITLE_LEN];
char file[64];
char type[12];
} TmRowStringsT;
static AppContextT *sCtx = NULL;
static WindowT *sTmWindow = NULL;
static WidgetT *sTmListView = NULL;
static WidgetT *sTmStatusLbl = NULL;
static const char **sCells = NULL; // dynamic array of cell pointers
static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings
// ============================================================
// onTmClose
// ============================================================
static void onTmClose(WindowT *win) {
shellUnregisterDesktopUpdate(shellTaskMgrRefresh);
arrfree(sCells);
arrfree(sRowStrs);
sCells = NULL;
sRowStrs = NULL;
sTmListView = NULL;
sTmStatusLbl = NULL;
sTmWindow = NULL;
dvxDestroyWindow(sCtx, win);
}
// ============================================================
// onTmEndTask
// ============================================================
static void onTmEndTask(WidgetT *w) {
(void)w;
if (!sTmListView) {
return;
}
int32_t sel = wgtListViewGetSelected(sTmListView);
if (sel < 0) {
return;
}
// Re-walk the app slot table in the same order as refreshTaskList()
// to map the selected row index back to the correct ShellAppT.
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();
return;
}
idx++;
}
}
}
// ============================================================
// onTmRun
// ============================================================
static void onTmRun(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "Applications (*.app)", "*.app" },
{ "All Files (*.*)", "*.*" }
};
char path[TM_MAX_PATH];
if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
shellLoadApp(sCtx, path);
}
}
// ============================================================
// onTmSwitchTo
// ============================================================
static void onTmSwitchTo(WidgetT *w) {
(void)w;
if (!sTmListView) {
return;
}
int32_t sel = wgtListViewGetSelected(sTmListView);
if (sel < 0) {
return;
}
// Same index-to-appId mapping as refreshTaskList. Scan the window
// stack top-down (highest Z first) to find the app's topmost window,
// restore it if minimized, then raise and focus it.
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) {
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++;
}
}
}
// ============================================================
// refreshTaskList
// ============================================================
static void refreshTaskList(void) {
if (!sTmListView) {
return;
}
// Reset dynamic arrays (keep allocations for reuse)
arrsetlen(sCells, 0);
arrsetlen(sRowStrs, 0);
int32_t rowCount = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
// Grow the per-row string storage
TmRowStringsT row = {0};
// Column 1: Title (from first visible window owned by this app)
for (int32_t w = 0; w < sCtx->stack.count; w++) {
WindowT *win = sCtx->stack.windows[w];
if (win->appId == app->appId && win->visible) {
snprintf(row.title, sizeof(row.title), "%s", win->title);
break;
}
}
// Column 2: Filename (basename of .app path)
char *sep = platformPathDirEnd(app->path);
const char *fname = sep ? sep + 1 : app->path;
snprintf(row.file, sizeof(row.file), "%.63s", fname);
// Column 3: Type
snprintf(row.type, sizeof(row.type), "%s", app->hasMainLoop ? "Task" : "Callback");
arrput(sRowStrs, row);
// Build cell pointers for this row
arrput(sCells, app->name);
arrput(sCells, sRowStrs[rowCount].title);
arrput(sCells, sRowStrs[rowCount].file);
arrput(sCells, sRowStrs[rowCount].type);
arrput(sCells, "Running");
rowCount++;
}
}
wgtListViewSetData(sTmListView, sCells, rowCount);
}
// ============================================================
// updateStatusText
// ============================================================
static void updateStatusText(void) {
if (!sTmStatusLbl) {
return;
}
static char buf[128];
int32_t count = shellRunningAppCount();
uint32_t totalKb;
uint32_t freeKb;
bool hasMem = platformGetMemoryInfo(&totalKb, &freeKb);
int32_t pos = 0;
if (count == 1) {
pos = snprintf(buf, sizeof(buf), "1 app");
} else {
pos = snprintf(buf, sizeof(buf), "%ld apps", (long)count);
}
if (hasMem && totalKb > 0) {
uint32_t usedKb = totalKb - freeKb;
snprintf(buf + pos, sizeof(buf) - pos, " | Memory: %lu/%lu MB", (unsigned long)(usedKb / 1024), (unsigned long)(totalKb / 1024));
}
wgtSetText(sTmStatusLbl, buf);
}
// ============================================================
// shellTaskMgrOpen
// ============================================================
void shellTaskMgrOpen(AppContextT *ctx) {
sCtx = ctx;
if (sTmWindow) {
// Already open — raise and focus
for (int32_t i = 0; i < ctx->stack.count; i++) {
if (ctx->stack.windows[i] == sTmWindow) {
wmRaiseWindow(&ctx->stack, &ctx->dirty, i);
wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1);
break;
}
}
return;
}
int32_t winW = 520;
int32_t winH = 280;
int32_t winX = (ctx->display.width - winW) / 2;
int32_t winY = (ctx->display.height - winH) / 3;
sTmWindow = dvxCreateWindow(ctx, "Task Manager", winX, winY, winW, winH, true);
if (!sTmWindow) {
return;
}
sTmWindow->onClose = onTmClose;
sTmWindow->appId = 0; // owned by the shell, not any app
WidgetT *root = wgtInitWindow(ctx, sTmWindow);
static ListViewColT tmCols[TM_COL_COUNT];
tmCols[0].title = "Name";
tmCols[0].width = wgtPercent(20);
tmCols[0].align = ListViewAlignLeftE;
tmCols[1].title = "Title";
tmCols[1].width = wgtPercent(30);
tmCols[1].align = ListViewAlignLeftE;
tmCols[2].title = "File";
tmCols[2].width = wgtPercent(22);
tmCols[2].align = ListViewAlignLeftE;
tmCols[3].title = "Type";
tmCols[3].width = wgtPercent(14);
tmCols[3].align = ListViewAlignLeftE;
tmCols[4].title = "Status";
tmCols[4].width = wgtPercent(14);
tmCols[4].align = ListViewAlignLeftE;
sTmListView = wgtListView(root);
sTmListView->weight = 100;
sTmListView->prefH = wgtPixels(160);
wgtListViewSetColumns(sTmListView, tmCols, TM_COL_COUNT);
WidgetT *btnRow = wgtHBox(root);
btnRow->spacing = wgtPixels(8);
sTmStatusLbl = wgtLabel(btnRow, "");
sTmStatusLbl->weight = 100;
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);
WidgetT *runBtn = wgtButton(btnRow, "Run...");
runBtn->onClick = onTmRun;
runBtn->prefW = wgtPixels(90);
shellRegisterDesktopUpdate(shellTaskMgrRefresh);
refreshTaskList();
updateStatusText();
dvxFitWindow(ctx, sTmWindow);
}
// ============================================================
// shellTaskMgrRefresh
// ============================================================
void shellTaskMgrRefresh(void) {
if (sTmWindow) {
refreshTaskList();
updateStatusText();
}
}

18
dvxshell/shellTaskMgr.h Normal file
View file

@ -0,0 +1,18 @@
// shellTaskMgr.h — System Task Manager
//
// The Task Manager is a shell-level component, not tied to any app.
// It is always available via Ctrl+Esc and persists even if the desktop
// app (Program Manager) is terminated.
#ifndef SHELL_TASKMGR_H
#define SHELL_TASKMGR_H
#include "dvxApp.h"
// Open or raise the Task Manager window.
void shellTaskMgrOpen(AppContextT *ctx);
// Refresh the task list (called by desktop update notification).
void shellTaskMgrRefresh(void);
#endif

47
mkcd.sh Executable file
View file

@ -0,0 +1,47 @@
#!/bin/bash
# mkcd.sh — Build DVX and create a CD-ROM ISO image for 86Box
#
# Usage: ./mkcd.sh
#
# Builds the full DVX stack (dvx, tasks, shell, apps), then creates
# an ISO 9660 image from the bin/ directory. The ISO uses short
# 8.3 filenames (-iso-level 1) for DOS compatibility.
#
# The ISO is placed in 86Box's data directory so it can be mounted
# as a CD-ROM drive.
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ISO_DIR="$HOME/.var/app/net._86box._86Box/data/86Box"
ISO_PATH="$ISO_DIR/dvx.iso"
# Build everything
echo "Building DVX..."
make -C "$SCRIPT_DIR" all
# Verify build output exists
if [ ! -f "$SCRIPT_DIR/bin/dvx.exe" ]; then
echo "ERROR: bin/dvx.exe not found — build failed?"
exit 1
fi
# Create the ISO image
# -iso-level 1: strict 8.3 filenames (DOS compatibility)
# -J: Joliet extensions (long names for Windows/Linux)
# -R: Rock Ridge (long names for Linux)
# -V: volume label
echo "Creating ISO image..."
mkisofs \
-iso-level 1 \
-J \
-R \
-V "DVX" \
-o "$ISO_PATH" \
"$SCRIPT_DIR/bin/"
echo "ISO created: $ISO_PATH"
echo "Size: $(du -h "$ISO_PATH" | cut -f1)"
echo ""
echo "In 86Box, mount $ISO_PATH as a CD-ROM drive."
echo "Then from DOS: D:\\DVX.EXE (or whatever drive letter)"

22
themes/cde.thm Normal file
View file

@ -0,0 +1,22 @@
; DVX Color Theme — CDE (Common Desktop Environment)
; Warm tan/brown palette inspired by Solaris/HP-UX CDE.
[colors]
desktop = 115,130,143
windowFace = 174,178,195
windowHighlight = 234,234,234
windowShadow = 100,100,120
activeTitleBg = 88,107,136
activeTitleFg = 255,255,255
inactiveTitleBg = 174,178,195
inactiveTitleFg = 0,0,0
contentBg = 174,178,195
contentFg = 0,0,0
menuBg = 174,178,195
menuFg = 0,0,0
menuHighlightBg = 88,107,136
menuHighlightFg = 255,255,255
buttonFace = 174,178,195
scrollbarBg = 174,178,195
scrollbarFg = 100,100,120
scrollbarTrough = 140,150,165

22
themes/geos.thm Normal file
View file

@ -0,0 +1,22 @@
; DVX Color Theme — GEOS Ensemble
; Motif-style 3D bevels with teal desktop and dark charcoal title bars.
[colors]
desktop = 0,128,128
windowFace = 192,192,192
windowHighlight = 255,255,255
windowShadow = 128,128,128
activeTitleBg = 48,48,48
activeTitleFg = 255,255,255
inactiveTitleBg = 160,160,160
inactiveTitleFg = 64,64,64
contentBg = 192,192,192
contentFg = 0,0,0
menuBg = 192,192,192
menuFg = 0,0,0
menuHighlightBg = 48,48,48
menuHighlightFg = 255,255,255
buttonFace = 192,192,192
scrollbarBg = 192,192,192
scrollbarFg = 128,128,128
scrollbarTrough = 160,160,160

22
themes/win31.thm Normal file
View file

@ -0,0 +1,22 @@
; DVX Color Theme — Windows 3.1
; Classic teal desktop with navy blue title bars.
[colors]
desktop = 0,128,128
windowFace = 192,192,192
windowHighlight = 255,255,255
windowShadow = 128,128,128
activeTitleBg = 0,0,128
activeTitleFg = 255,255,255
inactiveTitleBg = 192,192,192
inactiveTitleFg = 0,0,0
contentBg = 255,255,255
contentFg = 0,0,0
menuBg = 192,192,192
menuFg = 0,0,0
menuHighlightBg = 0,0,128
menuHighlightFg = 255,255,255
buttonFace = 192,192,192
scrollbarBg = 192,192,192
scrollbarFg = 0,0,0
scrollbarTrough = 192,192,192