594 lines
19 KiB
C
594 lines
19 KiB
C
// progman.c -- Program Manager application for DVX Shell
|
|
//
|
|
// Displays a grid of available apps from the apps/ directory.
|
|
// Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc).
|
|
//
|
|
// DXE App Contract:
|
|
// This is a callback-only DXE app (hasMainLoop = false). It exports two
|
|
// symbols: appDescriptor (metadata) and appMain (entry point). The shell
|
|
// calls appMain once; we create windows, register callbacks, and return 0.
|
|
// From that point on, the shell's event loop drives everything through
|
|
// our window callbacks (onClose, onMenu, widget onClick, etc.).
|
|
//
|
|
// Because we have no main loop, we don't need a dedicated task stack.
|
|
// The shell runs our callbacks in task 0 during dvxUpdate().
|
|
//
|
|
// Progman is special: it's the desktop app. It calls
|
|
// shellRegisterDesktopUpdate() so the shell notifies us whenever an app
|
|
// is loaded, reaped, or crashes, keeping our status bar and Task Manager
|
|
// list current without polling.
|
|
|
|
#include "dvxApp.h"
|
|
#include "dvxDlg.h"
|
|
#include "dvxPrefs.h"
|
|
#include "dvxWgt.h"
|
|
#include "box/box.h"
|
|
#include "button/button.h"
|
|
#include "label/label.h"
|
|
#include "statusBar/statBar.h"
|
|
#include "textInput/textInpt.h"
|
|
#include "dvxWm.h"
|
|
#include "dvxPlat.h"
|
|
#include "shellApp.h"
|
|
#include "shellInf.h"
|
|
#include "dvxRes.h"
|
|
#include "imageButton/imgBtn.h"
|
|
#include "scrollPane/scrlPane.h"
|
|
#include "wrapBox/wrapBox.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include "dvxMem.h"
|
|
#include "stb_ds_wrap.h"
|
|
|
|
// ============================================================
|
|
// Constants
|
|
// ============================================================
|
|
|
|
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
|
|
#define MAX_PATH_LEN 260
|
|
// Grid layout for app buttons: 4 columns, rows created dynamically
|
|
#define PM_TOOLTIP_LEN 128
|
|
#define PM_BTN_W 100
|
|
#define PM_BTN_H 24
|
|
#define PM_CELL_W 80
|
|
#define PM_CELL_H 64
|
|
#define PM_WIN_W 440
|
|
#define PM_WIN_H 340
|
|
#define PM_GRID_SPACING 8
|
|
#define PM_SYSINFO_WIN_W 400
|
|
#define PM_SYSINFO_WIN_H 360
|
|
|
|
// Menu command IDs
|
|
#define CMD_RUN 100
|
|
#define CMD_EXIT 101
|
|
#define CMD_CASCADE 200
|
|
#define CMD_TILE 201
|
|
#define CMD_TILE_H 202
|
|
#define CMD_TILE_V 203
|
|
#define CMD_MIN_ON_RUN 104
|
|
#define CMD_RESTORE_ALONE 105
|
|
#define CMD_ABOUT 300
|
|
#define CMD_TASK_MGR 301
|
|
#define CMD_SYSINFO 302
|
|
#define CMD_HELP 303
|
|
|
|
// ============================================================
|
|
// Module state
|
|
// ============================================================
|
|
|
|
// Each discovered .app file in the apps/ directory tree
|
|
typedef struct {
|
|
char name[SHELL_APP_NAME_MAX]; // short display name (from "name" resource or filename)
|
|
char tooltip[PM_TOOLTIP_LEN]; // description for tooltip (from "description" resource)
|
|
char path[MAX_PATH_LEN]; // full path
|
|
uint8_t *iconData; // 32x32 icon in display format (NULL if none)
|
|
int32_t iconW;
|
|
int32_t iconH;
|
|
int32_t iconPitch;
|
|
} AppEntryT;
|
|
|
|
// Module-level statics (s prefix). DXE apps use file-scoped statics because
|
|
// each DXE is a separate shared object with its own data segment. No risk
|
|
// of collision between apps even though the names look global.
|
|
static DxeAppContextT *sCtx = NULL;
|
|
static AppContextT *sAc = NULL;
|
|
static WindowT *sPmWindow = NULL;
|
|
static WidgetT *sStatusLabel = NULL;
|
|
static PrefsHandleT *sPrefs = NULL;
|
|
static bool sMinOnRun = false;
|
|
static bool sRestoreAlone = false;
|
|
static AppEntryT *sAppFiles = NULL; // stb_ds dynamic array
|
|
static int32_t sAppCount = 0;
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
int32_t appMain(DxeAppContextT *ctx);
|
|
static void buildPmWindow(void);
|
|
static void desktopUpdate(void);
|
|
static void onAppButtonClick(WidgetT *w);
|
|
static void onPmClose(WindowT *win);
|
|
static void onPmMenu(WindowT *win, int32_t menuId);
|
|
static void scanAppsDir(void);
|
|
static void scanAppsDirRecurse(const char *dirPath);
|
|
static void showAboutDialog(void);
|
|
static void showSystemInfo(void);
|
|
static void updateStatusText(void);
|
|
|
|
|
|
// ============================================================
|
|
// App descriptor
|
|
// ============================================================
|
|
|
|
// The shell reads this exported symbol to determine how to manage the app.
|
|
// hasMainLoop = false means the shell won't create a dedicated task; our
|
|
// appMain runs to completion and all subsequent work happens via callbacks.
|
|
// stackSize = 0 means "use the shell default" (irrelevant for callback apps).
|
|
AppDescriptorT appDescriptor = {
|
|
.name = "Program Manager",
|
|
.hasMainLoop = false,
|
|
.stackSize = SHELL_STACK_DEFAULT,
|
|
.priority = TS_PRIORITY_NORMAL
|
|
};
|
|
|
|
// ============================================================
|
|
// Static functions (alphabetical)
|
|
// ============================================================
|
|
|
|
// Build the main Program Manager window with app buttons, menus, and status bar.
|
|
// Window is centered horizontally and placed in the upper quarter vertically
|
|
// so spawned app windows don't hide behind it.
|
|
static void buildPmWindow(void) {
|
|
int32_t screenW = sAc->display.width;
|
|
int32_t screenH = sAc->display.height;
|
|
int32_t winW = PM_WIN_W;
|
|
int32_t winH = PM_WIN_H;
|
|
int32_t winX = (screenW - winW) / 2;
|
|
int32_t winY = (screenH - winH) / 4;
|
|
|
|
sPmWindow = dvxCreateWindow(sAc, "Program Manager", winX, winY, winW, winH, true);
|
|
|
|
if (!sPmWindow) {
|
|
return;
|
|
}
|
|
|
|
sPmWindow->onClose = onPmClose;
|
|
sPmWindow->onMenu = onPmMenu;
|
|
|
|
// Menu bar
|
|
MenuBarT *menuBar = wmAddMenuBar(sPmWindow);
|
|
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
|
|
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
|
|
wmAddMenuSeparator(fileMenu);
|
|
wmAddMenuItem(fileMenu, "E&xit DVX", CMD_EXIT);
|
|
|
|
MenuT *optMenu = wmAddMenu(menuBar, "&Options");
|
|
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun);
|
|
wmAddMenuCheckItem(optMenu, "&Restore when Alone", CMD_RESTORE_ALONE, sRestoreAlone);
|
|
|
|
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
|
|
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
|
|
wmAddMenuItem(windowMenu, "&Tile", CMD_TILE);
|
|
wmAddMenuItem(windowMenu, "Tile &Horizontally", CMD_TILE_H);
|
|
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
|
|
|
|
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
|
|
wmAddMenuItem(helpMenu, "&DVX Help\tF1", CMD_HELP);
|
|
wmAddMenuSeparator(helpMenu);
|
|
wmAddMenuItem(helpMenu, "&About DVX...", CMD_ABOUT);
|
|
wmAddMenuItem(helpMenu, "&System Information...", CMD_SYSINFO);
|
|
wmAddMenuSeparator(helpMenu);
|
|
wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR);
|
|
|
|
// wgtInitWindow creates the root VBox widget that the window's content
|
|
// area maps to. All child widgets are added to this root.
|
|
WidgetT *root = wgtInitWindow(sAc, sPmWindow);
|
|
|
|
// App button grid in a labeled frame. weight=100 tells the layout engine
|
|
// this frame should consume all available vertical space (flex weight).
|
|
WidgetT *appFrame = wgtFrame(root, "Applications");
|
|
appFrame->weight = 100;
|
|
|
|
if (sAppCount == 0) {
|
|
wgtLabel(appFrame, "(No applications found in apps/ directory)");
|
|
} else {
|
|
// ScrollPane provides scrollbars when icons overflow.
|
|
// WrapBox inside flows cells left-to-right, wrapping to
|
|
// the next row when the window width is exceeded.
|
|
WidgetT *scroll = wgtScrollPane(appFrame);
|
|
scroll->weight = 100;
|
|
wgtScrollPaneSetNoBorder(scroll, true);
|
|
|
|
WidgetT *wrap = wgtWrapBox(scroll);
|
|
wrap->weight = 100;
|
|
wrap->spacing = wgtPixels(PM_GRID_SPACING);
|
|
wrap->align = AlignCenterE;
|
|
|
|
for (int32_t i = 0; i < sAppCount; i++) {
|
|
WidgetT *cell = wgtVBox(wrap);
|
|
cell->align = AlignCenterE;
|
|
cell->minW = wgtPixels(PM_CELL_W);
|
|
cell->minH = wgtPixels(PM_CELL_H);
|
|
|
|
WidgetT *btn = NULL;
|
|
|
|
if (sAppFiles[i].iconData) {
|
|
int32_t dataSize = sAppFiles[i].iconPitch * sAppFiles[i].iconH;
|
|
uint8_t *iconCopy = (uint8_t *)malloc(dataSize);
|
|
|
|
if (iconCopy) {
|
|
memcpy(iconCopy, sAppFiles[i].iconData, dataSize);
|
|
btn = wgtImageButton(cell, iconCopy, sAppFiles[i].iconW, sAppFiles[i].iconH, sAppFiles[i].iconPitch);
|
|
btn->maxW = wgtPixels(sAppFiles[i].iconW + 4);
|
|
btn->maxH = wgtPixels(sAppFiles[i].iconH + 4);
|
|
}
|
|
}
|
|
|
|
if (!btn) {
|
|
btn = wgtButton(cell, sAppFiles[i].name);
|
|
btn->prefW = wgtPixels(PM_BTN_W);
|
|
btn->prefH = wgtPixels(PM_BTN_H);
|
|
}
|
|
|
|
btn->userData = &sAppFiles[i];
|
|
btn->onClick = onAppButtonClick;
|
|
|
|
if (sAppFiles[i].tooltip[0]) {
|
|
wgtSetTooltip(btn, sAppFiles[i].tooltip);
|
|
}
|
|
|
|
WidgetT *label = wgtLabel(cell, sAppFiles[i].name);
|
|
label->maxW = wgtPixels(PM_CELL_W);
|
|
wgtLabelSetAlign(label, AlignCenterE);
|
|
}
|
|
}
|
|
|
|
// Status bar at bottom; weight=100 on the label makes it fill the bar
|
|
// width so text can be left-aligned naturally.
|
|
WidgetT *statusBar = wgtStatusBar(root);
|
|
sStatusLabel = wgtLabel(statusBar, "");
|
|
sStatusLabel->weight = 100;
|
|
updateStatusText();
|
|
|
|
}
|
|
|
|
|
|
// Shell calls this via shellRegisterDesktopUpdate whenever an app is loaded,
|
|
// reaped, or crashes. We refresh the running count in the status bar.
|
|
// (Task Manager refresh is handled by the shell's shellDesktopUpdate.)
|
|
static void desktopUpdate(void) {
|
|
updateStatusText();
|
|
|
|
// Auto-restore if we're the only running app and minimized
|
|
if (sRestoreAlone && sPmWindow && sPmWindow->minimized) {
|
|
if (shellRunningAppCount() <= 1) {
|
|
wmRestoreMinimized(&sAc->stack, &sAc->dirty, &sAc->display, sPmWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Widget click handler for app grid buttons. userData was set to the
|
|
// AppEntryT pointer during window construction, giving us the .app path.
|
|
static void onAppButtonClick(WidgetT *w) {
|
|
AppEntryT *entry = (AppEntryT *)w->userData;
|
|
|
|
if (!entry) {
|
|
return;
|
|
}
|
|
|
|
shellLoadApp(sAc, entry->path);
|
|
updateStatusText();
|
|
|
|
if (sMinOnRun && sPmWindow) {
|
|
dvxMinimizeWindow(sAc, sPmWindow);
|
|
}
|
|
}
|
|
|
|
|
|
// Closing the Program Manager is equivalent to shutting down the entire shell.
|
|
// dvxQuit() signals the main event loop to exit, which triggers
|
|
// shellTerminateAllApps() to gracefully tear down all loaded DXEs.
|
|
static void onPmClose(WindowT *win) {
|
|
(void)win;
|
|
int32_t result = dvxMessageBox(sAc, "Exit DVX", "Are you sure you want to exit DVX?", MB_YESNO | MB_ICONQUESTION);
|
|
|
|
if (result == ID_YES) {
|
|
dvxQuit(sAc);
|
|
}
|
|
}
|
|
|
|
|
|
static void onPmMenu(WindowT *win, int32_t menuId) {
|
|
(void)win;
|
|
|
|
switch (menuId) {
|
|
case CMD_RUN:
|
|
{
|
|
FileFilterT filters[] = {
|
|
{ "Applications (*.app)", "*.app" },
|
|
{ "All Files (*.*)", "*.*" }
|
|
};
|
|
char path[MAX_PATH_LEN];
|
|
|
|
if (dvxFileDialog(sAc, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
|
|
shellLoadApp(sAc, path);
|
|
updateStatusText();
|
|
|
|
if (sMinOnRun && sPmWindow) {
|
|
dvxMinimizeWindow(sAc, sPmWindow);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case CMD_EXIT:
|
|
onPmClose(sPmWindow);
|
|
break;
|
|
|
|
case CMD_CASCADE:
|
|
dvxCascadeWindows(sAc);
|
|
break;
|
|
|
|
case CMD_TILE:
|
|
dvxTileWindows(sAc);
|
|
break;
|
|
|
|
case CMD_TILE_H:
|
|
dvxTileWindowsH(sAc);
|
|
break;
|
|
|
|
case CMD_TILE_V:
|
|
dvxTileWindowsV(sAc);
|
|
break;
|
|
|
|
case CMD_MIN_ON_RUN:
|
|
sMinOnRun = !sMinOnRun;
|
|
shellEnsureConfigDir(sCtx);
|
|
prefsSetBool(sPrefs, "options", "minimizeOnRun", sMinOnRun);
|
|
prefsSave(sPrefs);
|
|
break;
|
|
|
|
case CMD_RESTORE_ALONE:
|
|
sRestoreAlone = !sRestoreAlone;
|
|
shellEnsureConfigDir(sCtx);
|
|
prefsSetBool(sPrefs, "options", "restoreAlone", sRestoreAlone);
|
|
prefsSave(sPrefs);
|
|
break;
|
|
|
|
case CMD_HELP: {
|
|
char viewerPath[DVX_MAX_PATH];
|
|
char sysHlp[DVX_MAX_PATH];
|
|
snprintf(viewerPath, sizeof(viewerPath), "APPS%cKPUNCH%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP, DVX_PATH_SEP);
|
|
snprintf(sysHlp, sizeof(sysHlp), "%s%c%s", sCtx->appDir, DVX_PATH_SEP, "dvxhelp.hlp");
|
|
shellLoadAppWithArgs(sAc, viewerPath, sysHlp);
|
|
break;
|
|
}
|
|
|
|
case CMD_ABOUT:
|
|
showAboutDialog();
|
|
break;
|
|
|
|
case CMD_SYSINFO:
|
|
showSystemInfo();
|
|
break;
|
|
|
|
case CMD_TASK_MGR:
|
|
if (shellCtrlEscFn) {
|
|
shellCtrlEscFn(sAc);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Top-level scan entry point. Recursively walks apps/ looking for .app files.
|
|
// The apps/ path is relative to the working directory, which the shell sets
|
|
// to the root of the DVX install before loading any apps.
|
|
static void scanAppsDir(void) {
|
|
// Free icons from previous scan
|
|
for (int32_t i = 0; i < sAppCount; i++) {
|
|
free(sAppFiles[i].iconData);
|
|
}
|
|
|
|
arrfree(sAppFiles);
|
|
sAppFiles = NULL;
|
|
sAppCount = 0;
|
|
scanAppsDirRecurse("apps");
|
|
dvxLog("Progman: found %ld app(s)", (long)sAppCount);
|
|
}
|
|
|
|
|
|
// Recursive directory walker. Subdirectories under apps/ allow organizing
|
|
// apps (e.g., apps/games/, apps/tools/). Each .app file is a DXE3 shared
|
|
// object that the shell can dlopen(). We skip progman.app to avoid listing
|
|
// ourselves in the launcher grid.
|
|
static void scanAppsDirRecurse(const char *dirPath) {
|
|
DIR *dir = opendir(dirPath);
|
|
|
|
if (!dir) {
|
|
if (sAppCount == 0) {
|
|
dvxLog("Progman: %s directory not found", dirPath);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
struct dirent *ent;
|
|
|
|
while ((ent = readdir(dir)) != NULL) {
|
|
// Skip . and ..
|
|
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
// Copy d_name before recursion — readdir may use a shared buffer
|
|
char name[MAX_PATH_LEN];
|
|
snprintf(name, sizeof(name), "%s", ent->d_name);
|
|
|
|
char fullPath[MAX_PATH_LEN];
|
|
snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPath, DVX_PATH_SEP, name);
|
|
|
|
// Check if this is a directory -- recurse into it
|
|
struct stat st;
|
|
|
|
if (stat(fullPath, &st) == 0 && S_ISDIR(st.st_mode)) {
|
|
scanAppsDirRecurse(fullPath);
|
|
continue;
|
|
}
|
|
|
|
int32_t len = strlen(name);
|
|
|
|
if (len < 5) {
|
|
continue;
|
|
}
|
|
|
|
// Check for .app extension (case-insensitive)
|
|
const char *ext = name + len - 4;
|
|
|
|
if (strcasecmp(ext, ".app") != 0) {
|
|
continue;
|
|
}
|
|
|
|
// Skip ourselves
|
|
if (strcasecmp(name, "progman.app") == 0) {
|
|
continue;
|
|
}
|
|
|
|
AppEntryT newEntry;
|
|
memset(&newEntry, 0, sizeof(newEntry));
|
|
snprintf(newEntry.path, sizeof(newEntry.path), "%s", fullPath);
|
|
|
|
// Default name from filename (without .app extension)
|
|
int32_t nameLen = len - 4;
|
|
|
|
if (nameLen >= SHELL_APP_NAME_MAX) {
|
|
nameLen = SHELL_APP_NAME_MAX - 1;
|
|
}
|
|
|
|
memcpy(newEntry.name, name, nameLen);
|
|
newEntry.name[nameLen] = '\0';
|
|
|
|
if (newEntry.name[0] >= 'a' && newEntry.name[0] <= 'z') {
|
|
newEntry.name[0] -= 32;
|
|
}
|
|
|
|
// Override from embedded resources if available
|
|
newEntry.iconData = dvxResLoadIcon(sAc, fullPath, "icon32", &newEntry.iconW, &newEntry.iconH, &newEntry.iconPitch);
|
|
|
|
if (!newEntry.iconData) {
|
|
dvxLog("Progman: no icon32 resource in %s", name);
|
|
}
|
|
|
|
dvxResLoadText(fullPath, "name", newEntry.name, SHELL_APP_NAME_MAX);
|
|
dvxResLoadText(fullPath, "description", newEntry.tooltip, sizeof(newEntry.tooltip));
|
|
|
|
dvxLog("Progman: found %s (%s) icon=%s", newEntry.name, name, newEntry.iconData ? "yes" : "no");
|
|
arrput(sAppFiles, newEntry);
|
|
sAppCount = (int32_t)arrlen(sAppFiles);
|
|
|
|
dvxUpdate(sAc);
|
|
}
|
|
|
|
closedir(dir);
|
|
}
|
|
|
|
|
|
static void showAboutDialog(void) {
|
|
dvxMessageBox(sAc, "About DVX",
|
|
"DVX 1.0 - \"DOS Visual eXecutive\" GUI System.\n\n\"We have Windows at home.\"\n\nCopyright 2026 Scott Duensing\nKangaroo Punch Studios\nhttps://kangaroopunch.com",
|
|
MB_OK | MB_ICONINFO);
|
|
}
|
|
|
|
|
|
static void showSystemInfo(void) {
|
|
const char *info = shellGetSystemInfo();
|
|
|
|
if (!info || !info[0]) {
|
|
dvxMessageBox(sAc, "System Information", "No system information available.", MB_OK | MB_ICONINFO);
|
|
return;
|
|
}
|
|
|
|
// Create a window with a read-only text area
|
|
int32_t screenW = sAc->display.width;
|
|
int32_t screenH = sAc->display.height;
|
|
int32_t winW = PM_SYSINFO_WIN_W;
|
|
int32_t winH = PM_SYSINFO_WIN_H;
|
|
int32_t winX = (screenW - winW) / 2;
|
|
int32_t winY = (screenH - winH) / 4;
|
|
|
|
WindowT *win = dvxCreateWindow(sAc, "System Information", winX, winY, winW, winH, true);
|
|
|
|
if (!win) {
|
|
return;
|
|
}
|
|
|
|
WidgetT *root = wgtInitWindow(sAc, win);
|
|
WidgetT *ta = wgtTextArea(root, 4096);
|
|
ta->weight = 100;
|
|
wgtSetText(ta, info);
|
|
|
|
// Don't disable -- wgtSetEnabled(false) blocks all input including scrollbar
|
|
wgtSetReadOnly(ta, true);
|
|
}
|
|
|
|
|
|
static void updateStatusText(void) {
|
|
if (!sStatusLabel) {
|
|
return;
|
|
}
|
|
|
|
static char buf[64];
|
|
int32_t count = shellRunningAppCount();
|
|
|
|
if (count == 0) {
|
|
snprintf(buf, sizeof(buf), "No applications running");
|
|
} else if (count == 1) {
|
|
snprintf(buf, sizeof(buf), "1 application running");
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%ld applications running", (long)count);
|
|
}
|
|
|
|
wgtSetText(sStatusLabel, buf);
|
|
}
|
|
|
|
// ============================================================
|
|
// Entry point
|
|
// ============================================================
|
|
|
|
// The shell calls appMain exactly once after dlopen() resolves our symbols.
|
|
// We scan for apps, build the UI, register our desktop update callback, then
|
|
// return 0 (success). From here on the shell drives us through callbacks.
|
|
// Returning non-zero would signal a load failure and the shell would unload us.
|
|
int32_t appMain(DxeAppContextT *ctx) {
|
|
sCtx = ctx;
|
|
sAc = ctx->shellCtx;
|
|
|
|
// Set help file for F1 — system help lives in progman's app directory
|
|
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, "dvxhelp.hlp");
|
|
|
|
// Load saved preferences
|
|
char prefsPath[DVX_MAX_PATH];
|
|
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
|
|
sPrefs = prefsLoad(prefsPath);
|
|
sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false);
|
|
sRestoreAlone = prefsGetBool(sPrefs, "options", "restoreAlone", false);
|
|
|
|
scanAppsDir();
|
|
buildPmWindow();
|
|
|
|
// Register for state change notifications from the shell so our status
|
|
// bar and Task Manager stay current without polling
|
|
shellRegisterDesktopUpdate(desktopUpdate);
|
|
|
|
|
|
|
|
return 0;
|
|
}
|