diff --git a/dvx/Makefile b/dvx/Makefile index 74f4566..8aa737d 100644 --- a/dvx/Makefile +++ b/dvx/Makefile @@ -9,10 +9,13 @@ CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 OBJDIR = ../obj/dvx WOBJDIR = ../obj/dvx/widgets +POBJDIR = ../obj/dvx/platform LIBDIR = ../lib SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c +PSRCS = platform/dvxPlatformDos.c + WSRCS = widgets/widgetAnsiTerm.c \ widgets/widgetClass.c \ widgets/widgetCore.c \ @@ -46,6 +49,7 @@ WSRCS = widgets/widgetAnsiTerm.c \ widgets/widgetTreeView.c OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) +POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS)) WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS)) TARGET = $(LIBDIR)/libdvx.a @@ -53,19 +57,25 @@ TARGET = $(LIBDIR)/libdvx.a all: $(TARGET) -$(TARGET): $(OBJS) $(WOBJS) | $(LIBDIR) - $(AR) rcs $@ $(OBJS) $(WOBJS) +$(TARGET): $(OBJS) $(POBJS) $(WOBJS) | $(LIBDIR) + $(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS) $(RANLIB) $@ $(OBJDIR)/%.o: %.c | $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< +$(POBJDIR)/%.o: platform/%.c | $(POBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + $(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR) $(CC) $(CFLAGS) -c -o $@ $< $(OBJDIR): mkdir -p $(OBJDIR) +$(POBJDIR): + mkdir -p $(POBJDIR) + $(WOBJDIR): mkdir -p $(WOBJDIR) @@ -73,14 +83,17 @@ $(LIBDIR): mkdir -p $(LIBDIR) # Dependencies -$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h dvxTypes.h dvxPalette.h -$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h dvxTypes.h -$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h dvxTypes.h +$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h +$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h +$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h $(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h thirdparty/stb_image.h $(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h $(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h -$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h -$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h dvxApp.h dvxWidget.h widgets/widgetInternal.h dvxTypes.h dvxDraw.h +$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h +$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h widgets/widgetInternal.h dvxTypes.h dvxDraw.h + +# Platform file dependencies +$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h # Widget file dependencies WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h diff --git a/dvx/dvxApp.c b/dvx/dvxApp.c index 9954469..ee6d0d1 100644 --- a/dvx/dvxApp.c +++ b/dvx/dvxApp.c @@ -6,11 +6,11 @@ #include "dvxFont.h" #include "dvxCursor.h" +#include "platform/dvxPlatform.h" + #include #include #include -#include -#include #include "thirdparty/stb_image_write.h" @@ -43,7 +43,6 @@ static void executeSysMenuCmd(AppContextT *ctx, int32_t cmd); static WindowT *findWindowById(AppContextT *ctx, int32_t id); static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons); static void initColorScheme(AppContextT *ctx); -static void initMouse(AppContextT *ctx); static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t screenX, int32_t screenY); static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx); static void openSubMenu(AppContextT *ctx); @@ -60,22 +59,6 @@ static void updateTooltip(AppContextT *ctx); // Button pressed via keyboard — shared with widgetEvent.c for Space/Enter WidgetT *sKeyPressedBtn = NULL; -// Alt+key scan code to ASCII lookup table (indexed by scan code) -// BIOS INT 16h returns these scan codes with ascii=0 for Alt+key combos -static const char sAltScanToAscii[256] = { - // Alt+letters - [0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r', - [0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i', - [0x18] = 'o', [0x19] = 'p', [0x1E] = 'a', [0x1F] = 's', - [0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h', - [0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x2C] = 'z', - [0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', [0x30] = 'b', - [0x31] = 'n', [0x32] = 'm', - // Alt+digits - [0x78] = '1', [0x79] = '2', [0x7A] = '3', [0x7B] = '4', - [0x7C] = '5', [0x7D] = '6', [0x7E] = '7', [0x7F] = '8', - [0x80] = '9', [0x81] = '0', -}; // ============================================================ @@ -1260,8 +1243,8 @@ const BitmapFontT *dvxGetFont(const AppContextT *ctx) { int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) { memset(ctx, 0, sizeof(*ctx)); - // Disable Ctrl+C/Break termination - signal(SIGINT, SIG_IGN); + // Platform-specific initialization (signal handling, etc.) + platformInit(); // Initialize video if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) { @@ -1292,7 +1275,11 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_ ctx->cursorBg = packColor(&ctx->display, 0, 0, 0); // Initialize mouse - initMouse(ctx); + platformMouseInit(ctx->display.width, ctx->display.height); + ctx->mouseX = ctx->display.width / 2; + ctx->mouseY = ctx->display.height / 2; + ctx->prevMouseX = ctx->mouseX; + ctx->prevMouseY = ctx->mouseY; ctx->running = true; ctx->lastIconClickId = -1; @@ -1395,7 +1382,7 @@ bool dvxUpdate(AppContextT *ctx) { } else if (ctx->idleCallback) { ctx->idleCallback(ctx->idleCtx); } else { - __dpmi_yield(); + platformYield(); } // After compositing, release key-pressed button (one frame of animation) @@ -1919,45 +1906,6 @@ static void initColorScheme(AppContextT *ctx) { } -// ============================================================ -// initMouse -// ============================================================ - -static void initMouse(AppContextT *ctx) { - __dpmi_regs r; - - // Reset mouse driver - memset(&r, 0, sizeof(r)); - r.x.ax = 0x0000; - __dpmi_int(0x33, &r); - - // Set horizontal range to match screen width - memset(&r, 0, sizeof(r)); - r.x.ax = 0x0007; - r.x.cx = 0; - r.x.dx = ctx->display.width - 1; - __dpmi_int(0x33, &r); - - // Set vertical range to match screen height - memset(&r, 0, sizeof(r)); - r.x.ax = 0x0008; - r.x.cx = 0; - r.x.dx = ctx->display.height - 1; - __dpmi_int(0x33, &r); - - // Position cursor at center of screen - ctx->mouseX = ctx->display.width / 2; - ctx->mouseY = ctx->display.height / 2; - ctx->prevMouseX = ctx->mouseX; - ctx->prevMouseY = ctx->mouseY; - - // Set mouse position - memset(&r, 0, sizeof(r)); - r.x.ax = 0x0004; - r.x.cx = ctx->mouseX; - r.x.dx = ctx->mouseY; - __dpmi_int(0x33, &r); -} // ============================================================ @@ -2228,40 +2176,17 @@ static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) // ============================================================ static void pollKeyboard(AppContextT *ctx) { - __dpmi_regs r; - - // Read shift state once per poll (INT 16h, AH=12h) - memset(&r, 0, sizeof(r)); - r.x.ax = 0x1200; - __dpmi_int(0x16, &r); - int32_t shiftFlags = r.x.ax & 0xFF; + // Read modifier state once per poll + int32_t shiftFlags = platformKeyboardGetModifiers(); ctx->keyModifiers = shiftFlags; bool shiftHeld = (shiftFlags & 0x03) != 0; // left or right shift // Process buffered keys - while (1) { - // Check if key is available (INT 16h, enhanced function 11h) - r.x.ax = 0x1100; - __dpmi_int(0x16, &r); + PlatformKeyEventT evt; - // Zero flag set = no key available - if (r.x.flags & 0x40) { - break; - } - - // Read the key (INT 16h, enhanced function 10h) - r.x.ax = 0x1000; - __dpmi_int(0x16, &r); - - int32_t scancode = (r.x.ax >> 8) & 0xFF; - int32_t ascii = r.x.ax & 0xFF; - - // Enhanced INT 16h returns ascii=0xE0 for grey/extended keys - // (arrows, Home, End, Insert, Delete, etc. on 101-key keyboards). - // Normalize to 0 so all extended key checks work uniformly. - if (ascii == 0xE0) { - ascii = 0; - } + while (platformKeyboardRead(&evt)) { + int32_t scancode = evt.scancode; + int32_t ascii = evt.ascii; // Alt+Tab / Shift+Alt+Tab — cycle windows // Alt+Tab: scancode=0xA5, ascii=0x00 @@ -2453,7 +2378,7 @@ static void pollKeyboard(AppContextT *ctx) { // System menu keyboard navigation if (ctx->sysMenu.active) { // Alt+key — close system menu and let it fall through to accel dispatch - if (ascii == 0 && scancode < 256 && sAltScanToAscii[scancode]) { + if (ascii == 0 && platformAltScanToChar(scancode)) { closeSysMenu(ctx); // Fall through to dispatchAccelKey below } else if (ascii == 0x1B) { @@ -2541,8 +2466,8 @@ static void pollKeyboard(AppContextT *ctx) { } // Check for Alt+key (BIOS returns ascii=0 with specific scancodes) - if (ascii == 0 && scancode < 256 && sAltScanToAscii[scancode]) { - char accelKey = sAltScanToAscii[scancode]; + if (ascii == 0 && platformAltScanToChar(scancode)) { + char accelKey = platformAltScanToChar(scancode); if (dispatchAccelKey(ctx, accelKey)) { continue; @@ -2896,15 +2821,15 @@ nextKey:; // ============================================================ static void pollMouse(AppContextT *ctx) { - __dpmi_regs r; + int32_t mx; + int32_t my; + int32_t buttons; - memset(&r, 0, sizeof(r)); - r.x.ax = 0x0003; - __dpmi_int(0x33, &r); + platformMousePoll(&mx, &my, &buttons); - ctx->mouseX = r.x.cx; - ctx->mouseY = r.x.dx; - ctx->mouseButtons = r.x.bx; + ctx->mouseX = mx; + ctx->mouseY = my; + ctx->mouseButtons = buttons; } diff --git a/dvx/dvxComp.c b/dvx/dvxComp.c index 13ccff9..630edf1 100644 --- a/dvx/dvxComp.c +++ b/dvx/dvxComp.c @@ -1,6 +1,7 @@ // dvx_comp.c — Layer 3: Dirty rectangle compositor for DV/X GUI (optimized) #include "dvxComp.h" +#include "platform/dvxPlatform.h" #include @@ -109,62 +110,7 @@ void dirtyListMerge(DirtyListT *dl) { // ============================================================ void flushRect(DisplayT *d, const RectT *r) { - int32_t bpp = d->format.bytesPerPixel; - - // Caller (compositeAndFlush) already clips to screen bounds - int32_t x = r->x; - int32_t y = r->y; - int32_t w = r->w; - int32_t h = r->h; - - if (__builtin_expect(w <= 0 || h <= 0, 0)) { return; } - - int32_t rowBytes = w * bpp; - int32_t pitch = d->pitch; - uint8_t *src = d->backBuf + y * pitch + x * bpp; - uint8_t *dst = d->lfb + y * pitch + x * bpp; - - // Full-width flush: single large copy (write-combined memory loves sequential writes) - if (rowBytes == pitch) { - // Entire scanlines — copy as one contiguous block - int32_t totalBytes = pitch * h; - int32_t dwords = totalBytes >> 2; - int32_t remainder = totalBytes & 3; - __asm__ __volatile__ ( - "rep movsl" - : "+D"(dst), "+S"(src), "+c"(dwords) - : - : "memory" - ); - // Handle any trailing bytes (unlikely for aligned pitch) - while (remainder-- > 0) { - *dst++ = *src++; - } - } else { - // Partial scanlines — copy row by row with rep movsd - int32_t dwords = rowBytes >> 2; - int32_t remainder = rowBytes & 3; - for (int32_t i = 0; i < h; i++) { - int32_t dc = dwords; - uint8_t *s = src; - uint8_t *dd = dst; - __asm__ __volatile__ ( - "rep movsl" - : "+D"(dd), "+S"(s), "+c"(dc) - : - : "memory" - ); - // Trailing bytes (dd and s already advanced by rep movsl) - if (__builtin_expect(remainder > 0, 0)) { - int32_t rem = remainder; - while (rem-- > 0) { - *dd++ = *s++; - } - } - src += pitch; - dst += pitch; - } - } + platformFlushRect(d, r); } diff --git a/dvx/dvxDialog.c b/dvx/dvxDialog.c index 2528683..47e54f1 100644 --- a/dvx/dvxDialog.c +++ b/dvx/dvxDialog.c @@ -1,6 +1,7 @@ // dvxDialog.c — Modal dialogs for DV/X GUI #include "dvxDialog.h" +#include "platform/dvxPlatform.h" #include "dvxWidget.h" #include "widgets/widgetInternal.h" @@ -753,81 +754,13 @@ static void fdNavigate(const char *path) { // ============================================================ static bool fdValidateFilename(const char *name) { - // DOS 8.3 validation - static const char *reserved[] = { - "CON", "PRN", "AUX", "NUL", - "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", - "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", - NULL - }; + const char *error = platformValidateFilename(name); - if (!name || name[0] == '\0') { + if (error) { + dvxMessageBox(sFd.ctx, "Invalid Filename", error, MB_OK | MB_ICONERROR); return false; } - // Split into base and extension - const char *dot = strrchr(name, '.'); - int32_t baseLen; - int32_t extLen; - - if (dot) { - baseLen = (int32_t)(dot - name); - extLen = (int32_t)strlen(dot + 1); - } else { - baseLen = (int32_t)strlen(name); - extLen = 0; - } - - if (baseLen < 1 || baseLen > 8) { - dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename must be 1-8 characters before the extension.", MB_OK | MB_ICONERROR); - return false; - } - - if (extLen > 3) { - dvxMessageBox(sFd.ctx, "Invalid Filename", "Extension must be 3 characters or fewer.", MB_OK | MB_ICONERROR); - return false; - } - - // Check for invalid characters - for (const char *p = name; *p; p++) { - if (*p == '.') { - continue; - } - - if (*p < '!' || *p > '~') { - dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename contains invalid characters.", MB_OK | MB_ICONERROR); - return false; - } - - if (strchr(" \"*+,/:;<=>?[\\]|", *p)) { - dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename contains invalid characters.", MB_OK | MB_ICONERROR); - return false; - } - } - - // Check for multiple dots - if (dot && strchr(name, '.') != dot) { - dvxMessageBox(sFd.ctx, "Invalid Filename", "Filename may contain only one dot.", MB_OK | MB_ICONERROR); - return false; - } - - // Check reserved device names (compare base only, case-insensitive) - char base[9]; - int32_t copyLen = baseLen < 8 ? baseLen : 8; - - for (int32_t i = 0; i < copyLen; i++) { - base[i] = toupper((unsigned char)name[i]); - } - - base[copyLen] = '\0'; - - for (const char **r = reserved; *r; r++) { - if (strcmp(base, *r) == 0) { - dvxMessageBox(sFd.ctx, "Invalid Filename", "That name is a reserved device name.", MB_OK | MB_ICONERROR); - return false; - } - } - return true; } diff --git a/dvx/dvxDraw.c b/dvx/dvxDraw.c index 13a4e14..aeb79eb 100644 --- a/dvx/dvxDraw.c +++ b/dvx/dvxDraw.c @@ -1,6 +1,7 @@ // dvx_draw.c — Layer 2: Drawing primitives for DV/X GUI (optimized) #include "dvxDraw.h" +#include "platform/dvxPlatform.h" #include @@ -11,12 +12,6 @@ char accelParse(const char *text); static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h); static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp); -static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count); -static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count); -static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count); -static void spanFill8(uint8_t *dst, uint32_t color, int32_t count); -static void spanFill16(uint8_t *dst, uint32_t color, int32_t count); -static void spanFill32(uint8_t *dst, uint32_t color, int32_t count); // Bit lookup tables — avoids per-pixel shift on 486 (40+ cycle savings per shift) static const uint8_t sGlyphBit[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; @@ -571,20 +566,20 @@ void drawInit(BlitOpsT *ops, const DisplayT *d) { switch (d->format.bytesPerPixel) { case 1: - ops->spanFill = spanFill8; - ops->spanCopy = spanCopy8; + ops->spanFill = platformSpanFill8; + ops->spanCopy = platformSpanCopy8; break; case 2: - ops->spanFill = spanFill16; - ops->spanCopy = spanCopy16; + ops->spanFill = platformSpanFill16; + ops->spanCopy = platformSpanCopy16; break; case 4: - ops->spanFill = spanFill32; - ops->spanCopy = spanCopy32; + ops->spanFill = platformSpanFill32; + ops->spanCopy = platformSpanCopy32; break; default: - ops->spanFill = spanFill8; - ops->spanCopy = spanCopy8; + ops->spanFill = platformSpanFill8; + ops->spanCopy = platformSpanCopy8; break; } } @@ -990,190 +985,16 @@ void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, return; } - int32_t bpp = d->format.bytesPerPixel; - uint8_t *row = d->backBuf + y * d->pitch + x * bpp; + uint8_t *row = d->backBuf + y * d->pitch + x * d->format.bytesPerPixel; int32_t pitch = d->pitch; - // Inline rep stosl for 32bpp — avoids function pointer call overhead - // per scanline which is significant for the many 1px fills (Item 10) - if (__builtin_expect(bpp == 4, 1)) { - for (int32_t i = 0; i < h; i++) { - int32_t count = w; - uint8_t *dst = row; - __asm__ __volatile__ ( - "rep stosl" - : "+D"(dst), "+c"(count) - : "a"(color) - : "memory" - ); - row += pitch; - } - } else { - for (int32_t i = 0; i < h; i++) { - ops->spanFill(row, color, w); - row += pitch; - } + for (int32_t i = 0; i < h; i++) { + ops->spanFill(row, color, w); + row += pitch; } } -// ============================================================ -// spanCopy8 -// ============================================================ - -static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) { - // Align to 4 bytes - while (((uintptr_t)dst & 3) && count > 0) { - *dst++ = *src++; - count--; - } - - if (count >= 4) { - int32_t dwordCount = count >> 2; - __asm__ __volatile__ ( - "rep movsl" - : "+D"(dst), "+S"(src), "+c"(dwordCount) - : - : "memory" - ); - dst += dwordCount * 4; - src += dwordCount * 4; - } - - int32_t rem = count & 3; - while (rem-- > 0) { - *dst++ = *src++; - } -} - - -// ============================================================ -// spanCopy16 -// ============================================================ - -static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) { - // Handle odd leading pixel for dword alignment - if (((uintptr_t)dst & 2) && count > 0) { - *(uint16_t *)dst = *(const uint16_t *)src; - dst += 2; - src += 2; - count--; - } - - if (count >= 2) { - int32_t dwordCount = count >> 1; - __asm__ __volatile__ ( - "rep movsl" - : "+D"(dst), "+S"(src), "+c"(dwordCount) - : - : "memory" - ); - dst += dwordCount * 4; - src += dwordCount * 4; - } - - if (count & 1) { - *(uint16_t *)dst = *(const uint16_t *)src; - } -} - - -// ============================================================ -// spanCopy32 -// ============================================================ - -static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) { - __asm__ __volatile__ ( - "rep movsl" - : "+D"(dst), "+S"(src), "+c"(count) - : - : "memory" - ); -} - - -// ============================================================ -// spanFill8 -// ============================================================ - -static void spanFill8(uint8_t *dst, uint32_t color, int32_t count) { - uint8_t c = (uint8_t)color; - uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24); - - // Align to 4 bytes — skip if already aligned (Item 11) - if (__builtin_expect((uintptr_t)dst & 3, 0)) { - while (((uintptr_t)dst & 3) && count > 0) { - *dst++ = c; - count--; - } - } - - if (count >= 4) { - int32_t dwordCount = count >> 2; - __asm__ __volatile__ ( - "rep stosl" - : "+D"(dst), "+c"(dwordCount) - : "a"(dword) - : "memory" - ); - dst += dwordCount * 4; - } - - int32_t rem = count & 3; - while (rem-- > 0) { - *dst++ = c; - } -} - - -// ============================================================ -// spanFill16 -// ============================================================ - -static void spanFill16(uint8_t *dst, uint32_t color, int32_t count) { - uint16_t c = (uint16_t)color; - - // Handle odd leading pixel for dword alignment - if (((uintptr_t)dst & 2) && count > 0) { - *(uint16_t *)dst = c; - dst += 2; - count--; - } - - // Fill pairs of pixels as 32-bit dwords - if (count >= 2) { - uint32_t dword = ((uint32_t)c << 16) | c; - int32_t dwordCount = count >> 1; - __asm__ __volatile__ ( - "rep stosl" - : "+D"(dst), "+c"(dwordCount) - : "a"(dword) - : "memory" - ); - dst += dwordCount * 4; - } - - // Handle trailing odd pixel - if (count & 1) { - *(uint16_t *)dst = c; - } -} - - -// ============================================================ -// spanFill32 -// ============================================================ - -static void spanFill32(uint8_t *dst, uint32_t color, int32_t count) { - __asm__ __volatile__ ( - "rep stosl" - : "+D"(dst), "+c"(count) - : "a"(color) - : "memory" - ); -} - - // ============================================================ // textWidth // ============================================================ diff --git a/dvx/dvxVideo.c b/dvx/dvxVideo.c index ced9665..cff0b27 100644 --- a/dvx/dvxVideo.c +++ b/dvx/dvxVideo.c @@ -1,262 +1,15 @@ -// dvx_video.c — Layer 1: VESA VBE video backend for DV/X GUI +// dvx_video.c — Layer 1: Video backend for DV/X GUI +// +// Platform-independent video utilities. The actual VESA/VBE code +// now lives in dvxPlatformDos.c (or the platform file for whatever +// OS we're targeting). #include "dvxVideo.h" +#include "platform/dvxPlatform.h" #include "dvxPalette.h" -#include -#include #include -#include -#include -#include -#include -#include - -// ============================================================ -// Prototypes -// ============================================================ - -static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d); -static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp); -static int32_t mapLfb(DisplayT *d, uint32_t physAddr); -static int32_t setVesaMode(uint16_t mode); -static void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count); - - -// ============================================================ -// findBestMode -// ============================================================ - -static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d) { - __dpmi_regs r; - uint16_t bestMode = 0; - int32_t bestScore = -1; - DisplayT bestDisplay; - - memset(&bestDisplay, 0, sizeof(bestDisplay)); - - // Get VBE controller info - // Put "VBE2" signature at ES:DI in conventional memory - uint32_t infoSeg = __tb >> 4; - uint32_t infoOff = __tb & 0x0F; - - // Write "VBE2" at transfer buffer to request VBE 2.0 info - _farpokeb(_dos_ds, __tb + 0, 'V'); - _farpokeb(_dos_ds, __tb + 1, 'B'); - _farpokeb(_dos_ds, __tb + 2, 'E'); - _farpokeb(_dos_ds, __tb + 3, '2'); - - memset(&r, 0, sizeof(r)); - r.x.ax = 0x4F00; - r.x.es = infoSeg; - r.x.di = infoOff; - __dpmi_int(0x10, &r); - - if (r.x.ax != 0x004F) { - fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax); - return -1; - } - - // Verify VBE signature - char sig[5]; - for (int32_t i = 0; i < 4; i++) { - sig[i] = _farpeekb(_dos_ds, __tb + i); - } - sig[4] = '\0'; - - if (strcmp(sig, "VESA") != 0) { - fprintf(stderr, "VBE: Bad signature '%s'\n", sig); - return -1; - } - - // Check VBE version (need 2.0+) - uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4); - if (vbeVersion < 0x0200) { - fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n", - vbeVersion >> 8, vbeVersion & 0xFF); - return -1; - } - - // Get mode list pointer (far pointer at offset 14) - uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14); - uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16); - uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff; - - // Walk mode list - for (int32_t i = 0; i < 256; i++) { - uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2); - - if (mode == 0xFFFF) { - break; - } - - DisplayT candidate; - int32_t score = 0; - - memset(&candidate, 0, sizeof(candidate)); - getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp); - - if (score > bestScore) { - bestScore = score; - bestMode = mode; - bestDisplay = candidate; - } - } - - if (bestScore < 0) { - fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH); - return -1; - } - - *outMode = bestMode; - *d = bestDisplay; - return 0; -} - - -// ============================================================ -// getModeInfo -// ============================================================ - -static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) { - __dpmi_regs r; - - *score = -1; - - memset(&r, 0, sizeof(r)); - r.x.ax = 0x4F01; - r.x.cx = mode; - r.x.es = __tb >> 4; - r.x.di = __tb & 0x0F; - __dpmi_int(0x10, &r); - - if (r.x.ax != 0x004F) { - return; - } - - // Read mode attributes - uint16_t attr = _farpeekw(_dos_ds, __tb + 0); - - // Must have LFB support (bit 7) - if (!(attr & 0x0080)) { - return; - } - - // Must be a graphics mode (bit 4) - if (!(attr & 0x0010)) { - return; - } - - int32_t w = _farpeekw(_dos_ds, __tb + 18); - int32_t h = _farpeekw(_dos_ds, __tb + 20); - int32_t bpp = _farpeekb(_dos_ds, __tb + 25); - int32_t pitch = _farpeekw(_dos_ds, __tb + 16); - uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40); - - // Must match or exceed requested resolution - if (w < requestedW || h < requestedH) { - return; - } - - // Must be a supported bpp - if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) { - return; - } - - // Score this mode - int32_t s = 0; - - if (bpp == 16) { - s = 100; - } else if (bpp == 15) { - s = 90; - } else if (bpp == 32) { - s = 85; - } else if (bpp == 8) { - s = 70; - } - - // Prefer the user's preferred bpp - if (bpp == preferredBpp) { - s += 20; - } - - // Exact resolution match is preferred - if (w == requestedW && h == requestedH) { - s += 10; - } else { - s -= 10; - } - - *score = s; - - // Fill in display info - d->width = w; - d->height = h; - d->pitch = pitch; - - d->format.bitsPerPixel = bpp; - d->format.bytesPerPixel = (bpp + 7) / 8; - - // Read color masks from mode info - if (bpp >= 15) { - int32_t redSize = _farpeekb(_dos_ds, __tb + 31); - int32_t redPos = _farpeekb(_dos_ds, __tb + 32); - int32_t greenSize = _farpeekb(_dos_ds, __tb + 33); - int32_t greenPos = _farpeekb(_dos_ds, __tb + 34); - int32_t blueSize = _farpeekb(_dos_ds, __tb + 35); - int32_t bluePos = _farpeekb(_dos_ds, __tb + 36); - - d->format.redBits = redSize; - d->format.redShift = redPos; - d->format.redMask = ((1U << redSize) - 1) << redPos; - d->format.greenBits = greenSize; - d->format.greenShift = greenPos; - d->format.greenMask = ((1U << greenSize) - 1) << greenPos; - d->format.blueBits = blueSize; - d->format.blueShift = bluePos; - d->format.blueMask = ((1U << blueSize) - 1) << bluePos; - } - - // Store physical address in lfb field temporarily (will be remapped) - d->lfb = (uint8_t *)(uintptr_t)physAddr; -} - - -// ============================================================ -// mapLfb -// ============================================================ - -static int32_t mapLfb(DisplayT *d, uint32_t physAddr) { - __dpmi_meminfo info; - uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height; - - info.address = physAddr; - info.size = fbSize; - - if (__dpmi_physical_address_mapping(&info) != 0) { - fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr); - return -1; - } - - // Lock the region to prevent paging - __dpmi_meminfo lockInfo; - lockInfo.address = info.address; - lockInfo.size = fbSize; - __dpmi_lock_linear_region(&lockInfo); - - // Enable near pointers for direct access - if (__djgpp_nearptr_enable() == 0) { - fprintf(stderr, "VBE: Failed to enable near pointers\n"); - return -1; - } - - d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base); - - return 0; -} - // ============================================================ // packColor @@ -307,108 +60,12 @@ void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) { } -// ============================================================ -// setVesaMode -// ============================================================ - -static int32_t setVesaMode(uint16_t mode) { - __dpmi_regs r; - - memset(&r, 0, sizeof(r)); - r.x.ax = 0x4F02; - r.x.bx = mode | 0x4000; // bit 14 = use LFB - __dpmi_int(0x10, &r); - - if (r.x.ax != 0x004F) { - fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax); - return -1; - } - - return 0; -} - - // ============================================================ // videoInit // ============================================================ int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) { - uint16_t bestMode; - uint32_t physAddr; - - memset(d, 0, sizeof(*d)); - - // Find the best VESA mode - if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) { - return -1; - } - - // Save the physical address before we overwrite it - physAddr = (uint32_t)(uintptr_t)d->lfb; - - // Set the mode - if (setVesaMode(bestMode) != 0) { - return -1; - } - - // Map the LFB - if (mapLfb(d, physAddr) != 0) { - return -1; - } - - // Allocate backbuffer - uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height; - d->backBuf = (uint8_t *)malloc(fbSize); - - if (!d->backBuf) { - fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize); - __djgpp_nearptr_disable(); - return -1; - } - - memset(d->backBuf, 0, fbSize); - - // Set up palette for 8-bit mode - if (d->format.bitsPerPixel == 8) { - d->palette = (uint8_t *)malloc(768); - - if (!d->palette) { - fprintf(stderr, "VBE: Failed to allocate palette\n"); - free(d->backBuf); - d->backBuf = NULL; - __djgpp_nearptr_disable(); - return -1; - } - - dvxGeneratePalette(d->palette); - videoSetPalette(d->palette, 0, 256); - } - - // Initialize clip rect to full display - resetClipRect(d); - - fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n", - bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch); - - return 0; -} - - -// ============================================================ -// videoSetPalette -// ============================================================ - -static void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) { - // Set VGA DAC registers directly via port I/O - // Port 0x3C8 = write index, Port 0x3C9 = data (R, G, B in 6-bit values) - outportb(0x3C8, (uint8_t)firstEntry); - - for (int32_t i = 0; i < count; i++) { - int32_t idx = (firstEntry + i) * 3; - outportb(0x3C9, pal[idx + 0] >> 2); // VGA DAC uses 6-bit values - outportb(0x3C9, pal[idx + 1] >> 2); - outportb(0x3C9, pal[idx + 2] >> 2); - } + return platformVideoInit(d, requestedW, requestedH, preferredBpp); } @@ -417,23 +74,5 @@ static void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t coun // ============================================================ void videoShutdown(DisplayT *d) { - // Restore text mode (mode 3) - __dpmi_regs r; - memset(&r, 0, sizeof(r)); - r.x.ax = 0x0003; - __dpmi_int(0x10, &r); - - if (d->backBuf) { - free(d->backBuf); - d->backBuf = NULL; - } - - if (d->palette) { - free(d->palette); - d->palette = NULL; - } - - d->lfb = NULL; - - __djgpp_nearptr_disable(); + platformVideoShutdown(d); } diff --git a/dvx/platform/dvxPlatform.h b/dvx/platform/dvxPlatform.h new file mode 100644 index 0000000..6b280e8 --- /dev/null +++ b/dvx/platform/dvxPlatform.h @@ -0,0 +1,98 @@ +// dvxPlatform.h — Platform abstraction layer for DV/X GUI +// +// All OS-specific and CPU-specific code is isolated behind this +// interface. To port DV/X to a new platform, implement a new +// dvxPlatformXxx.c against this header. +#ifndef DVX_PLATFORM_H +#define DVX_PLATFORM_H + +#include "../dvxTypes.h" + +// ============================================================ +// Keyboard event +// ============================================================ + +typedef struct { + int32_t ascii; // ASCII value, 0 for extended/function keys + int32_t scancode; // PC scan code (0x48=Up, 0x50=Down, etc.) +} PlatformKeyEventT; + +// ============================================================ +// System lifecycle +// ============================================================ + +// One-time platform initialisation (signal handling, etc.) +void platformInit(void); + +// Cooperative multitasking yield (give up CPU when idle) +void platformYield(void); + +// ============================================================ +// Video +// ============================================================ + +// Initialise video mode, map framebuffer, allocate backbuffer. +// Fills in all DisplayT fields on success. Returns 0/-1. +int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp); + +// Shut down video — restore text mode, unmap framebuffer, free backbuffer. +void platformVideoShutdown(DisplayT *d); + +// Set palette entries (8-bit mode). pal is R,G,B triplets. +void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count); + +// ============================================================ +// Framebuffer flush +// ============================================================ + +// Copy a rectangle from d->backBuf to the display surface (d->lfb). +void platformFlushRect(const DisplayT *d, const RectT *r); + +// ============================================================ +// Optimised memory operations (span fill / copy) +// ============================================================ + +void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count); +void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count); +void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count); +void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count); +void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count); +void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count); + +// ============================================================ +// Input — Mouse +// ============================================================ + +// Initialise mouse driver, set movement range, centre cursor. +void platformMouseInit(int32_t screenW, int32_t screenH); + +// Read current mouse position and button state. +void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons); + +// ============================================================ +// Input — Keyboard +// ============================================================ + +// Return current modifier flags (BIOS shift-state format: +// bits 0-1 = shift, bit 2 = ctrl, bit 3 = alt). +int32_t platformKeyboardGetModifiers(void); + +// Read the next key from the keyboard buffer. +// Returns true if a key was available, false if the buffer was empty. +// Normalises extended-key markers (e.g. 0xE0 → 0). +bool platformKeyboardRead(PlatformKeyEventT *evt); + +// Map a scan code to its Alt+letter ASCII character. +// Returns the lowercase letter if the scan code corresponds to an +// Alt+letter/digit combo, or 0 if it does not. +char platformAltScanToChar(int32_t scancode); + +// ============================================================ +// File system +// ============================================================ + +// Validate a filename for the current platform. +// Returns NULL if valid, or a human-readable error string if invalid. +const char *platformValidateFilename(const char *name); + +#endif // DVX_PLATFORM_H diff --git a/dvx/platform/dvxPlatformDos.c b/dvx/platform/dvxPlatformDos.c new file mode 100644 index 0000000..757697e --- /dev/null +++ b/dvx/platform/dvxPlatformDos.c @@ -0,0 +1,841 @@ +// dvxPlatformDos.c — DOS/DJGPP platform implementation for DV/X GUI +// +// All BIOS calls, DPMI functions, port I/O, inline assembly, and +// DOS-specific file handling are isolated in this single file. + +#include "dvxPlatform.h" +#include "../dvxPalette.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// ============================================================ +// Prototypes +// ============================================================ + +static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d); +static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp); +static int32_t mapLfb(DisplayT *d, uint32_t physAddr); +static int32_t setVesaMode(uint16_t mode); + +// Alt+key scan code to ASCII lookup table (indexed by BIOS scan code). +// INT 16h returns these scan codes with ascii=0 for Alt+key combos. +static const char sAltScanToAscii[256] = { + // Alt+letters + [0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r', + [0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i', + [0x18] = 'o', [0x19] = 'p', [0x1E] = 'a', [0x1F] = 's', + [0x20] = 'd', [0x21] = 'f', [0x22] = 'g', [0x23] = 'h', + [0x24] = 'j', [0x25] = 'k', [0x26] = 'l', [0x2C] = 'z', + [0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v', [0x30] = 'b', + [0x31] = 'n', [0x32] = 'm', + // Alt+digits + [0x78] = '1', [0x79] = '2', [0x7A] = '3', [0x7B] = '4', + [0x7C] = '5', [0x7D] = '6', [0x7E] = '7', [0x7F] = '8', + [0x80] = '9', [0x81] = '0', +}; + + +// ============================================================ +// findBestMode +// ============================================================ + +static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d) { + __dpmi_regs r; + uint16_t bestMode = 0; + int32_t bestScore = -1; + DisplayT bestDisplay; + + memset(&bestDisplay, 0, sizeof(bestDisplay)); + + // Get VBE controller info + uint32_t infoSeg = __tb >> 4; + uint32_t infoOff = __tb & 0x0F; + + // Write "VBE2" at transfer buffer to request VBE 2.0 info + _farpokeb(_dos_ds, __tb + 0, 'V'); + _farpokeb(_dos_ds, __tb + 1, 'B'); + _farpokeb(_dos_ds, __tb + 2, 'E'); + _farpokeb(_dos_ds, __tb + 3, '2'); + + memset(&r, 0, sizeof(r)); + r.x.ax = 0x4F00; + r.x.es = infoSeg; + r.x.di = infoOff; + __dpmi_int(0x10, &r); + + if (r.x.ax != 0x004F) { + fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax); + return -1; + } + + // Verify VBE signature + char sig[5]; + for (int32_t i = 0; i < 4; i++) { + sig[i] = _farpeekb(_dos_ds, __tb + i); + } + sig[4] = '\0'; + + if (strcmp(sig, "VESA") != 0) { + fprintf(stderr, "VBE: Bad signature '%s'\n", sig); + return -1; + } + + // Check VBE version (need 2.0+) + uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4); + if (vbeVersion < 0x0200) { + fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n", + vbeVersion >> 8, vbeVersion & 0xFF); + return -1; + } + + // Get mode list pointer (far pointer at offset 14) + uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14); + uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16); + uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff; + + // Walk mode list + for (int32_t i = 0; i < 256; i++) { + uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2); + + if (mode == 0xFFFF) { + break; + } + + DisplayT candidate; + int32_t score = 0; + + memset(&candidate, 0, sizeof(candidate)); + getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp); + + if (score > bestScore) { + bestScore = score; + bestMode = mode; + bestDisplay = candidate; + } + } + + if (bestScore < 0) { + fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH); + return -1; + } + + *outMode = bestMode; + *d = bestDisplay; + return 0; +} + + +// ============================================================ +// getModeInfo +// ============================================================ + +static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) { + __dpmi_regs r; + + *score = -1; + + memset(&r, 0, sizeof(r)); + r.x.ax = 0x4F01; + r.x.cx = mode; + r.x.es = __tb >> 4; + r.x.di = __tb & 0x0F; + __dpmi_int(0x10, &r); + + if (r.x.ax != 0x004F) { + return; + } + + // Read mode attributes + uint16_t attr = _farpeekw(_dos_ds, __tb + 0); + + // Must have LFB support (bit 7) + if (!(attr & 0x0080)) { + return; + } + + // Must be a graphics mode (bit 4) + if (!(attr & 0x0010)) { + return; + } + + int32_t w = _farpeekw(_dos_ds, __tb + 18); + int32_t h = _farpeekw(_dos_ds, __tb + 20); + int32_t bpp = _farpeekb(_dos_ds, __tb + 25); + int32_t pitch = _farpeekw(_dos_ds, __tb + 16); + uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40); + + // Must match or exceed requested resolution + if (w < requestedW || h < requestedH) { + return; + } + + // Must be a supported bpp + if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) { + return; + } + + // Score this mode + int32_t s = 0; + + if (bpp == 16) { + s = 100; + } else if (bpp == 15) { + s = 90; + } else if (bpp == 32) { + s = 85; + } else if (bpp == 8) { + s = 70; + } + + // Prefer the user's preferred bpp + if (bpp == preferredBpp) { + s += 20; + } + + // Exact resolution match is preferred + if (w == requestedW && h == requestedH) { + s += 10; + } else { + s -= 10; + } + + *score = s; + + // Fill in display info + d->width = w; + d->height = h; + d->pitch = pitch; + + d->format.bitsPerPixel = bpp; + d->format.bytesPerPixel = (bpp + 7) / 8; + + // Read color masks from mode info + if (bpp >= 15) { + int32_t redSize = _farpeekb(_dos_ds, __tb + 31); + int32_t redPos = _farpeekb(_dos_ds, __tb + 32); + int32_t greenSize = _farpeekb(_dos_ds, __tb + 33); + int32_t greenPos = _farpeekb(_dos_ds, __tb + 34); + int32_t blueSize = _farpeekb(_dos_ds, __tb + 35); + int32_t bluePos = _farpeekb(_dos_ds, __tb + 36); + + d->format.redBits = redSize; + d->format.redShift = redPos; + d->format.redMask = ((1U << redSize) - 1) << redPos; + d->format.greenBits = greenSize; + d->format.greenShift = greenPos; + d->format.greenMask = ((1U << greenSize) - 1) << greenPos; + d->format.blueBits = blueSize; + d->format.blueShift = bluePos; + d->format.blueMask = ((1U << blueSize) - 1) << bluePos; + } + + // Store physical address in lfb field temporarily (will be remapped) + d->lfb = (uint8_t *)(uintptr_t)physAddr; +} + + +// ============================================================ +// mapLfb +// ============================================================ + +static int32_t mapLfb(DisplayT *d, uint32_t physAddr) { + __dpmi_meminfo info; + uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height; + + info.address = physAddr; + info.size = fbSize; + + if (__dpmi_physical_address_mapping(&info) != 0) { + fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr); + return -1; + } + + // Lock the region to prevent paging + __dpmi_meminfo lockInfo; + lockInfo.address = info.address; + lockInfo.size = fbSize; + __dpmi_lock_linear_region(&lockInfo); + + // Enable near pointers for direct access + if (__djgpp_nearptr_enable() == 0) { + fprintf(stderr, "VBE: Failed to enable near pointers\n"); + return -1; + } + + d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base); + + return 0; +} + + +// ============================================================ +// platformAltScanToChar +// ============================================================ + +char platformAltScanToChar(int32_t scancode) { + if (scancode < 0 || scancode > 255) { + return 0; + } + + return sAltScanToAscii[scancode]; +} + + +// ============================================================ +// platformFlushRect +// ============================================================ + +void platformFlushRect(const DisplayT *d, const RectT *r) { + int32_t bpp = d->format.bytesPerPixel; + + int32_t x = r->x; + int32_t y = r->y; + int32_t w = r->w; + int32_t h = r->h; + + if (__builtin_expect(w <= 0 || h <= 0, 0)) { + return; + } + + int32_t rowBytes = w * bpp; + int32_t pitch = d->pitch; + uint8_t *src = d->backBuf + y * pitch + x * bpp; + uint8_t *dst = d->lfb + y * pitch + x * bpp; + + // Full-width flush: single large copy + if (rowBytes == pitch) { + int32_t totalBytes = pitch * h; + int32_t dwords = totalBytes >> 2; + int32_t remainder = totalBytes & 3; + __asm__ __volatile__ ( + "rep movsl" + : "+D"(dst), "+S"(src), "+c"(dwords) + : + : "memory" + ); + while (remainder-- > 0) { + *dst++ = *src++; + } + } else { + // Partial scanlines — copy row by row with rep movsd + int32_t dwords = rowBytes >> 2; + int32_t remainder = rowBytes & 3; + for (int32_t i = 0; i < h; i++) { + int32_t dc = dwords; + uint8_t *s = src; + uint8_t *dd = dst; + __asm__ __volatile__ ( + "rep movsl" + : "+D"(dd), "+S"(s), "+c"(dc) + : + : "memory" + ); + if (__builtin_expect(remainder > 0, 0)) { + int32_t rem = remainder; + while (rem-- > 0) { + *dd++ = *s++; + } + } + src += pitch; + dst += pitch; + } + } +} + + +// ============================================================ +// platformInit +// ============================================================ + +void platformInit(void) { + // Disable Ctrl+C/Break termination + signal(SIGINT, SIG_IGN); +} + + +// ============================================================ +// platformKeyboardGetModifiers +// ============================================================ + +int32_t platformKeyboardGetModifiers(void) { + __dpmi_regs r; + + memset(&r, 0, sizeof(r)); + r.x.ax = 0x1200; + __dpmi_int(0x16, &r); + + return r.x.ax & 0xFF; +} + + +// ============================================================ +// platformKeyboardRead +// ============================================================ + +bool platformKeyboardRead(PlatformKeyEventT *evt) { + __dpmi_regs r; + + // Check if key is available (INT 16h, enhanced function 11h) + r.x.ax = 0x1100; + __dpmi_int(0x16, &r); + + // Zero flag set = no key available + if (r.x.flags & 0x40) { + return false; + } + + // Read the key (INT 16h, enhanced function 10h) + r.x.ax = 0x1000; + __dpmi_int(0x16, &r); + + evt->scancode = (r.x.ax >> 8) & 0xFF; + evt->ascii = r.x.ax & 0xFF; + + // Enhanced INT 16h returns ascii=0xE0 for grey/extended keys + // (arrows, Home, End, Insert, Delete, etc. on 101-key keyboards). + // Normalize to 0 so all extended key checks work uniformly. + if (evt->ascii == 0xE0) { + evt->ascii = 0; + } + + return true; +} + + +// ============================================================ +// platformMouseInit +// ============================================================ + +void platformMouseInit(int32_t screenW, int32_t screenH) { + __dpmi_regs r; + + // Reset mouse driver + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0000; + __dpmi_int(0x33, &r); + + // Set horizontal range + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0007; + r.x.cx = 0; + r.x.dx = screenW - 1; + __dpmi_int(0x33, &r); + + // Set vertical range + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0008; + r.x.cx = 0; + r.x.dx = screenH - 1; + __dpmi_int(0x33, &r); + + // Position cursor at center + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0004; + r.x.cx = screenW / 2; + r.x.dx = screenH / 2; + __dpmi_int(0x33, &r); +} + + +// ============================================================ +// platformMousePoll +// ============================================================ + +void platformMousePoll(int32_t *mx, int32_t *my, int32_t *buttons) { + __dpmi_regs r; + + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0003; + __dpmi_int(0x33, &r); + + *mx = r.x.cx; + *my = r.x.dx; + *buttons = r.x.bx; +} + + +// ============================================================ +// platformSpanCopy8 +// ============================================================ + +void platformSpanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) { + // Align to 4 bytes + while (((uintptr_t)dst & 3) && count > 0) { + *dst++ = *src++; + count--; + } + + if (count >= 4) { + int32_t dwordCount = count >> 2; + __asm__ __volatile__ ( + "rep movsl" + : "+D"(dst), "+S"(src), "+c"(dwordCount) + : + : "memory" + ); + dst += dwordCount * 4; + src += dwordCount * 4; + } + + int32_t rem = count & 3; + while (rem-- > 0) { + *dst++ = *src++; + } +} + + +// ============================================================ +// platformSpanCopy16 +// ============================================================ + +void platformSpanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) { + // Handle odd leading pixel for dword alignment + if (((uintptr_t)dst & 2) && count > 0) { + *(uint16_t *)dst = *(const uint16_t *)src; + dst += 2; + src += 2; + count--; + } + + if (count >= 2) { + int32_t dwordCount = count >> 1; + __asm__ __volatile__ ( + "rep movsl" + : "+D"(dst), "+S"(src), "+c"(dwordCount) + : + : "memory" + ); + dst += dwordCount * 4; + src += dwordCount * 4; + } + + if (count & 1) { + *(uint16_t *)dst = *(const uint16_t *)src; + } +} + + +// ============================================================ +// platformSpanCopy32 +// ============================================================ + +void platformSpanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) { + __asm__ __volatile__ ( + "rep movsl" + : "+D"(dst), "+S"(src), "+c"(count) + : + : "memory" + ); +} + + +// ============================================================ +// platformSpanFill8 +// ============================================================ + +void platformSpanFill8(uint8_t *dst, uint32_t color, int32_t count) { + uint8_t c = (uint8_t)color; + uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24); + + // Align to 4 bytes — skip if already aligned + if (__builtin_expect((uintptr_t)dst & 3, 0)) { + while (((uintptr_t)dst & 3) && count > 0) { + *dst++ = c; + count--; + } + } + + if (count >= 4) { + int32_t dwordCount = count >> 2; + __asm__ __volatile__ ( + "rep stosl" + : "+D"(dst), "+c"(dwordCount) + : "a"(dword) + : "memory" + ); + dst += dwordCount * 4; + } + + int32_t rem = count & 3; + while (rem-- > 0) { + *dst++ = c; + } +} + + +// ============================================================ +// platformSpanFill16 +// ============================================================ + +void platformSpanFill16(uint8_t *dst, uint32_t color, int32_t count) { + uint16_t c = (uint16_t)color; + + // Handle odd leading pixel for dword alignment + if (((uintptr_t)dst & 2) && count > 0) { + *(uint16_t *)dst = c; + dst += 2; + count--; + } + + // Fill pairs of pixels as 32-bit dwords + if (count >= 2) { + uint32_t dword = ((uint32_t)c << 16) | c; + int32_t dwordCount = count >> 1; + __asm__ __volatile__ ( + "rep stosl" + : "+D"(dst), "+c"(dwordCount) + : "a"(dword) + : "memory" + ); + dst += dwordCount * 4; + } + + // Handle trailing odd pixel + if (count & 1) { + *(uint16_t *)dst = c; + } +} + + +// ============================================================ +// platformSpanFill32 +// ============================================================ + +void platformSpanFill32(uint8_t *dst, uint32_t color, int32_t count) { + __asm__ __volatile__ ( + "rep stosl" + : "+D"(dst), "+c"(count) + : "a"(color) + : "memory" + ); +} + + +// ============================================================ +// platformValidateFilename — DOS 8.3 filename validation +// ============================================================ + +const char *platformValidateFilename(const char *name) { + static const char *reserved[] = { + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + NULL + }; + + if (!name || name[0] == '\0') { + return "Filename must not be empty."; + } + + // Split into base and extension + const char *dot = strrchr(name, '.'); + int32_t baseLen; + int32_t extLen; + + if (dot) { + baseLen = (int32_t)(dot - name); + extLen = (int32_t)strlen(dot + 1); + } else { + baseLen = (int32_t)strlen(name); + extLen = 0; + } + + if (baseLen < 1 || baseLen > 8) { + return "Filename must be 1-8 characters before the extension."; + } + + if (extLen > 3) { + return "Extension must be 3 characters or fewer."; + } + + // Check for invalid characters + for (const char *p = name; *p; p++) { + if (*p == '.') { + continue; + } + + if (*p < '!' || *p > '~') { + return "Filename contains invalid characters."; + } + + if (strchr(" \"*+,/:;<=>?[\\]|", *p)) { + return "Filename contains invalid characters."; + } + } + + // Check for multiple dots + if (dot && strchr(name, '.') != dot) { + return "Filename may contain only one dot."; + } + + // Check reserved device names (compare base only, case-insensitive) + char base[9]; + int32_t copyLen = baseLen < 8 ? baseLen : 8; + + for (int32_t i = 0; i < copyLen; i++) { + base[i] = toupper((unsigned char)name[i]); + } + + base[copyLen] = '\0'; + + for (const char **r = reserved; *r; r++) { + if (strcmp(base, *r) == 0) { + return "That name is a reserved device name."; + } + } + + return NULL; +} + + +// ============================================================ +// platformVideoInit +// ============================================================ + +int32_t platformVideoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) { + uint16_t bestMode; + uint32_t physAddr; + + memset(d, 0, sizeof(*d)); + + // Find the best VESA mode + if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) { + return -1; + } + + // Save the physical address before we overwrite it + physAddr = (uint32_t)(uintptr_t)d->lfb; + + // Set the mode + if (setVesaMode(bestMode) != 0) { + return -1; + } + + // Map the LFB + if (mapLfb(d, physAddr) != 0) { + return -1; + } + + // Allocate backbuffer + uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height; + d->backBuf = (uint8_t *)malloc(fbSize); + + if (!d->backBuf) { + fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize); + __djgpp_nearptr_disable(); + return -1; + } + + memset(d->backBuf, 0, fbSize); + + // Set up palette for 8-bit mode + if (d->format.bitsPerPixel == 8) { + d->palette = (uint8_t *)malloc(768); + + if (!d->palette) { + fprintf(stderr, "VBE: Failed to allocate palette\n"); + free(d->backBuf); + d->backBuf = NULL; + __djgpp_nearptr_disable(); + return -1; + } + + dvxGeneratePalette(d->palette); + platformVideoSetPalette(d->palette, 0, 256); + } + + // Initialize clip rect to full display + d->clipX = 0; + d->clipY = 0; + d->clipW = d->width; + d->clipH = d->height; + + fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n", + bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch); + + return 0; +} + + +// ============================================================ +// platformVideoSetPalette +// ============================================================ + +void platformVideoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) { + // Set VGA DAC registers directly via port I/O + outportb(0x3C8, (uint8_t)firstEntry); + + for (int32_t i = 0; i < count; i++) { + int32_t idx = (firstEntry + i) * 3; + outportb(0x3C9, pal[idx + 0] >> 2); + outportb(0x3C9, pal[idx + 1] >> 2); + outportb(0x3C9, pal[idx + 2] >> 2); + } +} + + +// ============================================================ +// platformVideoShutdown +// ============================================================ + +void platformVideoShutdown(DisplayT *d) { + // Restore text mode (mode 3) + __dpmi_regs r; + memset(&r, 0, sizeof(r)); + r.x.ax = 0x0003; + __dpmi_int(0x10, &r); + + if (d->backBuf) { + free(d->backBuf); + d->backBuf = NULL; + } + + if (d->palette) { + free(d->palette); + d->palette = NULL; + } + + d->lfb = NULL; + + __djgpp_nearptr_disable(); +} + + +// ============================================================ +// platformYield +// ============================================================ + +void platformYield(void) { + __dpmi_yield(); +} + + +// ============================================================ +// setVesaMode +// ============================================================ + +static int32_t setVesaMode(uint16_t mode) { + __dpmi_regs r; + + memset(&r, 0, sizeof(r)); + r.x.ax = 0x4F02; + r.x.bx = mode | 0x4000; // bit 14 = use LFB + __dpmi_int(0x10, &r); + + if (r.x.ax != 0x004F) { + fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax); + return -1; + } + + return 0; +}