Skip to content

Add experimental Windows support via windows.sh - #123

Open
adsr wants to merge 1 commit into
masterfrom
windows3
Open

Add experimental Windows support via windows.sh#123
adsr wants to merge 1 commit into
masterfrom
windows3

Conversation

@adsr

@adsr adsr commented Nov 10, 2025

Copy link
Copy Markdown
Contributor

windows support has been requested several times throughout the years, however i've always been reluctant to pollute the library with ifdefs and/or abstraction layers for this sake. we avoid this with a shell script (windows.sh) that does surgery on termbox2.h to generate a windows-compatible version. some headers are swapped, some functions are altered or replaced, but most of the library stays the same. a couple marker comments (__posix_(start|end)) are added to termbox2.h which windows.sh uses to anchor various sed/awk commands.

theoretically the test suite could run on windows. it would take some research to figure out the windows equivalents of things in tests/run.sh like xvkbd etc. github docs state that windows-latest has php pre-installed, and php does have windows ffi support. the memfd-based tests would have to be skipped (or proper rwfd support would need to be added to the windows lib).

instead of running the test suite, we ensure we can at least compile the keyboard demo on windows. in my opinion this suffices for experimental support.

windows support has been requested several times throughout the
years, however i've always been reluctant to pollute the library with
ifdefs and/or abstraction layers for this sake. we avoid this with a
shell script (`windows.sh`) that does surgery on `termbox2.h` to
generate a windows-compatible version. some headers are swapped, some
functions are altered or replaced, but most of the library stays the
same. a couple marker comments (`__posix_(start|end)`) are added to
`termbox2.h` which `windows.sh` uses to anchor various sed/awk
commands.

theoretically the test suite could run on windows. it would take some
research to figure out the windows equivalents of things in
`tests/run.sh` like `xvkbd` etc. github docs state that
`windows-latest` has php pre-installed, and php does have windows ffi
support. the memfd-based tests would have to be skipped (or proper
rwfd support would need to be added to the windows lib).

instead of running the test suite, we ensure we can at least compile
the keyboard demo on windows. in my opinion this suffices for
experimental support.
@cowboyd

cowboyd commented Nov 11, 2025

Copy link
Copy Markdown

Looking with an eye towards implementing wasm support which is very similar to posix, but will have different "cut points" than what is required for windows, so we would need to have separate places for _start/_end, and then we're back to ifdef by another name. It makes me wonder if it wouldn't be possible to define a host interface which was the minimal set of functions required for termbox2 to interact with its environment. For example, I imagine that signal handling will be different on posix, windows, and wasm, so rather than swapping out the implementation with sed, you can have a host specific function that is delegated to. E.g.

tb_host_init();
tb_host_denit(); 
tb_host_on_resize(handle_resize);

What it means to init, de-init, and install a signal handler is different on all three hosts (I've only listed those three, but there would be more). With respect to signal handling for example: Posix listens for SIGWINCH, windows does lord knows what, and with WASM it has to be provided via an externally linked function by Node or the browser.

Then, you could have a single section that you could swap out, right after the includes, that would have everything related to the side-effects of the host environment. An added advantage of this approach would be that the "patch" could be stored as a normal header file which would make long term maintenance much less painful.

Just a thought. It probably does entail some refactoring, but I think that having side-effects behind a well defined API will make the code much more readable while at the same time supporting subtle differences in host environment.

@tokyovigilante

Copy link
Copy Markdown

Just a test report, used this successfully to port my Nim TUI app to Windows (using the new Windows Terminal / Powershell). Thanks!

@adsr

adsr commented Nov 15, 2025

Copy link
Copy Markdown
Contributor Author

Just a test report, used this successfully to port my Nim TUI app to Windows (using the new Windows Terminal / Powershell). Thanks!

Excellent. Thank you for testing.

@sewbacca

Copy link
Copy Markdown

You did this to minimize ifdefs, but aren't posix start/end comments ifdefs in disguise?

Also imho this makes the single implementation header file less single implementation. You need to ship two distinct windows and a linux header files for this to work as well as a msys2 environment if you wanna compile this on windows (because AFAIK Windows doesn't have sed/awk natively).

@adsr

adsr commented Nov 29, 2025

Copy link
Copy Markdown
Contributor Author

You did this to minimize ifdefs, but aren't posix start/end comments ifdefs in disguise?

Sort of but they avoid the big else blocks for Windows code which IMO harms the readability of the main use-case of termbox, which is POSIX. The previous 2 PRs for Windows support were ifdef-based. I never felt good about merging them for this reason. The problem compounds if we include WASM support.

Also imho this makes the single implementation header file less single implementation. You need to ship two distinct windows and a linux header files for this to work as well as a msys2 environment if you wanna compile this on windows (because AFAIK Windows doesn't have sed/awk natively).

Good points. I don't love this solution either, but I still prefer it over ifdefs or going multi-file.

I place high value on readability. A few small ifdefs here or there are fine IMO, but when replacing entire functions it starts getting ugly and harder to follow. If it weren't a single-file library, there are ways we could make it cleaner. However I also don't want to sacrifice single-file for the sake of readability. That's how I arrived at the solution in this PR.

My view might change if lots of Windows developers started using termbox. In absence of that it's cleaner to optimize the experience for POSIX.

It's a good point that Windows users won't have sed or awk. There are options for them though -- msys2 as you mentioned, WSL, Cygwin. I don't interact with Windows developers enough to know how much friction that introduces. (On my Windows machine, the first thing I did was install WSL.) Would it prevent you from using the library?

EDIT: We could rewrite the code gen script in a language with Windows support like PHP. My guess is that'd introduce more friction than requiring WSL etc.

We could include a pre-generated Windows header in the repo, though I'm less inclined. Making it a downloadable artifact in GitHub CI is another option.

Thank you for the feedback.

@sewbacca

Copy link
Copy Markdown

Sort of but they avoid the big else blocks for Windows code which IMO harms the readability of the main use-case of termbox, which is POSIX. The previous 2 PRs for Windows support were ifdef-based. I never felt good about merging them for this reason. The problem compounds if we include WASM support.

My idea might be naive, but doesn't we only need 2 #ifdefs in total with an elseif for each platform? One at the header for defining os specific includes / structures. Then we add an abstraction layer for os specific functionality (haven't had a look at the code so this might be more complicated than I imagine), which will be implemented in the second #ifdef (again one elseif for each platform like posix, windows or wasm). These functions can then be called by more general functions implementing the API.

Good points. I don't love this solution either, but I still prefer it over ifdefs or going multi-file.
You also could go multifile and generate a single header file from it that will be distributed:

You basically already are multifile, because now code concerning windows is bundled in windows.sh. What you could do is go multifile all the way, add a c file for each backend, and generate a header file that pastes the different implementations in #ifdefs. This way the distributed header file stays single header and we get the added benefit of code clearity, by clearly seperating code concerning platform specifics. This add's the requirement to build a headerfile for each commit, but that could be done automatically using git hooks.

My view might change if lots of Windows developers started using termbox. In absence of that it's cleaner to optimize the experience for POSIX.

This sounds a bit like the chicken or the egg problem. Not a lot Windows devs will start using termbox if it is not cross platform yet.

Would it prevent you from using the library?

I've created Lua bindings to the library using the previous PR, which still lies unpublished in some private repo. LuaRocks is the defacto package manager for Lua is available on msys2 and native windows (as well as all unix systems). This means executing a script beforehand that uses unix like tools is only possible if the Windows installation of LuaRocks comes from msys2. It would not prevent using the library, but if you need to install additional tooling, the pool of potential users gets smaller.

We could include a pre-generated Windows header in the repo, though I'm less inclined. Making it a downloadable artifact in GitHub CI is another option.

When it comes to packaging then at least for windows pre-generated header files make it much easier (if we need unix tools). I personally would use the two #ifdef option I discussed above, as it has zero dependencies as well as doesn't require preprocessing.

Thank you for the feedback.

No problem. Take my comments as mere suggestion, as I am currently not really using termbox. Take what you think is valuable and toss the rest!

I wanted to create a CLI shell for Lua that does autocompletion popups while still running within the shell (not resetting the whole screen) and the libraries I found were unix only or had a dependency hell; and since TB2 does not support non fullscreen apps, I've opted to implement the TTY communication myself.

I have a few fullscreen CLI ideas, for which I will probably go back to TB2, because ncurses is a dependency hell as well. And then maybe I'll finally publish the bindings.

@adsr

adsr commented Nov 30, 2025

Copy link
Copy Markdown
Contributor Author

I can maybe warm up to that idea.

termbox2_base.h (today's termbox2.h minus platform-specific code)
termbox2_posix.c (platform-specific code)
termbox2_win.c (ditto)

termbox2_posix.h (termbox2_base.h + termbox2_posix.c, generated by Makefile rule via git hook or GitHub CI)
termbox2_win.h (termbox2_base.h + termbox2_win.c, ditto)

termbox2.h (copy of termbox2_posix.h for back-compat)

Thank you again for the input. I'll leave this PR open for more feedback. And maybe open a PR that implements the above.

and since TB2 does not support non fullscreen apps

If you haven't seen it already, check out #114. I didn't find an obvious portable way to scroll the primary screen. The PR seems to work in xterm, vte, and the Linux console. I'd like to test it on other terminals before merging.

The other open question there is whether to support x-offset and variable width inline TUIs. I'm not sure if there's any practical use for that to be worth the complexity. As written it only support full-width (and variable height of course).

@sewbacca

sewbacca commented Nov 30, 2025

Copy link
Copy Markdown

termbox2_posix.h (termbox2_base.h + termbox2_posix.c, generated by Makefile rule via git hook or GitHub CI)
termbox2_win.h (termbox2_base.h + termbox2_win.c, ditto)

I would personally advise against using platform specific header files. If this library is really meant to be cross platform then the platform specific details should not be visible in the API (and #include <termbox2_(win|posix).h> is an API).

If you haven't seen it already, check out #114. I didn't find an obvious portable way to scroll the primary screen. The PR seems to work in xterm, vte, and the Linux console. I'd like to test it on other terminals before merging.

Ohh that's exactly what I was looking for thanks for pointing that out to me! I haven't seen it, partly because my project is older than the PR =)

@cowboyd

cowboyd commented Dec 1, 2025

Copy link
Copy Markdown

termbox2_posix.h (termbox2_base.h + termbox2_posix.c, generated by Makefile rule via git hook or GitHub CI)
termbox2_win.h (termbox2_base.h + termbox2_win.c, ditto)

I would personally advise against using platform specific header files. If this library is really meant to be cross platform then the platform specific details should not be visible in the API (and #include <termbox2_(win|posix).h> is an API).

Could we have our cake and eat it too by forward declaring the platform api, and then just dropping in both platform specific .c files but surrounded by appropriate #ifdefs at build time? It accomplishes the goal of having a single .h file as a result, but it also lets you express the platform api as vanilla C rather than sed expressions which is friendly towards readability (and also language servers!)

@cowboyd

cowboyd commented Jan 9, 2026

Copy link
Copy Markdown

Could we have our cake and eat it too by forward declaring the platform api, and then just dropping in both platform specific .c files but surrounded by appropriate #ifdefs at build time? It accomplishes the goal of having a single .h file as a result, but it also lets you express the platform api as vanilla C rather than sed expressions which is friendly towards readability (and also language servers!

Coming back to this, I realize that is exactly what @sewbacca was suggesting. Sorry, I didn't get that.

I would personally advise against using platform specific header files. If this library is really meant to be cross platform then the platform specific details should not be visible in the API (and #include <termbox2_(win|posix).h> is an API).

I'm cool with either way, as long as it would be possible to have a termbox2_wasi.c ifdef in addition to win and posix that would have a big overlap with posix

What would we need to do to move this forward? I'd love to make this happen.

@adsr

adsr commented Jan 10, 2026

Copy link
Copy Markdown
Contributor Author

Assuming we're going multi-file, and assuming we want to keep offering amalgamated single-file versions, the remaining question is whether we include amalgamated single-file versions in source control or not.

Most projects copy termbox2.h into their repo. Those projects would either need to start copying multiple files, or run make to generate single-file versions, or download them as a release artifact.

Other projects include termbox2 as a git submodule. If we stop including single-file versions in the repo, those builds would break the next time they update. Maybe we could offer back-compat with something like:

// termbox2.h
#include <termbox2_base.h>
#ifdef TB_IMPL
#include <termbox2_posix.c>
#endif`

Either way, multi-file would be a big change. I'm not totally against it. I'd personally like to hear input from more library users. If we add all this complexity, and only one or two small projects end up using the library outside POSIX, maybe it wouldn't be worth it. If it unlocks some huge potential somewhere, then yeah maybe worth it.

@txgk

txgk commented Jan 10, 2026

Copy link
Copy Markdown
Contributor

Hi, I thought I'd just throw some input as a user of termbox2 :)

The current approach with the generator script doesn't look too friendly to work with. Imagine trying to fiddle with the logic on the for-Windows implementation part. I understand those are Windows users we are talking about but they're still people you know what I mean :D

In my opinion the best thing would be to keep code in the files for code, yeah.

Also I think that it'd be nice to keep living up to the standard of single-file header-only library, it's very convenient to use it that way. Yeah there'll be ifdefs but if done properly this is not that big of a deal (raw ifdefs sprinkled all over the file are obviously a bad thing - to avoid that, platform specific logic must be wrapped in some _ops structs (operations struct) like they do it in Linux for example). As a POSIX-oriented user myself, I won't mind having a couple of ifdefs that can be deleted from the file with two or three DEL presses.

Also I imagine that a use case of providing both Linux and Windows support with termbox2 in some terminal app project is much better off if it's all bundled in one file. Having to manage multiple termbox2 files in your build system recipe is a great pain in the... who likes touching their build recipes, anyways ;)

Best regards, Grigory

@cowboyd

cowboyd commented Jan 16, 2026

Copy link
Copy Markdown

What would be the disadvantage of making one .h file? It would bloat the size of it, but it would not change the size of the compiled artifact. Is the idea that the final termbox2.h should be readable by humans?

@sewbacca

Copy link
Copy Markdown

Either way, multi-file would be a big change. I'm not totally against it. I'd personally like to hear input from more library users. If we add all this complexity, and only one or two small projects end up using the library outside POSIX, maybe it wouldn't be worth it. If it unlocks some huge potential somewhere, then yeah maybe worth it.

I am unsure what goes against a #ifdef solution that scales O(plats). I have outlined 3 possible solutions. Consider each block as a skeleton for the termbox.h header file.

A naive solution that scales with 2 * #ifdef + 2 * plats * #if + 4 * #endif.

#ifdef TB_IMP
// setup platform detection
#ifdef TB_POSIX
// include posix headers
#elif TB_WIN
// include windows headers
#endif
#endif // TB_IMP headerblock

// <public includes>
// <public API definitions>

#ifdef TB_IMP
// abstract internal API definitions for OS specific calls
// implementation for public API
#ifdef TB_POSIX
// platform specific implementation for internal API on POSIX
#elif TB_WIN
// platform specific implementation for internal API on Windows
#endif // TB_<plat> block
#endif // TB_IMP implementation block

For each additional platform only 2 #ifdefs are added (one for includes at the top and one at the bottom)

You could alternatively reduce it to #ifdef + plats * #if + 2 * #endif if you follow this scheme

// <public includes>
// <public API definitions>

#ifdef TB_IMP
// abstract internal API definitions for OS specific calls
// implementation for public API

// detect platform
#if defined(TB_POSIX)
// posix includes
// platform specific implementation for internal API on POSIX
#elif defined(TB_WIN)
// windows includes
// platform specific implementation for internal API on Windows
#endif // TB_<plat> block
#endif // TB_IMP implementation block

This may or may not work for Windows as there are some constraints at which point you can include windows.h but if it works I would prefer this solution.

Alternatively this skeleton scales #ifdefs as 3 * #ifdef + plats * #if + 4 * #endif still getting rid of 2*plats -> 1*plats, but adding a constant overhead only for Windows:

#ifdef TB_IMP
// detect platform
#ifdef TB_WIN
#include<windows.h>
#endif
#endif // detection and windows.h workaround

// <public includes>
// <public API definitions>

#ifdef TB_IMP
// abstract internal API definitions for OS specific calls
// implementation for public API

#if defined(TB_POSIX)
// posix includes
// platform specific implementation for internal API on POSIX
#elif defined(TB_WIN)
// windows includes
// platform specific implementation for internal API on Windows
#endif // TB_<plat> block
#endif // TB_IMP implementation block

@cowboyd

cowboyd commented Feb 19, 2026

Copy link
Copy Markdown

I opened a proof-of-concept that demonstrates what splitting out platform specific files could look like here #129

It's comprised of a tb_platform_*() set of functions and then implementation files for each one: tb_posix.c, tb_wasm.c (and potentially tb_windows.c).

It's almost certain that I've picked the wrong abstractions since I don't really understand the intricacies of how the library fits together and so am not quite sure where to draw the lines, but the point is to mainly demonstrate the developer experience which unlocks mulitple platforms, while preserving a) single termbox2.h header file that is drop-in on all platforms b) developer experience where you can hack on any piece of it like tb_windows.c and all the symbols resolve and your language server will not report any errors.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants