Compare commits
2 commits
a4727754e3
...
fc9fc46c79
| Author | SHA1 | Date | |
|---|---|---|---|
| fc9fc46c79 | |||
| 157d79f2d6 |
30 changed files with 2899 additions and 868 deletions
3
Makefile
3
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
806
apps/ctrlpanel/ctrlpanel.c
Normal 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;
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxWm.h"
|
||||
#include "platform/dvxPlatform.h"
|
||||
#include "shellApp.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
|
@ -75,10 +76,11 @@ static void onMenu(WindowT *win, int32_t menuId);
|
|||
// Callback-only: the TextArea widget handles all interactive editing
|
||||
// entirely within the shell's event dispatch, so no dedicated task needed.
|
||||
AppDescriptorT appDescriptor = {
|
||||
.name = "Notepad",
|
||||
.hasMainLoop = false,
|
||||
.stackSize = SHELL_STACK_DEFAULT,
|
||||
.priority = TS_PRIORITY_NORMAL
|
||||
.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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
20
dvx/Makefile
20
dvx/Makefile
|
|
@ -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)
|
||||
|
|
|
|||
618
dvx/dvxApp.c
618
dvx/dvxApp.c
|
|
@ -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
|
||||
rectFill(d, ops, dr->x, dr->y, dr->w, dr->h, ctx->colors.desktop);
|
||||
// 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,66 +4172,77 @@ static void updateTooltip(AppContextT *ctx) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Find the widget under the cursor
|
||||
int32_t hitPart;
|
||||
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
|
||||
// Check minimized icons first (they sit outside any window)
|
||||
const char *tipText = NULL;
|
||||
int32_t iconIdx = wmMinimizedIconHit(&ctx->stack, &ctx->display, mx, my);
|
||||
|
||||
if (hitIdx < 0 || hitPart != 0) {
|
||||
return;
|
||||
if (iconIdx >= 0) {
|
||||
tipText = ctx->stack.windows[iconIdx]->title;
|
||||
}
|
||||
|
||||
WindowT *win = ctx->stack.windows[hitIdx];
|
||||
// Otherwise check widgets in the content area of a window
|
||||
if (!tipText) {
|
||||
int32_t hitPart;
|
||||
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
|
||||
|
||||
if (!win->widgetRoot) {
|
||||
return;
|
||||
}
|
||||
if (hitIdx < 0 || hitPart != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cx = mx - win->x - win->contentX;
|
||||
int32_t cy = my - win->y - win->contentY;
|
||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
||||
int32_t vx = cx + scrollX;
|
||||
int32_t vy = cy + scrollY;
|
||||
WindowT *win = ctx->stack.windows[hitIdx];
|
||||
|
||||
WidgetT *hit = widgetHitTest(win->widgetRoot, vx, vy);
|
||||
if (!win->widgetRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Walk into NO_HIT_RECURSE containers to find deepest child
|
||||
while (hit && hit->wclass && (hit->wclass->flags & WCLASS_NO_HIT_RECURSE)) {
|
||||
WidgetT *inner = NULL;
|
||||
int32_t cx = mx - win->x - win->contentX;
|
||||
int32_t cy = my - win->y - win->contentY;
|
||||
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
||||
int32_t vx = cx + scrollX;
|
||||
int32_t vy = cy + scrollY;
|
||||
|
||||
for (WidgetT *c = hit->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && vx >= c->x && vx < c->x + c->w && vy >= c->y && vy < c->y + c->h) {
|
||||
inner = c;
|
||||
WidgetT *hit = widgetHitTest(win->widgetRoot, vx, vy);
|
||||
|
||||
// Walk into NO_HIT_RECURSE containers to find deepest child
|
||||
while (hit && hit->wclass && (hit->wclass->flags & WCLASS_NO_HIT_RECURSE)) {
|
||||
WidgetT *inner = NULL;
|
||||
|
||||
for (WidgetT *c = hit->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && vx >= c->x && vx < c->x + c->w && vy >= c->y && vy < c->y + c->h) {
|
||||
inner = c;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inner) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (inner->tooltip) {
|
||||
hit = inner;
|
||||
break;
|
||||
}
|
||||
|
||||
if (inner->wclass && (inner->wclass->flags & WCLASS_NO_HIT_RECURSE)) {
|
||||
hit = inner;
|
||||
} else {
|
||||
WidgetT *deep = widgetHitTest(inner, vx, vy);
|
||||
hit = deep ? deep : inner;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inner) {
|
||||
break;
|
||||
if (!hit || !hit->tooltip) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the inner child has a tooltip, use it; otherwise check if it's another container
|
||||
if (inner->tooltip) {
|
||||
hit = inner;
|
||||
break;
|
||||
}
|
||||
|
||||
if (inner->wclass && (inner->wclass->flags & WCLASS_NO_HIT_RECURSE)) {
|
||||
hit = inner;
|
||||
} else {
|
||||
WidgetT *deep = widgetHitTest(inner, vx, vy);
|
||||
hit = deep ? deep : inner;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hit || !hit->tooltip) {
|
||||
return;
|
||||
tipText = hit->tooltip;
|
||||
}
|
||||
|
||||
// Show the tooltip
|
||||
ctx->tooltipText = hit->tooltip;
|
||||
ctx->tooltipText = tipText;
|
||||
|
||||
int32_t tw = textWidth(&ctx->font, hit->tooltip) + TOOLTIP_PAD * 2;
|
||||
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
|
||||
|
|
|
|||
50
dvx/dvxApp.h
50
dvx/dvxApp.h
|
|
@ -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);
|
||||
|
|
|
|||
375
dvx/dvxPrefs.c
375
dvx/dvxPrefs.c
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
62
dvx/dvxWm.c
62
dvx/dvxWm.c
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
20
dvx/thirdparty/ini/LICENSE
vendored
20
dvx/thirdparty/ini/LICENSE
vendored
|
|
@ -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.
|
||||
63
dvx/thirdparty/ini/README.md
vendored
63
dvx/thirdparty/ini/README.md
vendored
|
|
@ -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.
|
||||
274
dvx/thirdparty/ini/src/ini.c
vendored
274
dvx/thirdparty/ini/src/ini.c
vendored
|
|
@ -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;
|
||||
}
|
||||
20
dvx/thirdparty/ini/src/ini.h
vendored
20
dvx/thirdparty/ini/src/ini.h
vendored
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
360
dvxshell/shellTaskMgr.c
Normal 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
18
dvxshell/shellTaskMgr.h
Normal 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
47
mkcd.sh
Executable 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
22
themes/cde.thm
Normal 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
22
themes/geos.thm
Normal 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
22
themes/win31.thm
Normal 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
|
||||
Loading…
Add table
Reference in a new issue