diff --git a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj index 8935820f8..2e4cc96e4 100644 --- a/src/MeloNX/MeloNX.xcodeproj/project.pbxproj +++ b/src/MeloNX/MeloNX.xcodeproj/project.pbxproj @@ -536,6 +536,10 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; @@ -608,6 +612,10 @@ "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", + "$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries", ); MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX; diff --git a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate index e1e9ff137..281ba5550 100644 Binary files a/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate and b/src/MeloNX/MeloNX.xcodeproj/project.xcworkspace/xcuserdata/stossy11.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h b/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h index 49aa91213..89a1b6329 100644 --- a/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h +++ b/src/MeloNX/MeloNX/Core/Headers/Ryujinx-Header.h @@ -15,6 +15,8 @@ extern "C" { // Declare the main_ryujinx_sdl function, matching the signature int main_ryujinx_sdl(int argc, char **argv); +const char* get_game_controllers(); + #ifdef __cplusplus } #endif diff --git a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift index c968183dd..38af03e4c 100644 --- a/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift +++ b/src/MeloNX/MeloNX/Core/Swift/Ryujinx.swift @@ -10,7 +10,7 @@ import SwiftUI import SDL2 import GameController -struct Controller: Identifiable { +struct Controller: Identifiable, Hashable { let id: String let name: String } @@ -133,24 +133,34 @@ class Ryujinx { return args } - func getConnectedControllers() { + func getConnectedControllers() -> [Controller] { - - // Retrieve all connected controllers - let controllers = GCController.controllers() + guard let jsonPtr = get_game_controllers() else { + return [] + } - for controller in controllers { - if let controllerID = controller.vendorName { - // Assuming controller's name is used as the ID - let controllerName = controller.vendorName ?? "Unknown Controller" - - // You can customize the key format here - DispatchQueue.main.async { - self.controllerMap.append(Controller(id: controllerID, name: controllerName)) + // Convert the unmanaged memory (C string) to a Swift String + let jsonString = String(cString: jsonPtr) + + var controllers: [Controller] = [] + + // Splitting the string by newline + let lines = jsonString.components(separatedBy: "\n") + + // Parsing each line + for line in lines { + if line.contains(":") { + let parts = line.components(separatedBy: ":") + if parts.count == 2 { + let id = parts[0].trimmingCharacters(in: .whitespacesAndNewlines) + let name = parts[1].trimmingCharacters(in: .whitespacesAndNewlines) + controllers.append(Controller(id: id, name: name)) } } } + return controllers + } diff --git a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libMoltenVK.dylib b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libMoltenVK.dylib index e2f6bb2d9..3253ff11f 100755 Binary files a/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libMoltenVK.dylib and b/src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/libMoltenVK.dylib differ diff --git a/src/MeloNX/MeloNX/Dependencies/XCFrameworks/MoltenVK.xcframework/ios-arm64/MoltenVK.framework/MoltenVK b/src/MeloNX/MeloNX/Dependencies/XCFrameworks/MoltenVK.xcframework/ios-arm64/MoltenVK.framework/MoltenVK index e2f6bb2d9..3253ff11f 100755 Binary files a/src/MeloNX/MeloNX/Dependencies/XCFrameworks/MoltenVK.xcframework/ios-arm64/MoltenVK.framework/MoltenVK and b/src/MeloNX/MeloNX/Dependencies/XCFrameworks/MoltenVK.xcframework/ios-arm64/MoltenVK.framework/MoltenVK differ diff --git a/src/MeloNX/MeloNX/Views/ContentView.swift b/src/MeloNX/MeloNX/Views/ContentView.swift index 4c9c9267a..9dcb880ac 100644 --- a/src/MeloNX/MeloNX/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/Views/ContentView.swift @@ -18,7 +18,8 @@ struct ContentView: View { @State public var theWindow: UIWindow? = nil @State private var virtualController: GCVirtualController? @State var game: URL? = nil - @State var controllerss: [Controller] = [] + @State var controllersList: [Controller] = [] + @State var currentControllers: [Controller] = [] @State private var settings: [MoltenVKSettings] = [ MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"), @@ -56,40 +57,54 @@ struct ContentView: View { } var body: some View { - - - if let game { - ZStack { - SDLViewRepresentable { displayid in - start(displayid: 0) + NavigationStack { + + if let game { + ZStack { + SDLViewRepresentable { displayid in + start(displayid: 0) + } + Text("Loading...") + .onAppear { + // start(displayid: 0) + } } - Text("Loading...") - .onAppear { - // start(displayid: 0) - } - } - } else { - HStack { - GameListView(startemu: $game) - .onAppear() { - Ryujinx().getConnectedControllers() - } - - List { - ForEach($settings, id: \.self) { $setting in - HStack { - Text(setting.string) - .padding() - TextField("Value", text: $setting.value) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .onChange(of: setting.value) { newValue in - setenv(setting.string, newValue, 1) + } else { + HStack { + GameListView(startemu: $game) + .onAppear() { + Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in + controllersList = Ryujinx().getConnectedControllers() + controllersList.removeAll(where: { $0.id == "0" }) + } + } + + List { + Button { + controllersList = Ryujinx().getConnectedControllers() + controllersList.removeAll(where: { $0.id == "0" }) + } label: { + Text("Refresh") + } + ForEach(controllersList, id: \.self) { controller in + + Button { + if currentControllers.contains(where: { $0.id == controller.id }) { + currentControllers.removeAll(where: { $0.id == controller.id }) + } else { + currentControllers.append(controller) } + } label: { + Text(controller.name) + } + Spacer() + if currentControllers.contains(where: { $0.id == controller.id }) { + Image(systemName: "checkmark.circle.fill") + } } } - + } - } } } @@ -104,7 +119,7 @@ struct ContentView: View { debuglogs: false, tracelogs: false, listinputids: false, - inputids: ["1-dc180005-045e-0000-130b-0000ff870001"], // "2-1fd70005-057e-0000-0920-0000ff870000"], + inputids: currentControllers.map(\.id),// "1-dc180005-045e-0000-130b-0000ff870001"], // "2-1fd70005-057e-0000-0920-0000ff870000"], ryufullscreen: true ) diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index bd077bc7e..0997e15dc 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -39,6 +39,13 @@ using System.Threading; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using Key = Ryujinx.Common.Configuration.Hid.Key; +using System.Linq; + +public class GamepadInfo +{ + public string Id { get; set; } + public string Name { get; set; } +} namespace Ryujinx.Headless.SDL2 { @@ -125,6 +132,38 @@ namespace Ryujinx.Headless.SDL2 .WithNotParsed(errors => errors.Output()); } + [UnmanagedCallersOnly(EntryPoint = "get_game_controllers")] + public static unsafe IntPtr GetGamepadList() + { + List gamepads = new List(); + IGamepad gamepad; + _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; + } + private static InputConfig HandlePlayerConfiguration(string inputProfileName, string inputId, PlayerIndex index) { if (inputId == null) diff --git a/src/Ryujinx.Headless.SDL2/WindowBase.cs b/src/Ryujinx.Headless.SDL2/WindowBase.cs index 8b24f1b9d..9f5b50506 100644 --- a/src/Ryujinx.Headless.SDL2/WindowBase.cs +++ b/src/Ryujinx.Headless.SDL2/WindowBase.cs @@ -185,8 +185,8 @@ namespace Ryujinx.Headless.SDL2 FullscreenFlag = SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP; } - WindowHandle = SDL_GetWindowFromID(1); - // WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags()); + // WindowHandle = SDL_GetWindowFromID(1); + WindowHandle = SDL_CreateWindow($"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}", SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), SDL_WINDOWPOS_CENTERED_DISPLAY(DisplayId), Width, Height, DefaultFlags | FullscreenFlag | GetWindowFlags()); if (WindowHandle == IntPtr.Zero) { diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index db1a85e3a..49e7dd147 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -96,12 +96,12 @@ namespace Ryujinx.SDL2.Common SDL_EventState(SDL_EventType.SDL_CONTROLLERSENSORUPDATE, SDL_DISABLE); - string gamepadDbPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt"); + // string gamepadDbPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "SDL_GameControllerDB.txt"); - if (File.Exists(gamepadDbPath)) - { - SDL_GameControllerAddMappingsFromFile(gamepadDbPath); - } + // if (File.Exists(gamepadDbPath)) + // { + // SDL_GameControllerAddMappingsFromFile(gamepadDbPath); + // } _registeredWindowHandlers = new ConcurrentDictionary>(); _worker = new Thread(EventWorker);