Skip to content

Commit ba14f33

Browse files
committed
Initial Commit
0 parents  commit ba14f33

File tree

19 files changed

+1935
-0
lines changed

19 files changed

+1935
-0
lines changed

.github/FUNDING.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: finnvoor

.swiftformat

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--swiftversion 6.0
2+
3+
--enable blankLineAfterImports
4+
--enable blankLinesBetweenImports
5+
--enable blockComments
6+
--enable docComments
7+
--enable isEmpty
8+
--enable markTypes
9+
--enable organizeDeclarations
10+
11+
--disable numberFormatting
12+
--disable redundantNilInit
13+
--disable trailingCommas
14+
--disable wrapMultilineStatementBraces
15+
16+
--ifdef no-indent
17+
--funcattributes same-line
18+
--typeattributes same-line
19+
--storedvarattrs same-line
20+
--computedvarattrs same-line
21+
--ranges no-space
22+
--header strip
23+
--selfrequired log,debug,info,notice,warning,trace,error,critical,fault

Config.xcconfig

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MARKETING_VERSION = 0.0.1
2+
CURRENT_PROJECT_VERSION = 1
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
extension FxRect {
2+
var cgRect: CGRect {
3+
CGRect(
4+
x: CGFloat(left),
5+
y: CGFloat(bottom),
6+
width: CGFloat(right - left),
7+
height: CGFloat(top - bottom)
8+
)
9+
}
10+
11+
init(_ rect: CGRect) {
12+
self.init(
13+
left: Int32(rect.minX),
14+
bottom: Int32(rect.minY),
15+
right: Int32(rect.maxX),
16+
top: Int32(rect.maxY)
17+
)
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import SwiftUI
2+
3+
// MARK: - SwiftUIViewGenerator
4+
5+
@objc(SwiftUIViewGenerator) class SwiftUIViewGenerator: TileableEffect {
6+
// MARK: Internal
7+
8+
override var properties: [String: Any] {
9+
[
10+
kFxPropertyKey_MayRemapTime: false,
11+
kFxPropertyKey_VariesWhenParamsAreStatic: true
12+
]
13+
}
14+
15+
override func addParameters() throws {
16+
parameterCreationAPI.addStringParameter(
17+
withName: "Package Path (Drag & Drop)",
18+
parameterID: 1,
19+
defaultValue: "",
20+
parameterFlags: FxParameterFlags(kFxParameterFlag_DEFAULT)
21+
)
22+
23+
parameterCreationAPI.addPushButton(
24+
withName: "Compile",
25+
parameterID: 2,
26+
selector: #selector(SwiftUIViewGenerator.compile),
27+
parameterFlags: FxParameterFlags(kFxParameterFlag_DEFAULT)
28+
)
29+
}
30+
31+
override func pluginInstanceAddedToDocument() {
32+
compile()
33+
}
34+
35+
override func renderDestinationImage(
36+
sourceImages: [CIImage],
37+
pluginState _: Data?,
38+
at _: CMTime
39+
) -> CIImage {
40+
guard let view else { return .clear }
41+
return DispatchQueue.main.sync {
42+
let renderer = ImageRenderer(
43+
content: view
44+
.frame(width: sourceImages[0].extent.width, height: sourceImages[0].extent.height)
45+
)
46+
renderer.proposedSize = .init(sourceImages[0].extent.size)
47+
return CIImage(cgImage: renderer.cgImage!, options: [.applyOrientationProperty: true])
48+
}
49+
}
50+
51+
// MARK: Private
52+
53+
private var view: AnyView?
54+
}
55+
56+
extension SwiftUIViewGenerator {
57+
@objc func compile() {
58+
var packagePath: NSString = ""
59+
parameterRetrievalAPI.getStringParameterValue(&packagePath, fromParameter: 1)
60+
let package = URL(filePath: packagePath as String)
61+
62+
guard !(packagePath as String).isEmpty else { return }
63+
64+
do {
65+
let dylib = try swiftBuild(package: package)
66+
view = try loadView(from: dylib)
67+
} catch {
68+
print(error.localizedDescription)
69+
Task { await showAlert(message: error.localizedDescription) }
70+
}
71+
}
72+
73+
private func swiftBuild(package: URL) throws -> URL {
74+
let dylib = package.appendingPathComponent(".build/debug/lib\(package.lastPathComponent).dylib")
75+
try? FileManager.default.removeItem(at: dylib)
76+
77+
let process = Process()
78+
process.executableURL = URL(filePath: "/usr/bin/swift")
79+
process.arguments = ["build", "--package-path", package.path()]
80+
81+
let standardError = Pipe()
82+
process.standardError = standardError
83+
84+
let semaphore = DispatchSemaphore(value: 0)
85+
var error: Error?
86+
process.terminationHandler = { terminatedProcess in
87+
if terminatedProcess.terminationStatus != 0 {
88+
error = Error.swiftError(
89+
message: (try? standardError.fileHandleForReading.readToEnd())
90+
.map { String(decoding: $0, as: UTF8.self) } ?? "Unknown Error"
91+
)
92+
}
93+
semaphore.signal()
94+
}
95+
try process.run()
96+
semaphore.wait()
97+
if let error { throw error }
98+
return dylib
99+
}
100+
101+
private func loadView(from dylib: URL) throws -> AnyView {
102+
guard let handle = dlopen(dylib.path, RTLD_NOW | RTLD_LOCAL) else {
103+
throw Error.missingDylib(path: dylib)
104+
}
105+
defer { dlclose(handle) }
106+
guard let symbol = dlsym(handle, "createView") else {
107+
throw Error.missingSymbol
108+
}
109+
let pointer = unsafeBitCast(symbol, to: (@convention(c) () -> UnsafeMutableRawPointer).self)()
110+
let view = Unmanaged<AnyObject>.fromOpaque(pointer).takeRetainedValue()
111+
return view as! AnyView
112+
}
113+
114+
private func showAlert(message: String) async {
115+
CFUserNotificationDisplayNotice(
116+
0,
117+
kCFUserNotificationCautionAlertLevel,
118+
nil,
119+
nil,
120+
nil,
121+
"SwiftUIFX" as CFString,
122+
message as CFString,
123+
"Ok" as CFString
124+
)
125+
}
126+
}
127+
128+
// MARK: - Error
129+
130+
enum Error: Swift.Error, LocalizedError {
131+
case swiftError(message: String)
132+
case missingDylib(path: URL)
133+
case missingSymbol
134+
135+
// MARK: Internal
136+
137+
var errorDescription: String? {
138+
switch self {
139+
case let .swiftError(message):
140+
message
141+
case let .missingDylib(path):
142+
"Could not find dylib at \(path.path). Ensure your library product is set to type: .dynamic in your Package.swift file, and that the library name matches the package name."
143+
case .missingSymbol:
144+
"""
145+
Failed to load view from dylib. Make sure you include a createView function that returns a pointer to your view. Example:
146+
147+
@_cdecl("createView") public func createView() -> UnsafeMutableRawPointer {
148+
return Unmanaged.passRetained(
149+
AnyView(MyView()) as AnyObject
150+
).toOpaque()
151+
}
152+
"""
153+
}
154+
}
155+
}

Plugin/Supporting Files/Info.plist

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>CFBundleDevelopmentRegion</key>
6+
<string>$(DEVELOPMENT_LANGUAGE)</string>
7+
<key>CFBundleDisplayName</key>
8+
<string>$(PRODUCT_NAME)</string>
9+
<key>CFBundleExecutable</key>
10+
<string>$(EXECUTABLE_NAME)</string>
11+
<key>CFBundleIdentifier</key>
12+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13+
<key>CFBundleInfoDictionaryVersion</key>
14+
<string>6.0</string>
15+
<key>CFBundleName</key>
16+
<string>$(PRODUCT_NAME)</string>
17+
<key>CFBundlePackageType</key>
18+
<string>XPC!</string>
19+
<key>CFBundleShortVersionString</key>
20+
<string>$(MARKETING_VERSION)</string>
21+
<key>CFBundleVersion</key>
22+
<string>$(CURRENT_PROJECT_VERSION)</string>
23+
<key>NSHumanReadableCopyright</key>
24+
<string></string>
25+
<key>PlugInKit</key>
26+
<dict>
27+
<key>Attributes</key>
28+
<dict>
29+
<key>com.apple.protocol</key>
30+
<string>FxPlug</string>
31+
<key>com.apple.version</key>
32+
<string>1.1</string>
33+
</dict>
34+
<key>PrincipalClass</key>
35+
<string>FxPrincipal</string>
36+
<key>Protocol</key>
37+
<string>PROXPCProtocol</string>
38+
<key>Subsystems</key>
39+
<array>
40+
<string>NSViewService_PKSubsystem</string>
41+
</array>
42+
</dict>
43+
<key>ProPlugDictionaryVersion</key>
44+
<string>1.0</string>
45+
<key>ProPlugDynamicRegistration</key>
46+
<false/>
47+
<key>ProPlugPlugInGroupList</key>
48+
<array>
49+
<dict>
50+
<key>groupName</key>
51+
<string>SwiftUIFX</string>
52+
<key>uuid</key>
53+
<string>f3b088b3-6989-40ad-980c-080c82670c76</string>
54+
</dict>
55+
</array>
56+
<key>ProPlugPlugInList</key>
57+
<array>
58+
<dict>
59+
<key>className</key>
60+
<string>SwiftUIViewGenerator</string>
61+
<key>displayName</key>
62+
<string>SwiftUI View</string>
63+
<key>group</key>
64+
<string>f3b088b3-6989-40ad-980c-080c82670c76</string>
65+
<key>infoString</key>
66+
<string>A generator that renders a SwiftUI view.</string>
67+
<key>protocolNames</key>
68+
<array>
69+
<string>FxFilter</string>
70+
</array>
71+
<key>uuid</key>
72+
<string>3BE439C5-D15E-45FA-A077-A82D837E77B0</string>
73+
<key>version</key>
74+
<string>1.0</string>
75+
</dict>
76+
</array>
77+
<key>XPCService</key>
78+
<dict>
79+
<key>JoinExistingSession</key>
80+
<true/>
81+
<key>RunLoopType</key>
82+
<string>_NSApplicationMain</string>
83+
<key>ServiceType</key>
84+
<string>Application</string>
85+
<key>_AdditionalSubServices</key>
86+
<dict>
87+
<key>viewbridge</key>
88+
<true/>
89+
</dict>
90+
</dict>
91+
</dict>
92+
</plist>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#import <FxPlug/FxPlugSDK.h>

0 commit comments

Comments
 (0)