Skip to content

Support js/wasm build - #138

Open
ngs wants to merge 5 commits into
awesome-gocui:masterfrom
ngs:wasm-js-support
Open

Support js/wasm build#138
ngs wants to merge 5 commits into
awesome-gocui:masterfrom
ngs:wasm-js-support

Conversation

@ngs

@ngs ngs commented Jul 31, 2026

Copy link
Copy Markdown

Summary

This PR makes gocui compile for GOOS=js GOARCH=wasm, so gocui applications can run in a browser using tcell's WebAssembly backend (webfiles/).

Currently a js/wasm build fails because gui_others.go relies on tty ioctls and signals that do not exist on js:

gui_others.go:35:34: undefined: syscall.SIGWINCH
gui_others.go:38:37: undefined: syscall.SYS_IOCTL
gui_others.go:39:30: undefined: syscall.TIOCGWINSZ
gui_others.go:49:16: undefined: syscall.SIGWINCH

Changes

Commit 1 — build support

  • Exclude gui_others.go from js builds (//go:build !windows && !js)
  • Add gui_js.go with a getTermWindowSize implementation that asks the tcell screen for its size, which is how the WebAssembly backend reports the emulated terminal dimensions

Commit 2 — let the hosting page control the terminal size (optional but makes browser apps practical)

  • If the page defines terminalCells() returning [cols, rows], the terminal is sized to it at startup instead of tcell's default 80x24
  • A global tcellSetSize(cols, rows) is registered so the page can resize the running terminal from a resize listener; tcell posts an EventResize and gocui relayouts as usual
  • The page is treated as an untrusted boundary: thrown exceptions and malformed values are ignored. Screen.SetSize is looked up via an interface assertion because the tcell version pinned in go.mod (v2.4) predates it, so this PR requires no dependency bump; applications building with a newer tcell get the behavior automatically

Testing

  • go build ./... and go test ./... pass on darwin
  • GOOS=js GOARCH=wasm go build ./... now succeeds
  • Verified end to end with a real gocui application compiled to wasm and running in a browser via tcell's webfiles, including dynamic window resizing: https://koikoi.ngs.io (source: https://github.com/ngs/go-koikoi)

Exclude the tty/ioctl based getTermWindowSize from js builds and add a
js implementation that asks the tcell screen for its size, so gocui
applications can be compiled with GOOS=js GOARCH=wasm and run in a
browser via tcell's WebAssembly backend.

Claude-Session: https://claude.ai/code/session_01NP7bUvSDzvc2XwHE5YhX4o
ngs added a commit to ngs/go-koikoi that referenced this pull request Jul 31, 2026
- Switch gocui to the ngs/gocui fork with js/wasm support
  (upstream PR: awesome-gocui/gocui#138) and upgrade tcell to v2.13
- Abstract persistence behind storage.go / storage_js.go; the js build
  saves settings and game progress to localStorage
- Add web/ static assets: index.html (fullscreen terminal sized to the
  window via terminalCells/tcellSetSize, GitHub link overlay) and
  patched tcell.js webfiles (clear stale cell next to wide chars, force
  wide glyphs to 2ch, compute mouse cell coordinates per event)
- Add make wasm / make serve-wasm (Go dev server with port fallback)
- Add pages.yml workflow deploying dist/ to GitHub Pages (koikoi.ngs.io)

Claude-Session: https://claude.ai/code/session_01NP7bUvSDzvc2XwHE5YhX4o
If the page defines terminalCells() returning [cols, rows], the js/wasm
terminal is sized to it at startup instead of tcell's default 80x24, and
the page can call tcellSetSize(cols, rows) from a resize listener to
resize the running terminal. tcell's SetSize posts an EventResize, so
gocui relayouts as usual.

The page is treated as an untrusted boundary: exceptions thrown by
terminalCells() and malformed return values are ignored. Screen.SetSize
is looked up via an interface assertion because the tcell version pinned
here (v2.4) predates it; applications that build with a newer tcell get
the behavior automatically.

Claude-Session: https://claude.ai/code/session_01NP7bUvSDzvc2XwHE5YhX4o
@dankox

dankox commented Aug 19, 2026

Copy link
Copy Markdown

It might be worth to just update tcell to newer version where SetSize is available, provide a new API in gocui to enable SetSize and change the NewGui in gui.go on line 138 to:

	g.maxX, g.maxY = screen.Size()

No need for the gui_others.go and gui_windows.go anymore after that change (it's all handled by tcell).

Then in your wasm app, you could just use the gocui.SetSize without needing a JS wrapper to check for specific global functions.

ngs added 3 commits August 25, 2026 08:11
Follow up on the review of awesome-gocui#138: instead of teaching gocui about the
browser through a set of JS globals, get the terminal size from tcell on
every platform and expose tcell's Screen.SetSize as a gocui API.

- Bump tcell to v2.9.0. Screen.SetSize landed in v2.6.0, but v2.9.0 is
  the first release whose wScreen implements the full screenImpl
  interface, so it is the oldest version that builds for GOOS=js
  GOARCH=wasm.
- NewGui now always takes the size from screen.Size(), which makes the
  platform specific getTermWindowSize implementations redundant:
  gui_others.go (tty ioctl), gui_windows.go (GetConsoleScreenBufferInfo)
  and gui_js.go (the JS globals added by this branch) are all removed.
- Add Gui.SetSize, so an application can ask the terminal to resize.
  On js/wasm this lets the hosting page decide how many cells fit into
  the browser window, without gocui reaching into JS itself.

Note that tcell v2.9.0 requires Go 1.23, so the go directive of both
modules moves from 1.13 to 1.23.0.
Purely mechanical: gofmt reindents the Go 1.19 style doc comment code
blocks. The CI formatting job runs gofmt -s from the matrix Go version,
so this is needed before that matrix can move off Go 1.16. No code
changes.
The matrix was pinned to Go 1.13/1.15/1.16, which can no longer resolve
the module now that the go directive is 1.23.0. Also build the js/wasm
target and run the tests, neither of which the workflow covered.
@ngs

ngs commented Aug 24, 2026

Copy link
Copy Markdown
Author

Thanks @dankox, that is a much better shape than what I had. I reworked the branch along your suggestion.

What changed

  • NewGui now always takes the size from screen.Size(). With that, the platform specific getTermWindowSize implementations are redundant, so gui_others.go (tty ioctl), gui_windows.go (GetConsoleScreenBufferInfo) and the gui_js.go I had added are all gone. No syscall, no unsafe, no syscall/js in the package any more, and no runtime.GOOS branch in NewGui.
  • New Gui.SetSize(x, y int), a thin wrapper over tcell.Screen.SetSize. The new size is picked up by the next flush, which already reads screen.Size(), so nothing else had to change.
  • tcell is bumped to v2.9.0. One note on the version: Screen.SetSize actually landed back in v2.6.0, but v2.9.0 is the first release whose wScreen implements the full screenImpl interface (GetClipboard was missing before), so it is the oldest tag that compiles for GOOS=js GOARCH=wasm. Happy to move it to the latest v2.13.x if you prefer.

Two things worth your call

  1. Go version. tcell v2.9.0 requires Go 1.23, so the go directive of both modules moves from 1.13 to 1.23.0. That in turn breaks the CI matrix, which was pinned to 1.13/1.15/1.16, so I bumped it to 1.23/1.24/1.25 and added a GOOS=js GOARCH=wasm build and a go test ./... step (the workflow only checked formatting and built the examples). The Go 1.19 style doc comments then trip the gofmt -s job, so there is a separate, purely mechanical commit reformatting them — feel free to drop that one if you would rather handle it apart from this PR.
  2. Dropping the ioctl path. gui_others.go existed for FYI: gocui triggers a bug in Go 1.13 (pre-release) #33 (a size of 0x0 inside a docker container, waiting on SIGWINCH until a real size arrives). Modern tcell resolves the size itself and falls back sensibly, so I removed it as you suggested, but flagging it since it is the one behaviour this PR changes for non-wasm users.

Verified

go build ./... for darwin, linux, windows and js/wasm, plus go vet ./... and go test ./..., all green; the _examples module builds too. I also wired the app this came from against this branch and built it for js/wasm: the JS side is now just a resize listener calling into the app, which calls gocui.SetSize — no globals for gocui to discover.

For context on the tcell side: the wasm backend fixes I have been leaning on (gdamore/tcell#1150, gdamore/tcell#1151) are ones I sent upstream and gdamore merged earlier this month, so the browser path is in reasonable shape now.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants