Skip to content

call embedder C++ API directly #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,4 @@
import FlutterMacOS
import Foundation


func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
}
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {}
5 changes: 4 additions & 1 deletion Examples/counter/swift/runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ enum Counter {
title: "Counter",
appId: "com.example.counter"
)
let window = FlutterWindow(properties: viewProperties, project: dartProject)
let window = FlutterWindow(
properties: viewProperties,
project: dartProject
)
guard let window else {
exit(2)
}
Expand Down
22 changes: 17 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var targetPluginUsages = [Target.PluginUsage]()
var platformCxxSettings: [CXXSetting] = []
var platformSwiftSettings: [SwiftSetting] = [.swiftLanguageMode(.v5)]

func tryGuessSwiftLibRoot() -> String {
func tryGuessSwiftRoot() -> String {
let task = Process()
task.executableURL = URL(fileURLWithPath: "/bin/sh")
task.arguments = ["-c", "which swift"]
Expand All @@ -22,13 +22,14 @@ func tryGuessSwiftLibRoot() -> String {
try task.run()
let outputData = (task.standardOutput as! Pipe).fileHandleForReading.readDataToEndOfFile()
let path = URL(fileURLWithPath: String(decoding: outputData, as: UTF8.self))
return path.deletingLastPathComponent().path + "/../lib/swift"
return path.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
.path
} catch {
return "/usr/lib/swift"
return ""
}
}

let SwiftLibRoot = tryGuessSwiftLibRoot()
let SwiftRoot = tryGuessSwiftRoot()
var FlutterPlatform: String
var FlutterUnsafeLinkerFlags: [String] = []

Expand Down Expand Up @@ -235,10 +236,19 @@ packageDependencies += [

let FlutterELinuxBackend = FlutterELinuxBackendType.defaultBackend

let CxxIncludeDirs: [String] = [
"\(SwiftRoot)/usr/include",
"\(SwiftRoot)/usr/lib/swift",
"/usr/include/drm",
]

let CxxIncludeFlags = CxxIncludeDirs.flatMap { ["-I", $0] }

platformSwiftSettings += [
.define("DISPLAY_BACKEND_TYPE_\(FlutterELinuxBackend.displayBackendType)"),
.define("FLUTTER_TARGET_BACKEND_\(FlutterELinuxBackend.flutterTargetBackend)"),
.interoperabilityMode(.Cxx),
.unsafeFlags(CxxIncludeFlags),
]

targets += [
Expand Down Expand Up @@ -404,7 +414,7 @@ targets += [
.headerSearchPath("flutter-embedded-linux/src/third_party/rapidjson/include"),
// FIXME: .cxxLanguageStandard breaks Foundation compile
// FIXME: include path for swift/bridging.h
.unsafeFlags(["-pthread", "-I", SwiftLibRoot, "-I", "/usr/include/drm", "-std=c++17"]),
.unsafeFlags(["-pthread", "-std=c++17"] + CxxIncludeFlags),
],
linkerSettings: [
// .unsafeFlags(["-pthread"]),
Expand Down Expand Up @@ -446,6 +456,8 @@ platformCxxSettings += [
.headerSearchPath(
"../CxxFlutterSwift/flutter-embedded-linux/src/flutter/shell/platform/common/client_wrapper/include"
),
.headerSearchPath("../CxxFlutterSwift/flutter-embedded-linux/src/third_party/rapidjson/include"),
.unsafeFlags(CxxIncludeFlags),
]

#else
Expand Down
11 changes: 9 additions & 2 deletions Sources/CxxFlutterSwift/include/CxxFlutterSwift.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2023-2024 PADL Software Pty Ltd
// Copyright (c) 2023-2025 PADL Software Pty Ltd
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand All @@ -21,11 +21,18 @@

#include <flutter_messenger.h>
#include <flutter_elinux.h>
#include <flutter_elinux_engine.h>
#include <flutter_elinux_state.h>
#include <flutter_elinux_view.h>
#include <flutter_plugin_registrar.h>
// #include <flutter_elinux_state.h>
#include <flutter_platform_views.h>

#ifdef __cplusplus
#include <vector>
#include <string>

using CxxVectorOfString = std::vector<std::string, std::allocator<std::string>>;

extern "C" {
#endif

Expand Down
3 changes: 3 additions & 0 deletions Sources/CxxFlutterSwift/include/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module CxxFlutterSwift {
header "CxxFlutterSwift.h"
}
19 changes: 18 additions & 1 deletion Sources/FlutterSwift/Base/StringHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
//
//===----------------------------------------------------------------------===//

#if os(Linux)
#if os(Linux) && canImport(Glibc)

@_implementationOnly
import CxxFlutterSwift
import CxxStdlib

/// Compute the prefix sum of `seq`.
func scan<
S: Sequence, U
Expand Down Expand Up @@ -60,4 +65,16 @@ extension String {
return u32.withUnsafeBufferPointer { body($0.baseAddress!) }
}
}

extension Array where Element == String {
var cxxVector: CxxVectorOfString {
var tmp = CxxVectorOfString()

for element in self {
tmp.push_back(std.string(element))
}

return tmp
}
}
#endif
72 changes: 52 additions & 20 deletions Sources/FlutterSwift/Client/FlutterEngine.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (c) 2023-2024 PADL Software Pty Ltd
// Copyright (c) 2023-2025 PADL Software Pty Ltd
//
// Licensed under the Apache License, Version 2.0 (the License);
// you may not use this file except in compliance with the License.
Expand All @@ -17,17 +17,18 @@
#if os(Linux) && canImport(Glibc)
@_implementationOnly
import CxxFlutterSwift
import CxxStdlib

public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
var engine: FlutterDesktopEngineRef! // strong or weak ref
var pluginPublications = [String: Any]()
let project: DartProject
weak var viewController: FlutterViewController?
private var engine: flutter.FlutterELinuxEngine! // strong or weak ref
private var _binaryMessenger: FlutterDesktopMessenger!
private var ownsEngine = true
private var hasBeenRun = false

public init?(project: DartProject) {
public init?(project: DartProject, switches: [String: Any] = [:]) {
var properties = FlutterDesktopEngineProperties()

debugPrint("Initializing Flutter engine with \(project)")
Expand All @@ -48,7 +49,9 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
)
cStrings.withUnsafeMutableBufferPointer { pointer in
properties.dart_entrypoint_argv = pointer.baseAddress
self.engine = FlutterDesktopEngineCreate(&properties)
let engine = FlutterDesktopEngineCreate(&properties)
self.engine = unsafeBitCast(engine, to: flutter.FlutterELinuxEngine.self)
setSwitches(switches.map { key, value in "--\(key)=\(String(describing: value))" })
self._binaryMessenger = FlutterDesktopMessenger(engine: self.engine)
}
}
Expand All @@ -61,6 +64,10 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
shutDown()
}

private var _handle: FlutterDesktopEngineRef {
unsafeBitCast(engine, to: FlutterDesktopEngineRef.self)
}

// note we can't use public private(set) because we need the type to be FlutterDesktopMessenger!
// in order for callbacks to work (otherwise self must be first initialized). But we want to
// present a non-optional type to callers.
Expand All @@ -69,39 +76,31 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
}

public func run(entryPoint: String? = nil) -> Bool {
if hasBeenRun {
debugPrint("Cannot run an engine more than once.")
return false
}
let runSucceeded = FlutterDesktopEngineRun(engine, entryPoint)
if !runSucceeded {
debugPrint("Failed to start engine.")
}
hasBeenRun = true
return runSucceeded
guard !hasBeenRun else { return false }
hasBeenRun = engine.RunWithEntrypoint(entryPoint)
return hasBeenRun
}

public func shutDown() {
pluginPublications.removeAll()
if let engine, ownsEngine {
FlutterDesktopEngineDestroy(engine)
if engine != nil, ownsEngine {
FlutterDesktopEngineDestroy(_handle)
}
engine = nil
}

public func processMessages() -> UInt64 {
precondition(engine != nil)
return FlutterDesktopEngineProcessMessages(engine)
return FlutterDesktopEngineProcessMessages(_handle)
}

public func reloadSystemFonts() {
precondition(engine != nil)
FlutterDesktopEngineReloadSystemFonts(engine)
engine.ReloadSystemFonts()
}

func relinquishEngine() -> FlutterDesktopEngineRef {
ownsEngine = false
return engine
return _handle
}

public func registrar(for pluginKey: String) -> FlutterPluginRegistrar? {
Expand All @@ -116,5 +115,38 @@ public final class FlutterEngine: FlutterPluginRegistry, @unchecked Sendable {
public func valuePublished(by pluginKey: String) -> Any? {
pluginPublications[pluginKey]
}

public var isRunning: Bool {
engine.running()
}

func onVsync(lastFrameTimeNS: UInt64, vsyncIntervalTimeNS: UInt64) {
engine.OnVsync(lastFrameTimeNS, vsyncIntervalTimeNS)
}

public var isImpellerEnabled: Bool {
engine.IsImpellerEnabled()
}

public func setSystemSettings(textScalingFactor: Float, enableHighContrast: Bool) {
engine.SetSystemSettings(textScalingFactor, enableHighContrast)
}

public func setView(_ view: FlutterView) {
engine.SetView(view.view)
}

public func setSwitches(_ switches: [String]) {
engine.SetSwitches(switches.cxxVector)
}

func getRegistrar(pluginName: String) -> FlutterDesktopPluginRegistrarRef? {
FlutterDesktopEngineGetPluginRegistrar(_handle, pluginName)
}

var textureRegistrar: flutter.FlutterELinuxTextureRegistrar! {
engine.texture_registrar()
}
}

#endif
24 changes: 10 additions & 14 deletions Sources/FlutterSwift/Client/FlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,22 +116,25 @@ public final class FlutterDesktopPluginRegistrar: FlutterPluginRegistrar {
) {
self.engine = engine
pluginKey = pluginName
registrar = FlutterDesktopEngineGetPluginRegistrar(engine.engine, pluginName)
FlutterDesktopPluginRegistrarSetDestructionHandlerBlock(registrar!) { _ in
self.registrar = nil
registrar = engine.getRegistrar(pluginName: pluginName)
// FIXME: use std::function
FlutterDesktopPluginRegistrarSetDestructionHandlerBlock(registrar!) { [self] _ in
for (channel, detachFromEngine) in detachFromEngineCallbacks {
try? channel.removeMessageHandler()
detachFromEngine(self)
}
registrar = nil
}
}

public var binaryMessenger: FlutterBinaryMessenger? {
guard let registrar else { return nil }
return FlutterDesktopMessenger(
messenger: FlutterDesktopPluginRegistrarGetMessenger(registrar)
)
return FlutterDesktopMessenger(messenger: registrar.pointee.engine.messenger())
}

public var view: FlutterView? {
guard let registrar else { return nil }
let view = FlutterDesktopPluginRegistrarGetView(registrar)!
let view = registrar.pointee.engine.view()!
return FlutterView(view)
}

Expand Down Expand Up @@ -161,13 +164,6 @@ public final class FlutterDesktopPluginRegistrar: FlutterPluginRegistrar {
}
}

deinit {
for (channel, detachFromEngine) in detachFromEngineCallbacks {
try? channel.removeMessageHandler()
detachFromEngine(self)
}
}

public func lookupKey(for asset: String) -> String? {
guard let bundle = Bundle(path: engine.project.assetsPath) else {
return nil
Expand Down
21 changes: 15 additions & 6 deletions Sources/FlutterSwift/Client/FlutterTexture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,23 @@ public enum FlutterTexture {
}

public struct FlutterDesktopTextureRegistrar {
private let registrar: FlutterDesktopTextureRegistrarRef
private let registrar: flutter.FlutterELinuxTextureRegistrar

private var _handle: FlutterDesktopTextureRegistrarRef {
unsafeBitCast(registrar, to: FlutterDesktopTextureRegistrarRef.self)
}

public init(engine: FlutterEngine) {
registrar = FlutterDesktopEngineGetTextureRegistrar(engine.engine)
registrar = engine.textureRegistrar
}

init?(plugin: FlutterDesktopPluginRegistrar) {
guard let registrar = plugin.registrar else { return nil }
self.registrar = FlutterDesktopRegistrarGetTextureRegistrar(registrar)
let textureRegistrarHandle = FlutterDesktopRegistrarGetTextureRegistrar(registrar)
self.registrar = unsafeBitCast(
textureRegistrarHandle,
to: flutter.FlutterELinuxTextureRegistrar.self
)
}

public func registerExternalTexture(_ texture: FlutterTexture) -> Int64 {
Expand All @@ -166,7 +174,7 @@ public struct FlutterDesktopTextureRegistrar {
user_data: Unmanaged.passUnretained(config).toOpaque()
)
}
return FlutterDesktopTextureRegistrarRegisterExternalTexture(registrar, &textureInfo)
return registrar.RegisterTexture(&textureInfo)
}

public func unregisterExternalTexture(texture: FlutterTexture, id textureID: Int64) {
Expand All @@ -177,16 +185,17 @@ public struct FlutterDesktopTextureRegistrar {
case let .eglImageTexture(config): texturePtr = Unmanaged.passUnretained(config).toOpaque()
}

// FIXME: use std::function
FlutterDesktopTextureRegistrarUnregisterExternalTexture(
registrar,
_handle,
textureID,
_releaseAnyObject,
texturePtr
)
}

public func markExternalTextureFrameAvailable(textureID: Int64) {
FlutterDesktopTextureRegistrarMarkExternalTextureFrameAvailable(registrar, textureID)
registrar.MarkTextureFrameAvailable(textureID)
}
}

Expand Down
Loading
Loading