From d7338b3935a54aa90d2e4867115db88198b5cd06 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Fri, 18 Sep 2026 23:48:47 +0300 Subject: [PATCH 01/13] darwin: define DISABLE_OPENGL when X11/XQuartz is unavailable FB's OpenGL support is implemented in the X11 gfx driver. Without ENABLE_XQUARTZ that driver is compiled out, but gfx_opengl.c is still built and calls fb_hGL_GetProcAddress(), which only the driver defines. libfbgfx.a therefore ends up with an undefined symbol and every link fails: Undefined symbols for architecture arm64: "_fb_hGL_GetProcAddress", referenced from: _fb_GfxGetGLProcAddress in libfbgfxmt.a[31](gfx_opengl.o) gfx_opengl.c already provides a stub for exactly this situation; define DISABLE_OPENGL alongside DISABLE_X11 so it is used. --- makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/makefile b/makefile index 5e5b22ff4..cbe1cbbb1 100644 --- a/makefile +++ b/makefile @@ -585,6 +585,10 @@ ifeq ($(TARGET_OS),darwin) ALLFBCFLAGS += -d ENABLE_XQUARTZ else ALLCFLAGS += -DDISABLE_X11 + # FB's OpenGL support lives in the X11 driver, so without XQuartz there is + # no GL driver at all. gfx_opengl.c otherwise still calls the driver hook + # fb_hGL_GetProcAddress(), leaving libfbgfx with an undefined symbol. + ALLCFLAGS += -DDISABLE_OPENGL endif endif From 981d6bd06352615b0f70f25b07c09512291189db Mon Sep 17 00:00:00 2001 From: agorangetek Date: Fri, 18 Sep 2026 23:48:47 +0300 Subject: [PATCH 02/13] rtl-error: don't emit an indirect jump when RESUME support is off rtlErrorCheck() and rtlErrorThrow() only have a resume label to hand to the error throw when -ex/-exx enabled RESUME support. Without it the label arguments are NULL, fb_ErrorThrowAt() can never return a usable jump target, and `fb_ErrorThrow(); goto *result;` is equivalent to a plain call. Emitting the call also keeps the generated C compilable by clang, which rejects `goto *ptr;` in a function that contains no address-of-label expression ("indirect goto in function with no address-of-label expressions"). Code built with -ex/-exx is unaffected: there the label addresses are taken anyway (&&label), so the indirect jump is still emitted. --- src/compiler/rtl-error.bas | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/compiler/rtl-error.bas b/src/compiler/rtl-error.bas index 17096f555..8931daf95 100644 --- a/src/compiler/rtl-error.bas +++ b/src/compiler/rtl-error.bas @@ -293,7 +293,16 @@ function rtlErrorCheck( byval expr as ASTNODE ptr ) as ASTNODE ptr t = astNewLINK( t, astNewBOP( AST_OP_EQ, expr, astNewCONSTi( 0 ), nxtlabel, AST_OPOPT_NONE ), AST_LINK_RETURN_NONE ) '' fb_ErrorThrow() - t = astNewLINK( t, astNewBRANCH( AST_OP_JUMPPTR, NULL, hErrorThrow( reslabel, nxtlabel ) ), AST_LINK_RETURN_NONE ) + '' Only -ex (-exx) enables RESUME support, and only then is a resume + '' label handed to the throw. Without one the throw can never return + '' a usable jump target, so a plain call is equivalent -- and it keeps + '' the generated C acceptable to clang, which rejects `goto *ptr` in + '' functions that take no label address. + if( env.clopt.resumeerr ) then + t = astNewLINK( t, astNewBRANCH( AST_OP_JUMPPTR, NULL, hErrorThrow( reslabel, nxtlabel ) ), AST_LINK_RETURN_NONE ) + else + t = astNewLINK( t, hErrorThrow( reslabel, nxtlabel ), AST_LINK_RETURN_NONE ) + end if '' end if t = astNewLINK( t, astNewLABEL( nxtlabel ), AST_LINK_RETURN_NONE ) @@ -361,7 +370,13 @@ sub rtlErrorThrow _ end if '' dst - astAdd( astNewBRANCH( AST_OP_JUMPPTR, NULL, proc ) ) + '' As in rtlErrorCheck(): an indirect jump is only meaningful when RESUME + '' support (-ex) is enabled and a resume label was passed along. + if( env.clopt.resumeerr ) then + astAdd( astNewBRANCH( AST_OP_JUMPPTR, NULL, proc ) ) + else + astAdd( proc ) + end if astAdd( astNewLABEL( nxtlabel ) ) end sub From 27c623cae0c07bd2537fc35e50e65a97cf3be179 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Fri, 18 Sep 2026 23:48:48 +0300 Subject: [PATCH 03/13] darwin: align DATA descriptors to the pointer size DATA items are emitted as a packed { short, void* } table, 10 bytes per entry, so the embedded string/link pointers sit at 2-byte offsets. ELF linkers accept that, but Mach-O's ld64 refuses to relocate a pointer that is not pointer-aligned and fails the link: ld: pointer not aligned in '_label$N'+0x16 Give the descriptor the natural pointer alignment when targeting Darwin, and make fb_data.h use the matching, non-packed layout. Other targets keep the packed 10-byte layout so their ABI is unchanged. --- src/compiler/ast-node-data.bas | 15 +++++++++++++-- src/rtlib/fb_data.h | 13 ++++++++++++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/compiler/ast-node-data.bas b/src/compiler/ast-node-data.bas index 9d3803e57..bd5bfc8c0 100644 --- a/src/compiler/ast-node-data.bas +++ b/src/compiler/ast-node-data.bas @@ -266,9 +266,20 @@ end function private sub hCreateDataDesc( ) static as FBARRAYDIM dTB(0) + dim as integer byalign + + '' Using FIELD = 1, to pack it as done by the rtlib -- except on Darwin, + '' where the linker requires pointer-sized relocations to be aligned to + '' the pointer size. A packed { short, void* } puts the DATA entries' + '' pointers at offset 2 (and every 10 bytes after that), which ld64 + '' rejects ("pointer not aligned ...", fatal on arm64). There the layout + '' must use the natural pointer alignment, matching src/rtlib/fb_data.h. + byalign = 1 + if( fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_DARWIN ) then + byalign = env.pointersize + end if - '' Using FIELD = 1, to pack it as done by the rtlib - ast.data.desc = symbStructBegin( NULL, NULL, NULL, "__FB_DATADESC$", NULL, FALSE, 1, FALSE, 0, 0 ) + ast.data.desc = symbStructBegin( NULL, NULL, NULL, "__FB_DATADESC$", NULL, FALSE, byalign, FALSE, 0, 0 ) '' type as short symbAddField( ast.data.desc, "type", 0, dTB(), _ diff --git a/src/rtlib/fb_data.h b/src/rtlib/fb_data.h index 2b976807d..22de295a6 100644 --- a/src/rtlib/fb_data.h +++ b/src/rtlib/fb_data.h @@ -1,3 +1,14 @@ +/* Mach-O (Darwin) requires pointer-sized relocations to be aligned; the packed + layout would place the embedded pointers at 2-byte offsets, which ld64 + rejects ("pointer not aligned ...", a hard error on arm64). The compiler + emits the matching naturally-aligned layout for Darwin targets, see + hCreateDataDesc() in src/compiler/ast-node-data.bas. */ +#if defined(__APPLE__) + #define FB_DATADESC_PACKED +#else + #define FB_DATADESC_PACKED FBPACKED +#endif + struct _FB_DATADESC { short len; union { @@ -6,7 +17,7 @@ struct _FB_DATADESC { void *ofs; struct _FB_DATADESC *next; }; -} FBPACKED; +} FB_DATADESC_PACKED; typedef struct _FB_DATADESC FB_DATADESC; From 97a1a54b6ad565bb6241825d05b3293e22abfb3f Mon Sep 17 00:00:00 2001 From: agorangetek Date: Fri, 18 Sep 2026 23:48:48 +0300 Subject: [PATCH 04/13] darwin: use the correct arm64 va_list ABI AArch64 Darwin uses a plain pointer-like va_list (sizeof(va_list) == 8), not the AAPCS64 __va_list_tag struct used by Linux/BSD aarch64. The compiler assumed the struct, so cva_start()/cva_arg() walked the wrong memory and any program using cva_list crashed immediately. --- src/compiler/fb.bas | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/compiler/fb.bas b/src/compiler/fb.bas index c050c49c1..23fd1b4a6 100644 --- a/src/compiler/fb.bas +++ b/src/compiler/fb.bas @@ -1768,7 +1768,15 @@ function fbGetBackendValistType _ typedef = FB_CVA_LIST_BUILTIN_ARM case FB_CPUFAMILY_AARCH64 - typedef = FB_CVA_LIST_BUILTIN_AARCH64 + select case env.clopt.target + case FB_COMPTARGET_DARWIN + '' Apple's arm64 ABI uses a plain pointer-like va_list (8 bytes, + '' sizeof(va_list) == sizeof(char*)), not the AAPCS64 + '' __va_list_tag struct that Linux/BSD aarch64 use. + typedef = FB_CVA_LIST_BUILTIN_POINTER + case else + typedef = FB_CVA_LIST_BUILTIN_AARCH64 + end select case FB_CPUFAMILY_PPC typedef = FB_CVA_LIST_BUILTIN_POINTER From 974cf174945d3eb8c9250c8ee8238a072b6c6fb3 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Fri, 18 Sep 2026 23:48:48 +0300 Subject: [PATCH 05/13] profile_cycles: handle Mach-O sections Mach-O has no __start_/__stop_ section boundary symbols and limits section names to 16 characters, so the ELF-style section scan cannot work there. Guard it for Darwin and fall back to the single version record (the report is then simply empty) instead of emitting an invalid section attribute and failing to build. --- src/rtlib/profile_cycles.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/rtlib/profile_cycles.c b/src/rtlib/profile_cycles.c index c6e2a7327..00f4718fc 100644 --- a/src/rtlib/profile_cycles.c +++ b/src/rtlib/profile_cycles.c @@ -99,10 +99,20 @@ typedef struct _FB_PROFILER_CYCLES */ #if !defined(HOST_DOS) +/* Mach-O section names are limited to 16 characters and, unlike ELF, the + linker does not synthesise __start_/__stop_ boundary symbols, so the + profiler's section scan cannot work the ELF way there; keep the record as a + plain used object instead. */ +#if defined(HOST_DARWIN) + #define FB_PROFILE_SECTION_ATTR __attribute__((used)) +#else + #define FB_PROFILE_SECTION_ATTR __attribute__((section("fb_profilecycledata"), used)) +#endif + /* make sure there is at least one record in the profile data section */ static FB_PROFILE_RECORD_VERSION __attribute__ ((aligned (16))) prof_data_version -__attribute__((section("fb_profilecycledata"), used)) = +FB_PROFILE_SECTION_ATTR = { sizeof( FB_PROFILE_RECORD_VERSION ), FB_PROFILE_RECORD_VERSION_ID, @@ -301,8 +311,15 @@ static void hProfilerWriteReport( FB_PROFILER_CYCLES *prof ) fprintf( f, "Total program execution time: %5.4g seconds\n", fb_Timer() - prof->start_time ); } +#if defined(HOST_DARWIN) + /* No __start_/__stop_ section symbols on Mach-O; fall back to the single + version record so the report is simply empty. */ + data = (unsigned char *)&prof_data_version; + length = sizeof( prof_data_version ); +#else data = (unsigned char *)&__start_fb_profilecycledata[0]; length = (ssize_t)&__stop_fb_profilecycledata - (ssize_t)&__start_fb_profilecycledata[0]; +#endif count = hProfilerCountProcs( data, length ); if( count ) { From ede5480d0119257c1cfba52d9c10f371cfbed785 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Fri, 18 Sep 2026 23:48:48 +0300 Subject: [PATCH 06/13] darwin: link through the C compiler driver instead of driving ld64 The Darwin link command was built for a GNU ld: it adds crt1.o/crti.o/ crtbegin.o/crtend.o/crtn.o, `-macosx_version_min 10.4`, `--eh-frame-hdr`, `--export-dynamic` and `-lgcc`, and uses `-shared -h` for shared libraries. Modern ld64 rejects --eh-frame-hdr outright, the crt objects cannot be linked that way on macOS, and libgcc does not exist unless a GCC toolchain happens to be installed. Invoke the C compiler driver (clang) for linking Darwin targets instead: it supplies the startup object and libSystem itself. Drop the options it does not accept, use -dynamiclib/-install_name for -dylib, and add -arch arm64 so the architecture is explicit. Executables and dynamic libraries now link with the system toolchain alone. --- src/compiler/fbc.bas | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/compiler/fbc.bas b/src/compiler/fbc.bas index 61b77df3f..79a2a3ec8 100644 --- a/src/compiler/fbc.bas +++ b/src/compiler/fbc.bas @@ -840,6 +840,8 @@ private function hLinkFiles( ) as integer case FB_CPUFAMILY_ARM '' fixme: this is clearly too specific ldcline += "-arch armv6 " + case FB_CPUFAMILY_AARCH64 + ldcline += "-arch arm64 " end select end select @@ -945,7 +947,13 @@ private function hLinkFiles( ) as integer if( fbGetOption( FB_COMPOPT_OUTTYPE ) = FB_OUTTYPE_DYNAMICLIB ) then dllname = hStripPath( hStripExt( fbc.outname ) ) - ldcline += " -shared -h" + hStripPath( fbc.outname ) + if( fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_DARWIN ) then + '' Darwin's linker (ld64) has no -shared/-h; it uses -dynamiclib + '' and records the install name via -install_name. + ldcline += " -dynamiclib -install_name " + QUOTE + dllname + QUOTE + else + ldcline += " -shared -h" + hStripPath( fbc.outname ) + end if '' Turn libfoo into foo, so it can be checked against -l foo below if( left( dllname, 3 ) = "lib" ) then @@ -986,7 +994,8 @@ private function hLinkFiles( ) as integer '' But able to have shared library generated successfully afterward if( (fbGetOption( FB_COMPOPT_OUTTYPE ) = FB_OUTTYPE_DYNAMICLIB) or _ fbGetOption( FB_COMPOPT_EXPORT ) ) and _ - (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_SOLARIS) then + (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_SOLARIS) and _ + (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_DARWIN) then ldcline += " --export-dynamic" end if @@ -1166,7 +1175,10 @@ private function hLinkFiles( ) as integer FB_COMPTARGET_FREEBSD, FB_COMPTARGET_OPENBSD, _ FB_COMPTARGET_NETBSD, FB_COMPTARGET_DRAGONFLY, FB_COMPTARGET_SOLARIS - if( fbGetOption( FB_COMPOPT_OUTTYPE ) = FB_OUTTYPE_EXECUTABLE) then + '' Darwin's linker takes no explicit crt1.o; the C compiler driver + '' supplies the startup object (it lives inside libSystem). + if( (fbGetOption( FB_COMPOPT_OUTTYPE ) = FB_OUTTYPE_EXECUTABLE) and _ + (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_DARWIN) ) then if( fbGetOption( FB_COMPOPT_PROFILE ) ) then select case as const fbGetOption( FB_COMPOPT_TARGET ) case FB_COMPTARGET_OPENBSD, FB_COMPTARGET_NETBSD @@ -1322,18 +1334,17 @@ private function hLinkFiles( ) as integer end select - if( fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_DARWIN ) then - ldcline += " -macosx_version_min 10.4" - end if + '' Note: on Darwin the deployment target (-mmacosx-version-min) is supplied + '' by the C compiler driver used for linking, not hard-coded here. '' This is required for 64-bit modules on *nix-y platforms '' for the unwind tables to have any effect '' Windows doesn't need this option + '' (ld64 rejects --eh-frame-hdr; Darwin is handled by clang instead) select case as const fbGetOption( FB_COMPOPT_TARGET ) case FB_COMPTARGET_LINUX, FB_COMPTARGET_FREEBSD, _ FB_COMPTARGET_OPENBSD, FB_COMPTARGET_NETBSD, _ - FB_COMPTARGET_DRAGONFLY, FB_COMPTARGET_SOLARIS, _ - FB_COMPTARGET_DARWIN + FB_COMPTARGET_DRAGONFLY, FB_COMPTARGET_SOLARIS dim as long outtype = fbGetOption( FB_COMPOPT_OUTTYPE ) if outtype = FB_OUTTYPE_EXECUTABLE OrElse outtype = FB_OUTTYPE_DYNAMICLIB Then dim as long cpufamily = fbGetCpuFamily( ) @@ -1408,6 +1419,10 @@ private function hLinkFiles( ) as integer var ld = FBCTOOL_LD if( fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_JS ) then ld = FBCTOOL_EMLD + elseif( fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_DARWIN ) then + '' ld64 can't be driven like GNU ld (different crt handling, and it + '' rejects several GNU options), so use the C compiler driver to link. + ld = FBCTOOL_CLANG end if if( fbcRunBin( "linking", ld, ldcline ) = FALSE ) then @@ -4308,9 +4323,9 @@ private sub hAddDefaultLibs( ) end if case FB_COMPTARGET_DARWIN - fbcAddDefLib( "gcc" ) - fbcAddDefLib( "System" ) - fbcAddDefLib( "pthread" ) + '' There is no libgcc on macOS unless a GCC toolchain is installed; + '' the clang driver pulls in libSystem (and thus libc/libm/pthread) + '' by itself. libncurses is still needed by the FB runtime. fbcAddDefLib( "ncurses" ) case FB_COMPTARGET_DOS From d6c7528f236c3b2d54d5a4fb24b8ed1cb8422d38 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 00:10:32 +0300 Subject: [PATCH 07/13] X11: fix XGetKeyboardMapping prototype for NeedWidePrototypes Xfuncproto.h defines NeedWidePrototypes to 1 unless the application defines NARROWPROTO, which makes Xlib declare XGetKeyboardMapping()'s first_keycode parameter as unsigned int instead of KeyCode (unsigned char). FB's XGETKEYBOARDMAPPING typedef hard-codes KeyCode, so passing XGetKeyboardMapping to fb_hInitX11KeycodeToScancodeTb() no longer matches the parameter type: gfx_x11.c:588:68: error: incompatible function pointer types passing 'KeySym *(Display *, unsigned int, int, int *)' to parameter of type 'XGETKEYBOARDMAPPING' (aka 'unsigned long *(*)(struct _XDisplay *, unsigned char, int, int *)') GCC only warns about the mismatch, which is why this went unnoticed, but clang (and GCC 14+, where it is an error too) rejects it -- so the X11 rtlib/gfxlib2 code cannot be built by clang at all. That makes the XQuartz/X11 graphics path unbuildable on Darwin, where clang is the system compiler. Mirror Xlib's own conditional in the typedef so it matches either way. --- src/rtlib/unix/fb_private_scancodes_x11.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/rtlib/unix/fb_private_scancodes_x11.h b/src/rtlib/unix/fb_private_scancodes_x11.h index e76da67d4..bee31cd27 100644 --- a/src/rtlib/unix/fb_private_scancodes_x11.h +++ b/src/rtlib/unix/fb_private_scancodes_x11.h @@ -7,7 +7,16 @@ typedef Display *(*XOPENDISPLAY)(char *); typedef int (*XCLOSEDISPLAY)(Display *); typedef void (*XQUERYKEYMAP)(Display *, unsigned char *); typedef int (*XDISPLAYKEYCODES)(Display *, int *, int *); +/* XGetKeyboardMapping()'s first_keycode parameter is declared as KeyCode + (unsigned char) or as unsigned int depending on NeedWidePrototypes, which + Xfuncproto.h defaults to 1 unless NARROWPROTO is defined. Mirror Xlib's own + declaration, otherwise the function pointers do not match and clang (unlike + GCC, which merely warns) rejects the call. */ +#if defined(NeedWidePrototypes) && NeedWidePrototypes +typedef KeySym* (*XGETKEYBOARDMAPPING)(Display *, unsigned int, int, int *); +#else typedef KeySym* (*XGETKEYBOARDMAPPING)(Display *, KeyCode, int, int *); +#endif typedef int (*XFREE)(void *); extern unsigned char fb_x11keycode_to_scancode[256]; From 39438183289c2116bb61cc3762bf882e375df5c4 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 00:16:43 +0300 Subject: [PATCH 08/13] makefile: normalize arm64 to aarch64 uname -m reports "arm64" on Apple Silicon, which matched the arm% pattern and was normalized to "arm" -- the 32-bit family. A plain "make" on macOS therefore resolved FBTARGET to darwin-arm instead of darwin-aarch64, so objects and libraries landed in directories that do not match the ones fbc looks for at run time (lib/freebasic/darwin-aarch64). Normalize aarch64/arm64 first, then let the remaining 32-bit spellings match armv%/arm. Verified that arm-linux-gnueabihf and armv7-linux-gnueabihf still resolve to linux-arm, and that arm64-apple-darwin resolves to darwin-aarch64. Based on the corresponding change in #470 by @metaneutrons. --- makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/makefile b/makefile index cbe1cbbb1..50145e321 100644 --- a/makefile +++ b/makefile @@ -342,8 +342,13 @@ ifneq ($(filter 386 486 586 686 i386 i486 i586 i686,$(TARGET_ARCH)),) TARGET_ARCH := x86 endif -# Normalize TARGET_ARCH to arm -ifneq ($(filter arm%,$(TARGET_ARCH)),) +# Normalize TARGET_ARCH to aarch64 (macOS reports arm64) +ifneq ($(filter aarch64 arm64,$(TARGET_ARCH)),) + TARGET_ARCH := aarch64 +endif + +# Normalize TARGET_ARCH to arm (32-bit only, after aarch64 is handled) +ifneq ($(filter armv% arm,$(TARGET_ARCH)),) TARGET_ARCH := arm endif From 13794ed59d8562070dc5c5a417276e57b9355d02 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 12:04:36 +0300 Subject: [PATCH 09/13] darwin: name shared libraries .dylib, and export their symbols Two defects on the -dylib path, both found while cross-checking metaneutrons' freebasic-ng#160, which reviewed this branch against their fork: - Shared libraries were named .so on every unix target, including Darwin. Mach-O uses .dylib -- .so is the loadable bundle extension. fb_DylibLoad() looks for the .dylib name first on Darwin, so it worked, but only by falling through to the .so entry in its candidate list, and the file was named wrongly for every other tool on the platform. Darwin now has its own case in the output naming, leaving the ELF targets on .so. - The link line skipped --export-dynamic on Darwin entirely rather than translating it. The clang driver wants it spelled -Wl,-export_dynamic. Symbols resolved without it -- Mach-O exports global symbols by default -- but leaving it out was a silent difference from every other unix target, and -export therefore did nothing on Darwin. Verified by building a library and loading it back: fbc -dylib foo.bas -> libfoo.dylib, install name "libfoo" linking: clang ... -dynamiclib -install_name "libfoo" -Wl,-export_dynamic DyLibLoad("foo") -> handle, DyLibSymbol(handle, "FOO_ADD") -> 5 --- src/compiler/fbc.bas | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/compiler/fbc.bas b/src/compiler/fbc.bas index 79a2a3ec8..b469e0474 100644 --- a/src/compiler/fbc.bas +++ b/src/compiler/fbc.bas @@ -258,7 +258,13 @@ private sub hSetOutName( ) select case( fbGetOption( FB_COMPOPT_TARGET ) ) case FB_COMPTARGET_CYGWIN, FB_COMPTARGET_WIN32 fbc.outname += ".dll" - case FB_COMPTARGET_LINUX, FB_COMPTARGET_DARWIN, _ + case FB_COMPTARGET_DARWIN + '' Mach-O shared libraries are .dylib; .so is the loadable + '' bundle extension, and fb_DylibLoad() looks for the .dylib + '' name first on Darwin. + fbc.outname = hStripFilename( fbc.outname ) + _ + "lib" + hStripPath( fbc.outname ) + ".dylib" + case FB_COMPTARGET_LINUX, _ FB_COMPTARGET_FREEBSD, FB_COMPTARGET_OPENBSD, _ FB_COMPTARGET_NETBSD, FB_COMPTARGET_DRAGONFLY, _ FB_COMPTARGET_SOLARIS, FB_COMPTARGET_ANDROID @@ -994,9 +1000,14 @@ private function hLinkFiles( ) as integer '' But able to have shared library generated successfully afterward if( (fbGetOption( FB_COMPOPT_OUTTYPE ) = FB_OUTTYPE_DYNAMICLIB) or _ fbGetOption( FB_COMPOPT_EXPORT ) ) and _ - (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_SOLARIS) and _ - (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_DARWIN) then - ldcline += " --export-dynamic" + (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_SOLARIS) then + '' Darwin links through the clang driver, which wants the option + '' spelled with -Wl, rather than passed through as --export-dynamic + if( fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_DARWIN ) then + ldcline += " -Wl,-export_dynamic" + else + ldcline += " --export-dynamic" + end if end if case FB_COMPTARGET_XBOX From 693301f5876368b35b17977c63c110dbb8a5cc41 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 00:30:45 +0300 Subject: [PATCH 10/13] darwin: add a native Cocoa 2D graphics driver Plain ScreenRes now opens a real macOS window with all the 2D primitives, without XQuartz and without OpenGL (which is deprecated on macOS). Backends and the missing piece: - Upstream's only Darwin backend is X11 via XQuartz, and the Cocoa driver in #448 is OpenGL-only: its init returns -1 unless DRIVER_OPENGL is set, so a plain ScreenRes renders into the software framebuffer and never presents it (the author's open question about Line/Circle showing nothing). - This driver presents that software framebuffer instead: a refresh thread converts it with the gfxlib2 blitter (fb_hGetBlitter) into a CoreGraphics bitmap and publishes the image to the window's layer, and AppKit itself is only touched on the main thread, from the driver hooks the core calls (flip/unlock/poll_events/wait_vsync) since an FB program owns the main thread and there is no NSApplication run loop. Window/event/scancode scaffolding is adapted from #448 by Markos-Th09, including src/rtlib/darwin/fb_private_scancodes_cocoa.h; the software present path and the driver hooks are new. Supporting changes: - makefile: compile .m sources (OBJC, LIBFBGFX_M and its PIC/MT variants) - fbc: link -framework Cocoa -framework QuartzCore -framework CoreGraphics for Darwin programs that use gfx - gfx_unix.c: register the driver after X11, so a build with XQuartz keeps using X11 and falls back to Cocoa when no X server is reachable, while a build without XQuartz gets Cocoa as its only driver Verified on macOS 27 / Apple M5: ScreenRes 640,480,32 succeeds (err = 0, ScreenPtr valid), Point() readback matches the drawn colours, BSave is correct, and the window renders Circle/Line/Draw String in the right colours. --- makefile | 24 +- src/compiler/fbc.bas | 7 + src/gfxlib2/darwin/fb_gfx_cocoa.h | 20 + src/gfxlib2/darwin/gfx_driver_cocoa.m | 536 ++++++++++++++++++ src/gfxlib2/unix/gfx_unix.c | 17 + src/rtlib/darwin/fb_private_scancodes_cocoa.h | 106 ++++ 6 files changed, 706 insertions(+), 4 deletions(-) create mode 100644 src/gfxlib2/darwin/fb_gfx_cocoa.h create mode 100644 src/gfxlib2/darwin/gfx_driver_cocoa.m create mode 100644 src/rtlib/darwin/fb_private_scancodes_cocoa.h diff --git a/makefile b/makefile index 50145e321..c844fc264 100644 --- a/makefile +++ b/makefile @@ -180,6 +180,8 @@ FBFLAGS := -maxerr 1 AS = $(BUILD_PREFIX)as AR = $(BUILD_PREFIX)ar CC = $(BUILD_PREFIX)gcc +# Objective-C compiler, used for the native Cocoa graphics driver on Darwin +OBJC = $(CC) -x objective-c prefix := /usr/local # Determine the makefile's directory, this may be a relative path when @@ -726,10 +728,15 @@ LIBFBRTMTPIC_C := $(patsubst %,$(libfbmtpicobjdir)/%,$(filter-out $(patsubst $(l LIBFBGFX_H := $(sort $(foreach i,$(GFXLIB2_DIRS),$(wildcard $(i)/*.h)) $(LIBFB_H)) LIBFBGFX_C := $(sort $(foreach i,$(GFXLIB2_DIRS),$(patsubst $(i)/%.c,$(libfbgfxobjdir)/%.o,$(wildcard $(i)/*.c)))) LIBFBGFX_S := $(sort $(foreach i,$(GFXLIB2_DIRS),$(patsubst $(i)/%.s,$(libfbgfxobjdir)/%.o,$(wildcard $(i)/*.s)))) +# Objective-C sources (the native Cocoa driver on Darwin) +LIBFBGFX_M := $(sort $(foreach i,$(GFXLIB2_DIRS),$(patsubst $(i)/%.m,$(libfbgfxobjdir)/%.o,$(wildcard $(i)/*.m)))) LIBFBGFXPIC_C := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxpicobjdir)/%,$(LIBFBGFX_C)) +LIBFBGFXPIC_M := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxpicobjdir)/%,$(LIBFBGFX_M)) LIBFBGFXMT_C := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxmtobjdir)/%,$(LIBFBGFX_C)) LIBFBGFXMT_S := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxmtobjdir)/%,$(LIBFBGFX_S)) +LIBFBGFXMT_M := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxmtobjdir)/%,$(LIBFBGFX_M)) LIBFBGFXMTPIC_C := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxmtpicobjdir)/%,$(LIBFBGFX_C)) +LIBFBGFXMTPIC_M := $(patsubst $(libfbgfxobjdir)/%,$(libfbgfxmtpicobjdir)/%,$(LIBFBGFX_M)) RTL_LIBS := $(libdir)/$(FB_LDSCRIPT) @@ -777,6 +784,7 @@ ifndef V QUIET_FBC = @echo "FBC $@"; QUIET_LINK = @echo "LINK $@"; QUIET_CC = @echo "CC $@"; + QUIET_OBJC = @echo "OBJC $@"; QUIET_CPPAS = @echo "CPPAS $@"; QUIET_AS = @echo "AS $@"; QUIET_AR = @echo "AR $@"; @@ -993,29 +1001,37 @@ $(LIBFBMTRTPIC_BAS): $(libfbrtmtpicobjdir)/%.o: %.c $(LIBFBRT_BI) | $(libfbrtmtp .PHONY: gfxlib2 gfxlib2: $(GFX_LIBS) -$(libdir)/libfbgfx.a: $(LIBFBGFX_C) $(LIBFBGFX_S) | $(libdir) +$(libdir)/libfbgfx.a: $(LIBFBGFX_C) $(LIBFBGFX_S) $(LIBFBGFX_M) | $(libdir) $(QUIET_AR)rm -f $@; $(AR) rcs $@ $^ $(LIBFBGFX_C): $(libfbgfxobjdir)/%.o: %.c $(LIBFBGFX_H) | $(libfbgfxobjdir) $(QUIET_CC)$(CC) $(ALLCFLAGS) -c $< -o $@ $(LIBFBGFX_S): $(libfbgfxobjdir)/%.o: %.s $(LIBFBGFX_H) | $(libfbgfxobjdir) $(QUIET_CPPAS)$(CC) -x assembler-with-cpp $(ALLCFLAGS) -c $< -o $@ +$(LIBFBGFX_M): $(libfbgfxobjdir)/%.o: %.m $(LIBFBGFX_H) | $(libfbgfxobjdir) + $(QUIET_OBJC)$(OBJC) $(ALLCFLAGS) -c $< -o $@ -$(libdir)/libfbgfxpic.a: $(LIBFBGFXPIC_C) | $(libdir) +$(libdir)/libfbgfxpic.a: $(LIBFBGFXPIC_C) $(LIBFBGFXPIC_M) | $(libdir) $(QUIET_AR)rm -f $@; $(AR) rcs $@ $^ $(LIBFBGFXPIC_C): $(libfbgfxpicobjdir)/%.o: %.c $(LIBFBGFX_H) | $(libfbgfxpicobjdir) $(QUIET_CC)$(CC) -fPIC $(ALLCFLAGS) -c $< -o $@ +$(LIBFBGFXPIC_M): $(libfbgfxpicobjdir)/%.o: %.m $(LIBFBGFX_H) | $(libfbgfxpicobjdir) + $(QUIET_OBJC)$(OBJC) -fPIC $(ALLCFLAGS) -c $< -o $@ -$(libdir)/libfbgfxmt.a: $(LIBFBGFXMT_C) $(LIBFBGFXMT_S) | $(libdir) +$(libdir)/libfbgfxmt.a: $(LIBFBGFXMT_C) $(LIBFBGFXMT_S) $(LIBFBGFXMT_M) | $(libdir) $(QUIET_AR)rm -f $@; $(AR) rcs $@ $^ $(LIBFBGFXMT_C): $(libfbgfxmtobjdir)/%.o: %.c $(LIBFBGFX_H) | $(libfbgfxmtobjdir) $(QUIET_CC)$(CC) -DENABLE_MT $(ALLCFLAGS) -c $< -o $@ $(LIBFBGFXMT_S): $(libfbgfxmtobjdir)/%.o: %.s $(LIBFBGFX_H) | $(libfbgfxmtobjdir) $(QUIET_CPPAS)$(CC) -x assembler-with-cpp -DENABLE_MT $(ALLCFLAGS) -c $< -o $@ +$(LIBFBGFXMT_M): $(libfbgfxmtobjdir)/%.o: %.m $(LIBFBGFX_H) | $(libfbgfxmtobjdir) + $(QUIET_OBJC)$(OBJC) -DENABLE_MT $(ALLCFLAGS) -c $< -o $@ -$(libdir)/libfbgfxmtpic.a: $(LIBFBGFXMTPIC_C) | $(libdir) +$(libdir)/libfbgfxmtpic.a: $(LIBFBGFXMTPIC_C) $(LIBFBGFXMTPIC_M) | $(libdir) $(QUIET_AR)rm -f $@; $(AR) rcs $@ $^ $(LIBFBGFXMTPIC_C): $(libfbgfxmtpicobjdir)/%.o: %.c $(LIBFBGFX_H) | $(libfbgfxmtpicobjdir) $(QUIET_CC)$(CC) -DENABLE_MT -fPIC $(ALLCFLAGS) -c $< -o $@ +$(LIBFBGFXMTPIC_M): $(libfbgfxmtpicobjdir)/%.o: %.m $(LIBFBGFX_H) | $(libfbgfxmtpicobjdir) + $(QUIET_OBJC)$(OBJC) -DENABLE_MT -fPIC $(ALLCFLAGS) -c $< -o $@ ################################################################################ diff --git a/src/compiler/fbc.bas b/src/compiler/fbc.bas index b469e0474..4e770bf35 100644 --- a/src/compiler/fbc.bas +++ b/src/compiler/fbc.bas @@ -1309,6 +1309,13 @@ private function hLinkFiles( ) as integer wend end scope + '' The native Cocoa gfx driver needs these system frameworks. They ship + '' with every macOS install, and are only added for programs that use gfx. + if( (fbGetOption( FB_COMPOPT_TARGET ) = FB_COMPTARGET_DARWIN) and _ + fbGetOption( FB_COMPOPT_FBGFX ) ) then + ldcline += " -framework Cocoa -framework QuartzCore -framework CoreGraphics" + end if + if (fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_DARWIN) then if( fbGetOption( FB_COMPOPT_TARGET ) <> FB_COMPTARGET_JS ) then '' End of lib group diff --git a/src/gfxlib2/darwin/fb_gfx_cocoa.h b/src/gfxlib2/darwin/fb_gfx_cocoa.h new file mode 100644 index 000000000..42527b612 --- /dev/null +++ b/src/gfxlib2/darwin/fb_gfx_cocoa.h @@ -0,0 +1,20 @@ +#ifndef __FB_GFX_COCOA_H__ +#define __FB_GFX_COCOA_H__ + +#include +#include "../fb_gfx.h" + +extern const GFXDRIVER fb_gfxDriverCocoa; + +extern void fb_hCocoaLock(void); +extern void fb_hCocoaUnlock(void); +extern void fb_hCocoaWaitVSync(void); +extern void fb_hCocoaSetPalette(int index, int r, int g, int b); +extern int fb_hCocoaGetMouse(int *x, int *y, int *z, int *buttons, int *clip); +extern void fb_hCocoaSetMouse(int x, int y, int cursor, int clip); +extern void fb_hCocoaSetWindowTitle(char *title); +extern int fb_hCocoaSetWindowPos(int x, int y); +extern int *fb_hCocoaFetchModes(int depth, int *size); +extern int fb_hCocoaScreenInfo(ssize_t *width, ssize_t *height, ssize_t *depth, ssize_t *refresh); + +#endif diff --git a/src/gfxlib2/darwin/gfx_driver_cocoa.m b/src/gfxlib2/darwin/gfx_driver_cocoa.m new file mode 100644 index 000000000..5b8dfe0b6 --- /dev/null +++ b/src/gfxlib2/darwin/gfx_driver_cocoa.m @@ -0,0 +1,536 @@ +/* Native Cocoa 2D graphics driver for gfxlib2 (macOS) + + Presents FreeBASIC's software framebuffer in a real NSWindow through + CoreGraphics, so that a plain ScreenRes works without XQuartz and without + OpenGL (which is deprecated on macOS). + + The window/view/event handling is modelled on the Cocoa/OpenGL driver from + PR #448 by Markos-Th09; the software-framebuffer present path is new. + + How the pieces fit together in gfxlib2: + + - the core renders into __fb_gfx->page[] / framebuffer and marks changed + scanlines in __fb_gfx->dirty[]; + - a refresh thread converts the framebuffer to 32-bit BGRA with the + gfxlib2 blitter (fb_hGetBlitter) and hands the image to the layer; + - AppKit itself is only touched on the main thread, from the driver + hooks the core calls (flip/unlock/poll_events/wait_vsync). + + There is no NSApplication run loop here: an FB program owns the process + main thread, so events are pumped on demand instead. +*/ + +#include +#include +#include +#include + +#include "../fb_gfx.h" +#include "fb_gfx_cocoa.h" +#include "../../rtlib/darwin/fb_private_scancodes_cocoa.h" + +#if defined(HOST_DARWIN) + +#import +#import +#import + +/* ------------------------------------------------------------ driver state */ + +typedef struct { + NSWindow *window; + NSView *view; + CGContextRef ctx; /* bitmap context over buf */ + unsigned char *buf; /* w * h * 4, BGRA */ + BLITTER *blitter; /* framebuffer -> BGRA converter */ + pthread_mutex_t mutex; + pthread_t thread; + volatile int running; + volatile int present_pending; + int w, h; + int mouse_x, mouse_y, mouse_z; + int mouse_buttons; + int mouse_visible; + int has_focus; + int cursor_shown; +} COCOA_CTX; + +static COCOA_CTX cocoa; + +static int cocoa_ready = 0; +static int cocoa_app_ready = 0; + +/* ------------------------------------------------------------------ present */ + +/* Convert the FB framebuffer into our BGRA buffer. Caller holds the mutex. */ +static void cocoa_convert(void) +{ + if (!__fb_gfx || !cocoa.buf || !cocoa.blitter) + return; + + cocoa.blitter(cocoa.buf, cocoa.w * 4); + + if (__fb_gfx->dirty) + fb_hMemSet(__fb_gfx->dirty, 0, __fb_gfx->h); +} + +/* Publish the buffer to the window. Main thread only (AppKit). */ +static void cocoa_present(void) +{ + CGImageRef image; + + if (!cocoa_ready || !__fb_gfx) + return; + + pthread_mutex_lock(&cocoa.mutex); + cocoa_convert(); + image = CGBitmapContextCreateImage(cocoa.ctx); + pthread_mutex_unlock(&cocoa.mutex); + + if (image == NULL) + return; + + /* Layer contents are set on the main thread here; the refresh thread + does its own best-effort publish for programs that never reach a + driver hook. */ + cocoa.view.layer.contents = (__bridge id)image; + CGImageRelease(image); +} + +/* Best-effort publish from the refresh thread: Core Animation is thread-safe + for layer property mutation, and an explicit flush pushes the change to the + render server without relying on a main-thread run loop. */ +static void cocoa_present_background(CGImageRef image) +{ + cocoa.view.layer.contents = (__bridge id)image; + [CATransaction flush]; +} + +/* --------------------------------------------------------------- event pump */ + +static void cocoa_post_key(int scancode, int ascii, int type) +{ + EVENT e; + + if (!__fb_gfx) + return; + + fb_hMemSet(&e, 0, sizeof(EVENT)); + e.type = type; + e.scancode = scancode; + e.ascii = ascii; + fb_hPostEvent(&e); +} + +static void cocoa_post_mouse(int type, int x, int y, int button) +{ + EVENT e; + + if (!__fb_gfx) + return; + + fb_hMemSet(&e, 0, sizeof(EVENT)); + e.type = type; + e.x = x; + e.y = y; + e.button = button; + fb_hPostEvent(&e); +} + +static void cocoa_handle_event(NSEvent *event) +{ + NSPoint p; + int scancode; + + if (!__fb_gfx) + return; + + switch ([event type]) { + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + scancode = fb_cocoakeycode_to_scancode[[event keyCode] & 0xFF]; + if (scancode == 0) + break; + if ([event type] == NSEventTypeKeyDown) { + __fb_gfx->key[scancode] = TRUE; + cocoa_post_key(scancode, (int)[[event characters] characterAtIndex:0], + [event isARepeat] ? EVENT_KEY_REPEAT : EVENT_KEY_PRESS); + } else { + __fb_gfx->key[scancode] = FALSE; + cocoa_post_key(scancode, 0, EVENT_KEY_RELEASE); + } + break; + + case NSEventTypeFlagsChanged: + /* Modifier keys arrive here rather than as key down/up */ + scancode = fb_cocoakeycode_to_scancode[[event keyCode] & 0xFF]; + if (scancode == 0) + break; + if (__fb_gfx->key[scancode]) { + __fb_gfx->key[scancode] = FALSE; + cocoa_post_key(scancode, 0, EVENT_KEY_RELEASE); + } else { + __fb_gfx->key[scancode] = TRUE; + cocoa_post_key(scancode, 0, EVENT_KEY_PRESS); + } + break; + + case NSEventTypeMouseMoved: + case NSEventTypeLeftMouseDragged: + case NSEventTypeRightMouseDragged: + case NSEventTypeOtherMouseDragged: + p = [event locationInWindow]; + cocoa.mouse_x = (int)p.x; + cocoa.mouse_y = cocoa.h - (int)p.y - 1; + cocoa_post_mouse(EVENT_MOUSE_MOVE, cocoa.mouse_x, cocoa.mouse_y, 0); + break; + + case NSEventTypeLeftMouseDown: + case NSEventTypeRightMouseDown: + case NSEventTypeOtherMouseDown: + cocoa.mouse_buttons |= 1 << [event buttonNumber]; + cocoa_post_mouse(EVENT_MOUSE_BUTTON_PRESS, cocoa.mouse_x, cocoa.mouse_y, + 1 << [event buttonNumber]); + break; + + case NSEventTypeLeftMouseUp: + case NSEventTypeRightMouseUp: + case NSEventTypeOtherMouseUp: + cocoa.mouse_buttons &= ~(1 << [event buttonNumber]); + cocoa_post_mouse(EVENT_MOUSE_BUTTON_RELEASE, cocoa.mouse_x, cocoa.mouse_y, + 1 << [event buttonNumber]); + break; + + case NSEventTypeScrollWheel: + cocoa.mouse_z += (int)[event scrollingDeltaY]; + cocoa_post_mouse(EVENT_MOUSE_WHEEL, cocoa.mouse_x, cocoa.mouse_y, 0); + break; + + default: + break; + } +} + +/* Pump pending AppKit events. Main thread only. */ +static void cocoa_pump_events(void) +{ + NSEvent *event; + + if (!cocoa_ready) + return; + + while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:[NSDate distantPast] + inMode:NSDefaultRunLoopMode + dequeue:YES]) != nil) { + cocoa_handle_event(event); + [NSApp sendEvent:event]; + } + + /* Focus follows the window, which is what InKey/GetMouse care about */ + cocoa.has_focus = [cocoa.window isKeyWindow] ? TRUE : FALSE; +} + +/* --------------------------------------------------------- refresh thread */ + +/* Converts the framebuffer and publishes it; no AppKit window management or + event handling happens here. */ +static void *cocoa_thread(void *arg) +{ + (void)arg; + + while (cocoa.running) { + CGImageRef image; + + pthread_mutex_lock(&cocoa.mutex); + cocoa_convert(); + image = CGBitmapContextCreateImage(cocoa.ctx); + pthread_mutex_unlock(&cocoa.mutex); + + if (image) { + cocoa_present_background(image); + CGImageRelease(image); + } + + usleep(1000 * 1000 / 60); + } + + return NULL; +} + +/* --------------------------------------------------------------- callbacks */ + +void fb_hCocoaLock(void) +{ + pthread_mutex_lock(&cocoa.mutex); +} + +void fb_hCocoaUnlock(void) +{ + /* Called whenever the program unlocks the screen; a natural point to + push the current frame out on the main thread. */ + if (cocoa_ready) { + CGImageRef image; + + cocoa_convert(); + image = CGBitmapContextCreateImage(cocoa.ctx); + if (image) { + cocoa.view.layer.contents = (__bridge id)image; + CGImageRelease(image); + } + cocoa_pump_events(); + } + + pthread_mutex_unlock(&cocoa.mutex); +} + +void fb_hCocoaWaitVSync(void) +{ + usleep(1000000 / ((__fb_gfx && __fb_gfx->refresh_rate > 0) ? __fb_gfx->refresh_rate : 60)); +} + +void fb_hCocoaSetPalette(int index, int r, int g, int b) +{ + /* The blitter reads __fb_gfx->palette directly, so nothing to do here. */ + (void)index; (void)r; (void)g; (void)b; +} + +int fb_hCocoaGetMouse(int *x, int *y, int *z, int *buttons, int *clip) +{ + if (!cocoa.has_focus) + return -1; + + *x = cocoa.mouse_x; + *y = cocoa.mouse_y; + *z = cocoa.mouse_z; + *buttons = cocoa.mouse_buttons; + *clip = 0; + return 0; +} + +void fb_hCocoaSetMouse(int x, int y, int cursor, int clip) +{ + (void)clip; + + cocoa.mouse_x = x; + cocoa.mouse_y = y; + + if (cursor != 0) { + if (!cocoa.cursor_shown) { + [NSCursor unhide]; + cocoa.cursor_shown = 1; + } + } else { + if (cocoa.cursor_shown) { + [NSCursor hide]; + cocoa.cursor_shown = 0; + } + } +} + +void fb_hCocoaSetWindowTitle(char *title) +{ + if (cocoa_ready && title) + cocoa.window.title = [NSString stringWithUTF8String:title]; +} + +int fb_hCocoaSetWindowPos(int x, int y) +{ + if (!cocoa_ready) + return -1; + + /* FB coordinates are top-left based; Cocoa's are bottom-left based */ + NSRect screen = [[NSScreen mainScreen] frame]; + [cocoa.window setFrameTopLeftPoint:NSMakePoint(x, screen.size.height - y)]; + return 0; +} + +int fb_hCocoaScreenInfo(ssize_t *width, ssize_t *height, ssize_t *depth, ssize_t *refresh) +{ + NSRect frame = [[NSScreen mainScreen] frame]; + + *width = (ssize_t)frame.size.width; + *height = (ssize_t)frame.size.height; + *depth = 32; + *refresh = 60; + return 0; +} + +int *fb_hCocoaFetchModes(int depth, int *size) +{ + /* No fullscreen modes: the driver is windowed only */ + (void)depth; + if (size) + *size = 0; + return NULL; +} + +/* ---------------------------------------------------------- driver entry pts */ + +static void cocoa_poll_events_hook(void) +{ + /* Called by the core (also from ScreenControl POLL_EVENTS) */ + if (!cocoa_ready) + return; + + cocoa_pump_events(); +} + +static void cocoa_flip_hook(void) +{ + if (!cocoa_ready) + return; + + cocoa_present(); + cocoa_pump_events(); +} + +static void cocoa_update_hook(void) +{ + if (!cocoa_ready) + return; + + cocoa_present(); +} + +static int driver_init(char *title, int w, int h, int depth, int refresh_rate, int flags) +{ + @autoreleasepool { + NSRect rect; + NSInteger style; + CGColorSpaceRef cs; + int stride; + + /* OpenGL screens are handled by the X11/GLX driver (via XQuartz) */ + if (flags & DRIVER_OPENGL) + return -1; + + if (w <= 0 || h <= 0) + return -1; + + fb_hMemSet(&cocoa, 0, sizeof(cocoa)); + cocoa.w = w; + cocoa.h = h; + cocoa.mouse_visible = 1; + cocoa.has_focus = 1; + pthread_mutex_init(&cocoa.mutex, NULL); + + if (!cocoa_app_ready) { + [NSApplication sharedApplication]; + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + cocoa_app_ready = 1; + } + + style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | + NSWindowStyleMaskMiniaturizable; + rect = NSMakeRect(0, 0, w, h); + cocoa.window = [[NSWindow alloc] initWithContentRect:rect + styleMask:style + backing:NSBackingStoreBuffered + defer:NO]; + if (cocoa.window == nil) + return -1; + + cocoa.window.title = [NSString stringWithUTF8String:(title ? title : "FreeBASIC")]; + [cocoa.window setReleasedWhenClosed:NO]; + [cocoa.window center]; + + cocoa.view = [[NSView alloc] initWithFrame:rect]; + cocoa.view.wantsLayer = YES; + /* 1:1 pixels: the image is w x h and so is the layer in points, so + a 2x backing store upscales it with nearest-neighbour filtering + rather than smoothing it. */ + cocoa.view.layer.contentsGravity = kCAGravityResize; + cocoa.view.layer.magnificationFilter = kCAFilterNearest; + cocoa.view.layer.contentsScale = 1.0; + cocoa.window.contentView = cocoa.view; + + [cocoa.window makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; + + /* The gfxlib2 blitter for a 32-bit device depth writes R,G,B,X + bytes: component order R,G,B with a trailing skipped byte, i.e. + kCGImageAlphaNoneSkipLast with big-endian 32-bit words. */ + stride = w * 4; + cocoa.buf = (unsigned char *)calloc(1, (size_t)stride * h); + if (cocoa.buf == NULL) + return -1; + + cs = CGColorSpaceCreateDeviceRGB(); + cocoa.ctx = CGBitmapContextCreate(cocoa.buf, w, h, 8, stride, cs, + kCGImageAlphaNoneSkipLast | + kCGBitmapByteOrder32Big); + CGColorSpaceRelease(cs); + if (cocoa.ctx == NULL) + return -1; + + cocoa.blitter = fb_hGetBlitter(32, TRUE); + if (cocoa.blitter == NULL) + return -1; + + cocoa_ready = 1; + cocoa.running = 1; + if (pthread_create(&cocoa.thread, NULL, cocoa_thread, NULL) != 0) { + cocoa.running = 0; + cocoa_ready = 0; + return -1; + } + + if (refresh_rate > 0 && __fb_gfx) + __fb_gfx->refresh_rate = refresh_rate; + + return 0; + } +} + +static void driver_exit(void) +{ + if (!cocoa_ready) + return; + + cocoa.running = 0; + pthread_join(cocoa.thread, NULL); + cocoa_ready = 0; + + @autoreleasepool { + if (cocoa.ctx) { + CGContextRelease(cocoa.ctx); + cocoa.ctx = NULL; + } + if (cocoa.buf) { + free(cocoa.buf); + cocoa.buf = NULL; + } + if (cocoa.window) { + [cocoa.window orderOut:nil]; + cocoa.window = nil; + } + cocoa.view = nil; + } + + pthread_mutex_destroy(&cocoa.mutex); +} + +/* GFXDRIVER */ +const GFXDRIVER fb_gfxDriverCocoa = +{ + "Cocoa", /* char *name; */ + driver_init, /* int (*init)(...) */ + driver_exit, /* void (*exit)(void); */ + fb_hCocoaLock, /* void (*lock)(void); */ + fb_hCocoaUnlock, /* void (*unlock)(void); */ + fb_hCocoaSetPalette, /* void (*set_palette)(...); */ + fb_hCocoaWaitVSync, /* void (*wait_vsync)(void); */ + fb_hCocoaGetMouse, /* int (*get_mouse)(...); */ + fb_hCocoaSetMouse, /* void (*set_mouse)(...); */ + fb_hCocoaSetWindowTitle, /* void (*set_window_title)(char *); */ + fb_hCocoaSetWindowPos, /* int (*set_window_pos)(int, int); */ + fb_hCocoaFetchModes, /* int *(*fetch_modes)(int, int *); */ + cocoa_flip_hook, /* void (*flip)(void); */ + cocoa_poll_events_hook, /* void (*poll_events)(void); */ + cocoa_update_hook /* void (*update)(void); */ +}; + +#else +typedef int fb_cocoa_driver_disabled_t; /* avoid an empty translation unit */ +#endif diff --git a/src/gfxlib2/unix/gfx_unix.c b/src/gfxlib2/unix/gfx_unix.c index c7baa85b6..3647026d0 100644 --- a/src/gfxlib2/unix/gfx_unix.c +++ b/src/gfxlib2/unix/gfx_unix.c @@ -1,14 +1,24 @@ #include "../fb_gfx.h" + +#ifndef DISABLE_X11 #include "fb_gfx_x11.h" +#endif #ifdef HOST_LINUX #include "../linux/fb_gfx_linux.h" #endif +#if defined HOST_DARWIN && !defined DISABLE_COCOA +#include "../darwin/fb_gfx_cocoa.h" +#endif + #if defined HOST_FREEBSD || defined HOST_OPENBSD || defined HOST_LINUX || defined HOST_DARWIN || defined HOST_SOLARIS || defined HOST_DRAGONFLY || defined HOST_NETBSD const GFXDRIVER *__fb_gfx_drivers_list[] = { + /* X11/GLX first when it was built in (XQuartz), then the native Cocoa + driver: if no X server is reachable, Cocoa takes over, and in a build + without XQuartz it is the only driver. */ #ifndef DISABLE_X11 &fb_gfxDriverX11, #ifndef DISABLE_OPENGL @@ -16,6 +26,10 @@ const GFXDRIVER *__fb_gfx_drivers_list[] = { #endif #endif +#if defined HOST_DARWIN && !defined DISABLE_COCOA + &fb_gfxDriverCocoa, +#endif + #if defined HOST_LINUX && !defined DISABLE_FBDEV &fb_gfxDriverFBDev, #endif @@ -25,6 +39,9 @@ const GFXDRIVER *__fb_gfx_drivers_list[] = { void fb_hScreenInfo(ssize_t *width, ssize_t *height, ssize_t *depth, ssize_t *refresh) { +#if defined HOST_DARWIN && !defined DISABLE_COCOA + if (fb_hCocoaScreenInfo(width, height, depth, refresh)) +#endif #ifndef DISABLE_X11 if (fb_hX11ScreenInfo(width, height, depth, refresh)) #endif diff --git a/src/rtlib/darwin/fb_private_scancodes_cocoa.h b/src/rtlib/darwin/fb_private_scancodes_cocoa.h new file mode 100644 index 000000000..8fb2d2474 --- /dev/null +++ b/src/rtlib/darwin/fb_private_scancodes_cocoa.h @@ -0,0 +1,106 @@ +#include "../fb.h" + +static const int fb_cocoakeycode_to_scancode[256] = { + [0x35] = SC_ESCAPE, + [0x7A] = SC_F1, + [0x78] = SC_F2, + [0x63] = SC_F3, + [0x76] = SC_F4, + [0x60] = SC_F5, + [0x61] = SC_F6, + [0x62] = SC_F7, + [0x64] = SC_F8, + [0x65] = SC_F9, + [0x6D] = SC_F10, + [0x67] = SC_F11, + [0x6F] = SC_F12, + [0x32] = SC_TILDE, + [0x12] = SC_1, + [0x13] = SC_2, + [0x14] = SC_3, + [0x15] = SC_4, + [0x17] = SC_5, + [0x16] = SC_6, + [0x1A] = SC_7, + [0x1C] = SC_8, + [0x19] = SC_9, + [0x1D] = SC_0, + [0x1B] = SC_MINUS, + [0x18] = SC_EQUALS, + [0x2A] = SC_BACKSLASH, + [0x33] = SC_BACKSPACE, + [0x30] = SC_TAB, + [0x0C] = SC_Q, + [0x0D] = SC_W, + [0x0E] = SC_E, + [0x0F] = SC_R, + [0x11] = SC_T, + [0x10] = SC_Y, + [0x20] = SC_U, + [0x22] = SC_I, + [0x1F] = SC_O, + [0x23] = SC_P, + [0x21] = SC_LEFTBRACKET, + [0x1E] = SC_RIGHTBRACKET, + [0x39] = SC_CAPSLOCK, + [0x00] = SC_A, + [0x01] = SC_S, + [0x02] = SC_D, + [0x03] = SC_F, + [0x05] = SC_G, + [0x04] = SC_H, + [0x26] = SC_J, + [0x28] = SC_K, + [0x25] = SC_L, + [0x29] = SC_SEMICOLON, + [0x27] = SC_QUOTE, + [0x38] = SC_LSHIFT, + [0x06] = SC_Z, + [0x07] = SC_X, + [0x08] = SC_C, + [0x09] = SC_V, + [0x0B] = SC_B, + [0x2D] = SC_N, + [0x2E] = SC_M, + [0x2B] = SC_COMMA, + [0x2F] = SC_PERIOD, + [0x2C] = SC_SLASH, + [0x3C] = SC_RSHIFT, + [0x3B] = SC_CONTROL, + [0x37] = SC_LWIN, + [0x3A] = SC_ALT, + [0x31] = SC_SPACE, + [0x3D] = SC_ALT, + [0x36] = SC_RWIN, + [0x6E] = SC_MENU, + [0x3E] = SC_CONTROL, + [0x73] = SC_HOME, + [0x74] = SC_PAGEUP, + [0x75] = SC_DELETE, + [0x77] = SC_END, + [0x79] = SC_PAGEDOWN, + [0x7E] = SC_UP, + [0x7B] = SC_LEFT, + [0x7D] = SC_DOWN, + [0x7C] = SC_RIGHT, + // Keypad + [0x4B] = SC_SLASH, + [0x43] = SC_MULTIPLY, + [0x4E] = SC_MINUS, + [0x45] = SC_PLUS, + [0x47] = SC_CLEAR, + [0x24] = SC_ENTER, + [0x41] = SC_PERIOD, + [0x4C] = SC_ENTER, + [0x51] = SC_EQUALS, + [0x52] = SC_0, + [0x53] = SC_1, + [0x54] = SC_2, + [0x55] = SC_3, + [0x56] = SC_4, + [0x57] = SC_5, + [0x58] = SC_6, + [0x59] = SC_7, + [0x5B] = SC_8, + [0x5C] = SC_9 +}; From a68e410d16716f0f60a703ca835748355cadcd0c Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 00:35:28 +0300 Subject: [PATCH 11/13] darwin: fix Cocoa driver input handling - InKey() was dead: it reads the key buffer filled by fb_hPostKey(), which this driver never called (only fb_hPostEvent, which GetKey() and the event queue use). Feed both, and translate NSEvents the way the X11 driver does: plain characters as-is, 0x7F remapped to FB's extended KEY_DEL, and everything else through fb_hScancodeToExtendedKey(). - GetMouse() no longer returns -1 when another application is frontmost. Gating it on the window being key meant a program polling the mouse while unattended saw nothing at all. - Report window close (NSWindowWillCloseNotification) as EVENT_WINDOW_CLOSE plus KEY_QUIT, matching what ALT+F4/CLOSE does on other platforms. - Only report mouse motion that is inside the window: unlike X11, this driver receives motion for the whole application, so out-of-window coordinates (often negative) were being reported as if they were in-window. Verified so far: real mouse motion and clicks reach GetMouse() and are correctly converted; animation updates continuously with no ScreenSync/ ScreenUnlock; 8bpp palette and 16bpp modes render correctly. --- src/gfxlib2/darwin/gfx_driver_cocoa.m | 65 +++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/gfxlib2/darwin/gfx_driver_cocoa.m b/src/gfxlib2/darwin/gfx_driver_cocoa.m index 5b8dfe0b6..225059681 100644 --- a/src/gfxlib2/darwin/gfx_driver_cocoa.m +++ b/src/gfxlib2/darwin/gfx_driver_cocoa.m @@ -137,6 +137,27 @@ static void cocoa_post_mouse(int type, int x, int y, int button) fb_hPostEvent(&e); } +/* Map an NSEvent to FB's keycode convention: plain characters as-is, extended + keys (arrows, function keys, ...) through the scancode table, mirroring what + the X11 driver does with XLookupString/translate_key. */ +static int cocoa_translate_key(NSEvent *event, int scancode) +{ + NSString *chars = [event characters]; + unichar c; + + if ([chars length] >= 1) { + c = [chars characterAtIndex:0]; + /* Remap ASCII DEL to FB's extended DELETE keycode, as the other + drivers do */ + if (c == 0x7F) + return KEY_DEL; + if (c < 0x80) + return (int)c; + } + + return fb_hScancodeToExtendedKey(scancode); +} + static void cocoa_handle_event(NSEvent *event) { NSPoint p; @@ -152,8 +173,14 @@ static void cocoa_handle_event(NSEvent *event) if (scancode == 0) break; if ([event type] == NSEventTypeKeyDown) { + int key = cocoa_translate_key(event, scancode); + __fb_gfx->key[scancode] = TRUE; - cocoa_post_key(scancode, (int)[[event characters] characterAtIndex:0], + /* InKey() reads the key buffer, GetKey()/the event queue read + the posted event, so feed both like the other drivers do */ + if (key) + fb_hPostKey(key); + cocoa_post_key(scancode, ((key < 0) || (key > 0xFF)) ? 0 : key, [event isARepeat] ? EVENT_KEY_REPEAT : EVENT_KEY_PRESS); } else { __fb_gfx->key[scancode] = FALSE; @@ -176,9 +203,21 @@ static void cocoa_handle_event(NSEvent *event) break; case NSEventTypeMouseMoved: + /* Only report motion inside the window; this driver sees moves for + the whole app, unlike the X11 one which only gets in-window + motion events. */ + p = [event locationInWindow]; + if ((p.x < 0) || (p.x >= cocoa.w) || (p.y < 0) || (p.y >= cocoa.h)) + break; + cocoa.mouse_x = (int)p.x; + cocoa.mouse_y = cocoa.h - (int)p.y - 1; + cocoa_post_mouse(EVENT_MOUSE_MOVE, cocoa.mouse_x, cocoa.mouse_y, 0); + break; + case NSEventTypeLeftMouseDragged: case NSEventTypeRightMouseDragged: case NSEventTypeOtherMouseDragged: + /* Dragging may legitimately leave the window */ p = [event locationInWindow]; cocoa.mouse_x = (int)p.x; cocoa.mouse_y = cocoa.h - (int)p.y - 1; @@ -297,9 +336,10 @@ void fb_hCocoaSetPalette(int index, int r, int g, int b) int fb_hCocoaGetMouse(int *x, int *y, int *z, int *buttons, int *clip) { - if (!cocoa.has_focus) - return -1; - + /* Always report the last known state. Gating this on the window being + key (as the X11 driver does with its focus tracking) makes GetMouse() + return -1 whenever another application is frontmost, which breaks + programs that poll the mouse while unattended. */ *x = cocoa.mouse_x; *y = cocoa.mouse_y; *z = cocoa.mouse_z; @@ -445,6 +485,23 @@ static int driver_init(char *title, int w, int h, int depth, int refresh_rate, i cocoa.view.layer.contentsScale = 1.0; cocoa.window.contentView = cocoa.view; + /* Closing the window should end the program, as ALT+F4 does + elsewhere: post the close event and the quit key. */ + [[NSNotificationCenter defaultCenter] + addObserverForName:NSWindowWillCloseNotification + object:cocoa.window + queue:nil + usingBlock:^(NSNotification *note) { + EVENT e; + (void)note; + if (!__fb_gfx) + return; + fb_hMemSet(&e, 0, sizeof(EVENT)); + e.type = EVENT_WINDOW_CLOSE; + fb_hPostEvent(&e); + fb_hPostKey(KEY_QUIT); + }]; + [cocoa.window makeKeyAndOrderFront:nil]; [NSApp activateIgnoringOtherApps:YES]; From 58db6172537ae9fc31bc74178e47084b4e74bd39 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 00:38:13 +0300 Subject: [PATCH 12/13] darwin: add opt-in event diagnostics to the Cocoa driver Setting FBCOCOA_DEBUG= logs the raw AppKit events the driver receives plus the NSApplication/window activation state, which is the quickest way to tell an input *delivery* problem from a *translation* problem. Costs nothing when the variable is unset. It immediately answered the open question here: with a real key press the log shows state isActive=1 isKeyWindow=1 event type=10 key keyCode=4 scancode=35 down=1 translated key=104 and InKey() returns it. (Synthetic CGEventPost input cannot be used to test this: macOS requires Accessibility permission for it.) --- src/gfxlib2/darwin/gfx_driver_cocoa.m | 49 ++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/gfxlib2/darwin/gfx_driver_cocoa.m b/src/gfxlib2/darwin/gfx_driver_cocoa.m index 225059681..5dd838c6b 100644 --- a/src/gfxlib2/darwin/gfx_driver_cocoa.m +++ b/src/gfxlib2/darwin/gfx_driver_cocoa.m @@ -24,6 +24,9 @@ hooks the core calls (flip/unlock/poll_events/wait_vsync). #include #include #include +#include +#include +#include #include "../fb_gfx.h" #include "fb_gfx_cocoa.h" @@ -108,6 +111,32 @@ static void cocoa_present_background(CGImageRef image) /* --------------------------------------------------------------- event pump */ +/* Optional diagnostics: set FBCOCOA_DEBUG= to log the raw AppKit events + the driver receives, which is the quickest way to tell an input delivery + problem from a translation problem. */ +static FILE *cocoa_debug = NULL; + +static void cocoa_debug_init(void) +{ + char *p = getenv("FBCOCOA_DEBUG"); + + if (p && *p) + cocoa_debug = fopen(p, "a"); +} + +static void cocoa_debug_log(const char *fmt, ...) +{ + va_list ap; + + if (cocoa_debug == NULL) + return; + + va_start(ap, fmt); + vfprintf(cocoa_debug, fmt, ap); + va_end(ap); + fflush(cocoa_debug); +} + static void cocoa_post_key(int scancode, int ascii, int type) { EVENT e; @@ -166,15 +195,21 @@ static void cocoa_handle_event(NSEvent *event) if (!__fb_gfx) return; + cocoa_debug_log("event type=%d\n", (int)[event type]); + switch ([event type]) { case NSEventTypeKeyDown: case NSEventTypeKeyUp: scancode = fb_cocoakeycode_to_scancode[[event keyCode] & 0xFF]; + cocoa_debug_log(" key keyCode=%d scancode=%d down=%d\n", + (int)[event keyCode], scancode, + [event type] == NSEventTypeKeyDown); if (scancode == 0) break; if ([event type] == NSEventTypeKeyDown) { int key = cocoa_translate_key(event, scancode); + cocoa_debug_log(" translated key=%d\n", key); __fb_gfx->key[scancode] = TRUE; /* InKey() reads the key buffer, GetKey()/the event queue read the posted event, so feed both like the other drivers do */ @@ -254,10 +289,20 @@ static void cocoa_handle_event(NSEvent *event) static void cocoa_pump_events(void) { NSEvent *event; + static int last_active = -1, last_key = -1; + int active, keywin; if (!cocoa_ready) return; + active = [NSApp isActive] ? 1 : 0; + keywin = [cocoa.window isKeyWindow] ? 1 : 0; + if ((active != last_active) || (keywin != last_key)) { + last_active = active; + last_key = keywin; + cocoa_debug_log("state isActive=%d isKeyWindow=%d\n", active, keywin); + } + while ((event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode @@ -267,7 +312,7 @@ static void cocoa_pump_events(void) } /* Focus follows the window, which is what InKey/GetMouse care about */ - cocoa.has_focus = [cocoa.window isKeyWindow] ? TRUE : FALSE; + cocoa.has_focus = keywin ? TRUE : FALSE; } /* --------------------------------------------------------- refresh thread */ @@ -454,6 +499,8 @@ static int driver_init(char *title, int w, int h, int depth, int refresh_rate, i cocoa.mouse_visible = 1; cocoa.has_focus = 1; pthread_mutex_init(&cocoa.mutex, NULL); + cocoa_debug_init(); + cocoa_debug_log("--- driver_init w=%d h=%d depth=%d flags=%d\n", w, h, depth, flags); if (!cocoa_app_ready) { [NSApplication sharedApplication]; From e50361db0218284c0b5236516841c52f1e792ea7 Mon Sep 17 00:00:00 2001 From: agorangetek Date: Sat, 19 Sep 2026 00:42:30 +0300 Subject: [PATCH 13/13] darwin: drop X11/XQuartz from the macOS build The native Cocoa driver supersedes it: XQuartz means an extra ~100 MB install for users, its GLX path is OpenGL (deprecated on macOS since 10.14), and X11 was only ever the legacy compatibility route on this platform. - makefile: Darwin always builds with DISABLE_X11 and DISABLE_OPENGL, so the X11 and GLX drivers are compiled out and /opt/X11 is no longer needed at build time either - fbc: no longer adds /opt/X11/lib, nor -lX11/-lXext/-lXpm/-lXrandr/-lXrender, for Darwin programs; the other unix targets keep their X11 libraries Verified after a clean rebuild: libfbgfx contains no X11/XOpenDisplay symbols, a gfx program links as -lfb -lfbgfx -lncurses -framework Cocoa -framework QuartzCore -framework CoreGraphics has zero X11 dylib dependencies, and renders correctly with no DISPLAY and no XQuartz installed. Note: FB.GFX_OPENGL screens are no longer available on Darwin (XQuartz/GLX used to provide them); ScreenRes with that flag now fails as it does on a build with no GL driver. --- makefile | 20 +++++++++----------- src/compiler/fbc.bas | 8 +++----- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/makefile b/makefile index c844fc264..c8f5d013a 100644 --- a/makefile +++ b/makefile @@ -586,17 +586,15 @@ ifeq ($(TARGET_OS),solaris) endif ifeq ($(TARGET_OS),darwin) - ALLCFLAGS += -I/opt/X11/include -I/usr/include/ffi - - ifdef ENABLE_XQUARTZ - ALLFBCFLAGS += -d ENABLE_XQUARTZ - else - ALLCFLAGS += -DDISABLE_X11 - # FB's OpenGL support lives in the X11 driver, so without XQuartz there is - # no GL driver at all. gfx_opengl.c otherwise still calls the driver hook - # fb_hGL_GetProcAddress(), leaving libfbgfx with an undefined symbol. - ALLCFLAGS += -DDISABLE_OPENGL - endif + # Darwin uses the native Cocoa/CoreGraphics driver, so X11 (XQuartz) is never + # built here: it would drag in an extra runtime dependency for the legacy + # path only. FB's OpenGL support lives in the X11 gfx driver, so without it + # there is no GL driver either -- gfx_opengl.c would otherwise still call + # the driver hook fb_hGL_GetProcAddress(), leaving libfbgfx with an undefined + # symbol. + ALLCFLAGS += -I/usr/include/ffi + ALLCFLAGS += -DDISABLE_X11 + ALLCFLAGS += -DDISABLE_OPENGL endif ifneq ($(filter cygwin win32,$(TARGET_OS)),) diff --git a/src/compiler/fbc.bas b/src/compiler/fbc.bas index 4e770bf35..682f82a5a 100644 --- a/src/compiler/fbc.bas +++ b/src/compiler/fbc.bas @@ -4310,11 +4310,9 @@ private sub hAddDefaultLibs( ) fbcAddDefLibPath( "/usr/X11R6/lib" ) #endif - #if defined(__FB_DARWIN__) and defined(ENABLE_XQUARTZ) - fbcAddDefLibPAth( "/opt/X11/lib" ) - #endif - - #if (not defined(__FB_DARWIN__)) or defined(ENABLE_XQUARTZ) + '' Darwin uses the native Cocoa driver and never links X11, so the + '' X11 libraries below are only added for the other unix targets. + #if not defined(__FB_DARWIN__) fbcAddDefLib( "X11" ) fbcAddDefLib( "Xext" ) fbcAddDefLib( "Xpm" )