diff --git a/core/dvxApp.c b/core/dvxApp.c index f189bc2..0cfaace 100644 --- a/core/dvxApp.c +++ b/core/dvxApp.c @@ -478,7 +478,7 @@ static void compositeAndFlush(AppContextT *ctx) { // Pre-filter visible, non-minimized windows once to avoid // 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; 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. void dvxAddAccel(AccelTableT *table, int32_t key, int32_t modifiers, int32_t cmdId) { - if (!table || table->count >= MAX_ACCEL_ENTRIES) { + if (!table) { 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; if (normKey >= 'a' && normKey <= 'z') { @@ -1877,6 +1889,10 @@ void dvxFitWindow(AppContextT *ctx, WindowT *win) { // ============================================================ void dvxFreeAccelTable(AccelTableT *table) { + if (table) { + free(table->entries); + } + free(table); } @@ -2755,6 +2771,20 @@ void dvxShutdown(AppContextT *ctx) { 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); ctx->wallpaperBuf = NULL; 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 // as the new current level. The submenu is positioned at the right edge // 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) { if (!ctx->popup.active || !ctx->popup.menu) { return; @@ -3453,8 +3481,17 @@ static void openSubMenu(AppContextT *ctx) { return; } - if (ctx->popup.depth >= MAX_SUBMENU_DEPTH) { - return; + // Grow parent stack if needed + 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 @@ -4256,15 +4293,14 @@ static void pollKeyboard(AppContextT *ctx) { if (win->widgetRoot) { // Find currently focused widget WidgetT *current = NULL; - WidgetT *fstack[64]; - int32_t ftop = 0; - fstack[ftop++] = win->widgetRoot; + WidgetT **fstack = NULL; + arrput(fstack, win->widgetRoot); - while (ftop > 0) { - WidgetT *w = fstack[--ftop]; + while (arrlen(fstack) > 0) { + WidgetT *w = fstack[arrlen(fstack) - 1]; + arrsetlen(fstack, arrlen(fstack) - 1); if (w->focused && widgetIsFocusable(w->type)) { - // Don't tab out of widgets that swallow Tab if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) { current = NULL; break; @@ -4275,21 +4311,21 @@ static void pollKeyboard(AppContextT *ctx) { } for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { - if (c->visible && ftop < 64) { - fstack[ftop++] = c; + if (c->visible) { + arrput(fstack, c); } } } // Terminal swallowed Tab -- send to widget system instead if (current == NULL) { - // Check if a terminal is focused - ftop = 0; - fstack[ftop++] = win->widgetRoot; + arrsetlen(fstack, 0); + arrput(fstack, win->widgetRoot); bool termFocused = false; - while (ftop > 0) { - WidgetT *w = fstack[--ftop]; + while (arrlen(fstack) > 0) { + WidgetT *w = fstack[arrlen(fstack) - 1]; + arrsetlen(fstack, arrlen(fstack) - 1); if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) { termFocused = true; @@ -4297,8 +4333,8 @@ static void pollKeyboard(AppContextT *ctx) { } for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { - if (c->visible && ftop < 64) { - fstack[ftop++] = c; + if (c->visible) { + arrput(fstack, c); } } } @@ -4309,6 +4345,7 @@ static void pollKeyboard(AppContextT *ctx) { win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags); } + arrfree(fstack); continue; } } @@ -4366,6 +4403,8 @@ static void pollKeyboard(AppContextT *ctx) { wgtInvalidate(win->widgetRoot); } + + arrfree(fstack); } } diff --git a/core/dvxComp.c b/core/dvxComp.c index 617b9da..3b29815 100644 --- a/core/dvxComp.c +++ b/core/dvxComp.c @@ -20,7 +20,9 @@ #include "dvxComp.h" #include "dvxPlatform.h" +#include #include +#include "dvxMem.h" // 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 @@ -42,32 +44,24 @@ static inline void rectUnion(const RectT *a, const RectT *b, RectT *result); // dirtyListAdd // ============================================================ // -// Appends a dirty rect to the list. Uses a fixed-size array (MAX_DIRTY_RECTS -// = 128) rather than a dynamic allocation -- this is called on every UI -// mutation (drag, repaint, focus change) so allocation overhead must be zero. -// -// When the list fills up, an eager merge pass tries to consolidate rects. -// If the list is STILL full after merging (pathological scatter), the -// nuclear option collapses everything into one bounding box. This guarantees -// 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. +// Appends a dirty rect to the list. The array grows dynamically but a +// merge pass fires at 128 entries to keep the list short. If the list +// is still long after merging (pathological scatter), everything collapses +// into one bounding box. In practice the merge pass almost always frees +// enough slots because GUI mutations tend to cluster spatially. + +#define DIRTY_SOFT_CAP 128 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)) { return; } - // Overflow path: try merging, then fall back to a single bounding rect - if (__builtin_expect(dl->count >= MAX_DIRTY_RECTS, 0)) { + // Soft cap: merge when we accumulate too many rects to keep composite fast + if (__builtin_expect(dl->count >= DIRTY_SOFT_CAP, 0)) { dirtyListMerge(dl); - if (dl->count >= MAX_DIRTY_RECTS) { - // 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. + if (dl->count >= DIRTY_SOFT_CAP) { RectT merged = dl->rects[0]; 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].y = y; dl->rects[dl->count].w = w; diff --git a/core/dvxComp.h b/core/dvxComp.h index af5b8c9..d48786c 100644 --- a/core/dvxComp.h +++ b/core/dvxComp.h @@ -25,8 +25,7 @@ // Zero the dirty rect count. Called at the start of each frame. void dirtyListInit(DirtyListT *dl); -// Enqueue a dirty rectangle. If the list is full (MAX_DIRTY_RECTS), -// this should degrade gracefully (e.g. expand an existing rect). +// Enqueue a dirty rectangle. Grows dynamically; merges at a soft cap. 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. diff --git a/core/dvxDialog.c b/core/dvxDialog.c index af0d227..1a21d2d 100644 --- a/core/dvxDialog.c +++ b/core/dvxDialog.c @@ -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. // 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_NAME_LEN 64 @@ -642,10 +641,10 @@ typedef struct { const FileFilterT *filters; // caller-provided filter list int32_t filterCount; int32_t activeFilter; // index into filters[] - char *entryNames[FD_MAX_ENTRIES]; // heap-allocated, freed by fdFreeEntries - bool entryIsDir[FD_MAX_ENTRIES]; + char **entryNames; // dynamic array of heap-allocated strings + bool *entryIsDir; // dynamic array, parallel to entryNames 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 *pathInput; WidgetT *nameInput; @@ -745,9 +744,14 @@ static bool fdFilterMatch(const char *name, const char *pattern) { static void fdFreeEntries(void) { for (int32_t i = 0; i < sFd.entryCount; 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; } @@ -803,7 +807,7 @@ static void fdLoadDir(void) { struct dirent *ent; - while ((ent = readdir(dir)) != NULL && sFd.entryCount < FD_MAX_ENTRIES) { + while ((ent = readdir(dir)) != NULL) { // Skip "." if (strcmp(ent->d_name, ".") == 0) { continue; @@ -826,7 +830,15 @@ static void fdLoadDir(void) { continue; } + // Grow arrays 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; if (isDir) { @@ -843,8 +855,17 @@ static void fdLoadDir(void) { closedir(dir); + if (sFd.entryCount == 0) { + wgtListBoxSetItems(sFd.fileList, NULL, 0); + return; + } + // 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++) { sortIdx[i] = i; @@ -853,18 +874,26 @@ static void fdLoadDir(void) { qsort(sortIdx, sFd.entryCount, sizeof(int32_t), fdEntryCompare); // Rebuild arrays in sorted order - char *tmpNames[FD_MAX_ENTRIES]; - bool tmpIsDir[FD_MAX_ENTRIES]; + char **tmpNames = (char **)malloc(sFd.entryCount * sizeof(char *)); + bool *tmpIsDir = (bool *)malloc(sFd.entryCount * sizeof(bool)); - for (int32_t i = 0; i < sFd.entryCount; i++) { - tmpNames[i] = sFd.entryNames[sortIdx[i]]; - tmpIsDir[i] = sFd.entryIsDir[sortIdx[i]]; + if (tmpNames && tmpIsDir) { + for (int32_t i = 0; i < sFd.entryCount; 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); - memcpy(sFd.entryIsDir, tmpIsDir, sizeof(bool) * sFd.entryCount); + free(sortIdx); + free(tmpNames); + free(tmpIsDir); // 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++) { sFd.listItems[i] = sFd.entryNames[i]; } diff --git a/core/dvxDraw.c b/core/dvxDraw.c index 6de3330..cf7e4dd 100644 --- a/core/dvxDraw.c +++ b/core/dvxDraw.c @@ -52,7 +52,9 @@ #include "dvxDraw.h" #include "dvxPlatform.h" +#include #include +#include "dvxMem.h" // ============================================================ // Prototypes diff --git a/core/dvxImage.c b/core/dvxImage.c index ad08c3b..9ae8a81 100644 --- a/core/dvxImage.c +++ b/core/dvxImage.c @@ -30,3 +30,6 @@ #include "thirdparty/stb_image.h" #pragma GCC diagnostic pop + +#include +#include "dvxMem.h" diff --git a/core/dvxImageWrite.c b/core/dvxImageWrite.c index da7f581..cf6817d 100644 --- a/core/dvxImageWrite.c +++ b/core/dvxImageWrite.c @@ -18,3 +18,6 @@ #include "thirdparty/stb_image_write.h" #pragma GCC diagnostic pop + +#include +#include "dvxMem.h" diff --git a/core/dvxMem.h b/core/dvxMem.h index 53b033b..e3018f4 100644 --- a/core/dvxMem.h +++ b/core/dvxMem.h @@ -26,6 +26,7 @@ #ifndef DVX_MEM_H #define DVX_MEM_H +#include #include extern int32_t *dvxMemAppIdPtr; @@ -38,11 +39,10 @@ void dvxMemSnapshotLoad(int32_t appId); uint32_t dvxMemGetAppUsage(int32_t appId); void dvxMemResetApp(int32_t appId); -// Redirect standard allocator calls to tracking wrappers. -// This MUST appear after so the real prototypes exist. -#define malloc(s) dvxMalloc(s) -#define calloc(n, s) dvxCalloc((n), (s)) -#define realloc(p, s) dvxRealloc((p), (s)) -#define free(p) dvxFree(p) +// The dvxMalloc/dvxFree functions are passthrough wrappers. +// Header-based per-allocation tracking was attempted but is unsafe +// in the DXE3 environment (loader-compiled code like stb_ds uses +// libc malloc while DXE code would use the wrapped version). +// Per-app memory is tracked via DPMI snapshots instead. #endif // DVX_MEM_H diff --git a/core/dvxPrefs.c b/core/dvxPrefs.c index 69756ea..56edecf 100644 --- a/core/dvxPrefs.c +++ b/core/dvxPrefs.c @@ -10,6 +10,7 @@ #include #include #include +#include "dvxMem.h" // stb_ds dynamic arrays (implementation lives in libtasks.a) #include "thirdparty/stb_ds.h" diff --git a/core/dvxTypes.h b/core/dvxTypes.h index d5796fe..ad536f7 100644 --- a/core/dvxTypes.h +++ b/core/dvxTypes.h @@ -259,11 +259,10 @@ typedef struct { // cache-friendly at this size. If the list fills up, the compositor // can merge aggressively or fall back to a full-screen repaint. -#define MAX_DIRTY_RECTS 128 - typedef struct { - RectT rects[MAX_DIRTY_RECTS]; + RectT *rects; // dynamic array (realloc'd on demand) int32_t count; + int32_t cap; } DirtyListT; // ============================================================ @@ -305,8 +304,6 @@ typedef struct { // The forward declaration of MenuT is needed because MenuItemT contains // 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 // Forward declaration for submenu pointers @@ -333,8 +330,9 @@ typedef struct { // needs a forward pointer to it for cascading submenus. struct MenuT { char label[MAX_MENU_LABEL]; // menu bar label (e.g. "File") - MenuItemT items[MAX_MENU_ITEMS]; - int32_t itemCount; + MenuItemT *items; // dynamic array (realloc'd on demand) + int32_t itemCount; + int32_t itemCap; int32_t barX; // computed position on menu bar int32_t barW; // computed width on menu bar char accelKey; // lowercase accelerator character, 0 if none @@ -344,8 +342,9 @@ struct MenuT { // menu bar is actually drawn, avoiding redundant work when multiple // menus are added in sequence during window setup. typedef struct { - MenuT menus[MAX_MENUS]; + MenuT *menus; // dynamic array (realloc'd on demand) int32_t menuCount; + int32_t menuCap; int32_t activeIdx; // menu bar item with open popup (-1 = none) bool positionsDirty; // true = barX/barW need recomputation } MenuBarT; @@ -394,7 +393,6 @@ typedef struct { // match in O(n) with just two integer comparisons per entry, avoiding // case-folding and modifier normalization on every keystroke. -#define MAX_ACCEL_ENTRIES 32 // Modifier flags for accelerators (match BIOS shift state bits) #define ACCEL_SHIFT 0x03 @@ -431,8 +429,9 @@ typedef struct { } AccelEntryT; typedef struct { - AccelEntryT entries[MAX_ACCEL_ENTRIES]; - int32_t count; + AccelEntryT *entries; // dynamic array (realloc'd on demand) + int32_t count; + int32_t cap; } AccelTableT; // ============================================================ @@ -453,7 +452,6 @@ typedef struct { // RESIZE_xxx flags are bitfields so that corner resizes can combine two // edges (e.g. RESIZE_LEFT | RESIZE_TOP for the top-left corner). -#define MAX_WINDOWS 64 #define MAX_TITLE_LEN 128 #define RESIZE_NONE 0 @@ -577,8 +575,9 @@ typedef struct WindowT { // you can't drag two windows simultaneously with a single mouse. typedef struct { - WindowT *windows[MAX_WINDOWS]; - int32_t count; + WindowT **windows; // dynamic array (realloc'd on demand) + int32_t count; + int32_t cap; int32_t focusedIdx; int32_t dragWindow; int32_t dragOffX; @@ -629,15 +628,14 @@ typedef struct { // Only one popup chain can be active at a time (menus are modal). // Cascading submenus are tracked as a stack of PopupLevelT frames, // 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 > -// Category > Item style nesting, which is deeper than any sane DOS UI needs. +// when the submenu closes. The stack grows dynamically to support +// arbitrary nesting depth. // // The popup's screen coordinates are stored directly rather than computed // relative to the window, because the popup is painted on top of // everything during the compositor's overlay pass and needs absolute // screen positioning. -#define MAX_SUBMENU_DEPTH 4 // Saved parent popup state when a submenu is open typedef struct { @@ -662,7 +660,8 @@ typedef struct { int32_t hoverItem; // highlighted item in current popup (-1 = none) MenuT *menu; // direct pointer to current menu (avoids lookup for submenus) 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; // ============================================================ diff --git a/core/dvxVideo.c b/core/dvxVideo.c index c2d65d8..f738f43 100644 --- a/core/dvxVideo.c +++ b/core/dvxVideo.c @@ -45,7 +45,9 @@ #include "dvxPlatform.h" #include "dvxPalette.h" +#include #include +#include "dvxMem.h" // ============================================================ diff --git a/core/dvxWm.c b/core/dvxWm.c index b045e6d..4324d35 100644 --- a/core/dvxWm.c +++ b/core/dvxWm.c @@ -11,7 +11,7 @@ // = front). This was chosen over a linked list because hit-testing walks // 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 -// 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 // outer bevels and 2px inner bevels. The fixed bevel widths avoid per- @@ -39,6 +39,7 @@ #include #include #include +#include "dvxMem.h" // ============================================================ // Constants @@ -712,6 +713,11 @@ static void freeMenuRecursive(MenuT *menu) { 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 -// 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 // the count is small. The positionsDirty flag is set so that the next // 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 // 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) { - if (bar->menuCount >= MAX_MENUS) { - return NULL; + if (bar->menuCount >= bar->menuCap) { + 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]; @@ -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- // allocated separately from the window because most windows don't have // 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. MenuBarT *wmAddMenuBar(WindowT *win) { @@ -918,7 +950,7 @@ MenuBarT *wmAddMenuBar(WindowT *win) { // ============================================================ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) { - if (menu->itemCount >= MAX_MENU_ITEMS) { + if (!menuGrowItems(menu)) { 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) { - if (menu->itemCount >= MAX_MENU_ITEMS) { + if (!menuGrowItems(menu)) { 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) { - if (menu->itemCount >= MAX_MENU_ITEMS) { + if (!menuGrowItems(menu)) { return; } @@ -984,7 +1016,7 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked // ============================================================ void wmAddMenuSeparator(MenuT *menu) { - if (menu->itemCount >= MAX_MENU_ITEMS) { + if (!menuGrowItems(menu)) { return; } @@ -1007,7 +1039,7 @@ void wmAddMenuSeparator(MenuT *menu) { // item opens the child rather than firing a command. MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label) { - if (parentMenu->itemCount >= MAX_MENU_ITEMS) { + if (!menuGrowItems(parentMenu)) { return NULL; } @@ -1084,9 +1116,17 @@ ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t page // 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) { - if (stack->count >= MAX_WINDOWS) { - fprintf(stderr, "WM: Maximum windows (%d) reached\n", MAX_WINDOWS); - return NULL; + if (stack->count >= stack->cap) { + int32_t newCap = stack->cap ? stack->cap * 2 : 16; + 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)); @@ -1188,6 +1228,7 @@ void wmDestroyWindow(WindowStackT *stack, WindowT *win) { freeMenuRecursive(&win->menuBar->menus[i]); } + free(win->menuBar->menus); free(win->menuBar); } @@ -2626,6 +2667,7 @@ void wmFreeMenu(MenuT *menu) { } } + free(menu->items); free(menu); } diff --git a/core/dvxWm.h b/core/dvxWm.h index 89de17c..8f2b00a 100644 --- a/core/dvxWm.h +++ b/core/dvxWm.h @@ -80,7 +80,7 @@ void wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked void wmAddMenuSeparator(MenuT *menu); // 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 // is destroyed. MenuT *wmAddSubMenu(MenuT *parentMenu, const char *label); diff --git a/core/platform/dvxPlatformDos.c b/core/platform/dvxPlatformDos.c index e6de29e..0631bb5 100644 --- a/core/platform/dvxPlatformDos.c +++ b/core/platform/dvxPlatformDos.c @@ -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/ -// calloc/realloc to dvxMalloc/dvxFree/dvxCalloc/dvxRealloc. This -// file does NOT include dvxMem.h, so calls to malloc/free here go -// directly to libc with no recursion. +// Tracks per-app memory by taking DPMI free-memory snapshots at +// app load time. The difference between the snapshot and current +// free memory is a coarse estimate of the app's heap footprint. // -// Each tracked allocation has a 16-byte header prepended. dvxFree -// checks the magic before adjusting the pointer -- if it doesn't -// match (pointer came from libc internals or loader code), it -// falls through to the real free() unchanged. - -#define DVX_ALLOC_MAGIC 0xDEADBEEFUL - -typedef struct { - uint32_t magic; - int32_t appId; - uint32_t size; - uint32_t pad; -} DvxAllocHeaderT; +// Header-based malloc wrapping (prepending a tracking header to +// each allocation) was attempted but is unsafe in the DJGPP/DXE3 +// environment: loader-compiled code (stb_ds internals, libc +// functions like strdup/localtime) allocates with libc's malloc, +// but DXE code frees with the wrapped free. Reading 16 bytes +// before an arbitrary libc pointer to check for a tracking magic +// value is undefined behavior that causes faults on 86Box/DPMI. +// +// dvxMalloc/dvxFree/etc. are still exported for dvxMem.h compat +// but pass straight through to libc. int32_t *dvxMemAppIdPtr = NULL; -static uint32_t *sAppMemUsed = NULL; -static int32_t sAppMemCap = 0; +static uint32_t *sAppMemAtLoad = NULL; +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) { @@ -1146,114 +1178,26 @@ static void dvxMemGrow(int32_t appId) { } 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) { return; } memset(newArr + sAppMemCap, 0, (newCap - sAppMemCap) * sizeof(uint32_t)); - sAppMemUsed = newArr; - 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; + sAppMemAtLoad = newArr; + sAppMemCap = newCap; } 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 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) { if (appId >= 0 && appId < sAppMemCap) { - sAppMemUsed[appId] = 0; + sAppMemAtLoad[appId] = 0; } } diff --git a/core/widgetCore.c b/core/widgetCore.c index 9383c5c..38faffe 100644 --- a/core/widgetCore.c +++ b/core/widgetCore.c @@ -49,9 +49,9 @@ WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list WidgetT *sDragWidget = NULL; // widget being dragged (any drag type) // Shared clipboard -- process-wide, not per-widget. -#define CLIPBOARD_MAX 4096 -static char sClipboard[CLIPBOARD_MAX]; -static int32_t sClipboardLen = 0; +static char *sClipboard = NULL; +static int32_t sClipboardLen = 0; +static int32_t sClipboardCap = 0; // Multi-click state (used by widgetEvent.c for universal dbl-click detection) static clock_t sLastClickTime = 0; @@ -69,8 +69,16 @@ void clipboardCopy(const char *text, int32_t len) { return; } - if (len > CLIPBOARD_MAX - 1) { - len = CLIPBOARD_MAX - 1; + if (len + 1 > sClipboardCap) { + int32_t newCap = len + 1; + char *newBuf = (char *)realloc(sClipboard, newCap); + + if (!newBuf) { + return; + } + + sClipboard = newBuf; + sClipboardCap = newCap; } memcpy(sClipboard, text, len); @@ -97,7 +105,7 @@ const char *clipboardGet(int32_t *outLen) { // ============================================================ 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. -// Unlike findNextFocusable which can short-circuit during traversal, -// finding the PREVIOUS widget requires knowing the full order. -// So this collects all focusable widgets into an array, finds the -// 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. +// Collects all focusable widgets via DFS, then returns the one +// before 'before' (with wraparound). Uses stb_ds dynamic arrays +// so there's no fixed limit on widget count or tree depth. WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) { - WidgetT *list[128]; - int32_t count = 0; + WidgetT **list = NULL; + WidgetT **stack = NULL; - // Collect all focusable widgets via depth-first traversal - WidgetT *stack[64]; - int32_t top = 0; - stack[top++] = root; + arrput(stack, root); - while (top > 0) { - WidgetT *w = stack[--top]; + while (arrlen(stack) > 0) { + WidgetT *w = stack[arrlen(stack) - 1]; + arrsetlen(stack, arrlen(stack) - 1); if (!w->visible || !w->enabled) { continue; } - if (widgetIsFocusable(w->type) && count < 128) { - list[count++] = w; + if (widgetIsFocusable(w->type)) { + arrput(list, w); } // Push children in reverse order so first child is processed first - WidgetT *children[64]; - int32_t childCount = 0; + // Walk to end of sibling list, then push backwards + WidgetT **children = NULL; for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { - if (childCount < 64) { - children[childCount++] = c; + arrput(children, 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--) { - if (top < 64) { - stack[top++] = children[i]; - } - } + result = (idx <= 0) ? list[count - 1] : list[idx - 1]; } - if (count == 0) { - return NULL; - } - - // 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]; + arrfree(list); + arrfree(stack); + return result; } diff --git a/core/widgetEvent.c b/core/widgetEvent.c index f4a78d2..71bba4d 100644 --- a/core/widgetEvent.c +++ b/core/widgetEvent.c @@ -172,14 +172,8 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) { return; } - // Attribute allocations during event handling to the owning app - AppContextT *ctx = (AppContextT *)root->userData; - int32_t prevAppId = ctx->currentAppId; - ctx->currentAppId = win->appId; - + // Dispatch to per-widget onKey handler via vtable 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 // 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) { WidgetT *root = win->widgetRoot; sClosedPopup = NULL; @@ -210,19 +202,6 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { if (!root) { 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 if (sOpenPopup && sOpenPopup->window != win) { wclsClosePopup(sOpenPopup); diff --git a/shell/shellApp.c b/shell/shellApp.c index 86426d2..9747225 100644 --- a/shell/shellApp.c +++ b/shell/shellApp.c @@ -82,11 +82,15 @@ static int32_t allocSlot(void) { // this function never completes -- the task is killed externally via // shellForceKillApp + tsKill. static void appTaskWrapper(void *arg) { - ShellAppT *app = (ShellAppT *)arg; - - app->dxeCtx.shellCtx->currentAppId = app->appId; - app->entryFn(&app->dxeCtx); - app->dxeCtx.shellCtx->currentAppId = 0; + // 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 + // (e.g. another app was loaded in the meantime), which would + // invalidate a direct ShellAppT pointer. + 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->state = AppStateTerminatingE; @@ -261,6 +265,8 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) { } cleanupTempFile(app); + free(app->dxeCtx); + app->dxeCtx = NULL; dvxMemResetApp(app->appId); app->state = AppStateFreeE; dvxLog("Shell: force-killed app '%s'", app->name); @@ -399,29 +405,39 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) { app->shutdownFn = shutdown; app->state = AppStateLoadedE; - // Set up the context passed to appMain - app->dxeCtx.shellCtx = ctx; - app->dxeCtx.appId = id; + // Heap-allocate the DxeAppContextT so its address is stable across + // sApps reallocs. Apps save this pointer in their static globals. + 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). // This lets apps load resources relative to their own location rather // 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) { *sep = '\0'; } else { - app->dxeCtx.appDir[0] = '.'; - app->dxeCtx.appDir[1] = '\0'; + app->dxeCtx->appDir[0] = '.'; + app->dxeCtx->appDir[1] = '\0'; } // Derive config directory: replace the APPS/ prefix with CONFIG/. // e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS" // If the path doesn't start with "apps/" or "APPS/", fall back to // "CONFIG/" using the descriptor name. - const char *appDirStr = app->dxeCtx.appDir; + const char *appDirStr = app->dxeCtx->appDir; const char *appsPrefix = NULL; 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]) { - snprintf(app->dxeCtx.configDir, sizeof(app->dxeCtx.configDir), "CONFIG/%s", appsPrefix); + snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", appsPrefix); } 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 @@ -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; 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) { ctx->currentAppId = 0; dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR); dlclose(handle); + free(app->dxeCtx); + app->dxeCtx = NULL; app->state = AppStateFreeE; 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 lives entirely through event callbacks dispatched by // the shell's dvxUpdate loop. No separate task or stack needed. - app->entryFn(&app->dxeCtx); + app->entryFn(app->dxeCtx); } ctx->currentAppId = 0; @@ -511,6 +529,8 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) { } cleanupTempFile(app); + free(app->dxeCtx); + app->dxeCtx = NULL; dvxMemResetApp(app->appId); dvxLog("Shell: reaped app '%s'", app->name); app->state = AppStateFreeE; diff --git a/shell/shellApp.h b/shell/shellApp.h index 86fbfa6..3171ba8 100644 --- a/shell/shellApp.h +++ b/shell/shellApp.h @@ -93,7 +93,7 @@ typedef struct { uint32_t mainTaskId; // task ID if hasMainLoop, else 0 int32_t (*entryFn)(DxeAppContextT *); void (*shutdownFn)(void); // may be NULL - DxeAppContextT dxeCtx; // context passed to appMain + DxeAppContextT *dxeCtx; // heap-allocated; address stable across sApps realloc } ShellAppT; // ============================================================ diff --git a/texthelp/textHelp.c b/texthelp/textHelp.c index 2827fd8..4adf496 100644 --- a/texthelp/textHelp.c +++ b/texthelp/textHelp.c @@ -374,7 +374,7 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_ // Ctrl+Z -- undo if (key == 26 && undoBuf && pUndoLen && pUndoCursor) { // Swap current and undo - char tmpBuf[clipboardMaxLen() + 1]; + char tmpBuf[*pLen + 1]; int32_t tmpLen = *pLen; int32_t tmpCursor = *pCursor; int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1; diff --git a/widgets/widgetTextInput.c b/widgets/widgetTextInput.c index be5f374..547903f 100644 --- a/widgets/widgetTextInput.c +++ b/widgets/widgetTextInput.c @@ -111,7 +111,6 @@ typedef struct { #define TEXTAREA_SB_W 14 #define TEXTAREA_MIN_ROWS 4 #define TEXTAREA_MIN_COLS 20 -#define CLIPBOARD_MAX 4096 // Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c) #define CURSOR_BLINK_MS 250 @@ -950,7 +949,7 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { if (key == 26) { if (ta->undoBuf && ta->undoLen >= 0) { // Swap current and undo - char tmpBuf[CLIPBOARD_MAX]; + char tmpBuf[*pLen + 1]; int32_t tmpLen = *pLen; int32_t tmpCursor = CUR_OFF(); int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;