Add Airplay

This commit is contained in:
Stossy11 2025-02-09 16:31:26 +11:00
parent f57d24706b
commit 2d5f1d8015
7 changed files with 192 additions and 11 deletions

View File

@ -57,6 +57,7 @@ class Ryujinx {
@Published var controllerMap: [Controller] = [] @Published var controllerMap: [Controller] = []
@Published var metalLayer: CAMetalLayer? = nil @Published var metalLayer: CAMetalLayer? = nil
@Published var firmwareversion = "0" @Published var firmwareversion = "0"
@Published var emulationUIView = UIView()
var shouldMetal: Bool { var shouldMetal: Bool {
metalLayer == nil metalLayer == nil

View File

@ -75,7 +75,7 @@ struct ContentView: View {
// MARK: - Body // MARK: - Body
var body: some View { var body: some View {
if game != nil, quits == false { if game != nil, quits == false {
if Ryujinx.shared.metalLayer == nil { if isLoading {
emulationView emulationView
.onAppear() { .onAppear() {
// This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative // This is fro the old exiting game feature that didn't work properly. will look into it and figure out a better alternative
@ -187,8 +187,10 @@ struct ContentView: View {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if get_current_fps() != 0 { if Ryujinx.shared.metalLayer != nil {
isLoading = false withAnimation {
isLoading = false
}
isAnimating = false isAnimating = false
timer.invalidate() timer.invalidate()
} }
@ -217,6 +219,11 @@ struct ContentView: View {
refreshControllersList() refreshControllersList()
} }
Air.play(AnyView(
Text("Select Game")
.font(.system(size: 100))
))
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED") let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")

View File

@ -0,0 +1,143 @@
// Credit https://github.com/heestand-xyz/AirKit
import UIKit
import SwiftUI
public class Air {
static let shared = Air()
public var connected: Bool = false {
didSet {
connectionCallbacks.forEach({ $0(connected) })
}
}
var connectionCallbacks: [(Bool) -> ()] = []
var airScreen: UIScreen?
var airWindow: UIWindow?
var hostingController: UIHostingController<AnyView>?
var appIsActive: Bool { UIApplication.shared.applicationState == .active }
init() {
NotificationCenter.default.addObserver(self, selector: #selector(didConnect),
name: UIScreen.didConnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didDisconnect),
name: UIScreen.didDisconnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive),
name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive),
name: UIApplication.willResignActiveNotification, object: nil)
}
private func check() {
if let connectedScreen = UIScreen.screens.first(where: { $0 != .main }) {
add(screen: connectedScreen) { success in
guard success else { return }
self.connected = true
}
}
}
public static func play(_ view: AnyView) {
Air.shared.hostingController = UIHostingController<AnyView>(rootView: view)
Air.shared.check()
}
public static func stop() {
Air.shared.remove()
Air.shared.hostingController = nil
}
public static func connection(_ callback: @escaping (Bool) -> ()) {
Air.shared.connectionCallbacks.append(callback)
}
@objc func didConnect(sender: NSNotification) {
print("AirKit - Connect")
self.connected = true
guard let screen: UIScreen = sender.object as? UIScreen else { return }
add(screen: screen) { success in
guard success else { return }
self.connected = true
}
}
func add(screen: UIScreen, completion: @escaping (Bool) -> ()) {
print("AirKit - Add Screen")
airScreen = screen
airWindow = UIWindow(frame: airScreen!.bounds)
guard let viewController: UIViewController = hostingController else {
print("AirKit - Add - Failed: Hosting Controller Not Found")
completion(false)
return
}
findWindowScene(for: airScreen!) { windowScene in
guard let airWindowScene: UIWindowScene = windowScene else {
print("AirKit - Add - Failed: Window Scene Not Found")
completion(false)
return
}
self.airWindow?.rootViewController = viewController
self.airWindow?.windowScene = airWindowScene
self.airWindow?.isHidden = false
print("AirKit - Add Screen - Done")
completion(true)
}
}
func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) {
print("AirKit - Find Window Scene")
var matchingWindowScene: UIWindowScene? = nil
let scenes = UIApplication.shared.connectedScenes
for scene in scenes {
if let windowScene = scene as? UIWindowScene {
if windowScene.screen == screen {
matchingWindowScene = windowScene
break
}
}
}
guard let windowScene: UIWindowScene = matchingWindowScene else {
DispatchQueue.main.async {
self.findWindowScene(for: screen, shouldRecurse: false) { windowScene in
completion(windowScene)
}
}
return
}
completion(windowScene)
}
@objc func didDisconnect() {
print("AirKit - Disconnect")
remove()
connected = false
}
func remove() {
print("AirKit - Remove")
airWindow = nil
airScreen = nil
}
@objc func didBecomeActive() {
print("AirKit - App Active")
}
@objc func willResignActive() {
print("AirKit - App Inactive")
}
}

View File

@ -0,0 +1,13 @@
import UIKit
import SwiftUI
public extension View {
func airPlay() -> some View {
print("AirKit - airPlay")
Air.play(AnyView(self))
return self
}
}

View File

@ -10,11 +10,19 @@ import SwiftUI
// Emulation View // Emulation View
struct EmulationView: View { struct EmulationView: View {
@AppStorage("isVirtualController") var isVCA: Bool = true @AppStorage("isVirtualController") var isVCA: Bool = true
@State var isAirplaying = Air.shared.connected
var body: some View { var body: some View {
ZStack { ZStack {
MetalView() // The Emulation View if isAirplaying {
.ignoresSafeArea() Text("")
.edgesIgnoringSafeArea(.all) .onAppear {
Air.play(AnyView(MetalView().ignoresSafeArea()))
}
} else {
MetalView() // The Emulation View
.ignoresSafeArea()
.edgesIgnoringSafeArea(.all)
}
// Above Emulation View // Above Emulation View
@ -22,5 +30,13 @@ struct EmulationView: View {
ControllerView() // Virtual Controller ControllerView() // Virtual Controller
} }
} }
.onAppear {
Air.shared.connectionCallbacks.append { cool in
DispatchQueue.main.async {
isAirplaying = cool
print(cool)
}
}
}
} }
} }

View File

@ -11,14 +11,15 @@ import MetalKit
struct MetalView: UIViewRepresentable { struct MetalView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView { func makeUIView(context: Context) -> UIView {
let view = UIView()
let metalLayer = Ryujinx.shared.metalLayer! let metalLayer = Ryujinx.shared.metalLayer!
metalLayer.frame = view.bounds metalLayer.frame = Ryujinx.shared.emulationUIView.bounds
view.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3 Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3
view.layer.addSublayer(metalLayer) if !Ryujinx.shared.emulationUIView.subviews.contains(where: { $0 == metalLayer }) {
Ryujinx.shared.emulationUIView.layer.addSublayer(metalLayer)
}
return view return Ryujinx.shared.emulationUIView
} }
func updateUIView(_ uiView: UIView, context: Context) { func updateUIView(_ uiView: UIView, context: Context) {