Skip to content

Revive the Linux (SDL2 / GTK3) build - #67

Merged
desertkun merged 8 commits into
speccytools:mainfrom
morozov:linux-sdl-gtk-build
Aug 8, 2026
Merged

Revive the Linux (SDL2 / GTK3) build#67
desertkun merged 8 commits into
speccytools:mainfrom
morozov:linux-sdl-gtk-build

Conversation

@morozov

@morozov morozov commented Aug 7, 2026

Copy link
Copy Markdown
Member

The portable ./configure && make UIs had stopped building: FuseX's macOS/Windows additions were made without accounting for them. This gets GTK 3 and SDL 2 compiling and running again on Linux (a 48K image boots to BASIC under both), without adding any Linux-only machinery — each fix restores upstream behaviour or teaches an existing mechanism to cover the portable builds. Commits are split into upstream-Fuse fixes first, FuseX-specific fixes on top.

Upstream-Fuse

  • rzx.h — include <glib.h> for the GSList declarations (libspectrum.h no longer pulls it in).
  • ui/sdl/sdljoystick.c — fix the include (ui.hui/ui.h).

FuseX-specific

These gate on a UI_COCOA macro rather than "not Win32":

  • fusepb/config.h — add the UI_COCOA macro.
  • fuse.c — keep main() for the portable UIs; old_main() only under Cocoa.
  • ui/scaler/scaler.c — build the 32-bit scalers everywhere except Cocoa (GTK renders at 32bpp and was crashing on a NULL scaler).
  • settings.pl — emit portable C by default, Objective-C only under --cocoa.
  • menu_data.dat / menu.c — gate the WinSparkle "Check for Updates" item to Win32 and fix the GTK debug-log callback.
  • peripherals/fs/xfs.c + Makefile.am — always build the xfs RAM engine (the GDB vfile feature needs it); keep only the network engines Spectranet-only.

Building on Linux

git submodule update --init 3rdparty/libspectrum
(cd 3rdparty/libspectrum && autoreconf -fi \
    && ./configure --without-fake-glib --without-libgcrypt && make -j"$(nproc)")

LS="$PWD/3rdparty/libspectrum"
export LIBSPECTRUM_CFLAGS="-I$LS"
export LIBSPECTRUM_LIBS="$LS/.libs/libspectrum.a -laudiofile -lglib-2.0 -lm -lbz2 -lz"

./configure --with-gtk --without-pthread     # or --with-sdl for SDL2
make -j"$(nproc)"
./fuse --machine 48 --no-beta128

@morozov
morozov force-pushed the linux-sdl-gtk-build branch from d713d16 to c4f57d3 Compare August 8, 2026 00:29
@morozov

morozov commented Aug 8, 2026

Copy link
Copy Markdown
Member Author

The failure is unrelated. The workflow pulls an unpinned libspectrum dependency:

with:
key: fuse-app
libspectrum_ref: master

The commit that breaks the build: fuse-emulator/libspectrum@9b0aa02. The upstream commit that fixes the build: fuse-emulator/libspectrum@a45ca72.

UPD: fixed by #69.

morozov added 8 commits August 8, 2026 09:16
rzx.h declares functions returning GSList* but includes only libspectrum.h.
Since 2003 libspectrum's public header has deliberately not pulled in
<glib.h> -- upstream commit "Don't include <glib.h> in libspectrum.h; makes
it easy to include from other projects" -- so every consumer that uses a glib
type is expected to include <glib.h> itself, as event.h, keyboard.c, tape.c
and friends already do under HAVE_LIB_GLIB.

rzx.h never got that include. The omission has been latent in upstream Fuse
for years, masked by include ordering (most translation units reach rzx.h
only after something else has already pulled in glib). It surfaces on a
real-GLib libspectrum build (as required by the GTK UI) via a unit that
reaches rzx.h first -- e.g. peripherals/joystick.c -- which then fails with
"unknown type name 'GSList'". Add the guarded include, following the
established convention.
sdljoystick.c is shared with the GTK build via a textual #include of the .c
file. Its include of "ui.h" -- upstream spells it "ui/ui.h", as the very next
line already does for "ui/uijoystick.h" -- resolves against the source file's
own directory, ui/sdl/, where no ui.h exists, so the GTK build fails with
"ui.h: No such file or directory". Restore the upstream include path.
FuseX defines a per-UI macro for every user interface it builds
(UI_WIN32, UI_GTK, UI_SDL, ...) except the Cocoa app, whose Xcode target
sets no UI macro at all. That left shared core code unable to tell the
Cocoa build apart from the portable autotools builds, so it leaned on
"not Win32" as a proxy for "is Cocoa" -- which silently swept in the
SDL/GTK/Xlib builds and broke them.

Define UI_COCOA in the Cocoa build's config.h so those call sites can
name the Cocoa build positively, and convert the first of them:
machine.c gated its SetEmulationHz stub on __APPLE__, which excluded the
stub from autotools builds on macOS as well and left them unable to
link, the real implementation being in fusepb/FuseMenus.m. It also
enables the two commits that follow.
FuseX renamed the emulator entry from upstream's main() to old_main() for
every non-Win32 build, so its Cocoa app could supply its own main(). That
also stripped main() from the autotools UIs (SDL, GTK, Xlib, ...), which then
failed to link with "undefined reference to main".

Select old_main() via UI_COCOA rather than "everything that is not Win32":
Win32 keeps fuse_main() (called from WinMain), the Cocoa build keeps
old_main() (unused there, as Cocoa drives the emulator itself), and every
other build gets main() as upstream intended.
Upstream references the 32-bit scaler routines (scaler_Normal1x_32, ...)
directly in the scaler table. FuseX wrapped that column in a SCALER32()
macro that expands to the real routine only under _WIN32 and to NULL
otherwise, because its Cocoa Xcode project compiles only scalers16.o and
the _32 symbols do not exist there.

The autotools build always links scalers32.o, and FuseX's GTK display
renders at 32bpp and calls scaler_proc32, so on Linux that NULL was a
guaranteed crash the moment a frame was drawn. Gate the macro on
UI_COCOA instead: the real routines are used everywhere they are
compiled, and only the 16bpp-only Cocoa build (which never calls
scaler_proc32) keeps NULL.

Also stub uidisplay_set_next_hotswap_reason in scalerexpandtest,
alongside the ui_error and uidisplay_hotswap_gfx_mode stubs already
there. The test links ui/scaler/scaler.c without ui.c, and FuseX's
hotswap call in scaler_select_scaler left it undefined; since the test
is in noinst_PROGRAMS, that broke plain "make" on every autotools
platform.
Upstream Fuse ships a single, portable settings generator (settings.pl,
libxml2/INI backed). FuseX rewrote it to emit Objective-C backed by
NSUserDefaults for the Cocoa app, which cannot compile anywhere else --
the autotools build's settings.c pulled in <Foundation/...> and failed
immediately.

Rather than fork a third, parallel generator for Linux, teach the
existing settings.pl both dialects behind a --cocoa flag: with it, the
Objective-C used by the Cocoa app (fusepb now passes --cocoa); without
it, portable C for the autotools builds (the default the Makefile.am
rule already invokes).

The parts driven by settings.dat alone -- the defaults table,
settings_init, the getopt_long option set, settings_copy,
settings_get_rom_setting and settings_set_string -- are emitted once and
shared by both dialects; only the config file I/O, the string ownership
rules and the Cocoa-only ROM array bridging are emitted per mode. The
Cocoa output changes only in the #line directives the shared sections
now emit and in --no-banner moving from 444 to 256. The portable build
gains --no-banner, which until now existed only in the Cocoa output.

The portable read_config_file also keeps upstream 1.9.1's behaviour of
parsing into a scratch copy and warning rather than failing: an
unreadable or unparseable config file leaves the defaults in place
instead of aborting startup.

The portable INI reader recognises the settings that only the Cocoa
build stores, rather than reporting them as unknown, and bounds that
report to the offending line, since utils_read_file does not NUL
terminate the buffer. --no-banner takes the reserved getopt_long value
256, leaving the generated options to run from 257 where they cannot
collide with it.
Two FuseX menu additions never accounted for the widget/GTK UIs:

  * "Help/Check for Updates..." is wired to menu_help_check_for_updates,
    which only exists in the Win32 WinSparkle backend. The widget and GTK
    builds failed to link. Gate the menu entry to UI_WIN32 (the Cocoa app
    drives updates from its own XIB menus, not menu_data.dat).

  * menu_machine_debuglog did (void)action; but the GTK MENU_CALLBACK
    signature has no action parameter, so the GTK build failed to compile.
    Drop the line -- other no-op callbacks (e.g. menu_machine_nmi) already
    leave the body empty.
The whole xfs filesystem lived behind BUILD_SPECTRANET, but the GDB vfile
debugger feature -- compiled unconditionally and used by gdbserver.c --
depends on the RAM-backed xfs engine. With Spectranet off (the default when
pthreads/sockets are unavailable) the link failed on xfs_ram_engine and
xfs_reset.

Split xfs: the core (xfs.c, xfs_worker.c, xfs_fs.c) is now always built,
while only the network engines (xfs_https.c) and their HTTP/TLS/SSH stack
stay Spectranet-only. The http/https branches of the mount dispatcher in
xfs.c are guarded by BUILD_SPECTRANET so it references those engines only
when they are compiled.
@morozov
morozov force-pushed the linux-sdl-gtk-build branch from c4f57d3 to 153ee31 Compare August 8, 2026 16:18
@desertkun
desertkun merged commit d3db276 into speccytools:main Aug 8, 2026
2 checks passed
@morozov
morozov deleted the linux-sdl-gtk-build branch August 8, 2026 16:24
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