// 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 #include #include #include #include #include // ============================================================ // 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; }