diff --git a/MonoGame.Framework/Platform/Native/GamePad.Native.cs b/MonoGame.Framework/Platform/Native/GamePad.Native.cs index 5e2acfade28..c8df0e413d3 100644 --- a/MonoGame.Framework/Platform/Native/GamePad.Native.cs +++ b/MonoGame.Framework/Platform/Native/GamePad.Native.cs @@ -6,6 +6,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Runtime.InteropServices; namespace Microsoft.Xna.Framework.Input; @@ -13,6 +14,8 @@ static partial class GamePad { static int MaxSupported = MGP.GamePad_GetMaxSupported(); + static internal unsafe MGP_Platform* Handle; + class State { public int Identifier; @@ -32,11 +35,44 @@ class State private static readonly Dictionary _stateByIndex = new Dictionary(); private static readonly Dictionary _stateById = new Dictionary(); - internal static void Add(int identifier) + internal static unsafe void Add(int identifier) { var state = new State(); state.Identifier = identifier; - state.Caps.IsConnected = true; + + // Setup the caps for this device. + MGP_ControllerCaps caps; + MGP.GamePad_GetCaps(Handle, identifier, & caps); + { + state.Caps.IsConnected = true; + state.Caps.Identifier = Marshal.PtrToStringUTF8(caps.Identifier); + state.Caps.DisplayName = Marshal.PtrToStringUTF8(caps.DisplayName); + state.Caps.GamePadType = caps.GamePadType; + state.Caps.HasDPadUpButton = (caps.InputFlags & (1 << (int)ControllerInput.DpadUp)) != 0; + state.Caps.HasDPadDownButton = (caps.InputFlags & (1 << (int)ControllerInput.DpadDown)) != 0; + state.Caps.HasDPadLeftButton = (caps.InputFlags & (1 << (int)ControllerInput.DpadLeft)) != 0; + state.Caps.HasDPadRightButton = (caps.InputFlags & (1 << (int)ControllerInput.DpadRight)) != 0; + state.Caps.HasAButton = (caps.InputFlags & (1 << (int)ControllerInput.A)) != 0; + state.Caps.HasBButton = (caps.InputFlags & (1 << (int)ControllerInput.B)) != 0; + state.Caps.HasXButton = (caps.InputFlags & (1 << (int)ControllerInput.X)) != 0; + state.Caps.HasYButton = (caps.InputFlags & (1 << (int)ControllerInput.Y)) != 0; + state.Caps.HasLeftShoulderButton = (caps.InputFlags & (1 << (int)ControllerInput.LeftShoulder)) != 0; + state.Caps.HasRightShoulderButton = (caps.InputFlags & (1 << (int)ControllerInput.RightShoulder)) != 0; + state.Caps.HasLeftTrigger = (caps.InputFlags & (1 << (int)ControllerInput.LeftTrigger)) != 0; + state.Caps.HasRightTrigger = (caps.InputFlags & (1 << (int)ControllerInput.RightTrigger)) != 0; + state.Caps.HasBackButton = (caps.InputFlags & (1 << (int)ControllerInput.Back)) != 0; + state.Caps.HasStartButton = (caps.InputFlags & (1 << (int)ControllerInput.Start)) != 0; + state.Caps.HasLeftStickButton = (caps.InputFlags & (1 << (int)ControllerInput.LeftStick)) != 0; + state.Caps.HasLeftXThumbStick = (caps.InputFlags & (1 << (int)ControllerInput.LeftStickX)) != 0; + state.Caps.HasLeftYThumbStick = (caps.InputFlags & (1 << (int)ControllerInput.LeftStickY)) != 0; + state.Caps.HasRightStickButton = (caps.InputFlags & (1 << (int)ControllerInput.RightStick)) != 0; + state.Caps.HasRightXThumbStick = (caps.InputFlags & (1 << (int)ControllerInput.RightStickX)) != 0; + state.Caps.HasRightYThumbStick = (caps.InputFlags & (1 << (int)ControllerInput.RightStickY)) != 0; + state.Caps.HasBigButton = (caps.InputFlags & (1 << (int)ControllerInput.Guide)) != 0; + state.Caps.HasVoiceSupport = caps.HasVoiceSupport; + state.Caps.HasLeftVibrationMotor = caps.HasLeftVibrationMotor; + state.Caps.HasRightVibrationMotor = caps.HasRightVibrationMotor; + } // TODO: Maybe the platform should supply // the correct player index as some platforms @@ -179,8 +215,14 @@ private static GamePadState PlatformGetState(int index, GamePadDeadZone leftDead return new GamePadState(); } - private static bool PlatformSetVibration(int index, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger) + private static unsafe bool PlatformSetVibration(int index, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger) { - return false; + if (Handle == null) + return false; + + if (!_stateByIndex.TryGetValue(index, out var state)) + return false; + + return MGP.GamePad_SetVibration(Handle, state.Identifier, leftMotor, rightMotor, leftTrigger, rightTrigger); } } diff --git a/MonoGame.Framework/Platform/Native/GamePlatform.Native.cs b/MonoGame.Framework/Platform/Native/GamePlatform.Native.cs index 696f7a64d5b..c5878756e8c 100644 --- a/MonoGame.Framework/Platform/Native/GamePlatform.Native.cs +++ b/MonoGame.Framework/Platform/Native/GamePlatform.Native.cs @@ -41,6 +41,7 @@ public unsafe NativeGamePlatform(Game game) : base(game) Mouse.WindowHandle = _window.Handle; MessageBox._window = _window._handle; + GamePad.Handle = Handle; } internal static unsafe MGG_GraphicsSystem* GraphicsSystem diff --git a/MonoGame.Framework/Platform/Native/Platform.Interop.cs b/MonoGame.Framework/Platform/Native/Platform.Interop.cs index 919d0f3e52b..833fe5f0e15 100644 --- a/MonoGame.Framework/Platform/Native/Platform.Interop.cs +++ b/MonoGame.Framework/Platform/Native/Platform.Interop.cs @@ -199,6 +199,20 @@ internal struct MGP_Event public MGP_ControllerEvent Controller; } + +[StructLayout(LayoutKind.Sequential)] +internal struct MGP_ControllerCaps +{ + public nint Identifier; + public nint DisplayName; + public GamePadType GamePadType; + public uint InputFlags; + public bool HasLeftVibrationMotor; + public bool HasRightVibrationMotor; + public bool HasVoiceSupport; +} + + [MGHandle] internal readonly struct MGP_Platform { } @@ -335,8 +349,17 @@ public static partial int Window_ShowMessageBox( #endregion + #region GamePad [LibraryImport(MonoGameNativeDLL, EntryPoint = "MGP_GamePad_GetMaxSupported", StringMarshalling = StringMarshalling.Utf8)] public static partial int GamePad_GetMaxSupported(); + [LibraryImport(MonoGameNativeDLL, EntryPoint = "MGP_GamePad_GetCaps", StringMarshalling = StringMarshalling.Utf8)] + public static partial void GamePad_GetCaps(MGP_Platform* platform, int identifer, MGP_ControllerCaps* caps); + + [LibraryImport(MonoGameNativeDLL, EntryPoint = "MGP_GamePad_SetVibration", StringMarshalling = StringMarshalling.Utf8)] + [return: MarshalAs(UnmanagedType.U1)] + public static partial bool GamePad_SetVibration(MGP_Platform* platform, int identifer, float leftMotor, float rightMotor, float leftTrigger, float rightTrigger); + + #endregion } diff --git a/native/monogame/include/api_MGP.h b/native/monogame/include/api_MGP.h index 712ca7354ed..232b664935e 100644 --- a/native/monogame/include/api_MGP.h +++ b/native/monogame/include/api_MGP.h @@ -50,3 +50,5 @@ MG_EXPORT MGP_Cursor* MGP_Cursor_Create(MGSystemCursor cursor); MG_EXPORT MGP_Cursor* MGP_Cursor_CreateCustom(mgbyte* rgba, mgint width, mgint height, mgint originx, mgint originy); MG_EXPORT void MGP_Cursor_Destroy(MGP_Cursor* cursor); MG_EXPORT mgint MGP_GamePad_GetMaxSupported(); +MG_EXPORT void MGP_GamePad_GetCaps(MGP_Platform* platform, mgint identifer, MGP_ControllerCaps* caps); +MG_EXPORT mgbool MGP_GamePad_SetVibration(MGP_Platform* platform, mgint identifer, mgfloat leftMotor, mgfloat rightMotor, mgfloat leftTrigger, mgfloat rightTrigger); diff --git a/native/monogame/include/api_enums.h b/native/monogame/include/api_enums.h index fc091fda7fc..3d0db60d4f3 100644 --- a/native/monogame/include/api_enums.h +++ b/native/monogame/include/api_enums.h @@ -488,10 +488,12 @@ enum class MGMonoGamePlatform : mgint Windows = 4, WebGL = 5, XboxOne = 6, - PlayStation4 = 7, - PlayStation5 = 8, - NintendoSwitch = 9, - DesktopVK = 10, + WindowsGDK = 7, + XboxSeries = 8, + PlayStation4 = 9, + PlayStation5 = 10, + NintendoSwitch = 11, + DesktopVK = 12, }; enum class MGGraphicsBackend : mgint @@ -500,6 +502,7 @@ enum class MGGraphicsBackend : mgint OpenGL = 1, Vulkan = 2, Metal = 3, + DirectX12 = 4, }; enum class MGSystemCursor : mgint @@ -518,3 +521,17 @@ enum class MGSystemCursor : mgint Hand = 11, }; +enum class MGGamePadType : mgint +{ + Unknown = 0, + GamePad = 1, + Wheel = 2, + ArcadeStick = 3, + FlightStick = 4, + DancePad = 5, + Guitar = 6, + AlternateGuitar = 7, + DrumKit = 8, + BigButtonPad = 768, +}; + diff --git a/native/monogame/include/api_structs.h b/native/monogame/include/api_structs.h index 94e9ced54db..340f2167a02 100644 --- a/native/monogame/include/api_structs.h +++ b/native/monogame/include/api_structs.h @@ -234,3 +234,14 @@ union { }; #pragma pack(pop) +struct MGP_ControllerCaps +{ + void* Identifier; + void* DisplayName; + MGGamePadType GamePadType; + mguint InputFlags; + mgbool HasLeftVibrationMotor; + mgbool HasRightVibrationMotor; + mgbool HasVoiceSupport; +}; + diff --git a/native/monogame/sdl/MGP_sdl.cpp b/native/monogame/sdl/MGP_sdl.cpp index 1a03891d1f1..03d3e76af93 100644 --- a/native/monogame/sdl/MGP_sdl.cpp +++ b/native/monogame/sdl/MGP_sdl.cpp @@ -15,8 +15,9 @@ struct MGP_Platform { - std::vector windows; + std::vector windows; std::queue queued_events; + std::map controllers; }; static std::map s_keymap @@ -414,20 +415,28 @@ mgbool MGP_Platform_PollEvent(MGP_Platform* platform, MGP_Event& event_) break; case SDL_EventType::SDL_CONTROLLERDEVICEADDED: - SDL_GameControllerOpen(ev.cdevice.which); + { + auto controller = SDL_GameControllerOpen(ev.cdevice.which); + platform->controllers.emplace(ev.cdevice.which, controller); event_.Type = MGEventType::ControllerAdded; event_.Timestamp = ev.cdevice.timestamp; - event_.Controller.Id = SDL_JoystickGetDeviceInstanceID(ev.cdevice.which); + event_.Controller.Id = ev.cdevice.which; event_.Controller.Input = MGControllerInput::INVALID; event_.Controller.Value = 0; return true; + } case SDL_EventType::SDL_CONTROLLERDEVICEREMOVED: + { + auto controller = platform->controllers[ev.cdevice.which]; + platform->controllers.erase(ev.cdevice.which); + SDL_GameControllerClose(controller); event_.Type = MGEventType::ControllerRemoved; event_.Timestamp = ev.cdevice.timestamp; event_.Controller.Id = ev.cdevice.which; event_.Controller.Input = MGControllerInput::INVALID; event_.Controller.Value = 0; return true; + } case SDL_EventType::SDL_CONTROLLERBUTTONUP: event_.Type = MGEventType::ControllerStateChange; event_.Timestamp = ev.cbutton.timestamp; @@ -817,6 +826,37 @@ void MGP_Window_ExitFullScreen(MGP_Window* window) SDL_SetWindowFullscreen(window->window, 0); } +mgint MGP_Window_ShowMessageBox(MGP_Window* window, const char* title, const char* description, const char** buttons, mgint count) +{ + SDL_MessageBoxData data; + data.window = window->window; + data.title = title; + data.message = description; + data.colorScheme = nullptr; + data.flags = SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT; + + auto bdata = new SDL_MessageBoxButtonData[count]; + for (int i = 0; i < count; i++) + { + bdata[i].buttonid = i; + bdata[i].text = buttons[i]; + bdata[i].flags = 0; + } + + data.numbuttons = count; + data.buttons = bdata; + + int hit = -1; + int error = SDL_ShowMessageBox(&data, &hit); + + delete[] bdata; + + if (error == 0) + return hit; + + return -1; +} + void MGP_Mouse_SetVisible(MGP_Platform* platform, mgbool visible) { assert(platform != nullptr); @@ -860,33 +900,85 @@ mgint MGP_GamePad_GetMaxSupported() return 16; } -mgint MGP_Window_ShowMessageBox(MGP_Window* window, const char* title, const char* description, const char** buttons, mgint count) +inline uint32_t HasSDLButton(SDL_GameController* controller, SDL_GameControllerButton button) { - SDL_MessageBoxData data; - data.window = window->window; - data.title = title; - data.message = description; - data.colorScheme = nullptr; - data.flags = SDL_MESSAGEBOX_BUTTONS_LEFT_TO_RIGHT; + return SDL_GameControllerHasButton(controller, button) ? (1 << (uint32_t)FromSDLButton(button)) : 0; +} - auto bdata = new SDL_MessageBoxButtonData[count]; - for (int i = 0; i < count; i++) - { - bdata[i].buttonid = i; - bdata[i].text = buttons[i]; - bdata[i].flags = 0; - } +inline uint32_t HasSDLAxis(SDL_GameController* controller, SDL_GameControllerAxis axis) +{ + return SDL_GameControllerHasAxis(controller, axis) ? (1 << (uint32_t)FromSDLAxis(axis)) : 0; +} - data.numbuttons = count; - data.buttons = bdata; +void MGP_GamePad_GetCaps(MGP_Platform* platform, mgint identifer, MGP_ControllerCaps* caps) +{ + assert(platform); - int hit = -1; - int error = SDL_ShowMessageBox(&data, &hit); + auto pair = platform->controllers.find(identifer); + if (pair == platform->controllers.end()) + { + // Not connected or unknown... so nothing to set. + caps->Identifier = nullptr; + caps->DisplayName = nullptr; + caps->GamePadType = MGGamePadType::Unknown; + caps->InputFlags = 0; + caps->HasLeftVibrationMotor = false; + caps->HasRightVibrationMotor = false; + caps->HasVoiceSupport = false; + return; + } - delete [] bdata; + auto controller = pair->second; - if (error == 0) - return hit; + // This doesn't need to be thread safe, but be valid + // long enough for the caller to copy it. + static char identifier[34]; + { + auto joystick = SDL_GameControllerGetJoystick(controller); + auto guid = SDL_JoystickGetGUID(joystick); + SDL_GUIDToString(guid, identifier, 34); + } - return -1; + // Not connected or unknown... so nothing to set. + caps->Identifier = (void*)identifier; + caps->DisplayName = (void*)SDL_GameControllerName(controller); + caps->GamePadType = MGGamePadType::GamePad; + caps->HasRightVibrationMotor = caps->HasLeftVibrationMotor = SDL_GameControllerHasRumble(controller); + caps->HasVoiceSupport = false; + + caps->InputFlags = 0; + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_A); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_B); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_X); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_Y); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_DPAD_UP); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_BACK); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_START); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_GUIDE); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_LEFTSTICK); + caps->InputFlags |= HasSDLButton(controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK); + caps->InputFlags |= HasSDLAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + caps->InputFlags |= HasSDLAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + caps->InputFlags |= HasSDLAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + caps->InputFlags |= HasSDLAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + caps->InputFlags |= HasSDLAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + caps->InputFlags |= HasSDLAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); +} + +mgbool MGP_GamePad_SetVibration(MGP_Platform* platform, mgint identifer, mgfloat leftMotor, mgfloat rightMotor, mgfloat leftTrigger, mgfloat rightTrigger) +{ + assert(platform); + + auto pair = platform->controllers.find(identifer); + if (pair == platform->controllers.end()) + return false; + + auto supported = SDL_GameControllerRumble(pair->second, (UINT16)(leftMotor * 0xFFFF), (UINT16)(rightMotor * 0xFFFF), INT_MAX); + return supported == 0; } +