Code review findings · 27 July 2026
Give ARDI's Macintosh Toolbox reimplementation a native AppKit front end, so classic Mac applications run as first-class windows on Gershwin. This is a new component of roughly 1,000–2,000 lines, not a port of the 8,453-line 1993 NeXTSTEP front end. That old code is worth reading for its hard-won Mac semantics and worth almost nothing as source.
One question in this plan is genuinely unanswered, and it is not a detail: no emulator of any kind has ever used GNUstep AppKit for framebuffer display. Previous, Basilisk II and SheepShaver all use SDL, Cocoa, Qt or GTK. The blit path is unproven, GNUstep's backend has no shared-memory X extension anywhere in it, and the only published drawing benchmark dates from 2015. Spike that before writing anything else — see step 0.
Fork: github.com/pkgdemon/executor (Cliff Matthews' 2008 release, MIT, containing the NeXTSTEP front end). Build target is autc04's modern tree — C++17, CMake, and seventeen years of divergence. The two are read together: one for design, one for the contract.
Five agents read both trees. Four findings changed the shape of the work; two of them reversed assumptions this project was carrying.
The host-integration contract is far smaller than the 1993 code suggests, because that code carried an entire application around with it — registration nags, serial numbers, a splash screen, ARDI's phone number in five separate string constants.
| Front end | Lines | Note |
|---|---|---|
headless | 31 | Proves the true minimum surface. |
sdl2 | 472 | — |
qt | 599 | The template. The only high-level-toolkit front end, and the closest structural analogue to AppKit. |
wayland | 690 | — |
x | 1,495 | Source of the reusable keycode table. |
old nextstep | 8,453 | Design reference only. |
NSWindows are not possibleThe appealing idea — make each Mac window a real AppKit window and let Gershwin's window manager handle
them natively — does not survive contact with the code. The guest renders every Mac window into one flat
framebuffer, and Executor's Window Manager knows nothing about host windows. A grep for rootless
across the entire 2008 tree returns zero hits, and MacViewClass.m:39-45 apologises in its own comments
for being a hard singleton.
What is available is better than nothing and worse than the fantasy — see §5.
The modern tree has first-class rootless support that the 1993 code never had:
Framebuffer::rootless (vdriver.h:55), setRootlessRegion(RgnHandle)
(:131), isRootless() (:149), plus src/wind/windRootless.cpp.
Qt turns it on at qt.cpp:213 and shapes its window from the region at :244-249. Seamless
windowing is not research. It is a feature you switch on.
Every file in the old front end is dual-compiled #ifdef OPENSTEP. The NEXTSTEP branch is finished,
shipped code. The OpenStep branch is an abandoned port: tracking rects dead, keyboard translation gutted, dead keys
dropped, and at MacViewClass.m:2606 a declaration taking NSString by value, which
cannot compile. Read the NEXTSTEP branch for design; read the OpenStep branch as a list of what breaks.
Subclass one C++ class. There are no required free functions and no global symbols to define beyond a single typedef.
| Member | Called on | Constraint |
|---|---|---|
runEventLoop() | main thread | Blocks for the whole session. In AppKit this is [NSApp run] — roughly three lines. |
endEventLoop() | emulator thread | Must be async and thread-safe. [NSApp stop:] plus a posted dummy event, or it won't take effect until the next event arrives. |
setMode(w,h,bpp,gray) | emulator thread | Allocates the framebuffer. May block on the main thread; must not require the run loop to be spinning already. |
requestUpdate() | emulator thread | Called with the driver mutex held. Must never block. See trap 01. |
ctor(IEventListener*, int& argc, char**) | main thread | Runs before the emulator thread exists. |
default_vdriver.h | — | Three lines: using DefaultVDriver = GNUstepVideoDriver; Included by main.cpp, which is compiled as plain C++ — so this header and its includes must be Objective-C-free. |
Everything else has a working default. Cursor, title, scrap, beep, palette and rootless region are all optional — and Qt, the reference implementation, overrides only the two cursor methods.
// IEventListener — available as the protected member callbacks_ mouseButtonEvent(bool down, int h, int v); mouseMoved(int h, int v); keyboardEvent(bool down, unsigned char mkvkey); // Mac virtual key code suspendEvent(); // focus lost resumeEvent(bool updateClipboard); // focus gained requestQuit();
All are safe to call from the GUI thread. They marshal internally: the event sink queues a
std::function and fires a synthetic 68k interrupt, which the emulator thread services. The front end
never touches Toolbox state directly.
The toolkit owns the main thread; the emulator runs on a worker. That is exactly the AppKit model, and the impedance match is the single best thing about this port.
Two buffers, one image rep aliasing a buffer you own, no copy. The 1993 code invented this and the modern base class does the hard part for you.
A new directory, src/config/front-ends/gnustep/, mirroring how qt/ is
organised one for one.
| File | LOC | Purpose |
|---|---|---|
gnustep.mm | 380–450 | The driver. ExecutorView : NSView, a borderless ExecutorWindow : NSWindow, the app delegate, and every VideoDriver override. |
gnustepkeycodes.mm | 120–160 | Fallback unichar→MKV map, plus modifier decoding for -flagsChanged:. AppKit does not deliver modifiers as key events; Qt sidestepped this and you cannot. |
gnustep_ui.mm | 300–600 | The menu and windows, built in code. There is no nib — see below. Qt needs none of this, so it is pure delta against the reference implementation. |
gnustep_mainthread.mm | 50–70 | runOnMainThread(fn, wait) over -performSelectorOnMainThread:. The one file with no Qt counterpart — Qt got this free from its framework. |
available_geometry.mm | 40–55 | Screen rects from [NSScreen screens], bottom-left to top-left. Simpler than Qt's, which carries X11 multi-monitor workarounds. |
gnustep.h | 40–55 | Pure C++, no Objective-C. Objective-C types hidden behind #ifdef __OBJC__ @class … #else typedef struct objc_object … |
CMakeLists.txt | 35–45 | Locate GNUstep, apply -x objective-c++ to the .mm files, link. |
default_vdriver.h | 3 | The typedef. |
../x/x_keycodes.cpp | 0 | Reused verbatim. Listed as a source, exactly as Qt's CMakeLists already does. |
Every interface file in the 1993 tree is NeXT typedstream version 4 — the
pre-keyed-archiving format. Gorm cannot open them. GNUstep's model loader dispatches by signature to Gorm, keyed
.nib, .xib and .gmodel handlers; there is no typedstream path anywhere, so
loading them at runtime fails too. The conversion tools are dead ends: nib2gmodel was last touched in
2008 and requires Apple or NeXT libraries; nib2xib is maintained but runs only on OPENSTEP 4.2.
It costs almost nothing, because there is almost nothing in them. The live UI is one menu, one window with
the framebuffer view, one info panel, and four implemented actions. Of seventeen actions the nibs declare, thirteen
are dead. MacViewClass declares no instance variables at all, so all seven outlets its
metadata references are stale. One declared class has no source file anywhere in the tree. The rest is ARDI-era
registration and serial-number UI whose backing code is gone — you would be converting dead weight in order to
delete it.
Build the UI programmatically. That is the 300–600 line file above, and it also lets you delete the entire outlet layer — roughly 25 globals the old code copied out of the nib so plain C could reach them.
Three edits to existing files: add gnustep to the FRONT_ENDS cache list, add one
add_subdirectory, and ship the new CMakeLists.txt. Everything downstream — the
per-front-end executable, the target naming, the include path — is picked up automatically.
Each of these produces a failure that looks like something else — heap corruption, a mystery segfault, a link error about a symbol you never wrote. Getting them right up front costs nothing; getting them wrong costs days.
GNUstep's runtime is libobjc2, and GCC has no -fobjc-runtime= flag at all. gnustep-make's
own configure forces CC=clang. Since the .mm files ride
CMAKE_CXX_COMPILER, that means the entire project must be clang — not just the shim.
enable_language(OBJCXX)CMake's OBJCXX detection prefers clang++, while its CXX detection resolves to g++.
On a box with both, you get clang++ for one .mm and g++ for four hundred .cpp files
— a split C++ ABI across exactly the boundary the front end straddles, since that translation unit
consumes vdriver.h with shared_ptr, function and string in it.
Let .mm ride the CXX compiler and set the language per source file.
-fuse-ld=lldThe v2 Objective-C ABI depends on section-boundary symbols that GNU ld mishandles; the classic symptom is
cannot locate symbol __start___objc_selectors. gnustep-make warns about this itself and admits it
has no accurate test. gold is deprecated upstream, so lld is the answer.
Clang's default Objective-C runtime on Linux is not GNUstep 2.x — a bare
-fobjc-runtime=gnustep means 1.6. Compile a .mm without pinning the version and
clang emits the wrong personality function, after which catching a std::exception segfaults.
It was filed against LLVM and closed as invalid, the answer being "specify the runtime." CMake will cheerfully
compile Objective-C++ with no Objective-C flags whatsoever and hand you crashes that read as heap corruption.
Pin -fobjc-runtime=gnustep-2.2 per source file. Not 2.0 — they are ABI-identical, but 2.2
unlocks compiler fast paths, and it is what gnustep-make defaults to. Requires clang 18 or newer.
Two related corrections to widely-repeated advice: there is no -lobjcxx any more — it
was folded into libobjc.so in 2017, though libobjc2's own INSTALL file still describes it.
And Objective-C++ exception interop is genuinely regression-tested in both directions across libstdc++ and libc++,
so it works — provided the runtime flag is right.
The mechanics below follow the existing pattern in the tree: set the language explicitly per source file rather than enabling it project-wide.
find_program(GNUSTEP_CONFIG gnustep-config)
if(GNUSTEP_CONFIG)
execute_process(COMMAND ${GNUSTEP_CONFIG} --objc-flags OUTPUT_VARIABLE GS_OBJC_FLAGS
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(COMMAND ${GNUSTEP_CONFIG} --gui-libs OUTPUT_VARIABLE GS_GUI_LIBS
OUTPUT_STRIP_TRAILING_WHITESPACE)
separate_arguments(GS_OBJC_FLAGS_LIST UNIX_COMMAND "${GS_OBJC_FLAGS}")
separate_arguments(GS_GUI_LIBS_LIST UNIX_COMMAND "${GS_GUI_LIBS}")
add_library(front-end-gnustep
default_vdriver.h gnustep.h gnustep.mm gnustepkeycodes.mm
gnustep_mainthread.mm available_geometry.mm ../x/x_keycodes.cpp)
set_source_files_properties(gnustep.mm gnustepkeycodes.mm gnustep_mainthread.mm
available_geometry.mm PROPERTIES COMPILE_OPTIONS "-x;objective-c++")
target_compile_options(front-end-gnustep PRIVATE ${GS_OBJC_FLAGS_LIST})
# The version pin is the whole ballgame. See above.
set_property(SOURCE gnustep.mm gnustepkeycodes.mm gnustep_ui.mm
APPEND PROPERTY COMPILE_OPTIONS
${GS_OBJC_FLAGS_LIST}
-fobjc-runtime=gnustep-2.2
-fblocks -fexceptions -fobjc-exceptions -D_NATIVE_OBJC_EXCEPTIONS)
target_include_directories(front-end-gnustep PUBLIC .)
target_link_libraries(front-end-gnustep syn68k romlib ${GS_GUI_LIBS_LIST})
endif()
# Configure the whole project with:
# cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
# -DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld"
--objc-flags carries -MMD -MP, which fight CMake's own depfile handling — filter
those two out and keep the rest. --gui-libs is a superset of the base and objc link flags, so one call
covers the link side. Do not enable ARC; this codebase is manual retain/release throughout, though ARC is per-file
so mixing would be legal if you ever wanted it.
Worth adding a hard failure in CMake if the discovered flags mention -fobjc-runtime= while the C++
compiler is not clang. That single check converts the most likely misconfiguration from a runtime segfault into a
configure-time error message.
You get Mac windows floating over your desktop with no grey box around them. You do not get
NSWindows the window manager can move independently.
The README advertises "emulated windows are part of your desktop," and the subsystem docs state that in rootless mode "windows are not drawn onto the emulator framebuffer" but are instead "delegated to the host compositor." The code flatly contradicts both. The rootless path unions every visible window's structure region, plus the menu bar, into one region and makes one call. Windows are still drawn into the framebuffer; the region only selects which spans get copied and which are left transparent.
This matters beyond pedantry. Anyone scoping this project from the documentation would believe per-window host integration is already half-built. Settle the expectation in writing before any code is committed — and consider correcting those two doc files as a first, trivially reviewable pull request.
The single-framebuffer assumption is load-bearing at four independent levels. Two of them cannot be removed without breaking guest applications.
| Level | What it means |
|---|---|
| Every port shares one bitmap | Each window's GrafPort points at the same screen bitmap. Windows draw at absolute screen coordinates, clipped by a visible region. They have no independent backing surface — which is faithful, because the real Macintosh worked this way too. |
| The framebuffer lives in the guest's address space | It is mapped into the 68k memory map and published as low-memory globals. Guest code writes to screen memory directly, and there is an entire subsystem — a periodic per-strip checksummer — that exists solely to notice when applications bypass QuickDraw and do exactly this. Those writes address screen coordinates. There is no general way to route them into per-window surfaces. |
| Window chrome can be guest code | Frames are drawn by the Window Definition Procedure, which is a resource — and it can come from the application's own resource fork and run as 68k code. An app with a custom WDEF draws arbitrary chrome that no native titlebar can reproduce. |
| The guest manages its own windows | Hit-testing walks Executor's window list in its own Z-order; dragging is implemented by XOR-ing a grey outline into the shared framebuffer and polling to mouse-up. The host window manager has zero involvement, and occlusion is computed by Executor. Hand stacking to the host and its order can disagree with the visible region QuickDraw clips against. |
The historical evidence agrees. ARDI wrote the NeXTSTEP front end with full AppKit available and complete control of their own source, and used one window. Their wishlist file got as far as "give some thought to rootless windows during the code restructuring." What shipped thirty years later is the shape mask.
Five of the window features per-window integration would need are missing or inert: -setStyleMask:
has no implementation in libs-gui at all, so a window's style cannot change after creation;
-setOpaque: is a stub with a FIXME and no backend call behind it; window creation always
uses the shared screen visual, so there is no per-pixel ARGB transparency; shaped windows exist only as private
API used internally by drag views; and -addChildWindow: records children but never makes them follow
the parent. Window levels do map to real EWMH hints, but the always-above hint is never set —
"floating" relies on the window manager inferring it, which KWin and Mutter generally do not.
Leave the rootless flag at its default and Executor draws a normal Mac desktop in a normal window — every rootless path short-circuits cleanly. That drops the region mask and window transparency, which are the shakiest parts of GNUstep's backends and exactly where you least want to be on day three. Turn it on behind a flag once the basics hold.
| Subsystem | GNUstep | Days | Approach |
|---|---|---|---|
| Keyboard | Good on X11 | 4 | GNUstep's X11 backend puts the raw X11 hardware keycode in -[NSEvent keyCode], so x_keycode_to_mac_virt[] is reused verbatim. Ignore -characters entirely — the core runs the guest's own KCHR and would double-translate. |
| Mouse | Good | 1.5 | Six overrides. Set acceptsMouseMovedEvents, override -isFlipped to YES to delete all the Y-flip arithmetic, and fold the right button into the left. |
| Clipboard | Partial | 4 | NSPasteboard maps onto the 1995 code almost line for line. Interop is the risk: it needs the external gpbs daemon and cross-application exchange is documented as effectively plain-text-only. Keep a raw X11 fallback. |
| Sound | Not via AppKit | 6 | See below. Fully deferrable. |
| Printing | Do nothing | 0.5 | Already works and is entirely independent of the front end — the core generates PostScript and pipes it to lpr. Do not port NEXTprint.m. Do not touch MacPrintClass.h, which has no implementation file anywhere and zero references. |
The obvious assumption — Display PostScript is dead, so all of it must be rewritten — is wrong.
GNUstep ships live PS* and DPS* operators as a real C function-pointer dispatch table on
the graphics context, the same path AppKit uses internally. Colour, gstate, matrix, path, text and even the NeXT
compositing extensions are all present, and DPSPrintf is a genuine variadic implementation.
The total Display PostScript surface in the old front end is about ten call sites, and most compile unchanged
— only two need substituting, plus two header renames. The NSView printing callback protocol is
fully implemented too, none of it stubbed. And the pswrap input file that looked like a problem
contains exactly one line: % no longer needed.
The recommendation is still to leave printing alone, because the Linux path already works without any of this. But the cost of touching it later is much lower than it appears.
GNUstep's NSSound plays complete, pre-existing files through loadable sink bundles. There is no
PCM callback and no streaming-buffer API — nothing resembling SDL_AudioCallback. Executor
synthesises audio on the fly from the guest's snd resources at guest-chosen sample rates with hard
latency requirements, and that cannot be expressed through the current interface.
It matters less than it sounds. Executor's sound goes through its own SoundDriver abstraction —
a hunger model where the driver hands the core a buffer and a time window, the core fills it, the driver plays it. The
backend is a swappable class of roughly 170 lines. Worth knowing: modern Executor is silent on every current front
end anyway, since the only real driver in the tree belongs to the legacy SDL1 build, and the 1993 NeXTSTEP one was
never implemented either — both its functions are empty bodies.
The pragmatic default. A working 171-line SDL1 implementation already exists in the tree; moving it to
SDL_OpenAudioDevice is largely mechanical. No new dependency the project doesn't already have.
NEXTSPACE's Frameworks/SoundKit is a PulseAudio-backed GNUstep framework with a NeXT-style API.
SNDPlayStream exposes -playBuffer:size:tag: with write and empty callbacks —
precisely the hunger model, already built. GPL v2+, so it makes the resulting binary GPL.
NSSoundThe preferred endpoint if the in-progress work lands. Would need a new GSSoundSink exposing an
application-supplied buffer source. Nothing to this effect appears in libs-gui's public branches yet.
Because all three sit behind one interface, this is not a decision that has to be made now — and nothing else blocks on it, since the fake driver gives correct timing and silence.
Each of these cost someone a day in 1993, or will cost you one in 2026. They are ordered by when you will hit them.
requestUpdate() is always called with the driver mutex held, and your
draw path takes the same mutex. Marshal with waitUntilDone:NO. Blocking there hangs on the first
frame, every time. Conversely setMode and the cursor methods are called without the mutex and
may block.updateBuffer() emits is
NSAlphaFirstBitmapFormat | NSBitmapFormatThirtyTwoBitLittleEndian. That second constant arrived in
gnustep-gui 0.25. Verify it exists in your tree before writing anything else — if it doesn't, the fallback is
a swizzle loop costing a full-frame pass.[view allocateGState]. The old code does this with the comment "since we will be
repeatedly focused on." Without it, per-frame lockFocus is ruinous and you will misdiagnose it as the
conversion being slow.NSFlagsChanged events at all, so command, shift and option are simply dead, and it zeroes the keycode
for Enter and Delete.keyboardEvent calls for the modifier keys
themselves.YES unconditionally from
-performKeyEquivalent: while a guest application is running, or the host menu swallows ⌘Q,
⌘W and friends before the guest sees them.disableCursorRects once, wholesale. AppKit's cursor rectangles will otherwise fight
the emulator for the pointer continuously.resumeEvent while you're here. Qt hardcodes true, SDL2 hardcodes
false, and both are wrong. The 1995 code computed it correctly from the pasteboard change count, which
is what decides whether the guest needlessly reconverts the clipboard on every focus change.drawRect:.
Port the shape, retype every line.-step pump — run guest code until the host has an event pending, then yield.
Good heuristic; its asm-coroutine implementation is not.OldMacViewClass.m, all 2,907 lines. Superseded, not in the build, and its one unique
idea is dead code behind if (0 && …).blockinterrupts.m — the author #error'd it himself: "succumbed to
bitrot." Contains a disabled attempt to run Mac callbacks by suspending a thread and rewriting its program
counter.%%BeginDocument: comment to work around Word 5.HFS_XFerA 9,194-line utility for moving files on and off HFS volumes, which sounds exactly like what the desktop project
needs. Ignore it. It is a Mac Toolbox application, not a NeXTSTEP one — its only Objective-C is a 14-line
stub. Its 5,470-line HFS engine is wrapped first line to last in #if defined(OUTDATEDCODE), and it is
a superseded fork besides: the maintained copies in mainline are 30–130% larger and, critically,
endian-corrected. The HFS_XFer copies contain zero byte-swap macros against 168 in the mainline B-tree
alone. It only ever ran on big-endian 68k, backed by a 2.88 MB static RAM array. It only ever worked on
floppies. Three live defects were found in passing, including an inverted bounds check guarding a
memcpy.
Two things in it are worth ten minutes each: the copy engine is a compact, correct specification of what "copy a Mac file faithfully" means — create, data fork, resource fork, then restore Finder info and dates last — and the auto-mount hook sketches a design worth stealing at about forty lines.
| # | Step | Days | Done when |
|---|---|---|---|
| 0 | Spike the blit | 2 | An NSView blitting a 640×480 32-bit NSBitmapImageRep at 60 fps against back-cairo on X11, with the format verified correct. This is the only genuinely unknown question in the plan and nobody has answered it before. If it fails and the raw-Xlib escape hatch doesn't pan out, stop here — everything after it would be wasted. |
| 1 | Build skeleton | 1 | front-end-gnustep compiles and links, executor-gnustep runs headless-equivalent and exits cleanly. |
| 2 | Window and framebuffer | 2 | Event loop, setMode, requestUpdate and the draw path work against a solid grey framebuffer. No input yet. Non-rootless. |
| 3 | Mouse | 1.5 | The guest tracks the pointer and clicks land. Cursor shape and visibility follow. |
| 4 | Keyboard | 4 | Typing works in a real application, modifiers included, and ⌘-equivalents reach the guest rather than the host menu. |
| 5 | Clipboard, TEXT only | 1 | Copy and paste between a classic application and a GNUstep one. Parity with the current X11 front end. |
| 5b | App shell | 2 | Menu bar, about panel and window construction, all in code. Qt ships none of this; a native-feeling GNUstep app needs it. |
| — | Milestone one | ~8 | A usable native front end. Sound faked, printing via the existing path, single window. |
| 6 | Rootless | 3 | Mac windows float over the Gershwin desktop with no surrounding grey. |
| 7 | Clipboard flavours | 3 | PICT, TIFF and RTF, with the font-table synthesis. Exceeds every current front end. |
| 8 | Sound | 6 | A SoundDriver subclass against whichever backend won. |
Everything after it is ordinary work with a known shape. Step 0 is the only item that can invalidate the whole
design, and it costs two days to settle. If the cairo path can't sustain the blit, the mechanical escape hatch is
real — -[NSWindow windowRef] is implemented and on X11 hands back a structure exposing the
Display * and drawable, from which XShmPutImage is reachable. But no one has ever done
that either, and it may require an internal header that distributions don't ship. Find out on day one.
This review read Executor's two trees plus libs-gui and libs-back. Four more libraries
were not read, and each could materially change a conclusion here — two of them the most expensive
conclusions. Review these before committing to the architecture.
| Library | What it is | Why it could change this plan |
|---|---|---|
libs-opal |
Core Graphics for GNUstep | Highest potential impact. The top risk in this plan is blit throughput through
NSBitmapImageRep, which round-trips X with no shared memory and no surface caching. If Opal
offers a usable CGBitmapContext or CGImage path to the screen, that could be a
better route than AppKit's — and it would matter most in exactly the place the plan currently calls
unproven. Caveats already known: its indexed colour space file was last touched in 2010, and Opal's backend
was non-functional for years until a libs-back change landed in April 2026. |
libs-quartzcore |
CoreAnimation for GNUstep | Bears on rootless. This plan concludes that per-Mac-window host windows are impossible and that the shaped-mask approach is the ceiling. Layers are a third option neither the Qt nor Wayland front ends consider. If a working layer implementation exists, per-window compositing might be reachable without the core rewrite section 5 describes — still bounded by the same guest-writes-to-screen-memory problem, but worth knowing before the question is closed. |
NSSound in libs-gui |
Sound playback | The verdict here — that it cannot serve Executor's hunger model because it has no PCM callback
— was reached by reading the current implementation. Work is reportedly in progress. If a
streaming sink lands, the AppKit-native path becomes viable and the SDL2 and SoundKit options become
fallbacks rather than the plan. Worth confirming directly with whoever is doing it whether it is a new
GSSoundSink, a fork, or staged outside the public branches. |
libs-corebase |
CoreFoundation for GNUstep | Lowest impact but cheapest to check. Relevant to how much of the front end must be Objective-C at all — a C-level interop layer could let more of the bridge stay in plain C++17 rather than Objective-C++, which shrinks the surface exposed to the runtime and exception-interop traps in section 4. |
The Gershwin desktop sources are the other unread input, and they answer a question none of the above do: what the desktop already provides, so the front end integrates with its window manager and menu bar rather than duplicating them.
Stated plainly, because a plan that hides its unknowns is worse than one that names them. None of the following was verified, and no code in this review was compiled or executed:
gnustep-config --objc-flags on a current install. The flags above are
derived from gnustep-make's own makefiles, not observed.-dev packages install the internal header that gates the raw-Xlib escape
hatch.| Component | Licence | Consequence |
|---|---|---|
| Executor core | MIT | Cliff Matthews open-sourced it in 2008. No ROM, no Apple system software, no redistribution problem. |
| cxmon (debugger) | GPL v2+ | Optional and removable. A stock build is effectively GPL-bound; for a GPL distribution this is moot. |
| New GNUstep front end | your choice | MIT keeps it contributable upstream as a clean component. |
| SoundKit, if used | GPL v2+ | Makes the binary GPL. Fine for the distro; blocks an MIT-clean upstream contribution if the front end hard-depends on it. |
| GNUstep | LGPL / GPL | Library linkage as normal. |
The licensing story is unusually clean for retrocomputing: none of this requires an Apple ROM or a copy of Mac OS, which is the constraint that blocks Basilisk II, Mini vMac and the QEMU m68k path from ever shipping in an image.