No more hard coded limits. All dynamic.
This commit is contained in:
parent
a793941357
commit
97503080a5
20 changed files with 387 additions and 310 deletions
|
|
@ -478,7 +478,7 @@ static void compositeAndFlush(AppContextT *ctx) {
|
||||||
|
|
||||||
// Pre-filter visible, non-minimized windows once to avoid
|
// Pre-filter visible, non-minimized windows once to avoid
|
||||||
// re-checking visibility in the inner dirty-rect loop
|
// re-checking visibility in the inner dirty-rect loop
|
||||||
int32_t visibleIdx[MAX_WINDOWS];
|
int32_t visibleIdx[ws->count > 0 ? ws->count : 1];
|
||||||
int32_t visibleCount = 0;
|
int32_t visibleCount = 0;
|
||||||
|
|
||||||
for (int32_t j = 0; j < ws->count; j++) {
|
for (int32_t j = 0; j < ws->count; j++) {
|
||||||
|
|
@ -1446,10 +1446,22 @@ WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w,
|
||||||
// integer compare per entry.
|
// integer compare per entry.
|
||||||
|
|
||||||
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId) {
|
void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId) {
|
||||||
if (!table || table->count >= MAX_ACCEL_ENTRIES) {
|
if (!table) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (table->count >= table->cap) {
|
||||||
|
int32_t newCap = table->cap ? table->cap * 2 : 8;
|
||||||
|
AccelEntryT *newBuf = (AccelEntryT *)realloc(table->entries, newCap * sizeof(AccelEntryT));
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
table->entries = newBuf;
|
||||||
|
table->cap = newCap;
|
||||||
|
}
|
||||||
|
|
||||||
int32_t normKey = key;
|
int32_t normKey = key;
|
||||||
|
|
||||||
if (normKey >= 'a' && normKey <= 'z') {
|
if (normKey >= 'a' && normKey <= 'z') {
|
||||||
|
|
@ -1877,6 +1889,10 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void dvxFreeAccelTable(AccelTableT *table) {
|
void dvxFreeAccelTable(AccelTableT *table) {
|
||||||
|
if (table) {
|
||||||
|
free(table->entries);
|
||||||
|
}
|
||||||
|
|
||||||
free(table);
|
free(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2755,6 +2771,20 @@ 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->stack.windows);
|
||||||
|
ctx->stack.windows = NULL;
|
||||||
|
ctx->stack.count = 0;
|
||||||
|
ctx->stack.cap = 0;
|
||||||
|
|
||||||
|
free(ctx->popup.parentStack);
|
||||||
|
ctx->popup.parentStack = NULL;
|
||||||
|
ctx->popup.parentCap = 0;
|
||||||
|
|
||||||
|
free(ctx->dirty.rects);
|
||||||
|
ctx->dirty.rects = NULL;
|
||||||
|
ctx->dirty.count = 0;
|
||||||
|
ctx->dirty.cap = 0;
|
||||||
|
|
||||||
free(ctx->wallpaperBuf);
|
free(ctx->wallpaperBuf);
|
||||||
ctx->wallpaperBuf = NULL;
|
ctx->wallpaperBuf = NULL;
|
||||||
arrfree(ctx->videoModes);
|
arrfree(ctx->videoModes);
|
||||||
|
|
@ -3434,8 +3464,6 @@ static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx) {
|
||||||
// Pushes the current popup state onto parentStack and opens the submenu
|
// Pushes the current popup state onto parentStack and opens the submenu
|
||||||
// as the new current level. The submenu is positioned at the right edge
|
// as the new current level. The submenu is positioned at the right edge
|
||||||
// of the current popup, vertically aligned with the hovered item.
|
// of the current popup, vertically aligned with the hovered item.
|
||||||
// MAX_SUBMENU_DEPTH prevents runaway nesting from overflowing the stack.
|
|
||||||
|
|
||||||
static void openSubMenu(AppContextT *ctx) {
|
static void openSubMenu(AppContextT *ctx) {
|
||||||
if (!ctx->popup.active || !ctx->popup.menu) {
|
if (!ctx->popup.active || !ctx->popup.menu) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -3453,8 +3481,17 @@ static void openSubMenu(AppContextT *ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->popup.depth >= MAX_SUBMENU_DEPTH) {
|
// Grow parent stack if needed
|
||||||
return;
|
if (ctx->popup.depth >= ctx->popup.parentCap) {
|
||||||
|
int32_t newCap = ctx->popup.parentCap ? ctx->popup.parentCap * 2 : 4;
|
||||||
|
PopupLevelT *newBuf = (PopupLevelT *)realloc(ctx->popup.parentStack, newCap * sizeof(PopupLevelT));
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->popup.parentStack = newBuf;
|
||||||
|
ctx->popup.parentCap = newCap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push current state to parent stack
|
// Push current state to parent stack
|
||||||
|
|
@ -4256,15 +4293,14 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
if (win->widgetRoot) {
|
if (win->widgetRoot) {
|
||||||
// Find currently focused widget
|
// Find currently focused widget
|
||||||
WidgetT *current = NULL;
|
WidgetT *current = NULL;
|
||||||
WidgetT *fstack[64];
|
WidgetT **fstack = NULL;
|
||||||
int32_t ftop = 0;
|
arrput(fstack, win->widgetRoot);
|
||||||
fstack[ftop++] = win->widgetRoot;
|
|
||||||
|
|
||||||
while (ftop > 0) {
|
while (arrlen(fstack) > 0) {
|
||||||
WidgetT *w = fstack[--ftop];
|
WidgetT *w = fstack[arrlen(fstack) - 1];
|
||||||
|
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||||
|
|
||||||
if (w->focused && widgetIsFocusable(w->type)) {
|
if (w->focused && widgetIsFocusable(w->type)) {
|
||||||
// Don't tab out of widgets that swallow Tab
|
|
||||||
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||||
current = NULL;
|
current = NULL;
|
||||||
break;
|
break;
|
||||||
|
|
@ -4275,21 +4311,21 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||||
if (c->visible && ftop < 64) {
|
if (c->visible) {
|
||||||
fstack[ftop++] = c;
|
arrput(fstack, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal swallowed Tab -- send to widget system instead
|
// Terminal swallowed Tab -- send to widget system instead
|
||||||
if (current == NULL) {
|
if (current == NULL) {
|
||||||
// Check if a terminal is focused
|
arrsetlen(fstack, 0);
|
||||||
ftop = 0;
|
arrput(fstack, win->widgetRoot);
|
||||||
fstack[ftop++] = win->widgetRoot;
|
|
||||||
bool termFocused = false;
|
bool termFocused = false;
|
||||||
|
|
||||||
while (ftop > 0) {
|
while (arrlen(fstack) > 0) {
|
||||||
WidgetT *w = fstack[--ftop];
|
WidgetT *w = fstack[arrlen(fstack) - 1];
|
||||||
|
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||||
|
|
||||||
if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||||
termFocused = true;
|
termFocused = true;
|
||||||
|
|
@ -4297,8 +4333,8 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||||
if (c->visible && ftop < 64) {
|
if (c->visible) {
|
||||||
fstack[ftop++] = c;
|
arrput(fstack, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4309,6 +4345,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags);
|
win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(fstack);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4366,6 +4403,8 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
|
|
||||||
wgtInvalidate(win->widgetRoot);
|
wgtInvalidate(win->widgetRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(fstack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@
|
||||||
#include "dvxComp.h"
|
#include "dvxComp.h"
|
||||||
#include "dvxPlatform.h"
|
#include "dvxPlatform.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
||||||
// Rects within this many pixels of each other get merged even if they don't
|
// Rects within this many pixels of each other get merged even if they don't
|
||||||
// overlap. A small gap tolerance absorbs jitter from mouse movement and
|
// overlap. A small gap tolerance absorbs jitter from mouse movement and
|
||||||
|
|
@ -42,32 +44,24 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
|
||||||
// dirtyListAdd
|
// dirtyListAdd
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Appends a dirty rect to the list. Uses a fixed-size array (MAX_DIRTY_RECTS
|
// Appends a dirty rect to the list. The array grows dynamically but a
|
||||||
// = 128) rather than a dynamic allocation -- this is called on every UI
|
// merge pass fires at 128 entries to keep the list short. If the list
|
||||||
// mutation (drag, repaint, focus change) so allocation overhead must be zero.
|
// is still long after merging (pathological scatter), everything collapses
|
||||||
//
|
// into one bounding box. In practice the merge pass almost always frees
|
||||||
// When the list fills up, an eager merge pass tries to consolidate rects.
|
// enough slots because GUI mutations tend to cluster spatially.
|
||||||
// If the list is STILL full after merging (pathological scatter), the
|
|
||||||
// nuclear option collapses everything into one bounding box. This guarantees
|
#define DIRTY_SOFT_CAP 128
|
||||||
// the list never overflows, at the cost of potentially over-painting a large
|
|
||||||
// rect. In practice the merge pass almost always frees enough slots because
|
|
||||||
// GUI mutations tend to cluster spatially.
|
|
||||||
|
|
||||||
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||||
// Branch hint: degenerate rects are rare -- callers usually validate first
|
|
||||||
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overflow path: try merging, then fall back to a single bounding rect
|
// Soft cap: merge when we accumulate too many rects to keep composite fast
|
||||||
if (__builtin_expect(dl->count >= MAX_DIRTY_RECTS, 0)) {
|
if (__builtin_expect(dl->count >= DIRTY_SOFT_CAP, 0)) {
|
||||||
dirtyListMerge(dl);
|
dirtyListMerge(dl);
|
||||||
|
|
||||||
if (dl->count >= MAX_DIRTY_RECTS) {
|
if (dl->count >= DIRTY_SOFT_CAP) {
|
||||||
// Still full -- collapse the entire list plus the new rect into one
|
|
||||||
// bounding box. This is a last resort; it means the next flush will
|
|
||||||
// repaint a potentially large region, but at least we won't lose
|
|
||||||
// dirty information or crash.
|
|
||||||
RectT merged = dl->rects[0];
|
RectT merged = dl->rects[0];
|
||||||
|
|
||||||
for (int32_t i = 1; i < dl->count; i++) {
|
for (int32_t i = 1; i < dl->count; i++) {
|
||||||
|
|
@ -83,6 +77,19 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grow array if needed
|
||||||
|
if (dl->count >= dl->cap) {
|
||||||
|
int32_t newCap = dl->cap ? dl->cap * 2 : DIRTY_SOFT_CAP;
|
||||||
|
RectT *newBuf = (RectT *)realloc(dl->rects, newCap * sizeof(RectT));
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl->rects = newBuf;
|
||||||
|
dl->cap = newCap;
|
||||||
|
}
|
||||||
|
|
||||||
dl->rects[dl->count].x = x;
|
dl->rects[dl->count].x = x;
|
||||||
dl->rects[dl->count].y = y;
|
dl->rects[dl->count].y = y;
|
||||||
dl->rects[dl->count].w = w;
|
dl->rects[dl->count].w = w;
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@
|
||||||
// Zero the dirty rect count. Called at the start of each frame.
|
// Zero the dirty rect count. Called at the start of each frame.
|
||||||
void dirtyListInit(DirtyListT *dl);
|
void dirtyListInit(DirtyListT *dl);
|
||||||
|
|
||||||
// Enqueue a dirty rectangle. If the list is full (MAX_DIRTY_RECTS),
|
// Enqueue a dirty rectangle. Grows dynamically; merges at a soft cap.
|
||||||
// this should degrade gracefully (e.g. expand an existing rect).
|
|
||||||
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h);
|
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h);
|
||||||
|
|
||||||
// Consolidate the dirty list by merging overlapping and adjacent rects.
|
// Consolidate the dirty list by merging overlapping and adjacent rects.
|
||||||
|
|
|
||||||
|
|
@ -629,7 +629,6 @@ static int32_t wordWrapHeight(const BitmapFontT *font, const char *text, int32_t
|
||||||
// listbox to distinguish them from files, following the DOS convention.
|
// listbox to distinguish them from files, following the DOS convention.
|
||||||
|
|
||||||
// FD_MAX_PATH is 260 to match DOS MAX_PATH (including null terminator)
|
// FD_MAX_PATH is 260 to match DOS MAX_PATH (including null terminator)
|
||||||
#define FD_MAX_ENTRIES 512
|
|
||||||
#define FD_MAX_PATH 260
|
#define FD_MAX_PATH 260
|
||||||
#define FD_NAME_LEN 64
|
#define FD_NAME_LEN 64
|
||||||
|
|
||||||
|
|
@ -642,10 +641,10 @@ typedef struct {
|
||||||
const FileFilterT *filters; // caller-provided filter list
|
const FileFilterT *filters; // caller-provided filter list
|
||||||
int32_t filterCount;
|
int32_t filterCount;
|
||||||
int32_t activeFilter; // index into filters[]
|
int32_t activeFilter; // index into filters[]
|
||||||
char *entryNames[FD_MAX_ENTRIES]; // heap-allocated, freed by fdFreeEntries
|
char **entryNames; // dynamic array of heap-allocated strings
|
||||||
bool entryIsDir[FD_MAX_ENTRIES];
|
bool *entryIsDir; // dynamic array, parallel to entryNames
|
||||||
int32_t entryCount;
|
int32_t entryCount;
|
||||||
const char *listItems[FD_MAX_ENTRIES]; // pointers into entryNames, for listbox API
|
const char **listItems; // dynamic array, pointers into entryNames
|
||||||
WidgetT *fileList;
|
WidgetT *fileList;
|
||||||
WidgetT *pathInput;
|
WidgetT *pathInput;
|
||||||
WidgetT *nameInput;
|
WidgetT *nameInput;
|
||||||
|
|
@ -745,9 +744,14 @@ static bool fdFilterMatch(const char *name, const char *pattern) {
|
||||||
static void fdFreeEntries(void) {
|
static void fdFreeEntries(void) {
|
||||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||||
free(sFd.entryNames[i]);
|
free(sFd.entryNames[i]);
|
||||||
sFd.entryNames[i] = NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(sFd.entryNames);
|
||||||
|
free(sFd.entryIsDir);
|
||||||
|
free(sFd.listItems);
|
||||||
|
sFd.entryNames = NULL;
|
||||||
|
sFd.entryIsDir = NULL;
|
||||||
|
sFd.listItems = NULL;
|
||||||
sFd.entryCount = 0;
|
sFd.entryCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -803,7 +807,7 @@ static void fdLoadDir(void) {
|
||||||
|
|
||||||
struct dirent *ent;
|
struct dirent *ent;
|
||||||
|
|
||||||
while ((ent = readdir(dir)) != NULL && sFd.entryCount < FD_MAX_ENTRIES) {
|
while ((ent = readdir(dir)) != NULL) {
|
||||||
// Skip "."
|
// Skip "."
|
||||||
if (strcmp(ent->d_name, ".") == 0) {
|
if (strcmp(ent->d_name, ".") == 0) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -826,7 +830,15 @@ static void fdLoadDir(void) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Grow arrays
|
||||||
int32_t idx = sFd.entryCount;
|
int32_t idx = sFd.entryCount;
|
||||||
|
sFd.entryNames = (char **)realloc(sFd.entryNames, (idx + 1) * sizeof(char *));
|
||||||
|
sFd.entryIsDir = (bool *)realloc(sFd.entryIsDir, (idx + 1) * sizeof(bool));
|
||||||
|
|
||||||
|
if (!sFd.entryNames || !sFd.entryIsDir) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
sFd.entryIsDir[idx] = isDir;
|
sFd.entryIsDir[idx] = isDir;
|
||||||
|
|
||||||
if (isDir) {
|
if (isDir) {
|
||||||
|
|
@ -843,8 +855,17 @@ static void fdLoadDir(void) {
|
||||||
|
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
|
|
||||||
|
if (sFd.entryCount == 0) {
|
||||||
|
wgtListBoxSetItems(sFd.fileList, NULL, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Sort: build index array, sort, reorder
|
// Sort: build index array, sort, reorder
|
||||||
int32_t sortIdx[FD_MAX_ENTRIES];
|
int32_t *sortIdx = (int32_t *)malloc(sFd.entryCount * sizeof(int32_t));
|
||||||
|
|
||||||
|
if (!sortIdx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||||
sortIdx[i] = i;
|
sortIdx[i] = i;
|
||||||
|
|
@ -853,18 +874,26 @@ static void fdLoadDir(void) {
|
||||||
qsort(sortIdx, sFd.entryCount, sizeof(int32_t), fdEntryCompare);
|
qsort(sortIdx, sFd.entryCount, sizeof(int32_t), fdEntryCompare);
|
||||||
|
|
||||||
// Rebuild arrays in sorted order
|
// Rebuild arrays in sorted order
|
||||||
char *tmpNames[FD_MAX_ENTRIES];
|
char **tmpNames = (char **)malloc(sFd.entryCount * sizeof(char *));
|
||||||
bool tmpIsDir[FD_MAX_ENTRIES];
|
bool *tmpIsDir = (bool *)malloc(sFd.entryCount * sizeof(bool));
|
||||||
|
|
||||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
if (tmpNames && tmpIsDir) {
|
||||||
tmpNames[i] = sFd.entryNames[sortIdx[i]];
|
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||||
tmpIsDir[i] = sFd.entryIsDir[sortIdx[i]];
|
tmpNames[i] = sFd.entryNames[sortIdx[i]];
|
||||||
|
tmpIsDir[i] = sFd.entryIsDir[sortIdx[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(sFd.entryNames, tmpNames, sizeof(char *) * sFd.entryCount);
|
||||||
|
memcpy(sFd.entryIsDir, tmpIsDir, sizeof(bool) * sFd.entryCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(sFd.entryNames, tmpNames, sizeof(char *) * sFd.entryCount);
|
free(sortIdx);
|
||||||
memcpy(sFd.entryIsDir, tmpIsDir, sizeof(bool) * sFd.entryCount);
|
free(tmpNames);
|
||||||
|
free(tmpIsDir);
|
||||||
|
|
||||||
// Build listItems pointer array for the listbox
|
// Build listItems pointer array for the listbox
|
||||||
|
sFd.listItems = (const char **)realloc(sFd.listItems, sFd.entryCount * sizeof(const char *));
|
||||||
|
|
||||||
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
for (int32_t i = 0; i < sFd.entryCount; i++) {
|
||||||
sFd.listItems[i] = sFd.entryNames[i];
|
sFd.listItems[i] = sFd.entryNames[i];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,9 @@
|
||||||
#include "dvxDraw.h"
|
#include "dvxDraw.h"
|
||||||
#include "dvxPlatform.h"
|
#include "dvxPlatform.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Prototypes
|
// Prototypes
|
||||||
|
|
|
||||||
|
|
@ -30,3 +30,6 @@
|
||||||
#include "thirdparty/stb_image.h"
|
#include "thirdparty/stb_image.h"
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
|
||||||
|
|
@ -18,3 +18,6 @@
|
||||||
#include "thirdparty/stb_image_write.h"
|
#include "thirdparty/stb_image_write.h"
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#ifndef DVX_MEM_H
|
#ifndef DVX_MEM_H
|
||||||
#define DVX_MEM_H
|
#define DVX_MEM_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
extern int32_t *dvxMemAppIdPtr;
|
extern int32_t *dvxMemAppIdPtr;
|
||||||
|
|
@ -38,11 +39,10 @@ void dvxMemSnapshotLoad(int32_t appId);
|
||||||
uint32_t dvxMemGetAppUsage(int32_t appId);
|
uint32_t dvxMemGetAppUsage(int32_t appId);
|
||||||
void dvxMemResetApp(int32_t appId);
|
void dvxMemResetApp(int32_t appId);
|
||||||
|
|
||||||
// Redirect standard allocator calls to tracking wrappers.
|
// The dvxMalloc/dvxFree functions are passthrough wrappers.
|
||||||
// This MUST appear after <stdlib.h> so the real prototypes exist.
|
// Header-based per-allocation tracking was attempted but is unsafe
|
||||||
#define malloc(s) dvxMalloc(s)
|
// in the DXE3 environment (loader-compiled code like stb_ds uses
|
||||||
#define calloc(n, s) dvxCalloc((n), (s))
|
// libc malloc while DXE code would use the wrapped version).
|
||||||
#define realloc(p, s) dvxRealloc((p), (s))
|
// Per-app memory is tracked via DPMI snapshots instead.
|
||||||
#define free(p) dvxFree(p)
|
|
||||||
|
|
||||||
#endif // DVX_MEM_H
|
#endif // DVX_MEM_H
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
||||||
// stb_ds dynamic arrays (implementation lives in libtasks.a)
|
// stb_ds dynamic arrays (implementation lives in libtasks.a)
|
||||||
#include "thirdparty/stb_ds.h"
|
#include "thirdparty/stb_ds.h"
|
||||||
|
|
|
||||||
|
|
@ -259,11 +259,10 @@ typedef struct {
|
||||||
// cache-friendly at this size. If the list fills up, the compositor
|
// cache-friendly at this size. If the list fills up, the compositor
|
||||||
// can merge aggressively or fall back to a full-screen repaint.
|
// can merge aggressively or fall back to a full-screen repaint.
|
||||||
|
|
||||||
#define MAX_DIRTY_RECTS 128
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
RectT rects[MAX_DIRTY_RECTS];
|
RectT *rects; // dynamic array (realloc'd on demand)
|
||||||
int32_t count;
|
int32_t count;
|
||||||
|
int32_t cap;
|
||||||
} DirtyListT;
|
} DirtyListT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -305,8 +304,6 @@ typedef struct {
|
||||||
// The forward declaration of MenuT is needed because MenuItemT contains
|
// The forward declaration of MenuT is needed because MenuItemT contains
|
||||||
// a pointer to its parent type (circular reference between item and menu).
|
// a pointer to its parent type (circular reference between item and menu).
|
||||||
|
|
||||||
#define MAX_MENU_ITEMS 16
|
|
||||||
#define MAX_MENUS 8
|
|
||||||
#define MAX_MENU_LABEL 32
|
#define MAX_MENU_LABEL 32
|
||||||
|
|
||||||
// Forward declaration for submenu pointers
|
// Forward declaration for submenu pointers
|
||||||
|
|
@ -333,8 +330,9 @@ typedef struct {
|
||||||
// needs a forward pointer to it for cascading submenus.
|
// needs a forward pointer to it for cascading submenus.
|
||||||
struct MenuT {
|
struct MenuT {
|
||||||
char label[MAX_MENU_LABEL]; // menu bar label (e.g. "File")
|
char label[MAX_MENU_LABEL]; // menu bar label (e.g. "File")
|
||||||
MenuItemT items[MAX_MENU_ITEMS];
|
MenuItemT *items; // dynamic array (realloc'd on demand)
|
||||||
int32_t itemCount;
|
int32_t itemCount;
|
||||||
|
int32_t itemCap;
|
||||||
int32_t barX; // computed position on menu bar
|
int32_t barX; // computed position on menu bar
|
||||||
int32_t barW; // computed width on menu bar
|
int32_t barW; // computed width on menu bar
|
||||||
char accelKey; // lowercase accelerator character, 0 if none
|
char accelKey; // lowercase accelerator character, 0 if none
|
||||||
|
|
@ -344,8 +342,9 @@ struct MenuT {
|
||||||
// menu bar is actually drawn, avoiding redundant work when multiple
|
// menu bar is actually drawn, avoiding redundant work when multiple
|
||||||
// menus are added in sequence during window setup.
|
// menus are added in sequence during window setup.
|
||||||
typedef struct {
|
typedef struct {
|
||||||
MenuT menus[MAX_MENUS];
|
MenuT *menus; // dynamic array (realloc'd on demand)
|
||||||
int32_t menuCount;
|
int32_t menuCount;
|
||||||
|
int32_t menuCap;
|
||||||
int32_t activeIdx; // menu bar item with open popup (-1 = none)
|
int32_t activeIdx; // menu bar item with open popup (-1 = none)
|
||||||
bool positionsDirty; // true = barX/barW need recomputation
|
bool positionsDirty; // true = barX/barW need recomputation
|
||||||
} MenuBarT;
|
} MenuBarT;
|
||||||
|
|
@ -394,7 +393,6 @@ typedef struct {
|
||||||
// match in O(n) with just two integer comparisons per entry, avoiding
|
// match in O(n) with just two integer comparisons per entry, avoiding
|
||||||
// case-folding and modifier normalization on every keystroke.
|
// case-folding and modifier normalization on every keystroke.
|
||||||
|
|
||||||
#define MAX_ACCEL_ENTRIES 32
|
|
||||||
|
|
||||||
// Modifier flags for accelerators (match BIOS shift state bits)
|
// Modifier flags for accelerators (match BIOS shift state bits)
|
||||||
#define ACCEL_SHIFT 0x03
|
#define ACCEL_SHIFT 0x03
|
||||||
|
|
@ -431,8 +429,9 @@ typedef struct {
|
||||||
} AccelEntryT;
|
} AccelEntryT;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
AccelEntryT entries[MAX_ACCEL_ENTRIES];
|
AccelEntryT *entries; // dynamic array (realloc'd on demand)
|
||||||
int32_t count;
|
int32_t count;
|
||||||
|
int32_t cap;
|
||||||
} AccelTableT;
|
} AccelTableT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -453,7 +452,6 @@ typedef struct {
|
||||||
// RESIZE_xxx flags are bitfields so that corner resizes can combine two
|
// RESIZE_xxx flags are bitfields so that corner resizes can combine two
|
||||||
// edges (e.g. RESIZE_LEFT | RESIZE_TOP for the top-left corner).
|
// edges (e.g. RESIZE_LEFT | RESIZE_TOP for the top-left corner).
|
||||||
|
|
||||||
#define MAX_WINDOWS 64
|
|
||||||
#define MAX_TITLE_LEN 128
|
#define MAX_TITLE_LEN 128
|
||||||
|
|
||||||
#define RESIZE_NONE 0
|
#define RESIZE_NONE 0
|
||||||
|
|
@ -577,8 +575,9 @@ typedef struct WindowT {
|
||||||
// you can't drag two windows simultaneously with a single mouse.
|
// you can't drag two windows simultaneously with a single mouse.
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
WindowT *windows[MAX_WINDOWS];
|
WindowT **windows; // dynamic array (realloc'd on demand)
|
||||||
int32_t count;
|
int32_t count;
|
||||||
|
int32_t cap;
|
||||||
int32_t focusedIdx;
|
int32_t focusedIdx;
|
||||||
int32_t dragWindow;
|
int32_t dragWindow;
|
||||||
int32_t dragOffX;
|
int32_t dragOffX;
|
||||||
|
|
@ -629,15 +628,14 @@ typedef struct {
|
||||||
// Only one popup chain can be active at a time (menus are modal).
|
// Only one popup chain can be active at a time (menus are modal).
|
||||||
// Cascading submenus are tracked as a stack of PopupLevelT frames,
|
// Cascading submenus are tracked as a stack of PopupLevelT frames,
|
||||||
// where each level saves the parent menu's state so it can be restored
|
// where each level saves the parent menu's state so it can be restored
|
||||||
// when the submenu closes. MAX_SUBMENU_DEPTH of 4 allows File > Recent >
|
// when the submenu closes. The stack grows dynamically to support
|
||||||
// Category > Item style nesting, which is deeper than any sane DOS UI needs.
|
// arbitrary nesting depth.
|
||||||
//
|
//
|
||||||
// The popup's screen coordinates are stored directly rather than computed
|
// The popup's screen coordinates are stored directly rather than computed
|
||||||
// relative to the window, because the popup is painted on top of
|
// relative to the window, because the popup is painted on top of
|
||||||
// everything during the compositor's overlay pass and needs absolute
|
// everything during the compositor's overlay pass and needs absolute
|
||||||
// screen positioning.
|
// screen positioning.
|
||||||
|
|
||||||
#define MAX_SUBMENU_DEPTH 4
|
|
||||||
|
|
||||||
// Saved parent popup state when a submenu is open
|
// Saved parent popup state when a submenu is open
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -662,7 +660,8 @@ typedef struct {
|
||||||
int32_t hoverItem; // highlighted item in current popup (-1 = none)
|
int32_t hoverItem; // highlighted item in current popup (-1 = none)
|
||||||
MenuT *menu; // direct pointer to current menu (avoids lookup for submenus)
|
MenuT *menu; // direct pointer to current menu (avoids lookup for submenus)
|
||||||
int32_t depth; // 0 = top-level only, 1+ = submenu depth
|
int32_t depth; // 0 = top-level only, 1+ = submenu depth
|
||||||
PopupLevelT parentStack[MAX_SUBMENU_DEPTH];
|
PopupLevelT *parentStack; // dynamic array (realloc'd on demand)
|
||||||
|
int32_t parentCap;
|
||||||
} PopupStateT;
|
} PopupStateT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,9 @@
|
||||||
#include "dvxPlatform.h"
|
#include "dvxPlatform.h"
|
||||||
#include "dvxPalette.h"
|
#include "dvxPalette.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
68
core/dvxWm.c
68
core/dvxWm.c
|
|
@ -11,7 +11,7 @@
|
||||||
// = front). This was chosen over a linked list because hit-testing walks
|
// = front). This was chosen over a linked list because hit-testing walks
|
||||||
// the stack front-to-back every mouse event, and array iteration has
|
// the stack front-to-back every mouse event, and array iteration has
|
||||||
// better cache behavior on 486/Pentium. Raising a window is O(N) shift
|
// better cache behavior on 486/Pentium. Raising a window is O(N) shift
|
||||||
// but N is bounded by MAX_WINDOWS=64 and raise is infrequent.
|
// but N is bounded by the window count and raise is infrequent.
|
||||||
//
|
//
|
||||||
// - Window chrome uses a Motif/GEOS Ensemble visual style with fixed 4px
|
// - Window chrome uses a Motif/GEOS Ensemble visual style with fixed 4px
|
||||||
// outer bevels and 2px inner bevels. The fixed bevel widths avoid per-
|
// outer bevels and 2px inner bevels. The fixed bevel widths avoid per-
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include "dvxMem.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Constants
|
// Constants
|
||||||
|
|
@ -712,6 +713,11 @@ static void freeMenuRecursive(MenuT *menu) {
|
||||||
menu->items[i].subMenu = NULL;
|
menu->items[i].subMenu = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(menu->items);
|
||||||
|
menu->items = NULL;
|
||||||
|
menu->itemCount = 0;
|
||||||
|
menu->itemCap = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -861,7 +867,7 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Adds a top-level menu to a window's menu bar. The MenuT struct is
|
// Adds a top-level menu to a window's menu bar. The MenuT struct is
|
||||||
// stored inline in the MenuBarT's fixed-size array (MAX_MENUS=8) rather
|
// stored inline in the MenuBarT's dynamic array rather
|
||||||
// than heap-allocated, since menus are created once at window setup and
|
// than heap-allocated, since menus are created once at window setup and
|
||||||
// the count is small. The positionsDirty flag is set so that the next
|
// the count is small. The positionsDirty flag is set so that the next
|
||||||
// paint triggers computeMenuBarPositions to lay out all labels.
|
// paint triggers computeMenuBarPositions to lay out all labels.
|
||||||
|
|
@ -871,9 +877,35 @@ ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
||||||
// stored on the MenuT so the event loop can match keyboard shortcuts
|
// stored on the MenuT so the event loop can match keyboard shortcuts
|
||||||
// without re-parsing the label string on every keypress.
|
// without re-parsing the label string on every keypress.
|
||||||
|
|
||||||
|
static bool menuGrowItems(MenuT *menu) {
|
||||||
|
if (menu->itemCount < menu->itemCap) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t newCap = menu->itemCap ? menu->itemCap * 2 : 8;
|
||||||
|
MenuItemT *newBuf = (MenuItemT *)realloc(menu->items, newCap * sizeof(MenuItemT));
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
menu->items = newBuf;
|
||||||
|
menu->itemCap = newCap;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
|
MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
|
||||||
if (bar->menuCount >= MAX_MENUS) {
|
if (bar->menuCount >= bar->menuCap) {
|
||||||
return NULL;
|
int32_t newCap = bar->menuCap ? bar->menuCap * 2 : 8;
|
||||||
|
MenuT *newBuf = (MenuT *)realloc(bar->menus, newCap * sizeof(MenuT));
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bar->menus = newBuf;
|
||||||
|
bar->menuCap = newCap;
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuT *menu = &bar->menus[bar->menuCount];
|
MenuT *menu = &bar->menus[bar->menuCount];
|
||||||
|
|
@ -895,7 +927,7 @@ MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
|
||||||
// Allocates and attaches a menu bar to a window. The menu bar is heap-
|
// Allocates and attaches a menu bar to a window. The menu bar is heap-
|
||||||
// allocated separately from the window because most windows don't have
|
// allocated separately from the window because most windows don't have
|
||||||
// one, and keeping it out of WindowT saves ~550 bytes per window
|
// one, and keeping it out of WindowT saves ~550 bytes per window
|
||||||
// (MAX_MENUS * sizeof(MenuT)). wmUpdateContentRect is called to shrink
|
// wmUpdateContentRect is called to shrink
|
||||||
// the content area by CHROME_MENU_HEIGHT to make room for the bar.
|
// the content area by CHROME_MENU_HEIGHT to make room for the bar.
|
||||||
|
|
||||||
MenuBarT *wmAddMenuBar(WindowT *win) {
|
MenuBarT *wmAddMenuBar(WindowT *win) {
|
||||||
|
|
@ -918,7 +950,7 @@ MenuBarT *wmAddMenuBar(WindowT *win) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
|
void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
|
||||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
if (!menuGrowItems(menu)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -940,7 +972,7 @@ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
||||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
if (!menuGrowItems(menu)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -962,7 +994,7 @@ void wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked) {
|
||||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
if (!menuGrowItems(menu)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -984,7 +1016,7 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void wmAddMenuSeparator(MenuT *menu) {
|
void wmAddMenuSeparator(MenuT *menu) {
|
||||||
if (menu->itemCount >= MAX_MENU_ITEMS) {
|
if (!menuGrowItems(menu)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1007,7 +1039,7 @@ void wmAddMenuSeparator(MenuT *menu) {
|
||||||
// item opens the child rather than firing a command.
|
// item opens the child rather than firing a command.
|
||||||
|
|
||||||
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) {
|
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) {
|
||||||
if (parentMenu->itemCount >= MAX_MENU_ITEMS) {
|
if (!menuGrowItems(parentMenu)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1084,9 +1116,17 @@ ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page
|
||||||
// and wmRaiseWindow if desired.
|
// and wmRaiseWindow if desired.
|
||||||
|
|
||||||
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
|
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) {
|
||||||
if (stack->count >= MAX_WINDOWS) {
|
if (stack->count >= stack->cap) {
|
||||||
fprintf(stderr, "WM: Maximum windows (%d) reached\n", MAX_WINDOWS);
|
int32_t newCap = stack->cap ? stack->cap * 2 : 16;
|
||||||
return NULL;
|
WindowT **newBuf = (WindowT **)realloc(stack->windows, newCap * sizeof(WindowT *));
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
fprintf(stderr, "WM: Failed to grow window stack\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack->windows = newBuf;
|
||||||
|
stack->cap = newCap;
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowT *win = (WindowT *)malloc(sizeof(WindowT));
|
WindowT *win = (WindowT *)malloc(sizeof(WindowT));
|
||||||
|
|
@ -1188,6 +1228,7 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) {
|
||||||
freeMenuRecursive(&win->menuBar->menus[i]);
|
freeMenuRecursive(&win->menuBar->menus[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(win->menuBar->menus);
|
||||||
free(win->menuBar);
|
free(win->menuBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2626,6 +2667,7 @@ void wmFreeMenu(MenuT *menu) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
free(menu->items);
|
||||||
free(menu);
|
free(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked
|
||||||
void wmAddMenuSeparator(MenuT *menu);
|
void wmAddMenuSeparator(MenuT *menu);
|
||||||
|
|
||||||
// Create a cascading submenu attached to the parent menu. Returns the
|
// Create a cascading submenu attached to the parent menu. Returns the
|
||||||
// child MenuT to populate, or NULL if MAX_MENU_ITEMS is exhausted.
|
// child MenuT to populate, or NULL on allocation failure.
|
||||||
// The child MenuT is heap-allocated and freed when the parent window
|
// The child MenuT is heap-allocated and freed when the parent window
|
||||||
// is destroyed.
|
// is destroyed.
|
||||||
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label);
|
MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label);
|
||||||
|
|
|
||||||
|
|
@ -1113,31 +1113,63 @@ bool platformGetMemoryInfo(uint32_t *totalKb, uint32_t *freeKb) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Per-app memory tracking (header-based)
|
// Per-app memory tracking (DPMI snapshot)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Every DXE .c file includes dvxMem.h which #defines malloc/free/
|
// Tracks per-app memory by taking DPMI free-memory snapshots at
|
||||||
// calloc/realloc to dvxMalloc/dvxFree/dvxCalloc/dvxRealloc. This
|
// app load time. The difference between the snapshot and current
|
||||||
// file does NOT include dvxMem.h, so calls to malloc/free here go
|
// free memory is a coarse estimate of the app's heap footprint.
|
||||||
// directly to libc with no recursion.
|
|
||||||
//
|
//
|
||||||
// Each tracked allocation has a 16-byte header prepended. dvxFree
|
// Header-based malloc wrapping (prepending a tracking header to
|
||||||
// checks the magic before adjusting the pointer -- if it doesn't
|
// each allocation) was attempted but is unsafe in the DJGPP/DXE3
|
||||||
// match (pointer came from libc internals or loader code), it
|
// environment: loader-compiled code (stb_ds internals, libc
|
||||||
// falls through to the real free() unchanged.
|
// functions like strdup/localtime) allocates with libc's malloc,
|
||||||
|
// but DXE code frees with the wrapped free. Reading 16 bytes
|
||||||
#define DVX_ALLOC_MAGIC 0xDEADBEEFUL
|
// before an arbitrary libc pointer to check for a tracking magic
|
||||||
|
// value is undefined behavior that causes faults on 86Box/DPMI.
|
||||||
typedef struct {
|
//
|
||||||
uint32_t magic;
|
// dvxMalloc/dvxFree/etc. are still exported for dvxMem.h compat
|
||||||
int32_t appId;
|
// but pass straight through to libc.
|
||||||
uint32_t size;
|
|
||||||
uint32_t pad;
|
|
||||||
} DvxAllocHeaderT;
|
|
||||||
|
|
||||||
int32_t *dvxMemAppIdPtr = NULL;
|
int32_t *dvxMemAppIdPtr = NULL;
|
||||||
static uint32_t *sAppMemUsed = NULL;
|
static uint32_t *sAppMemAtLoad = NULL;
|
||||||
static int32_t sAppMemCap = 0;
|
static int32_t sAppMemCap = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// Passthrough allocators (dvxMem.h #defines redirect here)
|
||||||
|
void *dvxMalloc(size_t size) {
|
||||||
|
return malloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void *dvxCalloc(size_t nmemb, size_t size) {
|
||||||
|
return calloc(nmemb, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dvxFree(void *ptr) {
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void *dvxRealloc(void *ptr, size_t size) {
|
||||||
|
return realloc(ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint32_t dpmiGetFreeKb(void) {
|
||||||
|
__dpmi_free_mem_info memInfo;
|
||||||
|
|
||||||
|
if (__dpmi_get_free_memory_information(&memInfo) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memInfo.total_number_of_free_pages == 0xFFFFFFFFUL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return memInfo.total_number_of_free_pages * 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void dvxMemGrow(int32_t appId) {
|
static void dvxMemGrow(int32_t appId) {
|
||||||
|
|
@ -1146,114 +1178,26 @@ static void dvxMemGrow(int32_t appId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t newCap = appId + 16;
|
int32_t newCap = appId + 16;
|
||||||
uint32_t *newArr = (uint32_t *)realloc(sAppMemUsed, newCap * sizeof(uint32_t));
|
uint32_t *newArr = (uint32_t *)realloc(sAppMemAtLoad, newCap * sizeof(uint32_t));
|
||||||
|
|
||||||
if (!newArr) {
|
if (!newArr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(newArr + sAppMemCap, 0, (newCap - sAppMemCap) * sizeof(uint32_t));
|
memset(newArr + sAppMemCap, 0, (newCap - sAppMemCap) * sizeof(uint32_t));
|
||||||
sAppMemUsed = newArr;
|
sAppMemAtLoad = newArr;
|
||||||
sAppMemCap = newCap;
|
sAppMemCap = newCap;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void *dvxMalloc(size_t size) {
|
|
||||||
int32_t appId = dvxMemAppIdPtr ? *dvxMemAppIdPtr : 0;
|
|
||||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)malloc(sizeof(DvxAllocHeaderT) + size);
|
|
||||||
|
|
||||||
if (!hdr) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr->magic = DVX_ALLOC_MAGIC;
|
|
||||||
hdr->appId = appId;
|
|
||||||
hdr->size = (uint32_t)size;
|
|
||||||
hdr->pad = 0;
|
|
||||||
|
|
||||||
if (appId >= 0) {
|
|
||||||
dvxMemGrow(appId);
|
|
||||||
if (appId < sAppMemCap) { sAppMemUsed[appId] += (uint32_t)size; }
|
|
||||||
}
|
|
||||||
|
|
||||||
return hdr + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void *dvxCalloc(size_t nmemb, size_t size) {
|
|
||||||
size_t total = nmemb * size;
|
|
||||||
void *ptr = dvxMalloc(total);
|
|
||||||
|
|
||||||
if (ptr) {
|
|
||||||
memset(ptr, 0, total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void dvxFree(void *ptr) {
|
|
||||||
if (!ptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;
|
|
||||||
|
|
||||||
if (hdr->magic != DVX_ALLOC_MAGIC) {
|
|
||||||
// Not a tracked allocation (libc, stb_ds, loader) -- pass through
|
|
||||||
free(ptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t appId = hdr->appId;
|
|
||||||
|
|
||||||
if (appId >= 0 && appId < sAppMemCap) {
|
|
||||||
sAppMemUsed[appId] -= hdr->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr->magic = 0;
|
|
||||||
free(hdr);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void *dvxRealloc(void *ptr, size_t size) {
|
|
||||||
if (!ptr) {
|
|
||||||
return dvxMalloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
dvxFree(ptr);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
DvxAllocHeaderT *hdr = (DvxAllocHeaderT *)ptr - 1;
|
|
||||||
|
|
||||||
if (hdr->magic != DVX_ALLOC_MAGIC) {
|
|
||||||
// Not tracked -- pass through
|
|
||||||
return realloc(ptr, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t appId = hdr->appId;
|
|
||||||
uint32_t oldSize = hdr->size;
|
|
||||||
|
|
||||||
DvxAllocHeaderT *newHdr = (DvxAllocHeaderT *)realloc(hdr, sizeof(DvxAllocHeaderT) + size);
|
|
||||||
|
|
||||||
if (!newHdr) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appId >= 0 && appId < sAppMemCap) {
|
|
||||||
sAppMemUsed[appId] -= oldSize;
|
|
||||||
sAppMemUsed[appId] += (uint32_t)size;
|
|
||||||
}
|
|
||||||
|
|
||||||
newHdr->size = (uint32_t)size;
|
|
||||||
return newHdr + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dvxMemSnapshotLoad(int32_t appId) {
|
void dvxMemSnapshotLoad(int32_t appId) {
|
||||||
(void)appId;
|
if (appId >= 0) {
|
||||||
|
dvxMemGrow(appId);
|
||||||
|
|
||||||
|
if (appId < sAppMemCap) {
|
||||||
|
sAppMemAtLoad[appId] = dpmiGetFreeKb();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1262,13 +1206,25 @@ uint32_t dvxMemGetAppUsage(int32_t appId) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sAppMemUsed[appId];
|
uint32_t atLoad = sAppMemAtLoad[appId];
|
||||||
|
|
||||||
|
if (atLoad == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t nowFree = dpmiGetFreeKb();
|
||||||
|
|
||||||
|
if (nowFree >= atLoad) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (atLoad - nowFree) * 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void dvxMemResetApp(int32_t appId) {
|
void dvxMemResetApp(int32_t appId) {
|
||||||
if (appId >= 0 && appId < sAppMemCap) {
|
if (appId >= 0 && appId < sAppMemCap) {
|
||||||
sAppMemUsed[appId] = 0;
|
sAppMemAtLoad[appId] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list
|
||||||
WidgetT *sDragWidget = NULL; // widget being dragged (any drag type)
|
WidgetT *sDragWidget = NULL; // widget being dragged (any drag type)
|
||||||
|
|
||||||
// Shared clipboard -- process-wide, not per-widget.
|
// Shared clipboard -- process-wide, not per-widget.
|
||||||
#define CLIPBOARD_MAX 4096
|
static char *sClipboard = NULL;
|
||||||
static char sClipboard[CLIPBOARD_MAX];
|
static int32_t sClipboardLen = 0;
|
||||||
static int32_t sClipboardLen = 0;
|
static int32_t sClipboardCap = 0;
|
||||||
|
|
||||||
// Multi-click state (used by widgetEvent.c for universal dbl-click detection)
|
// Multi-click state (used by widgetEvent.c for universal dbl-click detection)
|
||||||
static clock_t sLastClickTime = 0;
|
static clock_t sLastClickTime = 0;
|
||||||
|
|
@ -69,8 +69,16 @@ void clipboardCopy(const char *text, int32_t len) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len > CLIPBOARD_MAX - 1) {
|
if (len + 1 > sClipboardCap) {
|
||||||
len = CLIPBOARD_MAX - 1;
|
int32_t newCap = len + 1;
|
||||||
|
char *newBuf = (char *)realloc(sClipboard, newCap);
|
||||||
|
|
||||||
|
if (!newBuf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sClipboard = newBuf;
|
||||||
|
sClipboardCap = newCap;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(sClipboard, text, len);
|
memcpy(sClipboard, text, len);
|
||||||
|
|
@ -97,7 +105,7 @@ const char *clipboardGet(int32_t *outLen) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t clipboardMaxLen(void) {
|
int32_t clipboardMaxLen(void) {
|
||||||
return CLIPBOARD_MAX - 1;
|
return 65536;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -378,73 +386,62 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
//
|
//
|
||||||
// Shift+Tab navigation: finds the previous focusable widget.
|
// Shift+Tab navigation: finds the previous focusable widget.
|
||||||
// Unlike findNextFocusable which can short-circuit during traversal,
|
// Collects all focusable widgets via DFS, then returns the one
|
||||||
// finding the PREVIOUS widget requires knowing the full order.
|
// before 'before' (with wraparound). Uses stb_ds dynamic arrays
|
||||||
// So this collects all focusable widgets into an array, finds the
|
// so there's no fixed limit on widget count or tree depth.
|
||||||
// target's index, and returns index-1 (with wraparound).
|
|
||||||
//
|
|
||||||
// The explicit stack-based DFS (rather than recursion) is used here
|
|
||||||
// because we need to push children in reverse order to get the same
|
|
||||||
// left-to-right depth-first ordering as the recursive version.
|
|
||||||
// Fixed-size arrays (128 widgets, 64 stack depth) are adequate for
|
|
||||||
// any reasonable dialog layout and avoid dynamic allocation.
|
|
||||||
|
|
||||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
|
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
|
||||||
WidgetT *list[128];
|
WidgetT **list = NULL;
|
||||||
int32_t count = 0;
|
WidgetT **stack = NULL;
|
||||||
|
|
||||||
// Collect all focusable widgets via depth-first traversal
|
arrput(stack, root);
|
||||||
WidgetT *stack[64];
|
|
||||||
int32_t top = 0;
|
|
||||||
stack[top++] = root;
|
|
||||||
|
|
||||||
while (top > 0) {
|
while (arrlen(stack) > 0) {
|
||||||
WidgetT *w = stack[--top];
|
WidgetT *w = stack[arrlen(stack) - 1];
|
||||||
|
arrsetlen(stack, arrlen(stack) - 1);
|
||||||
|
|
||||||
if (!w->visible || !w->enabled) {
|
if (!w->visible || !w->enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (widgetIsFocusable(w->type) && count < 128) {
|
if (widgetIsFocusable(w->type)) {
|
||||||
list[count++] = w;
|
arrput(list, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push children in reverse order so first child is processed first
|
// Push children in reverse order so first child is processed first
|
||||||
WidgetT *children[64];
|
// Walk to end of sibling list, then push backwards
|
||||||
int32_t childCount = 0;
|
WidgetT **children = NULL;
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||||
if (childCount < 64) {
|
arrput(children, c);
|
||||||
children[childCount++] = c;
|
}
|
||||||
|
|
||||||
|
for (int32_t i = arrlen(children) - 1; i >= 0; i--) {
|
||||||
|
arrput(stack, children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetT *result = NULL;
|
||||||
|
int32_t count = arrlen(list);
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
int32_t idx = -1;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < count; i++) {
|
||||||
|
if (list[i] == before) {
|
||||||
|
idx = i;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = childCount - 1; i >= 0; i--) {
|
result = (idx <= 0) ? list[count - 1] : list[idx - 1];
|
||||||
if (top < 64) {
|
|
||||||
stack[top++] = children[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count == 0) {
|
arrfree(list);
|
||||||
return NULL;
|
arrfree(stack);
|
||||||
}
|
return result;
|
||||||
|
|
||||||
// Find 'before' in the list
|
|
||||||
int32_t idx = -1;
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < count; i++) {
|
|
||||||
if (list[i] == before) {
|
|
||||||
idx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx <= 0) {
|
|
||||||
return list[count - 1]; // Wrap to last
|
|
||||||
}
|
|
||||||
|
|
||||||
return list[idx - 1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,14 +172,8 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attribute allocations during event handling to the owning app
|
// Dispatch to per-widget onKey handler via vtable
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
|
||||||
int32_t prevAppId = ctx->currentAppId;
|
|
||||||
ctx->currentAppId = win->appId;
|
|
||||||
|
|
||||||
wclsOnKey(focus, key, mod);
|
wclsOnKey(focus, key, mod);
|
||||||
|
|
||||||
ctx->currentAppId = prevAppId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -201,8 +195,6 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
// are also in content-buffer space (set during layout), so no
|
// are also in content-buffer space (set during layout), so no
|
||||||
// coordinate transform is needed for hit testing.
|
// coordinate transform is needed for hit testing.
|
||||||
|
|
||||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
|
|
||||||
|
|
||||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
WidgetT *root = win->widgetRoot;
|
WidgetT *root = win->widgetRoot;
|
||||||
sClosedPopup = NULL;
|
sClosedPopup = NULL;
|
||||||
|
|
@ -210,19 +202,6 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
if (!root) {
|
if (!root) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attribute allocations during event handling to the owning app
|
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
|
||||||
int32_t prevAppId = ctx->currentAppId;
|
|
||||||
ctx->currentAppId = win->appId;
|
|
||||||
|
|
||||||
widgetOnMouseInner(win, root, x, y, buttons);
|
|
||||||
|
|
||||||
ctx->currentAppId = prevAppId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons) {
|
|
||||||
// Close popups from other windows
|
// Close popups from other windows
|
||||||
if (sOpenPopup && sOpenPopup->window != win) {
|
if (sOpenPopup && sOpenPopup->window != win) {
|
||||||
wclsClosePopup(sOpenPopup);
|
wclsClosePopup(sOpenPopup);
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,15 @@ static int32_t allocSlot(void) {
|
||||||
// this function never completes -- the task is killed externally via
|
// this function never completes -- the task is killed externally via
|
||||||
// shellForceKillApp + tsKill.
|
// shellForceKillApp + tsKill.
|
||||||
static void appTaskWrapper(void *arg) {
|
static void appTaskWrapper(void *arg) {
|
||||||
ShellAppT *app = (ShellAppT *)arg;
|
// Look up the app by ID, not by pointer. The sApps array may have
|
||||||
|
// been reallocated between tsCreate and the first time this task runs
|
||||||
app->dxeCtx.shellCtx->currentAppId = app->appId;
|
// (e.g. another app was loaded in the meantime), which would
|
||||||
app->entryFn(&app->dxeCtx);
|
// invalidate a direct ShellAppT pointer.
|
||||||
app->dxeCtx.shellCtx->currentAppId = 0;
|
int32_t appId = (int32_t)(intptr_t)arg;
|
||||||
|
ShellAppT *app = &sApps[appId];
|
||||||
|
app->dxeCtx->shellCtx->currentAppId = app->appId;
|
||||||
|
app->entryFn(app->dxeCtx);
|
||||||
|
app->dxeCtx->shellCtx->currentAppId = 0;
|
||||||
|
|
||||||
// App returned from its main loop -- mark for reaping
|
// App returned from its main loop -- mark for reaping
|
||||||
app->state = AppStateTerminatingE;
|
app->state = AppStateTerminatingE;
|
||||||
|
|
@ -261,6 +265,8 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupTempFile(app);
|
cleanupTempFile(app);
|
||||||
|
free(app->dxeCtx);
|
||||||
|
app->dxeCtx = NULL;
|
||||||
dvxMemResetApp(app->appId);
|
dvxMemResetApp(app->appId);
|
||||||
app->state = AppStateFreeE;
|
app->state = AppStateFreeE;
|
||||||
dvxLog("Shell: force-killed app '%s'", app->name);
|
dvxLog("Shell: force-killed app '%s'", app->name);
|
||||||
|
|
@ -399,29 +405,39 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
app->shutdownFn = shutdown;
|
app->shutdownFn = shutdown;
|
||||||
app->state = AppStateLoadedE;
|
app->state = AppStateLoadedE;
|
||||||
|
|
||||||
// Set up the context passed to appMain
|
// Heap-allocate the DxeAppContextT so its address is stable across
|
||||||
app->dxeCtx.shellCtx = ctx;
|
// sApps reallocs. Apps save this pointer in their static globals.
|
||||||
app->dxeCtx.appId = id;
|
app->dxeCtx = (DxeAppContextT *)calloc(1, sizeof(DxeAppContextT));
|
||||||
|
|
||||||
|
if (!app->dxeCtx) {
|
||||||
|
ctx->currentAppId = 0;
|
||||||
|
dlclose(handle);
|
||||||
|
app->state = AppStateFreeE;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
app->dxeCtx->shellCtx = ctx;
|
||||||
|
app->dxeCtx->appId = id;
|
||||||
|
|
||||||
// Derive app directory from path (everything up to the last separator).
|
// Derive app directory from path (everything up to the last separator).
|
||||||
// This lets apps load resources relative to their own location rather
|
// This lets apps load resources relative to their own location rather
|
||||||
// than the shell's working directory.
|
// than the shell's working directory.
|
||||||
snprintf(app->dxeCtx.appDir, sizeof(app->dxeCtx.appDir), "%s", path);
|
snprintf(app->dxeCtx->appDir, sizeof(app->dxeCtx->appDir), "%s", path);
|
||||||
|
|
||||||
char *sep = platformPathDirEnd(app->dxeCtx.appDir);
|
char *sep = platformPathDirEnd(app->dxeCtx->appDir);
|
||||||
|
|
||||||
if (sep) {
|
if (sep) {
|
||||||
*sep = '\0';
|
*sep = '\0';
|
||||||
} else {
|
} else {
|
||||||
app->dxeCtx.appDir[0] = '.';
|
app->dxeCtx->appDir[0] = '.';
|
||||||
app->dxeCtx.appDir[1] = '\0';
|
app->dxeCtx->appDir[1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive config directory: replace the APPS/ prefix with CONFIG/.
|
// Derive config directory: replace the APPS/ prefix with CONFIG/.
|
||||||
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
|
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
|
||||||
// If the path doesn't start with "apps/" or "APPS/", fall back to
|
// If the path doesn't start with "apps/" or "APPS/", fall back to
|
||||||
// "CONFIG/<appname>" using the descriptor name.
|
// "CONFIG/<appname>" using the descriptor name.
|
||||||
const char *appDirStr = app->dxeCtx.appDir;
|
const char *appDirStr = app->dxeCtx->appDir;
|
||||||
const char *appsPrefix = NULL;
|
const char *appsPrefix = NULL;
|
||||||
|
|
||||||
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
|
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
|
||||||
|
|
@ -431,9 +447,9 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appsPrefix && appsPrefix[0]) {
|
if (appsPrefix && appsPrefix[0]) {
|
||||||
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", appsPrefix);
|
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", appsPrefix);
|
||||||
} else {
|
} else {
|
||||||
snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", desc->name);
|
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", desc->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launch. Set currentAppId before any app code runs so that
|
// Launch. Set currentAppId before any app code runs so that
|
||||||
|
|
@ -444,12 +460,14 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
uint32_t stackSize = desc->stackSize > 0 ? (uint32_t)desc->stackSize : TS_DEFAULT_STACK_SIZE;
|
uint32_t stackSize = desc->stackSize > 0 ? (uint32_t)desc->stackSize : TS_DEFAULT_STACK_SIZE;
|
||||||
int32_t priority = desc->priority;
|
int32_t priority = desc->priority;
|
||||||
|
|
||||||
int32_t taskId = tsCreate(desc->name, appTaskWrapper, app, stackSize, priority);
|
int32_t taskId = tsCreate(desc->name, appTaskWrapper, (void *)(intptr_t)id, stackSize, priority);
|
||||||
|
|
||||||
if (taskId < 0) {
|
if (taskId < 0) {
|
||||||
ctx->currentAppId = 0;
|
ctx->currentAppId = 0;
|
||||||
dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR);
|
dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR);
|
||||||
dlclose(handle);
|
dlclose(handle);
|
||||||
|
free(app->dxeCtx);
|
||||||
|
app->dxeCtx = NULL;
|
||||||
app->state = AppStateFreeE;
|
app->state = AppStateFreeE;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -460,7 +478,7 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
// The app creates its windows and returns. From this point on,
|
// The app creates its windows and returns. From this point on,
|
||||||
// the app lives entirely through event callbacks dispatched by
|
// the app lives entirely through event callbacks dispatched by
|
||||||
// the shell's dvxUpdate loop. No separate task or stack needed.
|
// the shell's dvxUpdate loop. No separate task or stack needed.
|
||||||
app->entryFn(&app->dxeCtx);
|
app->entryFn(app->dxeCtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->currentAppId = 0;
|
ctx->currentAppId = 0;
|
||||||
|
|
@ -511,6 +529,8 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanupTempFile(app);
|
cleanupTempFile(app);
|
||||||
|
free(app->dxeCtx);
|
||||||
|
app->dxeCtx = NULL;
|
||||||
dvxMemResetApp(app->appId);
|
dvxMemResetApp(app->appId);
|
||||||
dvxLog("Shell: reaped app '%s'", app->name);
|
dvxLog("Shell: reaped app '%s'", app->name);
|
||||||
app->state = AppStateFreeE;
|
app->state = AppStateFreeE;
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ typedef struct {
|
||||||
uint32_t mainTaskId; // task ID if hasMainLoop, else 0
|
uint32_t mainTaskId; // task ID if hasMainLoop, else 0
|
||||||
int32_t (*entryFn)(DxeAppContextT *);
|
int32_t (*entryFn)(DxeAppContextT *);
|
||||||
void (*shutdownFn)(void); // may be NULL
|
void (*shutdownFn)(void); // may be NULL
|
||||||
DxeAppContextT dxeCtx; // context passed to appMain
|
DxeAppContextT *dxeCtx; // heap-allocated; address stable across sApps realloc
|
||||||
} ShellAppT;
|
} ShellAppT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -374,7 +374,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
||||||
// Ctrl+Z -- undo
|
// Ctrl+Z -- undo
|
||||||
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
|
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
|
||||||
// Swap current and undo
|
// Swap current and undo
|
||||||
char tmpBuf[clipboardMaxLen() + 1];
|
char tmpBuf[*pLen + 1];
|
||||||
int32_t tmpLen = *pLen;
|
int32_t tmpLen = *pLen;
|
||||||
int32_t tmpCursor = *pCursor;
|
int32_t tmpCursor = *pCursor;
|
||||||
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ typedef struct {
|
||||||
#define TEXTAREA_SB_W 14
|
#define TEXTAREA_SB_W 14
|
||||||
#define TEXTAREA_MIN_ROWS 4
|
#define TEXTAREA_MIN_ROWS 4
|
||||||
#define TEXTAREA_MIN_COLS 20
|
#define TEXTAREA_MIN_COLS 20
|
||||||
#define CLIPBOARD_MAX 4096
|
|
||||||
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
|
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
|
||||||
#define CURSOR_BLINK_MS 250
|
#define CURSOR_BLINK_MS 250
|
||||||
|
|
||||||
|
|
@ -950,7 +949,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (key == 26) {
|
if (key == 26) {
|
||||||
if (ta->undoBuf && ta->undoLen >= 0) {
|
if (ta->undoBuf && ta->undoLen >= 0) {
|
||||||
// Swap current and undo
|
// Swap current and undo
|
||||||
char tmpBuf[CLIPBOARD_MAX];
|
char tmpBuf[*pLen + 1];
|
||||||
int32_t tmpLen = *pLen;
|
int32_t tmpLen = *pLen;
|
||||||
int32_t tmpCursor = CUR_OFF();
|
int32_t tmpCursor = CUR_OFF();
|
||||||
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue