Conversation
- src/lib/workbench.ts: pack/unpack IndexedDB stores + localStorage as a fflate zip; async API with worker, sync fallback when postMessage hits DataCloneError/OOM; emits phased progress (loading, reading, compressing, decompressing, restoring, finalizing). - src/lib/download.ts: helpers for "Download" right-click action; raw blob for a single file, fflate zip with the same OOM fallback for folders or multi-selection. - src/components/win2k/ProgressDialog.tsx: modal Win2k progress window reused by every long-running operation. - src/components/win2k/Progress.tsx: indeterminate (marquee) mode via CSS keyframe when percent is null. - src/components/win2k/Taskbar.tsx: reorder Start menu (Welcome, GitHub, sep, settings, sep, workbench, sep, Shut Down) wire Export/Import Workbench callbacks. - src/components/App.tsx: handlers for workbench export/import (with FileReader byte-level progress) and right-click download; shared progress state and info/error dialog. - src/components/Desktop.tsx, src/components/FolderWindow.tsx: new Download / Download as .zip context menu entry; onDownload prop drilled from App. - src/lib/ui-strings.ts: new keys for workbench export/import, progress phases, and downloads rename "Reset to Default" label to "Reset Workbench" in all 10 languages. - package.json, package-lock.json: add fflate dependency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A 261 MB .workbench file was driving Firefox past 6 GB of RAM during import. The previous flow buffered the full compressed bundle, paid a workerβmain-thread structured-clone of the entire decompressed archive (Uint8Arrays nested inside an object are not transferable), and then put every entry into a single IndexedDB transaction whose pending-write queue held yet another structured-clone copy until commit. Switch import to fflate's streaming Unzip, fed 1 MB at a time via File.slice().arrayBuffer() β Blob.stream() can preload the entire blob in some Firefox versions, slicing reads only the requested range from disk. Each entry's destination buffer is pre-allocated from originalSize so we skip the chunks[]+combined concatenation peak. Writes are batched to a 4 MB IDB transaction soft cap (a single oversized entry triggers an immediate flush) with a setTimeout(0) yield after each commit so GC can collect the just-closed transaction's clones before the next batch lands.
Skip a voice in renderSamples when wave AND vol each have any CTRL_DISABLED bit set, instead of requiring the same disabled bit on both sides. The bitwise form missed the (wave=RESET, vol=STOPPED) case that arises mid-song when the player ends a sample and stops volume separately, leaving getSample reading a stuck wave.pos and leaking the last sample byte into the mix. Auto-increment dram_addr on each DRAM read/write through port 0x107 to match real GF1 hardware and DOSBox; without it REP OUTSB / REP INSB to 0x107 funnel every byte through a single offset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/components/win2k/Window.tsx: new capBtnText helper for text-labelled caption buttons (sunken when active) onZoomToggle/zoomActive props render a 2x button before min/max/close - src/components/ConsoleView.tsx: new zoom prop multiplies the 640x480 container, both canvases, and the <pre> transform; mouse mapping unchanged (uses rect.width/rect.height); pixelated rendering added to the text canvas - src/components/EmulatorView.tsx: consoleZoom state (1 or 2), reset on EXE restart; propagated to Window clientW/clientH and to ConsoleView; onZoomToggle wired only when isConsole Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/components/win2k/Window.tsx: new svgFullscreen icon (four corner brackets) onFullscreenToggle prop renders the button after the 2Γ button - src/components/EmulatorView.tsx: handleFullscreenToggle uses the standard Fullscreen API on a wrapper ref around ConsoleView; fullscreenchange listener syncs isFullscreen so the browser ESC exit is reflected in state resize listener (active only in fullscreen) recomputes fsScale = min(vw/baseW, vh/baseH) to fit-aspect-ratio 4:3 with black bars ConsoleView is wrapped in a flex-centered 100vwΓ100vh container with an inner transform: scale(fsScale) scaler; both divs have no style when not fullscreen, so windowed behaviour is unchanged button wired only when isConsole; mouse handler already adapts via getBoundingClientRect post-transform Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/components/win2k/Window.tsx: capBtnText reserves 1px of bottom-right padding when not sunken to compensate for Tahoma's glyph offset, so "2Γ" sits visually centered svgFullscreen background position moved from "top 2px left 3px" to "top 1px left 2px" β the 9Γ9 icon in a 14Γ16 content area centers at (1.5, 2.5), so 1/2 biases up-left while 2/3 biased down-right Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/lib/emu/dos/mouse.ts:
add textCursorType/textScreenMask/textCursorMask/textCursorStart/textCursorEnd to DosMouseState
initialize defaults (screenMask=0x77FF, cursorMask=0x7700) in createDosMouseState and AX=00h reset
implement INT 33h AX=0Ah (BX=0 software masks, BX=1 hardware scanlines stored)
add getTextModeCursorOverride helper applying (orig AND screenMask) XOR cursorMask
- src/components/ConsoleView.tsx:
apply mouse cell override in drawTextModeBitmap, drawTextMode and DOM rendering loop
re-render on mouse move in text mode so the cursor follows the pointer
hide browser cursor (cursor: none) on text canvas and pre when DOS text mouse is visible
- src/lib/emu/dos/mouse.ts:
add getDisplayedTextCursor helper combining BIOS cursor and hardware mouse cursor
in hardware mode the unique CRTC text cursor follows the mouse cell with the
program's chosen scanlines (BIOS cursor suppressed while mouse is visible)
- src/components/ConsoleView.tsx:
drawTextModeBitmap, drawTextMode and DOM rendering now read the cursor through
getDisplayedTextCursor so hardware mouse mode replaces the application cursor
clamp cursor scanlines to charH-1 so 80x25 cursor settings (start/end up to 15)
do not render past the cell in 80x50 mode (charH=8)
hide browser cursor in both software and hardware text mouse modes
- src/lib/emu/dos/vga.ts:
add fontRAM Uint8Array (8 banks x 256 chars x 32 bytes = full plane 2 layout)
loadRomFontIntoBank copies the ROM 8x8 or 8x16 font into a chosen bank,
zeroing the unused tail so a previously taller font does not bleed through
getCharMapBanks decodes Sequencer reg 0x03 into mapA/mapB indices and a
fontSwitchActive flag (true when mapA != mapB - the 512-character mode
where attribute bit 3 picks the bank instead of fg intensity)
constructor now seeds bank 0 with the 8x16 ROM
- src/lib/emu/dos/video.ts:
INT 10h AX=1100h/1110h: copy CX chars x BH bytes from ES:BP into
fontRAM[BL*8192 + DX*32 ..]; AX=1110h also recalculates char height
AX=1101h/1102h/1104h: load matching ROM font into bank BL without recalc
AX=1103h: write BL into Sequencer Character Map Select (seqRegs[3])
AX=1120h/1121h: store user font pointer into INT 1Fh / INT 43h vector
AX=1122h/1123h: load matching ROM font into bank 0 + update INT 43h vector
AX=1111h/1112h/1114h still switch screenRows + charHeight as before; they
now also seed bank 0 from the matching ROM
setVideoMode reseeds bank 0 with the new mode's ROM font on each switch
drawCharGraphics resolves the bank via getCharMapBanks and reads from
fontRAM[bank*8192 + ch*32]
- src/components/ConsoleView.tsx:
drawTextModeBitmap reads from fontRAM with bank resolved per cell;
in 512-character mode foreground is restricted to 8 colors as on real VGA
drop unused VGA_FONT_8X8_ROM / VGA_FONT_8X16_ROM imports
- src/lib/dos-settings.ts:
textRenderer default flipped from 'dom' to 'canvas' so DOS programs that
redefine glyphs via INT 10h AX=11xx render correctly out of the box;
DOM mode (browser CSS font, allows text selection) remains available in
settings for users who prefer it
- src/components/win2k/Window.tsx:
new sysMenuPos state, opens a MenuDropdown anchored at the icon's
bottom-left when the title-bar icon is single-clicked
double-click on the icon closes the window (DOUBLE_CLICK_MS = 400)
menu items: Restore / Move / Size / Minimize / Maximize / Close (Alt+F4)
plus Zoom 2Γ and Full Screen entries (with checked state) when the
matching toggle callbacks are provided
items are grayed per WS_THICKFRAME / WS_MINIMIZEBOX / WS_MAXIMIZEBOX and
the current min/max state, mirroring real Windows behaviour
new onSystemMove / onSystemSize / fullscreenActive props
- src/components/EmulatorView.tsx:
handleSystemMove drives windowPos from the pointer until next click;
Escape cancels back to the original position
handleSystemSize drives canvasSize from the pointer in the same way and
applies the result to the emulator on commit
pass fullscreenActive=isFullscreen so the system-menu Full Screen entry
reflects the current state with a check mark
- src/lib/ui-strings.ts:
add sysMaximize / sysMove / sysSize / sysZoom2x / sysFullscreen to the
UiStrings interface
translate the new keys for the 10 supported locales (en / fr / de / es /
ja / zh-CN / pt-BR / it / pl / ko)
The DOS canvas renderer kept stale canvas.width / canvas.height after a
resolution switch, so a 640x480 mode 12h framebuffer drawn into a canvas
left at 640x400 (from a previous 80x25 text frame) was vertically clipped
and stretched by CSS scaling, hiding the bottom 80 pixels in apps like
NeoPaint.
- src/lib/emu/dos/vga.ts:
initFramebuffer detects when the new ImageData has different dimensions
than the previous one and notifies via the new onFramebufferResize hook
- src/lib/emu/emulator.ts:
new onVideoModeChange callback; initVgaModeXHook wires
vga.onFramebufferResize to it
- src/components/ConsoleView.tsx:
set onVideoModeChange to render() so the JSX re-applies width/height
props to the graphics canvas DOM element on each resolution change
add distinct key="gfx" / key="text" props on the two <canvas> branches
so React unmounts and remounts when switching between text and graphics
rendering paths instead of reusing the DOM node with stale dimensions
Text-mode rendering used to be stretched vertically by 1.2Γ so 25 rows of
16-px glyphs filled the 480-px window, which made some horizontal strokes
end up two CSS pixels tall while others stayed at one (visible thicker
bottom on letters like "o"). The window is now sized to match the active
video mode: 480 in graphics, ROWS Γ charH (= 400 for 80Γ25 and 80Γ50) in
text β every glyph row maps 1:1 to a CSS pixel.
- src/components/ConsoleView.tsx:
dispH now derives from emu.isGraphicsMode, screenRows and charHeight
new onScreenLayoutChange prop fires from emu.onVideoModeChange so the
parent can resize the surrounding window in lock-step
- src/components/EmulatorView.tsx:
consoleClientH passed to Window is computed from the live emu state
(graphics mode β 480, text β ROWS Γ charH); a layout-bump state forces
the recompute when ConsoleView reports a screen-layout change
The scaleX measurement effect did not include useCanvas in its deps. Apps
launched while the canvas renderer was active never mounted the <pre>
element on first render, the effect bailed early on a null preRef, and
scaleX stayed at its initial value of 1; switching to the DOM renderer
afterwards rendered the <pre> with no horizontal compensation, so 80 cols
worth of natural font advance overflowed the 640-px target and stretched
horizontally.
- src/components/ConsoleView.tsx:
add useCanvas to the scaleX useEffect deps and skip the measurement
while the canvas renderer is active so the run that matters always
fires once <pre> is in the DOM
The Win32 canvas renderer used to size its backing surface to
ceil(COLS Γ naturalCharW) (~768 for Cascadia Mono at 16 px) and let CSS
downsample to 640. The non-integer 768β640 ratio dropped one pixel from
some columns, so vertical strokes on glyphs like '0' or '||' rendered
unevenly thin/thick.
The fix renders the canvas at exactly COLS Γ 8 = 640 px and applies
ctx.scale(8/naturalCharW, 1) so the natural-width glyphs are uniformly
compressed into 8-px cells. The first attempt also shifted text up by
one row in cmd.exe: the new cw matched the JSX width={640} attribute
exactly, so the canvas-resize branch never fired and ctx.textBaseline
stayed at its default 'alphabetic' instead of 'top' β fillText then
drew glyphs above their intended y, hiding row 0 off-screen and putting
the cursor (drawn by fillRect, unaffected by textBaseline) one line
below the typed text.
- src/components/ConsoleView.tsx:
drawTextMode now sets canvas.width = COLS Γ 8 and applies
ctx.scale(8/naturalCharW, 1) so the font is uniformly squished into
each 8-px column with no browser downsampling artefact
move ctx.font and ctx.textBaseline = 'top' assignments out of the
canvas-resize branch so they are reapplied on every frame, even when
the JSX attributes already match the target dimensions
Android virtual keyboards (Gboard, etc.) deliver typed characters via
beforeinput events with `e.data` instead of keydown β keydown arrives
with keyCode=229, an empty `e.code` and `e.key="Unidentified"`, so the
existing scancode lookup never matches and nothing reaches the emulator.
Hook a beforeinput handler that synthesises both make and break codes
(Android emits no keyup), and add an `e.key` fallback in keydown for the
sources that do emit a keydown but leave `e.code` blank (Bluetooth
keyboards). On desktop, preventDefault in keydown cancels the would-be
text insertion, so beforeinput never fires for physical frappes β no
behavioural change there.
- src/components/ConsoleView.tsx:
add CHAR_TO_SCANCODE reverse table (US layout) for printable chars
add KEY_TO_SCANCODE / KEY_IS_EXTENDED fallbacks for empty-code events
skip preventDefault on keyCode=229 keydowns to avoid disturbing the
IME composition pipeline
new handleBeforeInput captures insertText / deleteContentBackward /
insertLineBreak and emits scan + scan|0x80 in one go since Android
sends no matching keyup
wire onBeforeInput on the hidden console input plus
autoCapitalize/autoComplete/autoCorrect="off" and spellcheck={false}
so the IME does not transform the typed character
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Waiting for merge of #46