// // Copyright 2026 Aarav Ravindra Kharade // import CSystem import Foundation // MARK: - GTK3 C Bindings via dlopen fileprivate let rtlDefault = UnsafeMutableRawPointer(bitPattern: -2) // RTLD_DEFAULT fileprivate var gtkHandle: UnsafeMutableRawPointer? = nil fileprivate var gdkHandle: UnsafeMutableRawPointer? = nil fileprivate func loadGtk() { if gtkHandle != nil { return } // Load dependency chain in order — inside initramfs, everything lives under /lib/ let depLibs = [ "/lib/libglib-2.0.so", "/lib/libgobject-2.0.so", "/lib/libgio-2.0.so", "/lib/libgmodule-2.0.so", "/lib/libcairo.so", "/lib/libcairo-gobject.so", "/lib/libpango-1.0.so", "/lib/libpangocairo-1.0.so", "/lib/libpangoft2-1.0.so", "/lib/libgdk_pixbuf-2.0.so", "/lib/libepoxy.so", "/lib/libgdk-3.so", ] for lib in depLibs { let h = dlopen(lib, RTLD_NOW | RTLD_GLOBAL) if h == nil, let err = dlerror() { print("[GtkBackend] warning: \(lib): \(String(cString: err))") } } // Now load GTK itself let path = "/lib/libgtk-3.so" gtkHandle = dlopen(path, RTLD_NOW | RTLD_GLOBAL) if gtkHandle == nil { if let err = dlerror() { print("[GtkBackend] FATAL: \(path): \(String(cString: err))") } else { print("[GtkBackend] FATAL: failed to load \(path)") } } else { print("[GtkBackend] GTK3 loaded from \(path)") } } fileprivate func getSym(_ name: String) -> T { loadGtk() let sym = dlsym(gtkHandle, name) if sym == nil { fatalError("[GtkBackend] symbol not found: \(name)") } return unsafeBitCast(sym, to: T.self) } // Function types fileprivate typealias GtkInitType = @convention(c) (UnsafeMutablePointer?, UnsafeMutablePointer>?>?) -> Void fileprivate typealias GtkWindowNewType = @convention(c) (Int32) -> UnsafeMutableRawPointer fileprivate typealias GtkWindowSetTitleType = @convention(c) (UnsafeMutableRawPointer, UnsafePointer) -> Void fileprivate typealias GtkWindowSetDefaultSizeType = @convention(c) (UnsafeMutableRawPointer, Int32, Int32) -> Void fileprivate typealias GtkContainerAddType = @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer) -> Void fileprivate typealias GtkBoxNewType = @convention(c) (Int32, Int32) -> UnsafeMutableRawPointer fileprivate typealias GtkLabelNewType = @convention(c) (UnsafePointer) -> UnsafeMutableRawPointer fileprivate typealias GtkLabelSetTextType = @convention(c) (UnsafeMutableRawPointer, UnsafePointer) -> Void fileprivate typealias GtkButtonNewWithLabelType = @convention(c) (UnsafePointer) -> UnsafeMutableRawPointer fileprivate typealias GtkButtonSetLabelType = @convention(c) (UnsafeMutableRawPointer, UnsafePointer) -> Void fileprivate typealias GtkWidgetShowAllType = @convention(c) (UnsafeMutableRawPointer) -> Void fileprivate typealias GtkMainType = @convention(c) () -> Void fileprivate typealias GtkMainQuitType = @convention(c) () -> Void fileprivate typealias GSignalConnectDataType = @convention(c) (UnsafeMutableRawPointer, UnsafePointer, UnsafeMutableRawPointer, UnsafeMutableRawPointer?, UnsafeMutableRawPointer?, Int32) -> Int fileprivate typealias GtkWidgetOverrideBackgroundColorType = @convention(c) (UnsafeMutableRawPointer, Int32, UnsafeRawPointer?) -> Void fileprivate typealias GtkWidgetSetHAlignType = @convention(c) (UnsafeMutableRawPointer, Int32) -> Void fileprivate typealias GtkWidgetSetVAlignType = @convention(c) (UnsafeMutableRawPointer, Int32) -> Void fileprivate typealias GtkBoxPackStartType = @convention(c) (UnsafeMutableRawPointer, UnsafeMutableRawPointer, Int32, Int32, UInt32) -> Void fileprivate typealias GtkWidgetSetMarginTopType = @convention(c) (UnsafeMutableRawPointer, Int32) -> Void fileprivate typealias GtkWidgetSetMarginBottomType = @convention(c) (UnsafeMutableRawPointer, Int32) -> Void fileprivate let _gtk_init: GtkInitType = getSym("gtk_init") fileprivate let _gtk_window_new: GtkWindowNewType = getSym("gtk_window_new") fileprivate let _gtk_window_set_title: GtkWindowSetTitleType = getSym("gtk_window_set_title") fileprivate let _gtk_window_set_default_size: GtkWindowSetDefaultSizeType = getSym("gtk_window_set_default_size") fileprivate let _gtk_container_add: GtkContainerAddType = getSym("gtk_container_add") fileprivate let _gtk_box_new: GtkBoxNewType = getSym("gtk_box_new") fileprivate let _gtk_label_new: GtkLabelNewType = getSym("gtk_label_new") fileprivate let _gtk_label_set_text: GtkLabelSetTextType = getSym("gtk_label_set_text") fileprivate let _gtk_button_new_with_label: GtkButtonNewWithLabelType = getSym("gtk_button_new_with_label") fileprivate let _gtk_button_set_label: GtkButtonSetLabelType = getSym("gtk_button_set_label") fileprivate let _gtk_widget_show_all: GtkWidgetShowAllType = getSym("gtk_widget_show_all") fileprivate let _gtk_main: GtkMainType = getSym("gtk_main") fileprivate let _gtk_main_quit: GtkMainQuitType = getSym("gtk_main_quit") fileprivate let _g_signal_connect_data: GSignalConnectDataType = getSym("g_signal_connect_data") fileprivate let _gtk_box_pack_start: GtkBoxPackStartType = getSym("gtk_box_pack_start") fileprivate let _gtk_widget_set_margin_top: GtkWidgetSetMarginTopType = getSym("gtk_widget_set_margin_top") fileprivate let _gtk_widget_set_margin_bottom: GtkWidgetSetMarginBottomType = getSym("gtk_widget_set_margin_bottom") // MARK: - SwiftCrossUI Backend public class GtkBackend: BackendFeatures.BaseStubs { public struct Widget { var node: UnsafeMutableRawPointer? } public struct Window { var node: UnsafeMutableRawPointer? } public required init() {} public var deviceClass: DeviceClass { .desktop } public static func earlySetup() { setenv("GDK_BACKEND", "wayland", 1) setenv("WAYLAND_DISPLAY", "wayland-0", 1) setenv("XDG_RUNTIME_DIR", "/run", 1) loadGtk() } public func runMainLoop(_ callback: @escaping @MainActor () -> Void) { _gtk_init(nil, nil) callback() _gtk_main() } public nonisolated func runInMainThread(action: @escaping @MainActor () -> Void) { Task { @MainActor in action() } } public func computeRootEnvironment(defaultEnvironment: EnvironmentValues) -> EnvironmentValues { return defaultEnvironment } public func setRootEnvironmentChangeHandler(to action: @escaping @Sendable @MainActor () -> Void) {} // - CoreWindowing - public func createWindow(withDefaultSize defaultSize: SIMD2?, id: String) -> Window { let win = _gtk_window_new(0) // GTK_WINDOW_TOPLEVEL let w = Int32(defaultSize?.x ?? 1024) let h = Int32(defaultSize?.y ?? 768) _gtk_window_set_default_size(win, w, h) return Window(node: win) } public func updateWindow(_ window: Window, title: String) { if let win = window.node { title.withCString { cstr in _gtk_window_set_title(win, cstr) } } } public func updateWindow(_ window: Window, resizability: WindowResizability) {} public func setChild(ofWindow window: Window, to child: Widget) { if let win = window.node, let w = child.node { _gtk_container_add(win, w) } } public func showWindow(_ window: Window) { if let win = window.node { _gtk_widget_show_all(win) } } // - GenericContainers - public func createContainer() -> Widget { // We just use a vertical box (GTK_ORIENTATION_VERTICAL = 1) let box = _gtk_box_new(1, 0) return Widget(node: box) } public func addChild(_ child: Widget, to container: Widget) { if let box = container.node, let w = child.node { _gtk_container_add(box, w) } } public func setChildren(_ children: [Widget], of container: Widget) {} public func removeChild(_ child: Widget, from container: Widget) {} public func removeAllChildren(of container: Widget) {} public func insert(_ child: Widget, into container: Widget, at index: Int) {} public func swap(childAt firstIndex: Int, withChildAt secondIndex: Int, in container: Widget) {} public func setPosition(ofChildAt index: Int, in container: Widget, to position: SIMD2) {} public func remove(childAt index: Int, from container: Widget) {} // - TextViews - public func createTextView() -> Widget { let label = _gtk_label_new("") return Widget(node: label) } public func updateTextView(_ textView: Widget, content: String, environment: EnvironmentValues) { if let label = textView.node { content.withCString { cstr in _gtk_label_set_text(label, cstr) } } } public func size(of text: String, whenDisplayedIn widget: Widget, proposedWidth: Int?, proposedHeight: Int?, environment: EnvironmentValues) -> SIMD2 { return SIMD2(text.count * 8, 16) } // - Buttons - public func createSimpleButton() -> Widget { let btn = _gtk_button_new_with_label("") return Widget(node: btn) } public func updateSimpleButton(_ button: Widget, label: String, environment: EnvironmentValues, action: @escaping () -> Void) { if let btn = button.node { label.withCString { cstr in _gtk_button_set_label(btn, cstr) } } } public func createButton(wrapping: Widget) -> Widget { let btn = _gtk_button_new_with_label("") return Widget(node: btn) } public func updateButton(_ button: Widget, environment: EnvironmentValues, action: @escaping () -> Void) {} public func buttonPadding(in environment: EnvironmentValues) -> SIMD2 { return SIMD2(8, 8) } public func defaultButtonStyle() -> ButtonStyle { return .bordered } public var scrollBarWidth: Int { return 10 } public var requiresImageUpdateOnScaleFactorChange: Bool { return false } public var requiresToggleSwitchSpacer: Bool { return false } public var supportedPickerStyles: [BackendPickerStyle] { return [] } // Windowing extra methods public func show(window: Window) {} public func setTitle(ofWindow window: Window, to title: String) {} public func updateWindow(_ window: Window, environment: EnvironmentValues) {} public func size(ofWindow window: Window) -> SIMD2 { return SIMD2(1024, 768) } public func setSize(ofWindow window: Window, to newSize: SIMD2) {} public func setSizeLimits(ofWindow window: Window, minimum minimumSize: SIMD2, maximum maximumSize: SIMD2?) {} public func isWindowProgrammaticallyResizable(_ window: Window) -> Bool { return false } public func setResizeHandler(ofWindow window: Window, to action: @escaping (SIMD2) -> Void) {} public func activate(window: Window) {} public func computeWindowEnvironment(window: Window, rootEnvironment: EnvironmentValues) -> EnvironmentValues { return rootEnvironment } public func setWindowEnvironmentChangeHandler(of window: Window, to action: @escaping @Sendable @MainActor () -> Void) {} public var defaultPaddingAmount: Int { return 10 } public func naturalSize(of widget: Widget) -> SIMD2 { return SIMD2(0, 0) } public func setSize(of widget: Widget, to size: SIMD2) {} public func show(widget: Widget) {} public var supportsMultipleWindows: Bool { return false } public var canOverrideWindowColorScheme: Bool { return false } public var restoresWindowFrames: Bool { return false } }