Skip to content

feat: Add WebAssembly browser port + fix Ctrl+Alt+F8 fast mode on all platforms - #159

Open
anomixer wants to merge 11 commits into
samkusin:mainfrom
anomixer:main
Open

feat: Add WebAssembly browser port + fix Ctrl+Alt+F8 fast mode on all platforms#159
anomixer wants to merge 11 commits into
samkusin:mainfrom
anomixer:main

Conversation

@anomixer

Copy link
Copy Markdown

feat: Add WebAssembly browser port + fix Ctrl+Alt+F8 fast mode on all platforms

Hi @samkusin,

I've been working on a WebAssembly/Emscripten port of Clemens IIGS that lets the emulator run directly in a browser — no installation needed. Along the way, I also found and fixed the Ctrl+Alt+F8 Fast Mode shortcut, which was broken on all platforms (not just web).

WASM Demo here: https://anomixer.github.io/clemens_iigs/


What's included

🌐 WebAssembly / Emscripten Browser Port

  • Implemented clem_host_platform_select_disk and clemens_host_import_disk to support disk image loading from the browser.
  • Used EM_JS to embed a JavaScript file picker that reads a user-selected disk image into Emscripten's MEMFS and passes it back to C++ via Module.ccall.
  • Implemented clemens_emscripten_mount_disk (exported via EMSCRIPTEN_KEEPALIVE) to receive the file path from JS and mount it in the emulator.
  • Fixed startup crashes by making get_local_user_directory return a valid VFS path (/home/web_user).
  • Updated host/CMakeLists.txt to export ccall and UTF8ToString runtime methods, preventing runtime aborts when JS calls into C++.
  • Modified clem_front.cpp to conditionally trigger disk selection via the platform API when disk icons are clicked in Emscripten builds.
  • Added build_emscripten.bat — a one-step build script that automatically downloads the Emscripten SDK and Ninja, then compiles the project.

⌨️ Ctrl+Alt+F8 Fast Mode Fix (affects all platforms)

The original code in clem_front.cpp hard-codes ImGuiKey_LeftAlt:

if (ImGui::IsKeyDown(ImGuiKey_LeftAlt)) {
    if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ...) {
        // Fast Mode, Pause, etc.
    }
}

This means Right Alt + Ctrl + F8 does nothing on Windows, Linux, and macOS — and on the web, even Left Alt detection is unreliable due to how Sokol maps modifier keys.

The fix replaces these with ImGui::GetIO().KeyAlt and ImGui::GetIO().KeyCtrl, which correctly detect either Left or Right modifier keys on all platforms. I also removed the alternative number key bindings (8, 5, 0, -) to avoid conflicts with emulated Apple IIGS software.

Final shortcut layout:

Shortcut Action
Ctrl + Alt + F8 Toggle Fast Mode
F5 Pause / Resume
F10 Mouse Lock
F11 Debugger
Ctrl + LAlt + RAlt + F1 IIGS Control Panel (web only)

Files Changed

File Description
host/clem_front.cpp Shortcut fix + Emscripten disk selection
host/clem_front.hpp Platform abstraction declarations
host/clem_host.hpp Host interface updates
host/clem_host_app.cpp Key mapping for IIGS Control Panel (web)
host/clem_host_platform.h Platform API definition
host/platform/host_emscripten.c Full Emscripten implementation
host/CMakeLists.txt Build export flags
host/resources/emscripten_shell.html Cleanup
build_emscripten.bat New automated build script
README.md Updated build instructions
.gitignore Updated

How to Test

Web version:

  1. Run .\build_emscripten.bat (Emscripten SDK downloaded automatically)
  2. Run .\run_emscripten.bat
  3. Open http://localhost:6931/clemens_iigs.html
  4. Test disk mounting via the disk icons in the UI
  5. Verify Ctrl+Alt+F8 toggles Fast Mode correctly

Desktop version:

  • Verify Ctrl+Alt+F8 now works with both Left and Right Alt/Ctrl keys

Note: Disk images loaded in the browser are written to Emscripten's in-memory filesystem (MEMFS) and are temporary — lost on page reload. This is expected behaviour.

@samkusin samkusin self-assigned this Feb 26, 2026
@samkusin samkusin added the enhancement New feature or request label Feb 26, 2026
@samkusin

samkusin commented Feb 26, 2026

Copy link
Copy Markdown
Owner

Seeing Clemens on the browser looks great.

NOTE: I understand this was developed with AI based on the included Agent.md plus some other markers. It would be good to know to what extent this is agent-driven vs human encouraged with assistance from Claude/Gemini/etc. I'm not wholly against AI assisted PRs if they are beneficial to the project (Emscripten support is an example.)

There are a few issues with the PR though that I'll have to resolve before merging:

  • It removes the existing workflows for building the other platforms (though this may have been done to avoid triggering the workflows on your environment.)
  • The inclusion of the IIgs ROM - I'm not sure this is allowed though its unlikely given the hardware's age and the emulator's reach to matter. I'm wondering if the ROM needs to be in github and instead supplied by the user during the build.

anomixer added 9 commits March 1, 2026 22:12
- Restore original build-linux.yml, build-macos.yml, build-windows.yml
  from upstream (samkusin/clemens_iigs) unchanged. The Emscripten
  deploy workflow (deploy-emscripten.yml) is a separate addition that
  does not conflict with the existing CI pipelines.

- Remove committed rom/rom.v3 (Apple IIgs ROM 3). The ROM is copyright
  Apple Computer, Inc. and should not be distributed in the repository.
  Users must supply their own ROM file.

- Add CLEMENS_ROM_PATH CMake cache variable (host/CMakeLists.txt) so
  users can specify a custom ROM path at configure time:
    cmake ... -DCLEMENS_ROM_PATH=/path/to/rom.v3
  Default path remains rom/rom.v3 (relative to project root).
  CMake now fails with a clear error message if the file is missing.

- Update build_emscripten.bat to check for ROM existence before
  building and print actionable instructions if it is not found.
  Also supports CLEMENS_ROM_PATH environment variable override.

- Update deploy-emscripten.yml to restore ROM at build time from
  a base64-encoded GitHub Secret (IIGS_ROM_V3_BASE64) so CI/CD
  can build the web demo without committing the ROM to the repo.

- Update .gitignore to exclude the entire rom/ directory.
GitHub Secrets have a 65,536 char limit. The Apple IIgs ROM (256KB)
encodes to ~349KB in base64, so a single secret is too large.

- Add split_rom_secret.ps1 to the project root. Run it once to split
  the ROM into 6 base64 chunks (each ~60KB). The output txt files are
  gitignored and will not be committed.
- Update deploy-emscripten.yml to concatenate IIGS_ROM_V3_BASE64_1
  through IIGS_ROM_V3_BASE64_6 and pipe them into base64 -d to
  reconstruct rom/rom.v3 before the CMake build step.
- Update .gitignore to exclude rom_secret_part*.txt output files.
…edDB cache

Instead of embedding the ROM at build time (which requires users to
have the file locally before building, and caused the GitHub Secret
size issue), the ROM is now loaded entirely at runtime in the browser.

How it works:
1. At startup, the emulator enters a WaitForRom state (Emscripten only).
2. clem_host_platform_load_rom() is called once, which runs async JS:
   - Checks IndexedDB ('ClemensIIGS' db, 'assets' store) for a cached ROM.
   - If found: loads it directly into MEMFS at /rom.v3 and signals ready.
   - If not found: shows a styled overlay with a 'Select ROM file' button.
3. When the user picks a file, it is validated (must be 262144 bytes),
   written to MEMFS, and saved to IndexedDB for future sessions.
4. Once ROM state is 'ready' (state=2), startup continues normally.
5. On subsequent page loads the ROM is restored from IndexedDB silently.

Changes:
- host/platform/host_emscripten.c: Add ROM state machine, EM_JS loader
  with IndexedDB persistence and styled file picker overlay.
  Exports clemens_emscripten_rom_ready/error as EMSCRIPTEN_KEEPALIVE
  for JS-to-C callbacks via Module.ccall.
- host/clem_host_platform.h: Declare clem_host_platform_load_rom() and
  clem_host_platform_rom_state() under CLEMENS_PLATFORM_EMSCRIPTEN.
- host/clem_startup_view.hpp/.cpp: Add WaitForRom mode between Initial
  and Preamble (Emscripten only, guarded by #ifdef __EMSCRIPTEN__).
  Other platforms are completely unaffected.
- host/CMakeLists.txt: Remove --preload-file ROM line and CLEMENS_ROM_PATH
  variable. No ROM needed at build time.
- .github/workflows/deploy-emscripten.yml: Remove ROM secret handling;
  build is now self-contained with no external dependencies.
- build_emscripten.bat: Remove ROM pre-check; add note about first-launch
  ROM selection.
- agent.md: rewritten to document all three tasks (disk selection,
  shortcut fix, PR feedback / runtime ROM loading). Includes full
  technical decision table and modified file list.
- README.md: update web demo description to explain ROM selection
  on first launch and IndexedDB persistence. Add step 4 to Emscripten
  build instructions noting the first-launch ROM prompt.
…ve split_rom_secret.ps1

The previous replace_file_content edit only replaced the beginning of
host_emscripten.c, leaving the entire original content duplicated from
line 297 onward. This caused 20+ 'redefinition of ...' errors during
the Emscripten build (GitHub Actions Deploy Emscripten samkusin#14 failure).

Fixed by removing the duplicated block (dangling EM_JS body +
redefined C functions for clem_host_platform_select_disk,
clemens_emscripten_mount_disk, get_process_executable_path,
get_local_user_directory, get_local_user_data_directory,
get_local_user_config_directory, and others).

Also removes split_rom_secret.ps1 (no longer needed since the ROM
is loaded at runtime; no secrets splitting required).
After the user selects rom.v3 via the browser file picker and
writeRomToMemfs() completes, the config_.romFilename was still empty.
This caused the Main view to show the Settings/Power-On screen asking
the user to locate the ROM again, instead of booting the Apple IIgs.

Fix: when romState == 2 (ROM ready in MEMFS at /rom.v3), set
config_.romFilename = 'rom.v3' before transitioning to Mode::Preamble.
This is the relative path that the emulator uses to read the ROM.
The #ifdef __EMSCRIPTEN__ / #endif block for WaitForRom swallowed the
'case Mode::Preamble:' label that was originally placed immediately
after it. This caused the Preamble code block to be dead code - it was
never reached regardless of mode_, leaving the startup view stuck with
no ImGui content (rendering just the clear-color blue background).

Fix: add 'case Mode::Preamble:' label between #endif and the preamble
body so the switch dispatches correctly to both WaitForRom (Emscripten)
and Preamble (all platforms).
@anomixer

anomixer commented Mar 1, 2026

Copy link
Copy Markdown
Author

Seeing Clemens on the browser looks great.

NOTE: I understand this was developed with AI based on the included Agent.md plus some other markers. It would be good to know to what extent this is agent-driven vs human encouraged with assistance from Claude/Gemini/etc. I'm not wholly against AI assisted PRs if they are beneficial to the project (Emscripten support is an example.)

There are a few issues with the PR though that I'll have to resolve before merging:

  • It removes the existing workflows for building the other platforms (though this may have been done to avoid triggering the workflows on your environment.)
  • The inclusion of the IIgs ROM - I'm not sure this is allowed though its unlikely given the hardware's age and the emulator's reach to matter. I'm wondering if the ROM needs to be in github and instead supplied by the user during the build.

Hi Sam, thanks for the detailed feedback and for taking time to review!
Here's a summary of what I've addressed:


1. AI Usage Transparency

This PR was developed with substantial AI assistance (Antigravity / Gemini Pro + Claude),
acting as a "super-IDE" pair programmer. The overall direction, architecture decisions, and
testing were human-guided — I specified what to build and validated the results at each step.
The AI handled boilerplate, C/JS interop details, and iterative bug-fixing. I think this is
roughly equivalent to "heavy use of GitHub Copilot + Stack Overflow", just more interactive.
I'm happy to be transparent about this in the Agent.md file (already included), which
documents what was built and how.


2. Restored Original CI Workflows

The existing build-linux.yml, build-macos.yml, and build-windows.yml workflows have
been fully restored to match your upstream versions exactly. The new Emscripten
deployment lives in a separate deploy-emscripten.yml and does not interfere with your
existing CI pipelines.


3. ROM Copyright — Now Fully Runtime-Loaded

The rom/rom.v3 file has been removed from the repository entirely (git rm --cached),
and rom/ is now in .gitignore.
Instead, the ROM is loaded at runtime in the browser, with no build-time dependency:

  • On first visit, a full-screen dialog prompts the user to select their own rom.v3 from
    their local machine. The file is validated (must be exactly 262,144 bytes / 256 KB).
  • The ROM is then written to Emscripten's in-memory filesystem (MEMFS) and simultaneously
    saved to the browser's IndexedDB for persistence.
  • On all subsequent visits the ROM loads silently from IndexedDB — the user never has to
    select it again.
  • The CI/CD build (deploy-emscripten.yml) requires no ROM file and no secrets at
    build time.
    The live demo is at: https://anomixer.github.io/clemens_iigs/

Now the merge is ok to go!
Let me know if there's anything else you'd like me to change before merging.

Thanks again!

@anomixer

anomixer commented Mar 1, 2026

Copy link
Copy Markdown
Author

One more note on the CI checks — all workflows on your end are showing
"Action required" status. This is GitHub's security gate for first-time
contributor PRs; no actual build errors.

To run them, go to your Actions tab
and click "Approve and run" on the pending workflow runs (Linux All Build,
Windows All Build, Deploy Emscripten).

Once approved they should all pass. 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants