Dumb number of changes to commit at once. Getting ready for Alpha 2.
This commit is contained in:
parent
157d79f2d6
commit
fc9fc46c79
30 changed files with 2838 additions and 832 deletions
3
Makefile
3
Makefile
|
|
@ -24,4 +24,5 @@ clean:
|
||||||
$(MAKE) -C dvxshell clean
|
$(MAKE) -C dvxshell clean
|
||||||
$(MAKE) -C apps 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 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
|
BINDIR = ../bin/apps
|
||||||
|
|
||||||
# App definitions: each is a subdir with a single .c file
|
# 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)
|
.PHONY: all clean $(APPS)
|
||||||
|
|
||||||
all: $(APPS)
|
all: $(APPS)
|
||||||
|
|
||||||
|
ctrlpanel: $(BINDIR)/ctrlpanel/ctrlpanel.app
|
||||||
progman: $(BINDIR)/progman/progman.app
|
progman: $(BINDIR)/progman/progman.app
|
||||||
notepad: $(BINDIR)/notepad/notepad.app
|
notepad: $(BINDIR)/notepad/notepad.app
|
||||||
clock: $(BINDIR)/clock/clock.app
|
clock: $(BINDIR)/clock/clock.app
|
||||||
dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.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
|
$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman
|
||||||
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
$(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 $<
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
|
||||||
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/
|
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)
|
$(OBJDIR)/progman.o: progman/progman.c | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
|
@ -51,6 +58,9 @@ $(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c | $(OBJDIR)
|
||||||
$(OBJDIR):
|
$(OBJDIR):
|
||||||
mkdir -p $(OBJDIR)
|
mkdir -p $(OBJDIR)
|
||||||
|
|
||||||
|
$(BINDIR)/ctrlpanel:
|
||||||
|
mkdir -p $(BINDIR)/ctrlpanel
|
||||||
|
|
||||||
$(BINDIR)/progman:
|
$(BINDIR)/progman:
|
||||||
mkdir -p $(BINDIR)/progman
|
mkdir -p $(BINDIR)/progman
|
||||||
|
|
||||||
|
|
@ -64,13 +74,15 @@ $(BINDIR)/dvxdemo:
|
||||||
mkdir -p $(BINDIR)/dvxdemo
|
mkdir -p $(BINDIR)/dvxdemo
|
||||||
|
|
||||||
# Dependencies
|
# 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)/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)/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)/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
|
$(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:
|
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)/progman/progman.app
|
||||||
rm -f $(BINDIR)/notepad/notepad.app
|
rm -f $(BINDIR)/notepad/notepad.app
|
||||||
rm -f $(BINDIR)/clock/clock.app
|
rm -f $(BINDIR)/clock/clock.app
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,14 @@ static void updateTime(void) {
|
||||||
return;
|
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);
|
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
|
||||||
sState.lastUpdate = now;
|
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 "dvxDialog.h"
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
|
#include "platform/dvxPlatform.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
@ -77,6 +78,7 @@ static void onMenu(WindowT *win, int32_t menuId);
|
||||||
AppDescriptorT appDescriptor = {
|
AppDescriptorT appDescriptor = {
|
||||||
.name = "Notepad",
|
.name = "Notepad",
|
||||||
.hasMainLoop = false,
|
.hasMainLoop = false,
|
||||||
|
.multiInstance = true,
|
||||||
.stackSize = SHELL_STACK_DEFAULT,
|
.stackSize = SHELL_STACK_DEFAULT,
|
||||||
.priority = TS_PRIORITY_NORMAL
|
.priority = TS_PRIORITY_NORMAL
|
||||||
};
|
};
|
||||||
|
|
@ -194,6 +196,7 @@ static void doOpen(void) {
|
||||||
if (buf) {
|
if (buf) {
|
||||||
fread(buf, 1, size, f);
|
fread(buf, 1, size, f);
|
||||||
buf[size] = '\0';
|
buf[size] = '\0';
|
||||||
|
platformStripLineEndings(buf, (int32_t)size);
|
||||||
wgtSetText(sTextArea, buf);
|
wgtSetText(sTextArea, buf);
|
||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +231,16 @@ static void doSave(void) {
|
||||||
return;
|
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);
|
fclose(f);
|
||||||
markClean();
|
markClean();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
|
#include "shellTaskMgr.h"
|
||||||
#include "shellInfo.h"
|
#include "shellInfo.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
@ -58,9 +59,6 @@
|
||||||
#define CMD_TASK_MGR 301
|
#define CMD_TASK_MGR 301
|
||||||
#define CMD_SYSINFO 302
|
#define CMD_SYSINFO 302
|
||||||
|
|
||||||
// Task Manager column count
|
|
||||||
#define TM_COL_COUNT 4
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Module state
|
// Module state
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -76,7 +74,6 @@ typedef struct {
|
||||||
// of collision between apps even though the names look global.
|
// of collision between apps even though the names look global.
|
||||||
static DxeAppContextT *sCtx = NULL;
|
static DxeAppContextT *sCtx = NULL;
|
||||||
static AppContextT *sAc = NULL;
|
static AppContextT *sAc = NULL;
|
||||||
static int32_t sMyAppId = 0;
|
|
||||||
static WindowT *sPmWindow = NULL;
|
static WindowT *sPmWindow = NULL;
|
||||||
static WidgetT *sStatusLabel = NULL;
|
static WidgetT *sStatusLabel = NULL;
|
||||||
static bool sMinOnRun = false;
|
static bool sMinOnRun = false;
|
||||||
|
|
@ -99,14 +96,6 @@ static void showAboutDialog(void);
|
||||||
static void showSystemInfo(void);
|
static void showSystemInfo(void);
|
||||||
static void updateStatusText(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
|
// 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,
|
// 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) {
|
static void desktopUpdate(void) {
|
||||||
updateStatusText();
|
updateStatusText();
|
||||||
|
|
||||||
if (sTmWindow) {
|
|
||||||
refreshTaskList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -390,165 +304,12 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_TASK_MGR:
|
case CMD_TASK_MGR:
|
||||||
buildTaskManager();
|
shellTaskMgrOpen(sAc);
|
||||||
break;
|
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.
|
// 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
|
// The apps/ path is relative to the working directory, which the shell sets
|
||||||
// to the root of the DVX install before loading any apps.
|
// to the root of the DVX install before loading any apps.
|
||||||
|
|
@ -681,14 +442,7 @@ static void updateStatusText(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static char buf[64];
|
static char buf[64];
|
||||||
|
int32_t count = shellRunningAppCount();
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
snprintf(buf, sizeof(buf), "No applications running");
|
snprintf(buf, sizeof(buf), "No applications running");
|
||||||
|
|
@ -712,7 +466,6 @@ static void updateStatusText(void) {
|
||||||
int32_t appMain(DxeAppContextT *ctx) {
|
int32_t appMain(DxeAppContextT *ctx) {
|
||||||
sCtx = ctx;
|
sCtx = ctx;
|
||||||
sAc = ctx->shellCtx;
|
sAc = ctx->shellCtx;
|
||||||
sMyAppId = ctx->appId;
|
|
||||||
|
|
||||||
scanAppsDir();
|
scanAppsDir();
|
||||||
buildPmWindow();
|
buildPmWindow();
|
||||||
|
|
@ -721,5 +474,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
// bar and Task Manager stay current without polling
|
// bar and Task Manager stay current without polling
|
||||||
shellRegisterDesktopUpdate(desktopUpdate);
|
shellRegisterDesktopUpdate(desktopUpdate);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return 0;
|
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
|
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||||
AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar
|
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
|
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
|
OBJDIR = ../obj/dvx
|
||||||
WOBJDIR = ../obj/dvx/widgets
|
WOBJDIR = ../obj/dvx/widgets
|
||||||
POBJDIR = ../obj/dvx/platform
|
POBJDIR = ../obj/dvx/platform
|
||||||
TOBJDIR = ../obj/dvx/thirdparty
|
|
||||||
LIBDIR = ../lib
|
LIBDIR = ../lib
|
||||||
|
|
||||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c dvxPrefs.c
|
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c dvxPrefs.c
|
||||||
|
|
||||||
PSRCS = platform/dvxPlatformDos.c
|
PSRCS = platform/dvxPlatformDos.c
|
||||||
|
|
||||||
TSRCS = thirdparty/ini/src/ini.c
|
|
||||||
|
|
||||||
WSRCS = widgets/widgetAnsiTerm.c \
|
WSRCS = widgets/widgetAnsiTerm.c \
|
||||||
widgets/widgetClass.c \
|
widgets/widgetClass.c \
|
||||||
|
|
@ -54,15 +52,14 @@ WSRCS = widgets/widgetAnsiTerm.c \
|
||||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||||
POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS))
|
POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS))
|
||||||
WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS))
|
WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS))
|
||||||
TOBJS = $(TOBJDIR)/ini.o
|
|
||||||
TARGET = $(LIBDIR)/libdvx.a
|
TARGET = $(LIBDIR)/libdvx.a
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) $(TOBJS) | $(LIBDIR)
|
$(TARGET): $(OBJS) $(POBJS) $(WOBJS) | $(LIBDIR)
|
||||||
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS) $(TOBJS)
|
$(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS)
|
||||||
$(RANLIB) $@
|
$(RANLIB) $@
|
||||||
|
|
||||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||||
|
|
@ -74,9 +71,6 @@ $(POBJDIR)/%.o: platform/%.c | $(POBJDIR)
|
||||||
$(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR)
|
$(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR)
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
$(TOBJDIR)/ini.o: thirdparty/ini/src/ini.c | $(TOBJDIR)
|
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
|
||||||
|
|
||||||
$(OBJDIR):
|
$(OBJDIR):
|
||||||
mkdir -p $(OBJDIR)
|
mkdir -p $(OBJDIR)
|
||||||
|
|
||||||
|
|
@ -86,9 +80,6 @@ $(POBJDIR):
|
||||||
$(WOBJDIR):
|
$(WOBJDIR):
|
||||||
mkdir -p $(WOBJDIR)
|
mkdir -p $(WOBJDIR)
|
||||||
|
|
||||||
$(TOBJDIR):
|
|
||||||
mkdir -p $(TOBJDIR)
|
|
||||||
|
|
||||||
$(LIBDIR):
|
$(LIBDIR):
|
||||||
mkdir -p $(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)/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)/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
|
# Platform file dependencies
|
||||||
$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h
|
$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h
|
||||||
|
|
||||||
# Thirdparty file dependencies
|
# Thirdparty file dependencies
|
||||||
$(TOBJDIR)/ini.o: thirdparty/ini/src/ini.c thirdparty/ini/src/ini.h
|
|
||||||
|
|
||||||
# Widget file dependencies
|
# Widget file dependencies
|
||||||
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
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)
|
$(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(OBJS) $(POBJS) $(WOBJS) $(TOBJS) $(TARGET)
|
rm -f $(OBJS) $(POBJS) $(WOBJS) $(TARGET)
|
||||||
|
|
|
||||||
540
dvx/dvxApp.c
540
dvx/dvxApp.c
|
|
@ -461,8 +461,18 @@ static void compositeAndFlush(AppContextT *ctx) {
|
||||||
// Set clip rect to this dirty rect
|
// Set clip rect to this dirty rect
|
||||||
setClipRect(d, dr->x, dr->y, dr->w, dr->h);
|
setClipRect(d, dr->x, dr->y, dr->w, dr->h);
|
||||||
|
|
||||||
// 1. Draw desktop background
|
// 1. Draw desktop background (wallpaper or solid color)
|
||||||
|
if (ctx->wallpaperBuf) {
|
||||||
|
int32_t bytesPerPx = d->format.bitsPerPixel / 8;
|
||||||
|
|
||||||
|
for (int32_t row = dr->y; row < dr->y + dr->h; row++) {
|
||||||
|
uint8_t *src = ctx->wallpaperBuf + row * ctx->wallpaperPitch + dr->x * bytesPerPx;
|
||||||
|
uint8_t *dst = d->backBuf + row * d->pitch + dr->x * bytesPerPx;
|
||||||
|
memcpy(dst, src, dr->w * bytesPerPx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
rectFill(d, ops, dr->x, dr->y, dr->w, dr->h, ctx->colors.desktop);
|
rectFill(d, ops, dr->x, dr->y, dr->w, dr->h, ctx->colors.desktop);
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Draw minimized window icons (under all windows)
|
// 2. Draw minimized window icons (under all windows)
|
||||||
wmDrawMinimizedIcons(d, ops, &ctx->colors, ws, dr);
|
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
|
// 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
|
// 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
|
// dvxInit
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1912,6 +2358,8 @@ void dvxShutdown(AppContextT *ctx) {
|
||||||
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
|
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(ctx->wallpaperBuf);
|
||||||
|
ctx->wallpaperBuf = NULL;
|
||||||
videoShutdown(&ctx->display);
|
videoShutdown(&ctx->display);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1922,6 +2370,10 @@ void dvxShutdown(AppContextT *ctx) {
|
||||||
|
|
||||||
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title) {
|
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title) {
|
||||||
wmSetTitle(win, &ctx->dirty, 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:
|
case SysMenuMinimizeE:
|
||||||
if (ctx->modalWindow != win) {
|
if (ctx->modalWindow != win) {
|
||||||
wmMinimize(&ctx->stack, &ctx->dirty, 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;
|
break;
|
||||||
|
|
||||||
|
|
@ -2259,10 +2715,11 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
||||||
if (ctx->lastIconClickId == iconWin->id &&
|
if (ctx->lastIconClickId == iconWin->id &&
|
||||||
(now - ctx->lastIconClickTime) < ctx->dblClickTicks) {
|
(now - ctx->lastIconClickTime) < ctx->dblClickTicks) {
|
||||||
// Double-click: restore minimized window
|
// Double-click: restore minimized window
|
||||||
// Dirty the entire icon strip area
|
// Dirty the entire icon area (may span multiple rows)
|
||||||
dirtyListAdd(&ctx->dirty, 0,
|
int32_t iconY;
|
||||||
ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING,
|
int32_t iconH;
|
||||||
ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
|
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
|
||||||
|
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
|
||||||
wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin);
|
wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin);
|
||||||
ctx->lastIconClickId = -1;
|
ctx->lastIconClickId = -1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2382,10 +2839,11 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
||||||
case HIT_MINIMIZE:
|
case HIT_MINIMIZE:
|
||||||
if (ctx->modalWindow != win) {
|
if (ctx->modalWindow != win) {
|
||||||
wmMinimize(&ctx->stack, &ctx->dirty, win);
|
wmMinimize(&ctx->stack, &ctx->dirty, win);
|
||||||
// Dirty the icon strip area so the new icon gets drawn
|
|
||||||
dirtyListAdd(&ctx->dirty, 0,
|
int32_t iconY;
|
||||||
ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING,
|
int32_t iconH;
|
||||||
ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
|
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
|
||||||
|
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
|
||||||
}
|
}
|
||||||
break;
|
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.
|
// GEOS's blue, giving DV/X its own identity.
|
||||||
|
|
||||||
static void initColorScheme(AppContextT *ctx) {
|
static void initColorScheme(AppContextT *ctx) {
|
||||||
DisplayT *d = &ctx->display;
|
// Load defaults into the RGB source array, then pack all at once
|
||||||
|
memcpy(ctx->colorRgb, sDefaultColors, sizeof(sDefaultColors));
|
||||||
// GEOS Ensemble Motif-style color scheme
|
dvxApplyColorScheme(ctx);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2748,7 +3188,8 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win)
|
||||||
//
|
//
|
||||||
// 1. Alt+Tab / Shift+Alt+Tab — window cycling (always works)
|
// 1. Alt+Tab / Shift+Alt+Tab — window cycling (always works)
|
||||||
// 2. Alt+F4 — close focused window
|
// 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)
|
// 4. Keyboard move/resize mode (arrow keys captured exclusively)
|
||||||
// 5. Alt+Space — system menu toggle
|
// 5. Alt+Space — system menu toggle
|
||||||
// 6. System menu keyboard navigation (arrows, enter, esc, accel)
|
// 6. System menu keyboard navigation (arrows, enter, esc, accel)
|
||||||
|
|
@ -2822,6 +3263,15 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
continue;
|
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
|
// F10 — activate menu bar
|
||||||
if (ascii == 0 && scancode == 0x44) {
|
if (ascii == 0 && scancode == 0x44) {
|
||||||
if (ctx->stack.focusedIdx >= 0) {
|
if (ctx->stack.focusedIdx >= 0) {
|
||||||
|
|
@ -3481,8 +3931,9 @@ static void refreshMinimizedIcons(AppContextT *ctx) {
|
||||||
|
|
||||||
if (!win->iconData && win->contentDirty) {
|
if (!win->iconData && win->contentDirty) {
|
||||||
if (count >= ctx->iconRefreshIdx) {
|
if (count >= ctx->iconRefreshIdx) {
|
||||||
int32_t ix = ICON_SPACING + iconIdx * (ICON_TOTAL_SIZE + ICON_SPACING);
|
int32_t ix;
|
||||||
int32_t iy = d->height - ICON_TOTAL_SIZE - ICON_SPACING;
|
int32_t iy;
|
||||||
|
wmMinimizedIconPos(d, iconIdx, &ix, &iy);
|
||||||
dirtyListAdd(&ctx->dirty, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE);
|
dirtyListAdd(&ctx->dirty, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE);
|
||||||
win->contentDirty = false;
|
win->contentDirty = false;
|
||||||
ctx->iconRefreshIdx = count + 1;
|
ctx->iconRefreshIdx = count + 1;
|
||||||
|
|
@ -3721,7 +4172,16 @@ static void updateTooltip(AppContextT *ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the widget under the cursor
|
// Check minimized icons first (they sit outside any window)
|
||||||
|
const char *tipText = NULL;
|
||||||
|
int32_t iconIdx = wmMinimizedIconHit(&ctx->stack, &ctx->display, mx, my);
|
||||||
|
|
||||||
|
if (iconIdx >= 0) {
|
||||||
|
tipText = ctx->stack.windows[iconIdx]->title;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise check widgets in the content area of a window
|
||||||
|
if (!tipText) {
|
||||||
int32_t hitPart;
|
int32_t hitPart;
|
||||||
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
|
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
|
||||||
|
|
||||||
|
|
@ -3758,7 +4218,6 @@ static void updateTooltip(AppContextT *ctx) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the inner child has a tooltip, use it; otherwise check if it's another container
|
|
||||||
if (inner->tooltip) {
|
if (inner->tooltip) {
|
||||||
hit = inner;
|
hit = inner;
|
||||||
break;
|
break;
|
||||||
|
|
@ -3777,10 +4236,13 @@ static void updateTooltip(AppContextT *ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the tooltip
|
tipText = hit->tooltip;
|
||||||
ctx->tooltipText = hit->tooltip;
|
}
|
||||||
|
|
||||||
int32_t tw = textWidth(&ctx->font, hit->tooltip) + TOOLTIP_PAD * 2;
|
// Show the tooltip
|
||||||
|
ctx->tooltipText = tipText;
|
||||||
|
|
||||||
|
int32_t tw = textWidth(&ctx->font, tipText) + TOOLTIP_PAD * 2;
|
||||||
int32_t th = ctx->font.charHeight + TOOLTIP_PAD * 2;
|
int32_t th = ctx->font.charHeight + TOOLTIP_PAD * 2;
|
||||||
|
|
||||||
// Position below and right of cursor
|
// 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 (*idleCallback)(void *ctx); // called instead of yield when non-NULL
|
||||||
void *idleCtx;
|
void *idleCtx;
|
||||||
WindowT *modalWindow; // if non-NULL, only this window receives input
|
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
|
// Tooltip state — tooltip appears after the mouse hovers over a widget
|
||||||
// with a tooltip string for a brief delay. Pre-computing W/H avoids
|
// with a tooltip string for a brief delay. Pre-computing W/H avoids
|
||||||
// re-measuring on every paint frame.
|
// re-measuring on every paint frame.
|
||||||
|
|
@ -95,6 +99,12 @@ typedef struct AppContextT {
|
||||||
// Mouse configuration (loaded from preferences)
|
// Mouse configuration (loaded from preferences)
|
||||||
int32_t wheelDirection; // 1 = normal, -1 = reversed
|
int32_t wheelDirection; // 1 = normal, -1 = reversed
|
||||||
clock_t dblClickTicks; // double-click speed in clock() ticks
|
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;
|
} AppContextT;
|
||||||
|
|
||||||
// Initialize the entire GUI stack: video mode, input devices, font,
|
// 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.
|
// 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);
|
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.
|
// Configure mouse behaviour. wheelDir: 1 = normal, -1 = reversed.
|
||||||
// dblClickMs: double-click speed in milliseconds (e.g. 500).
|
// dblClickMs: double-click speed in milliseconds (e.g. 500).
|
||||||
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
|
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
|
||||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold);
|
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
|
// Tear down the GUI stack in reverse order: destroy all windows, restore
|
||||||
// text mode, release input devices. Safe to call after a failed dvxInit().
|
// text mode, release input devices. Safe to call after a failed dvxInit().
|
||||||
void dvxShutdown(AppContextT *ctx);
|
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.
|
// Custom INI parser and writer. Stores entries as a dynamic array of
|
||||||
|
// section/key/value triples using stb_ds. Preserves insertion order
|
||||||
#include <stdlib.h>
|
// on save so the file remains human-readable.
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#include "dvxPrefs.h"
|
#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) {
|
void prefsFree(void) {
|
||||||
if (sIni) {
|
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
||||||
ini_free(sIni);
|
freeEntry(&sEntries[i]);
|
||||||
sIni = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(sEntries);
|
||||||
|
sEntries = NULL;
|
||||||
|
free(sFilePath);
|
||||||
|
sFilePath = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,7 +152,6 @@ bool prefsGetBool(const char *section, const char *key, bool defaultVal) {
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case-insensitive first character check covers true/yes/1 and false/no/0
|
|
||||||
char c = (char)tolower((unsigned char)val[0]);
|
char c = (char)tolower((unsigned char)val[0]);
|
||||||
|
|
||||||
if (c == 't' || c == 'y' || c == '1') {
|
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) {
|
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;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *val = ini_get(sIni, section, key);
|
return sEntries[idx].value;
|
||||||
|
|
||||||
return val ? val : defaultVal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -95,7 +210,231 @@ const char *prefsGetString(const char *section, const char *key, const char *def
|
||||||
bool prefsLoad(const char *filename) {
|
bool prefsLoad(const char *filename) {
|
||||||
prefsFree();
|
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
|
// Loads a configuration file at startup and provides typed accessors
|
||||||
// with caller-supplied defaults. The INI file is read-only at runtime;
|
// with caller-supplied defaults. Values can be modified at runtime
|
||||||
// values are queried by section + key. If the file is missing or a key
|
// and saved back to disk. If the file is missing or a key is absent,
|
||||||
// is absent, the default is returned silently.
|
// getters return the default silently.
|
||||||
//
|
|
||||||
// The underlying parser is rxi/ini (thirdparty/ini/src).
|
|
||||||
|
|
||||||
#ifndef DVX_PREFS_H
|
#ifndef DVX_PREFS_H
|
||||||
#define 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.
|
// Only one file may be loaded at a time; calling again frees the previous.
|
||||||
bool prefsLoad(const char *filename);
|
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);
|
void prefsFree(void);
|
||||||
|
|
||||||
// Retrieve a string value. Returns defaultVal if the section/key pair
|
// Retrieve a string value. Returns defaultVal if the key is not present.
|
||||||
// is not present. The returned pointer is valid until prefsFree().
|
// The returned pointer is valid until the key is modified or prefsFree().
|
||||||
const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
|
const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
|
||||||
|
|
||||||
// Retrieve an integer value. Returns defaultVal if the section/key pair
|
// Retrieve an integer value.
|
||||||
// is not present or cannot be parsed.
|
|
||||||
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal);
|
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal);
|
||||||
|
|
||||||
// Retrieve a boolean value. Recognises "true", "yes", "1" as true and
|
// Retrieve a boolean value. Recognises "true"/"yes"/"1" and "false"/"no"/"0".
|
||||||
// "false", "no", "0" as false (case-insensitive). Returns defaultVal
|
|
||||||
// for anything else or if the key is missing.
|
|
||||||
bool prefsGetBool(const char *section, const char *key, bool defaultVal);
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,30 @@ typedef struct {
|
||||||
uint32_t scrollbarTrough;
|
uint32_t scrollbarTrough;
|
||||||
} ColorSchemeT;
|
} 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
|
// 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 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 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 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 int32_t scrollbarThumbInfo(const ScrollbarT *sb, int32_t *thumbPos, int32_t *thumbSize);
|
||||||
static void wmMinWindowSize(const WindowT *win, int32_t *minW, int32_t *minH);
|
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
|
// 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.
|
// out left to right along the bottom of the screen, wrapping to the next
|
||||||
// This mirrors the DESQview/X and Windows 3.x convention of showing minimized
|
// row upward when the current row is full. This mirrors the Windows 3.x
|
||||||
// windows as icons at the bottom of the desktop.
|
// 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),
|
// The index is the ordinal among minimized windows (not the stack index),
|
||||||
// so icon positions stay packed when non-minimized windows exist between
|
// so icon positions stay packed when non-minimized windows exist between
|
||||||
// minimized ones in the stack. This avoids gaps in the icon strip.
|
// 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) {
|
void wmMinimizedIconPos(const DisplayT *d, int32_t index, int32_t *x, int32_t *y) {
|
||||||
*x = ICON_SPACING + index * (ICON_TOTAL_SIZE + ICON_SPACING);
|
int32_t cellW = ICON_TOTAL_SIZE + ICON_SPACING;
|
||||||
*y = d->height - 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 ix;
|
||||||
int32_t iy;
|
int32_t iy;
|
||||||
minimizedIconPos(d, iconIdx, &ix, &iy);
|
wmMinimizedIconPos(d, iconIdx, &ix, &iy);
|
||||||
iconIdx++;
|
iconIdx++;
|
||||||
|
|
||||||
// Check if icon intersects clip rect
|
// 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 ix;
|
||||||
int32_t iy;
|
int32_t iy;
|
||||||
minimizedIconPos(d, iconIdx, &ix, &iy);
|
wmMinimizedIconPos(d, iconIdx, &ix, &iy);
|
||||||
iconIdx++;
|
iconIdx++;
|
||||||
|
|
||||||
if (mx >= ix && mx < ix + ICON_TOTAL_SIZE &&
|
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
|
// Returns stack index of the minimized window, or -1
|
||||||
int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d, int32_t mx, int32_t my);
|
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
|
// Restore a maximized window to its pre-maximize geometry
|
||||||
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
|
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.
|
// describing why it's invalid. Used by the file dialog's save-as validation.
|
||||||
const char *platformValidateFilename(const char *name);
|
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
|
// Change the working directory, including drive letter on DOS. Standard
|
||||||
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
|
// chdir() does not switch drives under DJGPP; this wrapper calls setdisk()
|
||||||
// first when the path contains a drive prefix (e.g. "A:\DVX").
|
// first when the path contains a drive prefix (e.g. "A:\DVX").
|
||||||
void platformChdir(const char *path);
|
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
|
// Return a pointer to the last directory separator in path, or NULL if
|
||||||
// none is found. On DOS this checks both '/' and '\\' since DJGPP
|
// none is found. On DOS this checks both '/' and '\\' since DJGPP
|
||||||
// accepts either. On other platforms only '/' is recognised.
|
// accepts either. On other platforms only '/' is recognised.
|
||||||
char *platformPathDirEnd(const char *path);
|
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
|
#endif // DVX_PLATFORM_H
|
||||||
|
|
|
||||||
|
|
@ -1032,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
|
// platformInit
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1114,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
|
// platformMouseInit
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1705,6 +1753,21 @@ void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t cou
|
||||||
// Also frees the backbuffer, palette, and disables near pointers
|
// Also frees the backbuffer, palette, and disables near pointers
|
||||||
// (re-enables DJGPP's segment limit checking for safety).
|
// (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) {
|
void platformVideoShutdown(DisplayT *d) {
|
||||||
// INT 10h function 00h, mode 03h = 80x25 color text
|
// INT 10h function 00h, mode 03h = 80x25 color text
|
||||||
__dpmi_regs r;
|
__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
|
OBJDIR = ../obj/dvxshell
|
||||||
BINDIR = ../bin
|
BINDIR = ../bin
|
||||||
CONFIGDIR = ../bin/config
|
CONFIGDIR = ../bin/config
|
||||||
|
THEMEDIR = ../bin/config/themes
|
||||||
LIBDIR = ../lib
|
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))
|
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||||
TARGET = $(BINDIR)/dvx.exe
|
TARGET = $(BINDIR)/dvx.exe
|
||||||
|
|
||||||
.PHONY: all clean libs
|
.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:
|
libs:
|
||||||
$(MAKE) -C ../dvx
|
$(MAKE) -C ../dvx
|
||||||
|
|
@ -46,11 +49,18 @@ $(BINDIR):
|
||||||
$(CONFIGDIR):
|
$(CONFIGDIR):
|
||||||
mkdir -p $(CONFIGDIR)
|
mkdir -p $(CONFIGDIR)
|
||||||
|
|
||||||
|
$(THEMEDIR):
|
||||||
|
mkdir -p $(THEMEDIR)
|
||||||
|
|
||||||
|
$(THEMEDIR)/%.thm: ../themes/%.thm | $(THEMEDIR)
|
||||||
|
sed 's/$$/\r/' $< > $@
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
$(OBJDIR)/shellMain.o: shellMain.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h
|
$(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)/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)/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)/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:
|
clean:
|
||||||
rm -f $(OBJS) $(TARGET) $(BINDIR)/dvx.map $(BINDIR)/dvx.log
|
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"
|
#define SHELL_DESKTOP_APP "apps/progman/progman.app"
|
||||||
|
|
||||||
// Register a callback for app state changes (load, reap, crash).
|
// Register a callback for app state changes (load, reap, crash).
|
||||||
// The desktop app (Program Manager) calls this during appMain to receive
|
// Apps call this during appMain to receive notifications when app state
|
||||||
// notifications so it can refresh its task list / window list display.
|
// changes (load, reap, crash). Multiple callbacks are supported.
|
||||||
// Only one callback is supported — the desktop is always loaded first
|
|
||||||
// and is the only consumer.
|
|
||||||
void shellRegisterDesktopUpdate(void (*updateFn)(void));
|
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).
|
// Notify the desktop app that app state has changed (load, reap, crash).
|
||||||
void shellDesktopUpdate(void);
|
void shellDesktopUpdate(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,23 +29,43 @@
|
||||||
|
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "shellInfo.h"
|
#include "shellInfo.h"
|
||||||
|
#include "shellTaskMgr.h"
|
||||||
#include "dvxApp.h"
|
#include "dvxApp.h"
|
||||||
#include "dvxDialog.h"
|
#include "dvxDialog.h"
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
#include "dvxDraw.h"
|
#include "dvxDraw.h"
|
||||||
#include "dvxPrefs.h"
|
#include "dvxPrefs.h"
|
||||||
|
#include "platform/dvxPlatform.h"
|
||||||
#include "dvxVideo.h"
|
#include "dvxVideo.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "taskswitch.h"
|
#include "taskswitch.h"
|
||||||
|
|
||||||
#include <sys/dxe.h>
|
#include <sys/dxe.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <strings.h>
|
||||||
#include <math.h>
|
|
||||||
#include <time.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
|
// Prototypes
|
||||||
|
|
@ -144,14 +164,41 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
|
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
|
||||||
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
|
{ "_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
|
// dvxPrefs.h — preferences
|
||||||
DXE_EXPORT(prefsGetBool)
|
DXE_EXPORT(prefsGetBool)
|
||||||
DXE_EXPORT(prefsGetInt)
|
DXE_EXPORT(prefsGetInt)
|
||||||
DXE_EXPORT(prefsGetString)
|
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
|
// dvxApp.h — direct exports
|
||||||
|
DXE_EXPORT(dvxApplyColorScheme)
|
||||||
|
DXE_EXPORT(dvxChangeVideoMode)
|
||||||
|
DXE_EXPORT(dvxColorName)
|
||||||
|
DXE_EXPORT(dvxGetColor)
|
||||||
DXE_EXPORT(dvxInit)
|
DXE_EXPORT(dvxInit)
|
||||||
|
DXE_EXPORT(dvxLoadTheme)
|
||||||
|
DXE_EXPORT(dvxResetColorScheme)
|
||||||
|
DXE_EXPORT(dvxSaveTheme)
|
||||||
|
DXE_EXPORT(dvxSetColor)
|
||||||
DXE_EXPORT(dvxSetMouseConfig)
|
DXE_EXPORT(dvxSetMouseConfig)
|
||||||
|
DXE_EXPORT(dvxSetWallpaper)
|
||||||
DXE_EXPORT(dvxShutdown)
|
DXE_EXPORT(dvxShutdown)
|
||||||
DXE_EXPORT(dvxUpdate)
|
DXE_EXPORT(dvxUpdate)
|
||||||
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
|
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
|
||||||
|
|
@ -202,6 +249,8 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
|
|
||||||
// dvxVideo.h
|
// dvxVideo.h
|
||||||
DXE_EXPORT(packColor)
|
DXE_EXPORT(packColor)
|
||||||
|
DXE_EXPORT(resetClipRect)
|
||||||
|
DXE_EXPORT(setClipRect)
|
||||||
|
|
||||||
// dvxWm.h
|
// dvxWm.h
|
||||||
DXE_EXPORT(wmAddMenuBar)
|
DXE_EXPORT(wmAddMenuBar)
|
||||||
|
|
@ -213,6 +262,8 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(wmAddSubMenu)
|
DXE_EXPORT(wmAddSubMenu)
|
||||||
DXE_EXPORT(wmAddVScrollbar)
|
DXE_EXPORT(wmAddVScrollbar)
|
||||||
DXE_EXPORT(wmAddHScrollbar)
|
DXE_EXPORT(wmAddHScrollbar)
|
||||||
|
DXE_EXPORT(wmMinimizedIconPos)
|
||||||
|
DXE_EXPORT(wmMinimizedIconRect)
|
||||||
DXE_EXPORT(wmSetTitle)
|
DXE_EXPORT(wmSetTitle)
|
||||||
DXE_EXPORT(wmSetIcon)
|
DXE_EXPORT(wmSetIcon)
|
||||||
DXE_EXPORT(wmCreateMenu)
|
DXE_EXPORT(wmCreateMenu)
|
||||||
|
|
@ -389,9 +440,17 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
// tsCreate/tsKill/etc. are NOT exported because apps should not
|
// tsCreate/tsKill/etc. are NOT exported because apps should not
|
||||||
// manipulate the task system directly — the shell manages task
|
// manipulate the task system directly — the shell manages task
|
||||||
// lifecycle through shellLoadApp/shellForceKillApp.
|
// lifecycle through shellLoadApp/shellForceKillApp.
|
||||||
DXE_EXPORT(tsYield)
|
|
||||||
DXE_EXPORT(tsCurrentId)
|
|
||||||
DXE_EXPORT(tsActiveCount)
|
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
|
// dvxWm.h — direct window management
|
||||||
DXE_EXPORT(wmRaiseWindow)
|
DXE_EXPORT(wmRaiseWindow)
|
||||||
|
|
@ -402,80 +461,222 @@ DXE_EXPORT_TABLE(shellExportTable)
|
||||||
DXE_EXPORT(shellLog)
|
DXE_EXPORT(shellLog)
|
||||||
DXE_EXPORT(shellLoadApp)
|
DXE_EXPORT(shellLoadApp)
|
||||||
DXE_EXPORT(shellGetApp)
|
DXE_EXPORT(shellGetApp)
|
||||||
|
DXE_EXPORT(shellTaskMgrOpen)
|
||||||
DXE_EXPORT(shellForceKillApp)
|
DXE_EXPORT(shellForceKillApp)
|
||||||
DXE_EXPORT(shellRunningAppCount)
|
DXE_EXPORT(shellRunningAppCount)
|
||||||
DXE_EXPORT(shellRegisterDesktopUpdate)
|
DXE_EXPORT(shellRegisterDesktopUpdate)
|
||||||
|
DXE_EXPORT(shellUnregisterDesktopUpdate)
|
||||||
DXE_EXPORT(shellGetSystemInfo)
|
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
|
// libc / libm exports. DXE3 modules are relocatable objects, not
|
||||||
// re-exported here so the DXE3 loader can resolve the reference at
|
// fully linked executables. Every C library function a DXE calls
|
||||||
// dlopen time. Forgetting an entry produces a cryptic "unresolved
|
// must appear here so the loader can resolve it at dlopen time.
|
||||||
// symbol" error at load time — no lazy binding fallback exists.
|
// This is intentionally comprehensive to avoid "unresolved symbol"
|
||||||
|
// surprises when apps use standard functions.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
// libc — memory
|
// --- memory ---
|
||||||
DXE_EXPORT(malloc)
|
|
||||||
DXE_EXPORT(free)
|
|
||||||
DXE_EXPORT(calloc)
|
DXE_EXPORT(calloc)
|
||||||
|
DXE_EXPORT(free)
|
||||||
|
DXE_EXPORT(malloc)
|
||||||
DXE_EXPORT(realloc)
|
DXE_EXPORT(realloc)
|
||||||
|
|
||||||
// libc — string
|
// --- string / memory ops ---
|
||||||
DXE_EXPORT(memcpy)
|
DXE_EXPORT(memchr)
|
||||||
DXE_EXPORT(memset)
|
|
||||||
DXE_EXPORT(memmove)
|
|
||||||
DXE_EXPORT(memcmp)
|
DXE_EXPORT(memcmp)
|
||||||
DXE_EXPORT(strlen)
|
DXE_EXPORT(memcpy)
|
||||||
DXE_EXPORT(strcmp)
|
DXE_EXPORT(memmove)
|
||||||
DXE_EXPORT(strncmp)
|
DXE_EXPORT(memset)
|
||||||
DXE_EXPORT(strcpy)
|
DXE_EXPORT(strcasecmp)
|
||||||
DXE_EXPORT(strncpy)
|
|
||||||
DXE_EXPORT(strcat)
|
DXE_EXPORT(strcat)
|
||||||
DXE_EXPORT(strncat)
|
|
||||||
DXE_EXPORT(strchr)
|
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(strrchr)
|
||||||
|
DXE_EXPORT(strspn)
|
||||||
DXE_EXPORT(strstr)
|
DXE_EXPORT(strstr)
|
||||||
DXE_EXPORT(strtol)
|
DXE_EXPORT(strtok)
|
||||||
|
|
||||||
// libc — I/O
|
// --- ctype ---
|
||||||
DXE_EXPORT(printf)
|
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(fprintf)
|
||||||
DXE_EXPORT(sprintf)
|
DXE_EXPORT(fputs)
|
||||||
DXE_EXPORT(snprintf)
|
DXE_EXPORT(fscanf)
|
||||||
|
DXE_EXPORT(printf)
|
||||||
DXE_EXPORT(puts)
|
DXE_EXPORT(puts)
|
||||||
DXE_EXPORT(fopen)
|
DXE_EXPORT(snprintf)
|
||||||
DXE_EXPORT(fclose)
|
DXE_EXPORT(sprintf)
|
||||||
DXE_EXPORT(fread)
|
DXE_EXPORT(sscanf)
|
||||||
DXE_EXPORT(fwrite)
|
DXE_EXPORT(vfprintf)
|
||||||
|
DXE_EXPORT(vprintf)
|
||||||
|
DXE_EXPORT(vsnprintf)
|
||||||
|
DXE_EXPORT(vsprintf)
|
||||||
|
|
||||||
|
// --- character I/O ---
|
||||||
|
DXE_EXPORT(fgetc)
|
||||||
DXE_EXPORT(fgets)
|
DXE_EXPORT(fgets)
|
||||||
DXE_EXPORT(fseek)
|
DXE_EXPORT(fputc)
|
||||||
DXE_EXPORT(ftell)
|
DXE_EXPORT(getc)
|
||||||
|
DXE_EXPORT(putc)
|
||||||
|
DXE_EXPORT(putchar)
|
||||||
|
DXE_EXPORT(ungetc)
|
||||||
|
|
||||||
|
// --- file I/O ---
|
||||||
|
DXE_EXPORT(fclose)
|
||||||
DXE_EXPORT(feof)
|
DXE_EXPORT(feof)
|
||||||
DXE_EXPORT(ferror)
|
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
|
// --- directory ---
|
||||||
DXE_EXPORT(sin)
|
DXE_EXPORT(closedir)
|
||||||
DXE_EXPORT(cos)
|
DXE_EXPORT(mkdir)
|
||||||
DXE_EXPORT(sqrt)
|
|
||||||
|
|
||||||
// libc — time
|
|
||||||
DXE_EXPORT(clock)
|
|
||||||
DXE_EXPORT(time)
|
|
||||||
DXE_EXPORT(localtime)
|
|
||||||
|
|
||||||
// libc — directory
|
|
||||||
DXE_EXPORT(opendir)
|
DXE_EXPORT(opendir)
|
||||||
DXE_EXPORT(readdir)
|
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(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)
|
DXE_EXPORT(qsort)
|
||||||
|
|
||||||
|
// --- random ---
|
||||||
DXE_EXPORT(rand)
|
DXE_EXPORT(rand)
|
||||||
DXE_EXPORT(srand)
|
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
|
DXE_EXPORT_END
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,9 +26,11 @@
|
||||||
|
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "shellInfo.h"
|
#include "shellInfo.h"
|
||||||
|
#include "shellTaskMgr.h"
|
||||||
#include "dvxDialog.h"
|
#include "dvxDialog.h"
|
||||||
#include "dvxPrefs.h"
|
#include "dvxPrefs.h"
|
||||||
#include "platform/dvxPlatform.h"
|
#include "platform/dvxPlatform.h"
|
||||||
|
#include "thirdparty/stb_ds.h"
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
|
|
@ -51,7 +53,9 @@ static jmp_buf sCrashJmp;
|
||||||
// the recovery code which signal fired (for logging/diagnostics).
|
// the recovery code which signal fired (for logging/diagnostics).
|
||||||
static volatile int sCrashSignal = 0;
|
static volatile int sCrashSignal = 0;
|
||||||
static FILE *sLogFile = NULL;
|
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
|
// Prototypes
|
||||||
|
|
@ -93,12 +97,31 @@ static void crashHandler(int sig) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void shellDesktopUpdate(void) {
|
void shellDesktopUpdate(void) {
|
||||||
if (sDesktopUpdateFn) {
|
for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) {
|
||||||
sDesktopUpdateFn();
|
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
|
// idleYield — called when no dirty rects need compositing
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -213,7 +236,21 @@ void shellLog(const char *fmt, ...) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void shellRegisterDesktopUpdate(void (*updateFn)(void)) {
|
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);
|
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal);
|
||||||
shellLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s", wheelStr, (long)dblClick, accelStr);
|
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) {
|
if (result != 0) {
|
||||||
|
|
@ -324,6 +397,10 @@ int main(int argc, char *argv[]) {
|
||||||
// app tasks CPU time during quiet periods.
|
// app tasks CPU time during quiet periods.
|
||||||
sCtx.idleCallback = idleYield;
|
sCtx.idleCallback = idleYield;
|
||||||
sCtx.idleCtx = &sCtx;
|
sCtx.idleCtx = &sCtx;
|
||||||
|
sCtx.onCtrlEsc = ctrlEscHandler;
|
||||||
|
sCtx.ctrlEscCtx = &sCtx;
|
||||||
|
sCtx.onTitleChange = titleChangeHandler;
|
||||||
|
sCtx.titleChangeCtx = &sCtx;
|
||||||
|
|
||||||
// Load the desktop app
|
// Load the desktop app
|
||||||
int32_t desktopId = shellLoadApp(&sCtx, SHELL_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