Skip to content

Commit

Permalink
Added vibration and caps to native gamepad API.
Browse files Browse the repository at this point in the history
  • Loading branch information
tomspilman committed Oct 7, 2024
1 parent 5fa2fac commit 87f2868
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 33 deletions.
50 changes: 46 additions & 4 deletions MonoGame.Framework/Platform/Native/GamePad.Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace Microsoft.Xna.Framework.Input;

static partial class GamePad
{
static int MaxSupported = MGP.GamePad_GetMaxSupported();

static internal unsafe MGP_Platform* Handle;

class State
{
public int Identifier;
Expand All @@ -32,11 +35,44 @@ class State
private static readonly Dictionary<int, State> _stateByIndex = new Dictionary<int, State>();
private static readonly Dictionary<int, State> _stateById = new Dictionary<int, State>();

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
Expand Down Expand Up @@ -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);
}
}
1 change: 1 addition & 0 deletions MonoGame.Framework/Platform/Native/GamePlatform.Native.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions MonoGame.Framework/Platform/Native/Platform.Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 { }

Expand Down Expand Up @@ -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
}
2 changes: 2 additions & 0 deletions native/monogame/include/api_MGP.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
25 changes: 21 additions & 4 deletions native/monogame/include/api_enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -500,6 +502,7 @@ enum class MGGraphicsBackend : mgint
OpenGL = 1,
Vulkan = 2,
Metal = 3,
DirectX12 = 4,
};

enum class MGSystemCursor : mgint
Expand All @@ -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,
};

11 changes: 11 additions & 0 deletions native/monogame/include/api_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

142 changes: 117 additions & 25 deletions native/monogame/sdl/MGP_sdl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@

struct MGP_Platform
{
std::vector<MGP_Window*> windows;
std::vector<MGP_Window*> windows;
std::queue<MGP_Event> queued_events;
std::map<mgint, SDL_GameController*> controllers;
};

static std::map<int, MGKeys> s_keymap
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

0 comments on commit 87f2868

Please sign in to comment.