forked from MeloNX/MeloNX
Fix Controllers, Add Auto Controller, Fix touch screen, Rewrite SDL Controller Detection.
This commit is contained in:
parent
1d16bf0c94
commit
a166494e33
Binary file not shown.
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user