import ExpoModulesCoreimport Securityimport UIKitpublic final class T3NativeControlsModule: Module { public func definition() -> ModuleDefinition { Name("T3NativeControls") Function("getShowcasePairingUrl") { let arguments = ProcessInfo.processInfo.arguments guard let flagIndex = arguments.firstIndex(of: "--showcasePairingUrl"), arguments.indices.contains(flagIndex + 1) else { return nil as String? } return arguments[flagIndex + 1] } Function("getShowcaseScene") { () -> String? in let scenePath = NSHomeDirectory() + "/Library/Caches/T3ShowcaseScene" if let storedScene = try? String(contentsOfFile: scenePath, encoding: .utf8) .trimmingCharacters(in: .whitespacesAndNewlines), !storedScene.isEmpty { return storedScene } let arguments = ProcessInfo.processInfo.arguments guard let flagIndex = arguments.firstIndex(of: "--showcaseScene"), arguments.indices.contains(flagIndex + 1) else { return nil as String? } return arguments[flagIndex + 1] } // The palette is fixed for the whole capture, so it only ever arrives as a // launch argument — unlike the scene, which the runner rewrites in place. Function("getShowcaseTheme") { () -> String? in let arguments = ProcessInfo.processInfo.arguments guard let flagIndex = arguments.firstIndex(of: "--showcaseTheme"), arguments.indices.contains(flagIndex + 1) else { return nil as String? } return arguments[flagIndex + 1] } Function("getShowcaseOrientation") { () -> String? in let arguments = ProcessInfo.processInfo.arguments guard let flagIndex = arguments.firstIndex(of: "--showcaseOrientation"), arguments.indices.contains(flagIndex + 1) else { return nil as String? } return arguments[flagIndex + 1] } // Rotates the interface without Simulator menu UI scripting, which CI // runners cannot perform (osascript is denied Accessibility access there). AsyncFunction("applyShowcaseOrientation") { (orientation: String) in guard #available(iOS 16.0, *) else { return } let mask: UIInterfaceOrientationMask = orientation == "landscape" ? .landscapeRight : .portrait for case let windowScene as UIWindowScene in UIApplication.shared.connectedScenes { windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: mask)) { error in NSLog("T3NativeControls applyShowcaseOrientation(\(orientation)) failed: \(error)") } for window in windowScene.windows { window.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations() } } }.runOnQueue(.main) // The geometry request above can fail transiently (for example before the // scene is foreground-active), so callers poll this until it settles. // Screen bounds — not the scene's interface orientation — decide the // answer because they match the captured framebuffer: with iPadOS // windowing active, a floating landscape window still reports a portrait // screen, and screenshots would come out portrait. AsyncFunction("getInterfaceOrientation") { () -> String in guard let windowScene = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first else { return "unknown" } let bounds = windowScene.screen.coordinateSpace.bounds return bounds.width > bounds.height ? "landscape" : "portrait" }.runOnQueue(.main) Function("prepareShowcaseCapture") { for itemClass in [kSecClassGenericPassword, kSecClassInternetPassword] { SecItemDelete([kSecClass as String: itemClass] as CFDictionary) } } Function("markShowcaseReady") { (scene: String) in let readyPath = NSHomeDirectory() + "/Library/Caches/T3ShowcaseReadyScene" try? scene.write(toFile: readyPath, atomically: true, encoding: .utf8) } }}
import ExpoModulesCore
import Security
import UIKit
public final class T3NativeControlsModule: Module {
public func definition() -> ModuleDefinition {
Name("T3NativeControls")
Function("getShowcasePairingUrl") {
let arguments = ProcessInfo.processInfo.arguments
guard
let flagIndex = arguments.firstIndex(of: "--showcasePairingUrl"),
arguments.indices.contains(flagIndex + 1)
else {
return nil as String?
}
return arguments[flagIndex + 1]
}
Function("getShowcaseScene") { () -> String? in
let scenePath = NSHomeDirectory() + "/Library/Caches/T3ShowcaseScene"
if let storedScene = try? String(contentsOfFile: scenePath, encoding: .utf8)
.trimmingCharacters(in: .whitespacesAndNewlines), !storedScene.isEmpty {
return storedScene
}
let arguments = ProcessInfo.processInfo.arguments
guard
let flagIndex = arguments.firstIndex(of: "--showcaseScene"),
arguments.indices.contains(flagIndex + 1)
else {
return nil as String?
}
return arguments[flagIndex + 1]
}
// The palette is fixed for the whole capture, so it only ever arrives as a
// launch argument — unlike the scene, which the runner rewrites in place.
Function("getShowcaseTheme") { () -> String? in
let arguments = ProcessInfo.processInfo.arguments
guard
let flagIndex = arguments.firstIndex(of: "--showcaseTheme"),
arguments.indices.contains(flagIndex + 1)
else {
return nil as String?
}
return arguments[flagIndex + 1]
}
Function("getShowcaseOrientation") { () -> String? in
let arguments = ProcessInfo.processInfo.arguments
guard
let flagIndex = arguments.firstIndex(of: "--showcaseOrientation"),
arguments.indices.contains(flagIndex + 1)
else {
return nil as String?
}
return arguments[flagIndex + 1]
}
// Rotates the interface without Simulator menu UI scripting, which CI
// runners cannot perform (osascript is denied Accessibility access there).
AsyncFunction("applyShowcaseOrientation") { (orientation: String) in
guard #available(iOS 16.0, *) else { return }
let mask: UIInterfaceOrientationMask = orientation == "landscape" ? .landscapeRight : .portrait
for case let windowScene as UIWindowScene in UIApplication.shared.connectedScenes {
windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: mask)) { error in
NSLog("T3NativeControls applyShowcaseOrientation(\(orientation)) failed: \(error)")
}
for window in windowScene.windows {
window.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}
}.runOnQueue(.main)
// The geometry request above can fail transiently (for example before the
// scene is foreground-active), so callers poll this until it settles.
// Screen bounds — not the scene's interface orientation — decide the
// answer because they match the captured framebuffer: with iPadOS
// windowing active, a floating landscape window still reports a portrait
// screen, and screenshots would come out portrait.
AsyncFunction("getInterfaceOrientation") { () -> String in
guard
let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first
else {
return "unknown"
}
let bounds = windowScene.screen.coordinateSpace.bounds
return bounds.width > bounds.height ? "landscape" : "portrait"
}.runOnQueue(.main)
Function("prepareShowcaseCapture") {
for itemClass in [kSecClassGenericPassword, kSecClassInternetPassword] {
SecItemDelete([kSecClass as String: itemClass] as CFDictionary)
}
}
Function("markShowcaseReady") { (scene: String) in
let readyPath = NSHomeDirectory() + "/Library/Caches/T3ShowcaseReadyScene"
try? scene.write(toFile: readyPath, atomically: true, encoding: .utf8)
}
}
}