diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..08dfb09 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required (VERSION 3.1) + +project ("WinGamingInput" LANGUAGES CXX) + +# require c++20 +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +add_library (WinGamingInput SHARED "src/WindowsGamingInput.cpp" "include/WindowsGamingInput.h" "exports.def") + +# use static runtime lib for msvc +set_target_properties(WinGamingInput PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +target_link_libraries(WinGamingInput PRIVATE runtimeobject) diff --git a/exports.def b/exports.def new file mode 100644 index 0000000..c3f8976 --- /dev/null +++ b/exports.def @@ -0,0 +1,25 @@ +EXPORTS + AddControllerChanged=?AddControllerChanged@WindowsGamingInput@@YAXP6AXW4EventType@1@W4ControllerType@1@V?$variant@_KV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@@std@@@Z@Z + RemoveControllerChanged=?RemoveControllerChanged@WindowsGamingInput@@YAXP6AXW4EventType@1@W4ControllerType@1@V?$variant@_KV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@@std@@@Z@Z + + Gamepad_GetCount=?GetCount@Gamepad@WindowsGamingInput@@YA_KXZ + Gamepad_IsConnected=?IsConnected@Gamepad@WindowsGamingInput@@YA_N_K@Z + Gamepad_IsWireless=?IsWireless@Gamepad@WindowsGamingInput@@YA_N_KAEA_N@Z + Gamepad_GetBatteryStatus=?GetBatteryStatus@Gamepad@WindowsGamingInput@@YA_N_KAEAW4BatteryStatus@2@AEAN@Z + Gamepad_GetState=?GetState@Gamepad@WindowsGamingInput@@YA_N_KAEAUGamepadState@2@@Z + Gamepad_GetVibration=?GetVibration@Gamepad@WindowsGamingInput@@YA_N_KAEAUVibration@2@@Z + Gamepad_SetVibration=?SetVibration@Gamepad@WindowsGamingInput@@YA_N_KAEBUVibration@2@@Z + + RawGameController_GetCount=?GetCount@RawGameController@WindowsGamingInput@@YA_KXZ + RawGameController_GetControllers=?GetControllers@RawGameController@WindowsGamingInput@@YA_KPEAUDescription@RawController@2@_K@Z + RawGameController_GetController=?GetController@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@AEAUDescription@RawController@2@@Z + RawGameController_GetButtonLabel=?GetButtonLabel@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@_KAEAW4ButtonLabel@2@@Z + RawGameController_IsConnected=?IsConnected@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@@Z + RawGameController_IsWireless=?IsWireless@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@AEA_N@Z + RawGameController_GetBatteryStatus=?GetBatteryStatus@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@AEAW4BatteryStatus@2@AEAN@Z + RawGameController_GetState=?GetState@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@PEA_N_KPEAW4SwitchPosition@2@2PEAN2AEA_K@Z + RawGameController_IsVibrating=?IsVibrating@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@@Z + RawGameController_SetVibration=?SetVibration@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@N@Z + RawGameController_HasVibration=?HasVibration@RawGameController@WindowsGamingInput@@YA_NV?$basic_string_view@_WU?$char_traits@_W@std@@@std@@@Z + + diff --git a/include/WindowsGamingInput.h b/include/WindowsGamingInput.h new file mode 100644 index 0000000..09dcbf2 --- /dev/null +++ b/include/WindowsGamingInput.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#ifndef NO_MIN_MAX +#define NO_MIN_MAX +#endif + +#include + +#ifndef DLLEXPORT +#define DLLEXPORT +#endif + +namespace WindowsGamingInput +{ + // == ABI::Windows::Gaming::Input::GamepadButtons + enum class GamepadButtons : unsigned int + { + None = 0, + Menu = 0x1, + View = 0x2, + A = 0x4, + B = 0x8, + X = 0x10, + Y = 0x20, + DPadUp = 0x40, + DPadDown = 0x80, + DPadLeft = 0x100, + DPadRight = 0x200, + LeftShoulder = 0x400, + RightShoulder = 0x800, + LeftThumbstick = 0x1000, + RightThumbstick = 0x2000, + Paddle1 = 0x4000, + Paddle2 = 0x8000, + Paddle3 = 0x10000, + Paddle4 = 0x20000, + }; + + DEFINE_ENUM_FLAG_OPERATORS(GamepadButtons) + + // == ABI::Windows::Gaming::Input::GamepadReading + struct GamepadState + { + uint64_t Timestamp; + GamepadButtons Buttons; + double LeftTrigger; + double RightTrigger; + double LeftThumbstickX; + double LeftThumbstickY; + double RightThumbstickX; + double RightThumbstickY; + }; + + // == ABI::Windows::Gaming::Input::GamepadVibration + struct Vibration + { + double LeftMotor = 0; + double RightMotor = 0; + double LeftTrigger = 0; + double RightTrigger = 0; + }; + + // == ABI::Windows::System::Power::BatteryStatus + enum class BatteryStatus + { + NotPresent = 0, + Discharging = 1, + Idle = 2, + Charging = 3, + }; + + // == ABI::Windows::Gaming::Input::GameControllerSwitchPosition + enum class SwitchPosition + { + Center = 0, + Up = 1, + UpRight = 2, + Right = 3, + DownRight = 4, + Down = 5, + DownLeft = 6, + Left = 7, + UpLeft = 8, + }; + + enum class ButtonLabel : int // == ABI::Windows::Gaming::Input::GameControllerButtonLabel + { + None = 0, + XboxBack = 1, + XboxStart = 2, + XboxMenu = 3, + XboxView = 4, + XboxUp = 5, + XboxDown = 6, + XboxLeft = 7, + XboxRight = 8, + XboxA = 9, + XboxB = 10, + XboxX = 11, + XboxY = 12, + XboxLeftBumper = 13, + XboxLeftTrigger = 14, + XboxLeftStickButton = 15, + XboxRightBumper = 16, + XboxRightTrigger = 17, + XboxRightStickButton = 18, + XboxPaddle1 = 19, + XboxPaddle2 = 20, + XboxPaddle3 = 21, + XboxPaddle4 = 22, + Mode = 23, + Select = 24, + Menu = 25, + View = 26, + Back = 27, + Start = 28, + Options = 29, + Share = 30, + Up = 31, + Down = 32, + Left = 33, + Right = 34, + LetterA = 35, + LetterB = 36, + LetterC = 37, + LetterL = 38, + LetterR = 39, + LetterX = 40, + LetterY = 41, + LetterZ = 42, + Cross = 43, + Circle = 44, + Square = 45, + Triangle = 46, + LeftBumper = 47, + LeftTrigger = 48, + LeftStickButton = 49, + Left1 = 50, + Left2 = 51, + Left3 = 52, + RightBumper = 53, + RightTrigger = 54, + RightStickButton = 55, + Right1 = 56, + Right2 = 57, + Right3 = 58, + Paddle1 = 59, + Paddle2 = 60, + Paddle3 = 61, + Paddle4 = 62, + Plus = 63, + Minus = 64, + DownLeftArrow = 65, + DialLeft = 66, + DialRight = 67, + Suspension = 68, + }; + + enum class ControllerType + { + RawController, + Gamepad, + }; + + enum class EventType + { + ControllerAdded, + ControllerRemoved, + }; + + using ControllerChanged_t = void (*)(EventType type, ControllerType controller, std::variant uid); + DLLEXPORT void AddControllerChanged(ControllerChanged_t cb); + DLLEXPORT void RemoveControllerChanged(ControllerChanged_t cb); + + namespace Gamepad + { + DLLEXPORT size_t GetCount(); + DLLEXPORT bool IsConnected(size_t index); + DLLEXPORT bool GetState(size_t index, GamepadState& state); + + DLLEXPORT bool SetVibration(size_t index, const Vibration& vibration); + DLLEXPORT bool GetVibration(size_t index, Vibration& vibration); + + DLLEXPORT bool IsWireless(size_t index, bool& wireless); + DLLEXPORT bool GetBatteryStatus(size_t index, BatteryStatus& status, double& battery); + } + + namespace RawController + { + struct Description + { + wchar_t uid[256]; + wchar_t display_name[256]; + + size_t button_count; + size_t switches_count; + size_t axis_count; + }; + // + DLLEXPORT size_t GetCount(); + DLLEXPORT size_t GetControllers(Description* controllers, size_t count); + DLLEXPORT bool GetController(std::wstring_view uid, Description& description); + DLLEXPORT bool GetButtonLabel(std::wstring_view uid, size_t button, ButtonLabel& label); + + DLLEXPORT bool IsConnected(std::wstring_view uid); + DLLEXPORT bool GetState(std::wstring_view uid, bool* buttons, size_t button_count, SwitchPosition* switches, size_t switch_count, double* axis, size_t axis_count, uint64_t& timestamp); + + DLLEXPORT bool SetVibration(std::wstring_view uid, double vibration); + DLLEXPORT bool IsVibrating(std::wstring_view uid); + DLLEXPORT bool HasVibration(std::wstring_view uid); + + DLLEXPORT bool IsWireless(std::wstring_view uid, bool& wireless); + DLLEXPORT bool GetBatteryStatus(std::wstring_view uid, BatteryStatus& status, double& battery); + } +} + diff --git a/src/WindowsGamingInput.cpp b/src/WindowsGamingInput.cpp new file mode 100644 index 0000000..01dbee6 --- /dev/null +++ b/src/WindowsGamingInput.cpp @@ -0,0 +1,842 @@ +#define DLLEXPORT __declspec(dllexport) + +#include "../include/WindowsGamingInput.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Gaming::Input; +using namespace ABI::Windows::Devices::Haptics; +using namespace Microsoft::WRL; +using namespace Wrappers; + +RoInitializeWrapper g_ro{RO_INIT_MULTITHREADED}; +std::mutex g_cb_mutex; +std::vector g_callbacks; + +#pragma region Gamepad +IGamepadStatics* g_gamepad_statics = nullptr; +using GamepadPtr = ComPtr; +std::vector g_gamepads; +std::shared_mutex g_gamepad_mutex; + +void ScanGamepads() +{ + ComPtr> gamepads; + auto hr = g_gamepad_statics->get_Gamepads(&gamepads); + assert(SUCCEEDED(hr)); + + uint32_t count = 0; + hr = gamepads->get_Size(&count); + assert(SUCCEEDED(hr)); + +#ifdef _DEBUG + std::cout << count << " gamepads are connected" << std::endl; +#endif + + for (uint32_t i = 0; i < count; ++i) + { + ComPtr gamepad; + hr = gamepads->GetAt(i, &gamepad); + assert(SUCCEEDED(hr)); + + std::unique_lock lock(g_gamepad_mutex); + const auto it = std::ranges::find(std::as_const(g_gamepads), gamepad); + if (it == g_gamepads.cend()) + { + g_gamepads.emplace_back(gamepad); +#ifdef _DEBUG + std::cout << "inserted new gamepad" << std::endl; +#endif + lock.unlock(); + + std::scoped_lock cb_lock(g_cb_mutex); + for (const auto& cb : g_callbacks) + { + cb(WindowsGamingInput::EventType::ControllerAdded, WindowsGamingInput::ControllerType::Gamepad, g_gamepads.size() - 1); + } + } + } +} + +EventRegistrationToken g_add_gamepad_token; +HRESULT OnGamepadAdded(IInspectable* sender, IGamepad* gamepad) +{ +#ifdef _DEBUG + std::cout << "OnGamepadAdded" << std::endl; +#endif + + const ComPtr ptr{ gamepad }; + + std::unique_lock lock(g_gamepad_mutex); + const auto it = std::ranges::find(std::as_const(g_gamepads), ptr); + if (it != g_gamepads.cend()) + return S_OK; + + size_t index = (size_t)-1; + // check if we still got a free index in our internal list + for(size_t i = 0; i < g_gamepads.size(); ++i) + { + if (!g_gamepads[i]) + { + g_gamepads[i] = ptr; +#ifdef _DEBUG + std::cout << "OnGamepadAdded:inserted new gamepad at empty index" << std::endl; +#endif + index = i; + } + } + + // no free index + if (index == (size_t)-1) + { + g_gamepads.emplace_back(gamepad); + index = g_gamepads.size() - 1; +#ifdef _DEBUG + std::cout << "inserted new gamepad at the end" << std::endl; +#endif + } + + lock.unlock(); + + std::scoped_lock cb_lock(g_cb_mutex); + for (const auto& cb : g_callbacks) + { + cb(WindowsGamingInput::EventType::ControllerAdded, WindowsGamingInput::ControllerType::Gamepad, index); + } + + return S_OK; +} + +EventRegistrationToken g_remove_gamepad_token; +HRESULT OnGamepadRemoved(IInspectable* sender, IGamepad* gamepad) +{ +#ifdef _DEBUG + std::cout << "OnGamepadRemoved" << std::endl; +#endif + + std::unique_lock lock(g_gamepad_mutex); + for (size_t i = 0; i < g_gamepads.size(); ++i) + { + if(g_gamepads[i].Get() == gamepad) + { + g_gamepads[i].Reset(); +#ifdef _DEBUG + std::cout << "OnGamepadRemoved: removed known gamepad from internal list" << std::endl; +#endif + lock.unlock(); + + std::scoped_lock cb_lock(g_cb_mutex); + for (const auto& cb : g_callbacks) + { + cb(WindowsGamingInput::EventType::ControllerRemoved, WindowsGamingInput::ControllerType::Gamepad, i); + } + + break; + } + } + + return S_OK; +} +#pragma endregion + +#pragma region RawGameController +IRawGameControllerStatics* g_rcontroller_statics = nullptr; +using RControllerPtr = ComPtr; + +struct wstring_hash +{ + using is_transparent = void; + using key_equal = std::equal_to<>; + using hash_type = std::hash; + size_t operator()(std::wstring_view str) const { return hash_type{}(str); } + size_t operator()(const std::wstring& str) const { return hash_type{}(str); } + size_t operator()(const wchar_t* str) const { return hash_type{}(str); } +}; + +std::unordered_map g_rcontrollers; +std::shared_mutex g_rcontroller_mutex; + +// https://docs.microsoft.com/en-us/uwp/api/windows.devices.haptics.knownsimplehapticscontrollerwaveforms +// ABI::Windows::Devices::Haptics::IKnownSimpleHapticsControllerWaveformsStatics::get_RumbleContinuous() +constexpr uint16_t kRumbleContinuous = 0x1005; + +void ScanRawGameControllers() +{ + ComPtr> controllers; + auto hr = g_rcontroller_statics->get_RawGameControllers(&controllers); + assert(SUCCEEDED(hr)); + + uint32_t count; + hr = controllers->get_Size(&count); + assert(SUCCEEDED(hr)); + +#ifdef _DEBUG + std::cout << count << " controllers are connected" << std::endl; +#endif + + // check for all connected controllers + for (uint32_t i = 0; i < count; ++i) + { + ComPtr controller; + hr = controllers->GetAt(i, &controller); + assert(SUCCEEDED(hr)); + + ComPtr controller2; + hr = controller.As(&controller2); + if (FAILED(hr)) // I guess shouldn't fail, idk (?) + continue; + + HSTRING tmp_name; + hr = controller2->get_NonRoamableId(&tmp_name); + if (FAILED(hr)) + continue; + + std::wstring name = WindowsGetStringRawBuffer(tmp_name, nullptr); + if (name.empty()) + continue; + + std::unique_lock lock(g_rcontroller_mutex); + if (!g_rcontrollers.contains(name)) + { + g_rcontrollers.emplace(name, controller); + lock.unlock(); +#ifdef _DEBUG + std::wcout << L"inserted new controller with uid: " << name << std::endl; +#endif + + std::scoped_lock cb_lock(g_cb_mutex); + for (const auto& cb : g_callbacks) + { + cb(WindowsGamingInput::EventType::ControllerAdded, WindowsGamingInput::ControllerType::RawController, name); + } + } + } +} + +EventRegistrationToken g_add_rcontroller_token; + +HRESULT OnRawGameControllerAdded(IInspectable* sender, IRawGameController* controller) +{ +#ifdef _DEBUG + std::cout << "OnRawGameControllerAdded" << std::endl; +#endif + + ComPtr controller2; + HRESULT hr = controller->QueryInterface(__uuidof(IRawGameController2), &controller2); + if (SUCCEEDED(hr)) + { + HSTRING tmp_name; + hr = controller2->get_NonRoamableId(&tmp_name); + if (SUCCEEDED(hr)) + { + const std::wstring name = WindowsGetStringRawBuffer(tmp_name, nullptr); + std::unique_lock lock(g_rcontroller_mutex); + if (!g_rcontrollers.contains(name)) + { + g_rcontrollers.emplace(name, controller); +#ifdef _DEBUG + std::wcout << L"OnRawGameControllerAdded: added new controller with uid: " << name << std::endl; +#endif + lock.unlock(); + + std::scoped_lock cb_lock(g_cb_mutex); + for (const auto& cb : g_callbacks) + { + cb(WindowsGamingInput::EventType::ControllerAdded, WindowsGamingInput::ControllerType::RawController, name); + } + } + } + } + + return S_OK; +} + +EventRegistrationToken g_remove_rcontroller_token; + +HRESULT OnRawGameControllerRemoved(IInspectable* sender, IRawGameController* controller) +{ +#ifdef _DEBUG + std::cout << "OnRawGameControllerRemoved" << std::endl; +#endif + + ComPtr controller2; + HRESULT hr = controller->QueryInterface(IID_PPV_ARGS(&controller2)); + if (SUCCEEDED(hr)) + { + HSTRING tmp_name; + hr = controller2->get_NonRoamableId(&tmp_name); + if (SUCCEEDED(hr)) + { + const std::wstring name = WindowsGetStringRawBuffer(tmp_name, nullptr); + + std::unique_lock lock(g_rcontroller_mutex); + const auto erased = g_rcontrollers.erase(name) == 1; + lock.unlock(); +#ifdef _DEBUG + std::cout << "OnRawGameControllerRemoved: removed known controller: " << erased << std::endl; +#endif + + std::scoped_lock cb_lock(g_cb_mutex); + for (const auto& cb : g_callbacks) + { + cb(WindowsGamingInput::EventType::ControllerRemoved, WindowsGamingInput::ControllerType::RawController, name); + } + } + } + + return S_OK; +} +#pragma endregion + +BOOL WINAPI DllMain(HINSTANCE hinstance, DWORD reason, LPVOID reserved) +{ + if (reason == DLL_PROCESS_ATTACH) + { + std::thread([]() + { + if (!g_gamepad_statics) + { + auto hr = RoGetActivationFactory(HStringReference(L"Windows.Gaming.Input.Gamepad").Get(), + __uuidof(IGamepadStatics), (void**)&g_gamepad_statics); + assert(SUCCEEDED(hr)); + + hr = g_gamepad_statics->add_GamepadAdded( + Callback<__FIEventHandler_1_Windows__CGaming__CInput__CGamepad>(OnGamepadAdded).Get(), + &g_add_gamepad_token); + assert(SUCCEEDED(hr)); + + hr = g_gamepad_statics->add_GamepadRemoved( + Callback<__FIEventHandler_1_Windows__CGaming__CInput__CGamepad>(OnGamepadRemoved).Get(), + &g_remove_gamepad_token); + assert(SUCCEEDED(hr)); + +#ifdef _DEBUG + std::cout << "Windows.Gaming.Input.Gamepad initialized" << std::endl; +#endif + } + + ScanGamepads(); + + if (!g_rcontroller_statics) + { + auto hr = RoGetActivationFactory(HStringReference(L"Windows.Gaming.Input.RawGameController").Get(), + __uuidof(IRawGameControllerStatics), (void**)&g_rcontroller_statics); + assert(SUCCEEDED(hr)); + + hr = g_rcontroller_statics->add_RawGameControllerAdded( + Callback<__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController>(OnRawGameControllerAdded). + Get(), + &g_add_rcontroller_token); + assert(SUCCEEDED(hr)); + + hr = g_rcontroller_statics->add_RawGameControllerRemoved( + Callback<__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController>( + OnRawGameControllerRemoved).Get(), + &g_remove_rcontroller_token); + assert(SUCCEEDED(hr)); + +#ifdef _DEBUG + std::cout << "Windows.Gaming.Input.RawGameController initialized" << std::endl; +#endif + } + ScanRawGameControllers(); + }).detach(); + } + else if (reason == DLL_PROCESS_DETACH) + { + // callbacks detach + { + std::scoped_lock lock(g_cb_mutex); + g_callbacks.clear(); + } + + // gamepad detach + { + std::scoped_lock lock(g_gamepad_mutex); + g_gamepads.clear(); + if (g_gamepad_statics) + { + g_gamepad_statics->remove_GamepadAdded(g_add_rcontroller_token); + g_gamepad_statics->remove_GamepadRemoved(g_remove_rcontroller_token); + } + } + + + // raw game controller detach + { + std::scoped_lock lock(g_rcontroller_mutex); + g_rcontrollers.clear(); + if (g_rcontroller_statics) + { + g_rcontroller_statics->remove_RawGameControllerAdded(g_add_rcontroller_token); + g_rcontroller_statics->remove_RawGameControllerRemoved(g_remove_rcontroller_token); + } + } + } + + return TRUE; +} + +namespace WindowsGamingInput +{ + void AddControllerChanged(ControllerChanged_t cb) + { + std::scoped_lock lock(g_cb_mutex); + if(std::ranges::find(std::as_const(g_callbacks), cb) == g_callbacks.cend()) + g_callbacks.emplace_back(cb); + } + + void RemoveControllerChanged(ControllerChanged_t cb) + { + std::scoped_lock lock(g_cb_mutex); + const auto rm = std::ranges::remove(g_callbacks, cb); + g_callbacks.erase(rm.begin(), rm.end()); + } + + bool GetBatteryInfo(ComPtr battery_info, BatteryStatus& status, double& battery) + { + ComPtr report; + HRESULT hr = battery_info->TryGetBatteryReport(&report); + if (FAILED(hr) || !report) + return false; + + static_assert(sizeof(BatteryStatus) == sizeof(ABI::Windows::System::Power::BatteryStatus)); + hr = report->get_Status((ABI::Windows::System::Power::BatteryStatus*)&status); + assert(SUCCEEDED(hr)); + + ComPtr<__FIReference_1_int> remaining_ptr, full_ptr; + report->get_RemainingCapacityInMilliwattHours(&remaining_ptr); + report->get_FullChargeCapacityInMilliwattHours(&full_ptr); + + int remaining = 0, full = 0; + if (remaining_ptr) + { + hr = remaining_ptr->get_Value(&remaining); + assert(SUCCEEDED(hr)); + } + + if (full_ptr) + { + hr = full_ptr->get_Value(&full); + assert(SUCCEEDED(hr)); + } + + // remaining is always 100 when connected and status discharching (?!) -> check for IsWireless before + battery = full <= 0 ? 0 : static_cast(remaining) / static_cast(full); + return true; + } + + namespace Gamepad + { + size_t GetCount() + { + std::shared_lock lock(g_gamepad_mutex); + return g_gamepads.size(); + } + + bool IsConnected(size_t index) + { + GamepadState tmp; + return GetState(index, tmp); + } + + bool GetState(size_t index, GamepadState& state) + { + std::shared_lock lock(g_gamepad_mutex); + if (index >= g_gamepads.size()) + return false; + + const auto gamepad = g_gamepads[index]; + lock.unlock(); + + if (!gamepad) + return false; + + return SUCCEEDED(gamepad->GetCurrentReading((GamepadReading*)&state)); + } + + bool SetVibration(size_t index, const Vibration& vibration) + { + std::shared_lock lock(g_gamepad_mutex); + if (index >= g_gamepads.size()) + return false; + + const auto gamepad = g_gamepads[index]; + lock.unlock(); + + if (!gamepad) + return false; + + static_assert(sizeof(Vibration) == sizeof(GamepadVibration)); + GamepadVibration tmp; + memcpy(&tmp, &vibration, sizeof(GamepadVibration)); + return SUCCEEDED(gamepad->put_Vibration(tmp)); + } + + bool GetVibration(size_t index, Vibration& vibration) + { + std::shared_lock lock(g_gamepad_mutex); + if (index >= g_gamepads.size()) + return false; + + const auto gamepad = g_gamepads[index]; + lock.unlock(); + + if (!gamepad) + return false; + + static_assert(sizeof(Vibration) == sizeof(GamepadVibration)); + return SUCCEEDED(gamepad->get_Vibration((GamepadVibration*)&vibration)); + } + + bool IsWireless(size_t index, bool& wireless) + { + std::shared_lock lock(g_gamepad_mutex); + if (index >= g_gamepads.size()) + return false; + + const auto gamepad = g_gamepads[index]; + lock.unlock(); + + if (!gamepad) + return false; + + ComPtr controller; + auto hr = gamepad.As(&controller); + assert(SUCCEEDED(hr)); + + static_assert(sizeof(bool) == sizeof(boolean)); + return SUCCEEDED(controller->get_IsWireless((boolean*)&wireless)); + } + + bool GetBatteryStatus(size_t index, BatteryStatus& status, double& battery) + { + std::shared_lock lock(g_gamepad_mutex); + if (index >= g_gamepads.size()) + return false; + + const auto gamepad = g_gamepads[index]; + lock.unlock(); + + if (!gamepad) + return false; + + ComPtr battery_info; + auto hr = gamepad.As(&battery_info); + assert(SUCCEEDED(hr)); + + return GetBatteryInfo(battery_info, status, battery); + } + } + + namespace RawGameController + { + size_t GetCount() + { + std::shared_lock lock(g_rcontroller_mutex); + return g_rcontrollers.size(); + } + + size_t GetControllers(RawController::Description* controllers, size_t count) + { + std::shared_lock lock(g_rcontroller_mutex); + if (controllers == nullptr) + return g_rcontrollers.size(); // return size if no buffer have been given + + size_t result = 0; + for (const auto& kv : g_rcontrollers) + { + if (result >= count) + break; + + ComPtr controller2; + kv.second.As(&controller2); + + HSTRING tmp_name; + controller2->get_DisplayName(&tmp_name); + + const std::wstring name = WindowsGetStringRawBuffer(tmp_name, nullptr); + + wcscpy_s(controllers[result].uid, kv.first.c_str()); + wcscpy_s(controllers[result].display_name, name.c_str()); + + controllers[result].axis_count = 0; + kv.second->get_AxisCount((int*)&controllers[result].axis_count); + + controllers[result].button_count = 0; + kv.second->get_ButtonCount((int*)&controllers[result].button_count); + + controllers[result].switches_count = 0; + kv.second->get_SwitchCount((int*)&controllers[result].switches_count); + + ++result; + } + + return result; + } + + bool GetController(std::wstring_view uid, RawController::Description& description) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + auto controller = it->second; + lock.unlock(); + + description.axis_count = 0; + controller->get_AxisCount((int*)&description.axis_count); + + description.button_count = 0; + controller->get_ButtonCount((int*)&description.button_count); + + description.switches_count = 0; + controller->get_SwitchCount((int*)&description.switches_count); + return true; + } + + bool IsConnected(std::wstring_view uid) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + return it != g_rcontrollers.cend(); + } + + bool GetState(std::wstring_view uid, bool* buttons, size_t button_count, SwitchPosition* switches, size_t switch_count, double* axis, size_t axis_count, uint64_t& timestamp) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + ComPtr controller; + auto hr = it->second.As(&controller); + assert(SUCCEEDED(hr)); + lock.unlock(); + + static_assert(sizeof(bool) == sizeof(boolean)); + hr = controller->GetCurrentReading((uint32_t)button_count, (boolean*)buttons, (uint32_t)switch_count, (GameControllerSwitchPosition*)switches, (uint32_t)axis_count, (double*)axis, ×tamp); + return SUCCEEDED(hr); + } + + bool HasVibration(std::wstring_view uid) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + ComPtr controller; + auto hr = it->second.As(&controller); + assert(SUCCEEDED(hr)); + lock.unlock(); + + ComPtr> haptics; + hr = controller->get_SimpleHapticsControllers(&haptics); + if (FAILED(hr)) + return false; + + uint32_t count = 0; + haptics->get_Size(&count); // motor_count (?) + + for (uint32_t i = 0; i < count; ++i) + { + ISimpleHapticsController* haptic; + haptics->GetAt(i, &haptic); + + ComPtr> feedbacks; + hr = haptic->get_SupportedFeedback(&feedbacks); + if (FAILED(hr)) + return false; + + uint32_t feedback_count = 0; + feedbacks->get_Size(&feedback_count); + for (uint32_t j = 0; j < feedback_count; ++j) + { + ISimpleHapticsControllerFeedback* feedback; + feedbacks->GetAt(j, &feedback); + + uint16_t waveform = 0; + feedback->get_Waveform(&waveform); + if (waveform == kRumbleContinuous) + return true; + } + } + + return false; + } + + bool SetVibration(std::wstring_view uid, double vibration) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + ComPtr controller; + auto hr = it->second.As(&controller); + assert(SUCCEEDED(hr)); + lock.unlock(); + + ComPtr> haptics; + hr = controller->get_SimpleHapticsControllers(&haptics); + assert(SUCCEEDED(hr)); + + bool result = false; + uint32_t count = 0; + haptics->get_Size(&count); + for (uint32_t i = 0; i < count; ++i) + { + ISimpleHapticsController* haptic; + haptics->GetAt(i, &haptic); + + if (vibration <= 0.000001) + { + haptic->StopFeedback(); + continue; + } + + ComPtr> feedbacks; + hr = haptic->get_SupportedFeedback(&feedbacks); + if (FAILED(hr)) + return false; + + uint32_t feedback_count = 0; + feedbacks->get_Size(&feedback_count); + for (uint32_t j = 0; j < feedback_count; ++j) + { + ISimpleHapticsControllerFeedback* feedback; + feedbacks->GetAt(j, &feedback); + + uint16_t waveform = 0; + feedback->get_Waveform(&waveform); + if (waveform == kRumbleContinuous) + { + haptic->SendHapticFeedbackWithIntensity(feedback, vibration); + result = true; + break; + } + } + } + return result; + } + + bool IsVibrating(std::wstring_view uid) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + ComPtr controller; + auto hr = it->second.As(&controller); + assert(SUCCEEDED(hr)); + lock.unlock(); + + ComPtr> haptics; + hr = controller->get_SimpleHapticsControllers(&haptics); + assert(SUCCEEDED(hr)); + + uint32_t count = 0; + haptics->get_Size(&count); + for (uint32_t i = 0; i < count; ++i) + { + ISimpleHapticsController* haptic; + haptics->GetAt(i, &haptic); + + ComPtr> feedbacks; + hr = haptic->get_SupportedFeedback(&feedbacks); + if (FAILED(hr)) + return false; + + uint32_t feedback_count = 0; + feedbacks->get_Size(&feedback_count); + for (uint32_t j = 0; j < feedback_count; ++j) + { + ISimpleHapticsControllerFeedback* feedback; + feedbacks->GetAt(j, &feedback); + + uint16_t waveform = 0; + feedback->get_Waveform(&waveform); + if (waveform == kRumbleContinuous) + { + ABI::Windows::Foundation::TimeSpan ts{}; + feedback->get_Duration(&ts); + if (ts.Duration != 0) + return true; + } + } + } + return false; + } + + bool IsWireless(std::wstring_view uid, bool& wireless) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + ComPtr controller; + auto hr = it->second.As(&controller); + assert(SUCCEEDED(hr)); + lock.unlock(); + + static_assert(sizeof(bool) == sizeof(boolean)); + return SUCCEEDED(controller->get_IsWireless((boolean*)&wireless)); + } + + bool GetBatteryStatus(std::wstring_view uid, BatteryStatus& status, double& battery) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + ComPtr controller; + auto hr = it->second.As(&controller); + assert(SUCCEEDED(hr)); + lock.unlock(); + + ComPtr battery_info; + hr = controller.As(&battery_info); + if(FAILED(hr) || !battery_info) + return false; + + return GetBatteryInfo(battery_info, status, battery); + } + + bool GetButtonLabel(std::wstring_view uid, size_t button, ButtonLabel& label) + { + std::shared_lock lock(g_rcontroller_mutex); + const auto it = g_rcontrollers.find(uid); + if (it == g_rcontrollers.cend()) + return false; + + auto controller = it->second; + lock.unlock(); + + int max_count = 0; + controller->get_ButtonCount(&max_count); + if ((int)button >= max_count) + return false; + + static_assert(sizeof(ButtonLabel) == sizeof(GameControllerButtonLabel)); + return SUCCEEDED(controller->GetButtonLabel((int)button, (GameControllerButtonLabel*)&label)); + } + } +}