Port Qt editor to macOS
ober
2d5e27c9ee4bd1740250b6458a4e784ac6e1abfb
--- a/Makefile +++ b/Makefile @@ -1,14 +1,45 @@ SCHEME = scheme JERBOA = $(HOME)/mine/jerboa -JSH = vendor/jerboa-shell/src -COREUTILS = $(HOME)/mine/jerboa-coreutils/lib +JSH = $(if $(wildcard vendor/jerboa-shell/src/jsh),vendor/jerboa-shell/src,$(HOME)/mine/jerboa-shell/src) +COREUTILS = $(if $(wildcard $(HOME)/mine/jerboa-coreutils/lib),$(HOME)/mine/jerboa-coreutils/lib,$(HOME)/mine/jerboa-shell/vendor/jerboa-coreutils/lib) GHERKIN = $(HOME)/mine/gherkin/src LIBDIRS = --libdirs lib:$(JERBOA)/lib:$(JSH):$(COREUTILS):$(GHERKIN):$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla/src:$(HOME)/mine/chez-qt JERBUILD = $(SCHEME) --libdirs $(JERBOA)/lib --script $(JERBOA)/jerbuild.ss -export LD_LIBRARY_PATH := .:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/chez-qt:vendor/jerboa-shell:$(LD_LIBRARY_PATH) + +# --- Platform detection ------------------------------------------------------- +UNAME_S := $(shell uname -s) +ifeq ($(UNAME_S),Darwin) + SHLIB_EXT := dylib + # -undefined dynamic_lookup: allow symbols resolved at load time from the host process + SHLIB_FLAGS := -dynamiclib -Wl,-undefined,dynamic_lookup + PRELOAD_VAR := DYLD_INSERT_LIBRARIES + export DYLD_LIBRARY_PATH := .:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/chez-qt:$(HOME)/mine/jerboa-shell:vendor/jerboa-shell:$(DYLD_LIBRARY_PATH) + PTY_LINK := + XVFB_RUN := + export QT_QPA_PLATFORM := cocoa + QT_INC_FALLBACK := /opt/homebrew/opt/qt/include + QSCI_EXTRA_INC := $(shell brew --prefix qscintilla2 2>/dev/null)/include + TS_INC := $(shell brew --prefix tree-sitter 2>/dev/null)/include + TS_LIB_DIR := $(shell brew --prefix tree-sitter 2>/dev/null)/lib +else + SHLIB_EXT := so + SHLIB_FLAGS := -shared -fPIC + PRELOAD_VAR := LD_PRELOAD + export LD_LIBRARY_PATH := .:$(HOME)/mine/chez-pcre2:$(HOME)/mine/chez-scintilla:$(HOME)/mine/chez-qt:vendor/jerboa-shell:$(LD_LIBRARY_PATH) + PTY_LINK := -lutil + XVFB_RUN := xvfb-run -a + QT_INC_FALLBACK := /usr/include/x86_64-linux-gnu/qt6 + QSCI_EXTRA_INC := + TS_INC := /opt/tree-sitter-include + TS_LIB_DIR := /opt/tree-sitter-lib +endif +PRELOAD_ENV := $(PRELOAD_VAR)=./qt_chez_shim.$(SHLIB_EXT) +# ----------------------------------------------------------------------------- + export CHEZ_SCINTILLA_LIB := $(HOME)/mine/chez-scintilla export CHEZ_PCRE2_LIB := $(HOME)/mine/chez-pcre2 -export CHEZ_QT_LIB := $(HOME)/mine/chez-qt +export JSH_FFI_LIB := $(HOME)/mine/jerboa-shell +export CHEZ_QT_LIB := . export CHEZ_QT_SHIM_DIR := . .PHONY: all build rebuild run test-tier0 test-tier2 test-tier3 test-tier4 test-tier5 test-org test-extra test clean clean-generated \ @@ -31,47 +62,117 @@ rebuild: run: build $(SCHEME) $(LIBDIRS) --script main.ss -repl_shim.so: support/repl_shim.c - gcc -shared -fPIC -O2 -o repl_shim.so support/repl_shim.c -Wall - -vterm_shim.so: support/vterm_shim.c - gcc -shared -fPIC -O2 -o vterm_shim.so support/vterm_shim.c -lvterm -Wall - -QT_INC := $(shell qmake6 -query QT_INSTALL_HEADERS 2>/dev/null || echo /usr/include/x86_64-linux-gnu/qt6) +repl_shim.$(SHLIB_EXT): support/repl_shim.c + gcc $(SHLIB_FLAGS) -O2 -o repl_shim.$(SHLIB_EXT) support/repl_shim.c -Wall + +VTERM_CFLAGS := $(shell pkg-config --cflags vterm 2>/dev/null) +VTERM_LIBS := $(shell pkg-config --libs vterm 2>/dev/null || echo -lvterm) + +vterm_shim.$(SHLIB_EXT): support/vterm_shim.c + gcc $(SHLIB_FLAGS) -O2 $(VTERM_CFLAGS) -o vterm_shim.$(SHLIB_EXT) support/vterm_shim.c $(VTERM_LIBS) -Wall + +support/pty_shim.$(SHLIB_EXT): support/pty_shim.c + gcc $(SHLIB_FLAGS) -O2 -o support/pty_shim.$(SHLIB_EXT) support/pty_shim.c $(PTY_LINK) -Wall + +ifeq ($(UNAME_S),Darwin) +# On macOS, grammars are compiled as object files (each with its own bundled +# parser.h), then linked into the shim. ABI-15 grammars use the shared +# include dir; ABI-14 grammars use their own bundled tree_sitter/ subdir. +TS_GRAMMAR_LIBS := \ + -L/opt/homebrew/opt/tree-sitter-go/lib -ltree-sitter-go \ + -L/opt/homebrew/opt/tree-sitter-python/lib -ltree-sitter-python \ + -L/opt/homebrew/opt/tree-sitter-ruby/lib -ltree-sitter-ruby + +# Compile each grammar object with the correct include path. +# ABI15 grammars (use shared include): +TS_ABI15 := c cpp bash javascript rust css +# ABI14 grammars (use bundled parser.h from their own tree_sitter/ subdir): +TS_ABI14 := json java html lua scheme + +TS_GRAMMAR_OBJS := $(foreach l,$(TS_ABI15),support/grammars/$(l)/parser.o) \ + $(foreach l,$(TS_ABI14),support/grammars/$(l)/parser.o) \ + support/grammars/cpp/scanner.o \ + support/grammars/bash/scanner.o \ + support/grammars/javascript/scanner.o \ + support/grammars/rust/scanner.o \ + support/grammars/css/scanner.o \ + support/grammars/html/scanner.o \ + support/grammars/lua/scanner.o + +# Pattern rules for ABI15 grammars +$(foreach l,$(TS_ABI15),support/grammars/$(l)/parser.o): support/grammars/%/parser.o: support/grammars/%/parser.c + gcc -c -O2 -I$(TS_INC) -Isupport/grammars/include -o $@ $< + +$(foreach l,$(TS_ABI15),support/grammars/$(l)/scanner.o): support/grammars/%/scanner.o: support/grammars/%/scanner.c + gcc -c -O2 -I$(TS_INC) -Isupport/grammars/include -o $@ $< + +# Pattern rules for ABI14 grammars (use bundled tree_sitter/ inside grammar dir) +$(foreach l,$(TS_ABI14),support/grammars/$(l)/parser.o): support/grammars/%/parser.o: support/grammars/%/parser.c + gcc -c -O2 -I$(TS_INC) -Isupport/grammars/$* -o $@ $< + +$(foreach l,$(TS_ABI14),support/grammars/$(l)/scanner.o): support/grammars/%/scanner.o: support/grammars/%/scanner.c + gcc -c -O2 -I$(TS_INC) -Isupport/grammars/$* -o $@ $< + +else +TS_GRAMMAR_LIBS := +TS_GRAMMAR_OBJS := +endif + +support/treesitter_shim.$(SHLIB_EXT): support/treesitter_shim.c support/treesitter_queries.c $(TS_GRAMMAR_OBJS) + gcc $(SHLIB_FLAGS) -O2 -I$(TS_INC) -Isupport/grammars/include \ + -o support/treesitter_shim.$(SHLIB_EXT) \ + support/treesitter_shim.c support/treesitter_queries.c \ + $(TS_GRAMMAR_OBJS) \ + -L$(TS_LIB_DIR) -ltree-sitter $(TS_GRAMMAR_LIBS) -Wall + +QT_INC := $(shell qmake6 -query QT_INSTALL_HEADERS 2>/dev/null || echo $(QT_INC_FALLBACK)) QT_SHIM_H := vendor -libqt_shim.so: vendor/qt_shim.cpp - g++ -shared -fPIC -std=c++17 -O2 \ +ifeq ($(UNAME_S),Darwin) +QT_CFLAGS := $(shell pkg-config --cflags Qt6Widgets Qt6Gui Qt6Core 2>/dev/null) +QT_LIBS := $(shell pkg-config --libs Qt6Widgets Qt6Gui Qt6Core 2>/dev/null) +QSCI_CFLAGS := -I$(QSCI_EXTRA_INC) +QSCI_LIBS := -L/opt/homebrew/lib -lqscintilla2_qt6 +else +QT_CFLAGS := -I$(QT_INC) -I$(QT_INC)/QtCore -I$(QT_INC)/QtGui -I$(QT_INC)/QtWidgets -I$(QT_INC)/Qsci +QT_LIBS := -lQt6Core -lQt6Gui -lQt6Widgets +QSCI_CFLAGS := +QSCI_LIBS := -lqscintilla2_qt6 +endif + +libqt_shim.$(SHLIB_EXT): vendor/qt_shim.cpp + g++ $(SHLIB_FLAGS) -std=c++17 -O2 \ -DJEMACS_CHEZ_SMP -DQT_SCINTILLA_AVAILABLE \ - -I$(QT_SHIM_H) -I$(QT_INC) -I$(QT_INC)/QtCore -I$(QT_INC)/QtGui -I$(QT_INC)/QtWidgets -I$(QT_INC)/Qsci \ + -I$(QT_SHIM_H) $(QT_CFLAGS) $(QSCI_CFLAGS) \ + -I$(QT_INC)/Qsci \ vendor/qt_shim.cpp \ - -o libqt_shim.so \ - -lQt6Core -lQt6Gui -lQt6Widgets -lqscintilla2_qt6 + -o libqt_shim.$(SHLIB_EXT) \ + $(QT_LIBS) $(QSCI_LIBS) -qt_chez_shim.so: vendor/qt_chez_shim.c vendor/qt_shim.h - gcc -shared -fPIC -O2 -o qt_chez_shim.so vendor/qt_chez_shim.c -Ivendor -Wall +qt_chez_shim.$(SHLIB_EXT): vendor/qt_chez_shim.c vendor/qt_shim.h + gcc $(SHLIB_FLAGS) -O2 -o qt_chez_shim.$(SHLIB_EXT) vendor/qt_chez_shim.c -Ivendor -DQT_SCINTILLA_AVAILABLE -Wall -run-qt: build repl_shim.so libqt_shim.so vterm_shim.so qt_chez_shim.so - LD_PRELOAD=./qt_chez_shim.so $(SCHEME) $(LIBDIRS) --script qt-main.ss +run-qt: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) qt_chez_shim.$(SHLIB_EXT) + $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script qt-main.ss # Headless Qt with automation REPL (for Claude). Uses xvfb-run for -# virtual display (static binary needs xcb). Auto-assigned REPL port. -run-qt-test: build repl_shim.so libqt_shim.so vterm_shim.so qt_chez_shim.so +# virtual display on Linux (static binary needs xcb). Auto-assigned REPL port. +run-qt-test: build repl_shim.$(SHLIB_EXT) libqt_shim.$(SHLIB_EXT) vterm_shim.$(SHLIB_EXT) qt_chez_shim.$(SHLIB_EXT) @rm -f $(HOME)/.jerboa-repl-port - xvfb-run -a LD_PRELOAD=./qt_chez_shim.so \ + $(XVFB_RUN) $(PRELOAD_ENV) \ $(SCHEME) $(LIBDIRS) --script qt-main.ss --repl 0 & @for i in $$(seq 1 20); do \ [ -f $(HOME)/.jerboa-repl-port ] && break; \ sleep 0.3; \ done @if [ -f $(HOME)/.jerboa-repl-port ]; then \ - echo "jemacs-qt running (headless). REPL port: $$(grep -oP '\\d+' $(HOME)/.jerboa-repl-port)"; \ + echo "jemacs-qt running (headless). REPL port: $$(grep -oE '[0-9]+' $(HOME)/.jerboa-repl-port | head -1)"; \ else \ echo "ERROR: REPL port file not created after 6s"; exit 1; \ fi stop-qt-test: - @PORT=$$(grep -oP '\\d+' $(HOME)/.jerboa-repl-port 2>/dev/null); \ + @PORT=$$(grep -oE '[0-9]+' $(HOME)/.jerboa-repl-port 2>/dev/null | head -1); \ if [ -n "$$PORT" ]; then \ PID=$$(lsof -ti :$$PORT 2>/dev/null | head -1); \ [ -n "$$PID" ] && kill $$PID && echo "Killed jemacs-qt (PID $$PID)" || echo "No running jemacs-qt found"; \ @@ -212,8 +313,8 @@ test-debug-repl: $(SCHEME) $(LIBDIRS) --program tests/test-debug-repl.ss test-qt: build - QT_QPA_PLATFORM=offscreen LD_PRELOAD=./qt_chez_shim.so $(SCHEME) $(LIBDIRS) --script tests/test-qt.ss - QT_QPA_PLATFORM=offscreen LD_PRELOAD=./qt_chez_shim.so $(SCHEME) $(LIBDIRS) --script tests/test-qt-part2.ss + QT_QPA_PLATFORM=offscreen $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script tests/test-qt.ss + QT_QPA_PLATFORM=offscreen $(PRELOAD_ENV) $(SCHEME) $(LIBDIRS) --script tests/test-qt-part2.ss # End-to-end Qt tests: Xvfb + xdotool + IPC REPL (requires xvfb, xdotool, nc) test-qt-e2e: --- a/lib/jerboa-emacs/async.sls +++ b/lib/jerboa-emacs/async.sls @@ -552,8 +552,8 @@ (let ([v (hashtable-ref cache key - '#{miss o8ujppxdmbs3cyrnmt4czdz0a-1})]) - (if (eq? v '#{miss o8ujppxdmbs3cyrnmt4czdz0a-2}) + '#{miss i8j2fi1rqgjlhflvdweso8ikz-1})]) + (if (eq? v '#{miss i8j2fi1rqgjlhflvdweso8ikz-2}) (if (null? default) #f (car default)) v))) (def (weak-cache-set! cache key value) --- a/lib/jerboa-emacs/pty.sls +++ b/lib/jerboa-emacs/pty.sls @@ -31,13 +31,16 @@ (let ((v (getenv "JEMACS_STATIC"))) (and v (not (string=? v "")) (not (string=? v "0"))))) + (define shlib-ext + (if (string-contains (symbol->string (machine-type)) "osx") "dylib" "so")) + (define pty-shim-loaded (if static-build? #f ; symbols already linked in via Sforeign_symbol registration (load-shared-object (let ((dir (or (getenv "JERBOA_EMACS_SUPPORT") (string-append (or (getenv "HOME") ".") "/mine/jerboa-emacs/support")))) - (string-append dir "/pty_shim.so"))))) + (string-append dir "/pty_shim." shlib-ext))))) ;;; ======================================================================== ;;; FFI bindings --- a/lib/jerboa/repl-socket.sls +++ b/lib/jerboa/repl-socket.sls @@ -53,15 +53,19 @@ #f ; static build: symbols registered via Sforeign_symbol (begin (load-shared-object #f) - ;; Load repl_shim.so for GC-safe subprocess/file I/O helpers. + ;; Load repl_shim.so/.dylib for GC-safe subprocess/file I/O helpers. ;; Try CWD paths, then next to the binary via /proc/self/exe. - (let* ([exe-dir (_exe-dir)] + (let* ([mt (symbol->string (machine-type))] + [ext (if (and (>= (string-length mt) 3) + (string=? (substring mt (- (string-length mt) 3) (string-length mt)) "osx")) + "dylib" "so")] + [exe-dir (_exe-dir)] [paths (append - (list "repl_shim.so" - "./repl_shim.so" - "support/repl_shim.so") + (list (string-append "repl_shim." ext) + (string-append "./repl_shim." ext) + (string-append "support/repl_shim." ext)) (if exe-dir - (list (string-append exe-dir "repl_shim.so")) + (list (string-append exe-dir "repl_shim." ext)) '()))]) (let try ((ps paths)) (if (null? ps) @@ -94,10 +98,16 @@ (define c-deactivate (foreign-procedure "repl_deactivate_thread" () void)) (define c-activate (foreign-procedure "repl_activate_thread" () int)) - ;; errno - (define c-errno-location (foreign-procedure "__errno_location" () void*)) + ;; errno — use __error on macOS/FreeBSD, __errno_location on Linux + (define c-errno-location + (let ((mt (symbol->string (machine-type)))) + (if (or (memq (machine-type) '(a6fb ta6fb i3fb ti3fb arm64fb)) + (and (>= (string-length mt) 3) + (string=? (substring mt (- (string-length mt) 3) (string-length mt)) "osx"))) + (foreign-procedure "__error" () void*) + (foreign-procedure "__errno_location" () void*)))) (define (get-errno) (foreign-ref 'int (c-errno-location) 0)) - (define EAGAIN 11) + (define EAGAIN (if (memq (machine-type) '(tarm64osx ta6osx a6osx arm64osx)) 35 11)) (define EINTR 4) ;; Constants --- a/src/jerboa-emacs/treesitter.ss +++ b/src/jerboa-emacs/treesitter.ss @@ -57,6 +57,13 @@ (let ((v (getenv "JEMACS_STATIC"))) (and v (not (string=? v "")) (not (string=? v "0"))))) +(define shlib-ext + (let ((mt (symbol->string (machine-type)))) + (if (and (>= (string-length mt) 3) + (string=? (substring mt (- (string-length mt) 3) (string-length mt)) "osx")) + "dylib" + "so"))) + (define ts-shim-loaded (if static-build? #f ;; symbols already linked in via Sforeign_symbol registration @@ -66,7 +73,7 @@ (load-shared-object (let ((dir (or (getenv "JERBOA_EMACS_SUPPORT") (string-append (or (getenv "HOME") ".") "/mine/jerboa-emacs/support")))) - (string-append dir "/treesitter_shim.so"))))))) + (string-append dir "/treesitter_shim." shlib-ext))))))) ;;;============================================================================ ;;; FFI bindings --- a/src/jerboa-emacs/vtscreen.ss +++ b/src/jerboa-emacs/vtscreen.ss @@ -46,6 +46,13 @@ ;;; FFI: load vterm_shim.so and bind C functions ;;;============================================================================ +(def shlib-ext + (let ((mt (symbol->string (machine-type)))) + (if (and (>= (string-length mt) 3) + (string=? (substring mt (- (string-length mt) 3) (string-length mt)) "osx")) + "dylib" + "so"))) + (def vterm-shim-loaded (let ((static? (let ((v (getenv "JEMACS_STATIC"))) (and v (not (string=? v "")) (not (string=? v "0")))))) @@ -53,7 +60,7 @@ #f ; symbols already linked into the binary (let ((dir (or (getenv "JERBOA_EMACS_SUPPORT") (string-append (or (getenv "HOME") ".") "/mine/jerboa-emacs")))) - (load-shared-object (string-append dir "/vterm_shim.so")))))) + (load-shared-object (string-append dir "/vterm_shim." shlib-ext)))))) ;; Core lifecycle (def ffi-jvt-new (foreign-procedure "jvt_new" (int int) void*)) --- a/support/pty_shim.c +++ b/support/pty_shim.c @@ -2,10 +2,15 @@ * pty_shim.c — PTY (pseudo-terminal) subprocess support via forkpty(3). * * Extracted from gerbil-emacs/pty.ss begin-ffi block. - * Compile: cc -shared -fPIC -o pty_shim.so pty_shim.c -lutil + * Linux: cc -shared -fPIC -o pty_shim.so pty_shim.c -lutil + * macOS: cc -dynamiclib -o pty_shim.dylib pty_shim.c */ +#ifdef __APPLE__ +#include <util.h> +#else #include <pty.h> +#endif #include <unistd.h> #include <fcntl.h> #include <signal.h> @@ -52,7 +57,21 @@ int pty_spawn(const char *cmd, const char *envp, int rows, int cols) { } if (envp && envp[0]) { +#ifdef __APPLE__ + /* macOS lacks clearenv(); clear via unsetenv on each key */ + { + extern char **environ; + while (environ && environ[0]) { + char *key = strdup(environ[0]); + char *eq = strchr(key, '='); + if (eq) *eq = '\0'; + unsetenv(key); + free(key); + } + } +#else clearenv(); +#endif const char *p = envp; while (*p) { const char *nl = strchr(p, '\n'); --- a/support/treesitter_shim.c +++ b/support/treesitter_shim.c @@ -20,6 +20,9 @@ /* ======================================================================== * Language registry — extern declarations for statically linked grammars. * Each grammar archive exports a single tree_sitter_<lang>() function. + * + * On macOS, all grammar symbols are declared weak so the dylib links even + * when only a subset of grammars is installed. Missing grammars return NULL. * ======================================================================== */ extern const TSLanguage *tree_sitter_c(void); --- a/vendor/qt_shim.cpp +++ b/vendor/qt_shim.cpp @@ -461,6 +461,26 @@ static void* qt_thread_main(void* arg) { extern "C" qt_application_t qt_application_create(int argc, char** argv) { (void)argc; (void)argv; +#ifdef __APPLE__ + // macOS/Cocoa requires QApplication to be created on the main thread. + // The calling thread IS the main thread (Chez primordial thread), so we + // create QApplication here directly — no pthread needed. + // The event loop is driven by repeated qt_application_process_events() calls + // from the Scheme polling loop in qt-app-exec! (every 50ms). + // Since g_qt_main_thread == QThread::currentThread() for ALL Qt calls + // from Scheme, QT_VOID/QT_RETURN always take the direct path — no + // BlockingQueuedConnection marshaling needed. + if (!getenv("QT_QPA_PLATFORM")) { + setenv("QT_QPA_PLATFORM", "cocoa", 0); + } + g_qt_main_thread = QThread::currentThread(); + auto* app = new QApplication(s_argc, s_argv); + QAccessible::setActive(false); + qt_verbose_log_note_qt_thread(); + g_event_loop_running.store(true, std::memory_order_release); + return app; +#else + // Linux: spawn a dedicated Qt pthread so QApplication runs on its own thread. // Initialize the ready semaphore. sem_init(&g_qt_ready_sem, 0, 0); // Start the dedicated Qt thread. @@ -472,6 +492,7 @@ extern "C" qt_application_t qt_application_create(int argc, char** argv) { sem_wait(&g_qt_ready_sem); sem_destroy(&g_qt_ready_sem); return QCoreApplication::instance(); +#endif } extern "C" int qt_application_exec(qt_application_t app) { @@ -495,23 +516,42 @@ extern "C" int qt_application_is_running(void) { } extern "C" void qt_application_quit(qt_application_t app) { - // QCoreApplication::quit() posts a QuitEvent — thread-safe. (void)app; +#ifdef __APPLE__ + // On macOS, Qt runs via processEvents() — no exec() event loop. + // Just clear the running flag so the Scheme polling loop exits. + g_event_loop_running.store(false, std::memory_order_release); + QCoreApplication::processEvents(); // drain any pending events +#else + // QCoreApplication::quit() posts a QuitEvent — thread-safe. QCoreApplication::quit(); +#endif } extern "C" void qt_application_process_events(qt_application_t app) { +#ifdef __APPLE__ + // On macOS, we ARE the Qt main thread — call directly. + (void)app; + QCoreApplication::processEvents(QEventLoop::AllEvents, 50); +#else // Must run on Qt thread — use dispatch. QT_VOID((void)app; QCoreApplication::processEvents()); +#endif } extern "C" void qt_application_destroy(qt_application_t app) { +#ifdef __APPLE__ + // On macOS, QApplication was created on the calling thread (no pthread). + // Delete it here directly. + delete static_cast<QApplication*>(app); +#else // Qt thread owns the QApplication and deletes it in qt_thread_main. // By the time we are called, qt_application_is_running() has returned false // (the Scheme polling loop exited), so the Qt thread is finishing cleanup. // pthread_join waits for it to fully exit and frees thread resources. (void)app; pthread_join(g_qt_thread, nullptr); +#endif } // Expose is_qt_main_thread() to C code (for use by Gerbil trampolines in libqt.ss).