Fix Controllers, Add Auto Controller, Fix touch screen, Rewrite SDL Controller Detection.

This commit is contained in:
Stossy11 2025-02-01 16:37:58 +11:00
parent 1d16bf0c94
commit a166494e33
12 changed files with 150 additions and 144 deletions

View File

@ -12,12 +12,12 @@
<key>Ryujinx.xcscheme_^#shared#^_</key> <key>Ryujinx.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>1</integer> <integer>2</integer>
</dict> </dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key> <key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>2</integer> <integer>1</integer>
</dict> </dict>
</dict> </dict>
<key>SuppressBuildableAutocreation</key> <key>SuppressBuildableAutocreation</key>

View File

@ -9,7 +9,7 @@
#define RyujinxHeader #define RyujinxHeader
#import "SDL2/SDL.h" #include <SDL2/SDL.h>
#import "utils.h" #import "utils.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -40,8 +40,6 @@ int get_current_fps();
void initialize(); void initialize();
const char* get_game_controllers();
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -10,64 +10,48 @@ import GameController
import UIKit import UIKit
import SwiftUI import SwiftUI
func waitforcontroller() { var hostingController: UIHostingController<ControllerView>? // Store reference to prevent deallocation
if let window = theWindow {
// Function to recursively search for GCControllerView func waitForController() {
func findGCControllerView(in view: UIView) -> UIView? { guard let window = theWindow else { return }
// Check if current view is GCControllerView
if String(describing: type(of: view)) == "ControllerView" { // Function to search for an existing UIHostingController with ControllerView
return view func findGCControllerView(in view: UIView) -> UIHostingController<ControllerView>? {
} if let hostingVC = view.next as? UIHostingController<ControllerView> {
return hostingVC
// Search through subviews
for subview in view.subviews {
if let found = findGCControllerView(in: subview) {
return found
}
}
return nil
} }
let controllerView = ControllerView() for subview in view.subviews {
let controllerHostingController = UIHostingController(rootView: controllerView) if let found = findGCControllerView(in: subview) {
let containerView = TransparentHostingContainerView(frame: window.bounds) return found
containerView.backgroundColor = .clear }
}
controllerHostingController.view.frame = containerView.bounds
controllerHostingController.view.backgroundColor = .clear
containerView.addSubview(controllerHostingController.view)
class LandscapeViewController: UIViewController { return nil
override var supportedInterfaceOrientations: UIInterfaceOrientationMask { }
return .landscape
let controllerView = ControllerView()
let newHostingController = UIHostingController(rootView: controllerView)
// Store reference globally to prevent deallocation
hostingController = newHostingController
let containerView = newHostingController.view!
containerView.backgroundColor = .clear
containerView.frame = window.bounds
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if findGCControllerView(in: window) == nil {
window.addSubview(containerView)
window.bringSubviewToFront(containerView)
if let sdlWindow = SDL_GetWindowFromID(1) {
SDL_SetWindowPosition(sdlWindow, 0, 0)
} }
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { timer.invalidate() // Stop the timer after adding the view
return .landscapeLeft
}
} }
let landscapeVC = LandscapeViewController()
landscapeVC.modalPresentationStyle = .fullScreen
window.rootViewController?.present(landscapeVC, animated: false, completion: nil)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if findGCControllerView(in: window) == nil {
window.addSubview(containerView)
window.bringSubviewToFront(containerView)
let window = SDL_GetWindowFromID(1)
SDL_SetWindowPosition(window, 0, 0);
timer.invalidate()
} else {
timer.invalidate()
}
}
} }
} }

View File

@ -23,10 +23,7 @@ extension UIWindow {
if UserDefaults.standard.bool(forKey: "isVirtualController") { if UserDefaults.standard.bool(forKey: "isVirtualController") {
if let window = theWindow { if let window = theWindow {
waitForController()
waitforcontroller()
} }
} }
} }

View File

@ -279,35 +279,58 @@ class Ryujinx {
} }
} }
func getConnectedControllers() -> [Controller] { private func generateGamepadId(joystickIndex: Int32) -> String? {
var guid = SDL_JoystickGetDeviceGUID(joystickIndex)
guard let jsonPtr = get_game_controllers() else { if guid.data.0 == 0 && guid.data.1 == 0 && guid.data.2 == 0 && guid.data.3 == 0 {
return [] return nil
} }
// Convert the unmanaged memory (C string) to a Swift String let reorderedGUID: [UInt8] = [
let jsonString = String(cString: jsonPtr) guid.data.3, guid.data.2, guid.data.1, guid.data.0,
guid.data.5, guid.data.4,
guid.data.7, guid.data.6,
guid.data.8, guid.data.9,
guid.data.10, guid.data.11, guid.data.12, guid.data.13, guid.data.14, guid.data.15
]
let guidString = reorderedGUID.map { String(format: "%02X", $0) }.joined().lowercased()
func substring(_ str: String, _ start: Int, _ end: Int) -> String {
let startIdx = str.index(str.startIndex, offsetBy: start)
let endIdx = str.index(str.startIndex, offsetBy: end)
return String(str[startIdx..<endIdx])
}
let formattedGUID = "\(substring(guidString, 0, 8))-\(substring(guidString, 8, 12))-\(substring(guidString, 12, 16))-\(substring(guidString, 16, 20))-\(substring(guidString, 20, 32))"
return "\(joystickIndex)-\(formattedGUID)"
}
func getConnectedControllers() -> [Controller] {
var controllers: [Controller] = [] var controllers: [Controller] = []
// Splitting the string by newline let numJoysticks = SDL_NumJoysticks()
let lines = jsonString.components(separatedBy: "\n")
for i in 0..<numJoysticks {
// Parsing each line if let controller = SDL_GameControllerOpen(i) {
for line in lines { let guid = generateGamepadId(joystickIndex: i)
if line.contains(":") { let name = String(cString: SDL_GameControllerName(controller))
let parts = line.components(separatedBy: ":")
if parts.count == 2 { print("Controller \(i): \(name), GUID: \(guid ?? "")")
let id = parts[0].trimmingCharacters(in: .whitespacesAndNewlines)
let name = parts[1].trimmingCharacters(in: .whitespacesAndNewlines) guard let guid else {
controllers.append(Controller(id: id, name: name)) SDL_GameControllerClose(controller)
return []
} }
controllers.append(Controller(id: guid, name: name))
SDL_GameControllerClose(controller)
} }
} }
return controllers return controllers
} }
func removeFirmware() { func removeFirmware() {

View File

@ -91,10 +91,36 @@ struct ContentView: View {
mainMenuView mainMenuView
.onAppear() { .onAppear() {
quits = false quits = false
initControllerObservers()
} }
} }
} }
private func initControllerObservers() {
NotificationCenter.default.addObserver(
forName: .GCControllerDidConnect,
object: nil,
queue: .main) { notification in
if let controller = notification.object as? GCController {
print("Controller connected: \(controller.productCategory)")
refreshControllersList()
}
}
NotificationCenter.default.addObserver(
forName: .GCControllerDidDisconnect,
object: nil,
queue: .main) { notification in
if let controller = notification.object as? GCController {
print("Controller disconnected: \(controller.productCategory)")
refreshControllersList()
}
}
}
// MARK: - View Components // MARK: - View Components
private var emulationView: some View { private var emulationView: some View {
@ -175,15 +201,17 @@ struct ContentView: View {
private var mainMenuView: some View { private var mainMenuView: some View {
MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller) MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
.onAppear() { .onAppear() {
refreshControllersList() Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { timer in
refreshControllersList()
}
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
if !isJIT, useTrollStore { let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
askForJIT()
} if !isJIT, useTrollStore {
askForJIT()
}
} }
} }
@ -229,13 +257,21 @@ struct ContentView: View {
controllersList.removeAll(where: { $0.id == "0"}) controllersList.removeAll(where: { $0.id == "0"})
if controllersList.count > 2 { currentControllers = []
let controller = controllersList[2]
currentControllers.append(controller) if controllersList.count == 1 {
} else if let controller = controllersList.first(where: { $0.id == onscreencontroller.id }), !controllersList.isEmpty { let controller = controllersList[0]
currentControllers.append(controller) currentControllers.append(controller)
} else if (controllersList.count - 1) >= 1 {
for controller in controllersList {
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
currentControllers.append(controller)
}
}
} }
} }
func showAlert(title: String, message: String, showOk: Bool, completion: @escaping (Bool) -> Void) { func showAlert(title: String, message: String, showOk: Bool, completion: @escaping (Bool) -> Void) {
DispatchQueue.main.async { DispatchQueue.main.async {

View File

@ -198,7 +198,7 @@ struct SettingsView: View {
ForEach(controllersList) { controller in ForEach(currentControllers) { controller in
var customBinding: Binding<Bool> { var customBinding: Binding<Bool> {
Binding( Binding(
@ -223,14 +223,26 @@ struct SettingsView: View {
} }
.tint(.blue) .tint(.blue)
} label: { } label: {
let controller = String((controllersList.firstIndex(where: { $0.id == controller.id }) ?? 0) + 1)
if let controller = currentControllers.firstIndex(where: { $0.id == controller.id } ) {
Text("Player \(controller)") Text("Player \(controller + 1)")
.onAppear() {
// print(currentControllers.firstIndex(where: { $0.id == controller.id }) ?? 0)
print(currentControllers.count)
if currentControllers.count > 2 {
print(currentControllers[1])
print(currentControllers[2])
}
}
}
} }
} }
} }
.onMove { from, to in
currentControllers.move(fromOffsets: from, toOffset: to)
}
} header: { } header: {
Text("Input Selector") Text("Input Selector")
.font(.title3.weight(.semibold)) .font(.title3.weight(.semibold))

View File

@ -95,12 +95,6 @@ using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SDL2; using SDL2;
public class GamepadInfo
{
public string Id { get; set; }
public string Name { get; set; }
}
namespace Ryujinx.Headless.SDL2 namespace Ryujinx.Headless.SDL2
{ {
class Program class Program
@ -316,44 +310,6 @@ namespace Ryujinx.Headless.SDL2
_emulationContext = null; _emulationContext = null;
} }
} }
[UnmanagedCallersOnly(EntryPoint = "get_game_controllers")]
public static unsafe IntPtr GetGamepadList()
{
List<GamepadInfo> gamepads = new List<GamepadInfo>();
IGamepad gamepad;
if (_inputManager == null)
{
_inputManager = new InputManager(new SDL2KeyboardDriver(), new SDL2GamepadDriver());
}
// Collect gamepads from the keyboard driver
foreach (string id in _inputManager.KeyboardDriver.GamepadsIds)
{
gamepad = _inputManager.KeyboardDriver.GetGamepad(id);
gamepads.Add(new GamepadInfo { Id = id, Name = gamepad.Name });
gamepad.Dispose();
}
// Collect gamepads from the gamepad driver
foreach (string id in _inputManager.GamepadDriver.GamepadsIds)
{
gamepad = _inputManager.GamepadDriver.GetGamepad(id);
gamepads.Add(new GamepadInfo { Id = id, Name = gamepad.Name });
gamepad.Dispose();
}
// Serialize the gamepad list to a custom string format
string result = string.Join("\n", gamepads.Select(g => $"{g.Id}:{g.Name}")); // Ensure System.Linq is available
// Convert the string to unmanaged memory
IntPtr ptr = Marshal.StringToHGlobalAnsi(result);
return ptr;
}
[UnmanagedCallersOnly(EntryPoint = "get_game_info")] [UnmanagedCallersOnly(EntryPoint = "get_game_info")]
public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr) public static GameInfoNative GetGameInfoNative(int descriptor, IntPtr extensionPtr)