I made an open-source project that will take an existing macOS swift project and make it easily compile into a Linux (GTK/ or Qt). It's part of a new quality Swift-first OS I am making called QuillOS.
I'm eager for help and for feedback.
I made an open-source project that will take an existing macOS swift project and make it easily compile into a Linux (GTK/ or Qt). It's part of a new quality Swift-first OS I am making called QuillOS.
I'm eager for help and for feedback.
Okay. This is huge.
thanks, also looking for people who have Swift code (there is very little open source swift code) who can give me example apps to compile against this to find any missing holes. The simpler/smaller the better but any size good.
it's also easy to convert any working ios app and adapt it to macOS with AI and then also cross-compile to Linux with this
who else should i talk to about this?
I will take a look at your project, it looks so big. I am astonished. Unfortunately, my main projects all require CALayer support and that is listed as incomplete today. I will check it out though.
Question, how did you handle all of the action-target concepts in UIKit (which requires the Objective-C concept of selectors)? I haven't had time to look at it, just a curiosity.
OK just added CALayer
we moved selector dispatch from runtime to compile time.
Selector is just an opaque token. On Linux there's no ObjC runtime, so QuillFoundation defines public struct Selector: Hashable { let name: String } β a plain Swift struct, no objc_msgSend anywhere.
A SwiftSyntax source-lowering pass (quill-lower-appkit) rewrites the app's source before compilation. Apple sources stay byte-for-byte unmodified in the repo; at build-prep time the pass strips @objc/@IBAction and rewrites every #selector(Foo.bar(_:)) into the token Selector("bar(_:)"). So the naming side of target-action compiles as-is.
The same pass generates the dispatch table statically. For every class whose methods were referenced by #selector, it emits a conformance to QuillActionDispatching β a generated func quillPerform(_ selector: Selector, with sender: Any?) containing a switch selector.name whose cases call the real methods directly. ObjC's dynamic lookup becomes a compiler-checked switch: if the method vanishes, you get a compile error instead of unrecognized selector.
Controls fire through the protocol. NSControl/UIControl's send-action path is essentially (target as? QuillActionDispatching)?.quillPerform(action, with: self) β same shape as Apple's sendAction(_:to:from:), minus the runtime. UndoManager, and as of today CADisplayLink(target:selector:), dispatch the same way.
caveats: selector-based NotificationCenter.addObserver(_:selector:...) is currently a compile-shim no-op (the durable plan is the same generated-switch treatment), and responds(to:)-style dynamic introspection is harder β but for target-action specifically, static generation covers it because #selector sites name the method at compile time by construction.
I think The solution is generated static reflection: the registry is compiled, not discovered. ObjC answers "what does this object respond to?" at runtime because the compiler threw that information away. Our lowering pass has the information β so it writes it down as Swift
Does usage of Quill require a SwiftSyntax source lowering phase or is that opt-in? Like, if a user would rather just write the code themselves and #if out code?
lowering is opt-in. It's part of the porting pipeline for unmodified upstream apps, not part of the build. If you're writing your own code, nothing you compile ever goes through SwiftSyntax.
There are two ways to consume Quill:
import SwiftUI resolves to a module that re-exports the SwiftUI implementation, same for Foundation-adjacent and the various Apple frameworks) and build with stock swift build. Where you'd hit something Apple-only, you do exactly what you suggested β #if os(Linux) it out or write the portable form yourself. This is how the hand-written apps in the repo work today; there's no source-rewriting phase anywhere in their build graph.@objc/#selector (Linux Swift has no ObjectiveC overlay, so these are compiler-level unavailable, not just missing-API), @Model/SwiftData macros, and a few similar attribute-level idioms. The lowering runs once at vendor time and emits plain Swift source, which then builds with ordinary SwiftPM β it's not a build-tool plugin, so even ported apps are just normal Swift packages after that step.So for the user you're describing, the rule of thumb is: the lowering exists to do for code you're not allowed to edit what #if does for code you are. If you're willing to avoid (or conditionally compile around) @objc, #selector, and the SwiftData macros in your own source, you never need it β and that's a fully supported way to use the project.
Does it work with Canvas-wrapped CoreGraphics and SceneKit views?
All of this sounds very cool.
I am hoping that this part gets solved with GNUstepβs runtime interop in the future.
Maybe your project will allow me not to have to rely on that:
Yes, SceneKit and CoreGraphics
the lowering is automatic, seamless