├── .gitattributes ├── .gitignore ├── Build.bat ├── CMakeLists.txt ├── CMakeSettings.json ├── DeclareDPIAware.manifest ├── DualSense4Windows.exe.manifest ├── LICENSE ├── README.md ├── external ├── ViGEm │ ├── Client.h │ └── Common.h ├── crc │ └── CRC.h ├── hidapi │ └── hidapi.h └── sigslot │ └── signal.hpp ├── include ├── Base64.hxx ├── ColorPicker.hxx ├── Core.hxx ├── DualSense.hxx ├── DualSense4Windows.hxx ├── DualSenseInfo.hxx ├── DualSenseList.hxx ├── DualSenseListEntry.hxx ├── DualSenseListHeader.hxx ├── Interface.hxx ├── Logger.hxx ├── MainWindow.hxx ├── Math.hxx ├── Squirrel.hxx ├── Types.hxx ├── Utils.hxx └── Window.hxx └── src ├── Base64.cxx ├── ColorPicker.cxx ├── DualSense.cxx ├── DualSense4Windows.cxx ├── DualSenseList.cxx ├── DualSenseListEntry.cxx ├── DualSenseListHeader.cxx ├── Interface.cxx ├── MainWindow.cxx └── Utils.cxx /.gitattributes: -------------------------------------------------------------------------------- 1 | *.hxx linguist-language=C++ 2 | *.cxx linguist-language=C++ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | 13 | bin 14 | lib 15 | out 16 | build 17 | .vs 18 | projects 19 | external 20 | *.ninja 21 | -------------------------------------------------------------------------------- /Build.bat: -------------------------------------------------------------------------------- 1 | cmake -G "Visual Studio 16 2019" -B "projects/Visual Studio 2019" -S ./ 2 | cmake --build "projects/Visual Studio 2019" --config Debug 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | set(CMAKE_CXX_STANDARD 17) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | 5 | set(APP DualSense4Windows) 6 | set(QT E:/libs/Qt/6.0.2/6.0.2/msvc2019_64) 7 | set(ASSET_PACKER AssetPacker) 8 | project(${APP}) 9 | 10 | 11 | set(CMAKE_SUPPRESS_REGENERATION true) 12 | set(SRC_INC 13 | include/Core.hxx 14 | include/DualSense4Windows.hxx 15 | include/DualSense.hxx 16 | include/Math.hxx 17 | include/Utils.hxx 18 | include/Interface.hxx 19 | include/Types.hxx 20 | include/Window.hxx 21 | include/MainWindow.hxx 22 | include/ColorPicker.hxx 23 | include/DualSenseInfo.hxx 24 | include/DualSenseList.hxx 25 | include/DualSenseListHeader.hxx 26 | include/DualSenseListEntry.hxx 27 | ) 28 | 29 | set(SRC 30 | src/DualSense4Windows.cxx 31 | src/DualSense.cxx 32 | src/Utils.cxx 33 | src/Interface.cxx 34 | src/MainWindow.cxx 35 | src/ColorPicker.cxx 36 | src/DualSenseList.cxx 37 | src/DualSenseListHeader.cxx 38 | src/DualSenseListEntry.cxx 39 | DualSense4Windows.exe.manifest 40 | DeclareDPIAware.manifest 41 | ) 42 | 43 | set(ASSETS "$/assets") 44 | 45 | 46 | add_executable(${APP} WIN32 ${SRC} ${SRC_INC}) 47 | target_link_directories(${APP} PUBLIC lib/) 48 | target_link_directories(${APP} PUBLIC ${QT}/lib) 49 | target_link_libraries(${APP} comctl32.lib) 50 | target_link_libraries(${APP} hidapi.lib) 51 | target_link_libraries(${APP} setupapi.lib) 52 | target_link_libraries(${APP} ViGEmClient.lib) 53 | target_link_libraries(${APP} Qt6Core.lib) 54 | target_link_libraries(${APP} Qt6GUI.lib) 55 | target_link_libraries(${APP} Qt6Widgets.lib) 56 | target_include_directories(${APP} PUBLIC external/) 57 | target_include_directories(${APP} PUBLIC ${QT}/include) 58 | target_include_directories(${APP} PRIVATE include/) 59 | 60 | 61 | add_custom_command(TARGET ${APP} POST_BUILD 62 | COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/bin" "$") 63 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "cmakeCommandArgs": "", 11 | "buildCommandArgs": "", 12 | "ctestCommandArgs": "", 13 | "variables": [] 14 | }, 15 | { 16 | "name": "x64-Release", 17 | "generator": "Ninja", 18 | "configurationType": "RelWithDebInfo", 19 | "buildRoot": "${projectDir}\\out\\build\\${name}", 20 | "installRoot": "${projectDir}\\out\\install\\${name}", 21 | "cmakeCommandArgs": "", 22 | "buildCommandArgs": "", 23 | "ctestCommandArgs": "", 24 | "inheritEnvironments": [ "msvc_x64_x64" ], 25 | "variables": [] 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /DeclareDPIAware.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DualSense4Windows.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DualSenseController4Windows 5 | 6 | 7 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marcel Kulina 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DualSense4Windows 2 | DualSense Support for any game based on emulation provided by [ViGEm](https://github.com/ViGEm/ViGEmClient). 3 | 4 | ## Features 5 | Get the most out of your DualSense controller. 6 | Full support for any game using XInput. 7 | Enhanced features like touchpad mouse controls. 8 | Freely bind any action your controller is capable of to your desire. 9 | * Creates a virtual DualShock4(Xbox Support WIP) from any connected DualSense. 10 | * Lightbar Support 11 | * Player LED Support 12 | * Adaptive Trigger Support 13 | * Rumble Support 14 | * WIP: 15 | * Support for touchpad 16 | * Multiple Controllers 17 | * Profiles 18 | * Persistence 19 | 20 | ## Installation 21 | Just grab the latest release under [releases](https://github.com/broken-bytes/DualSense4Windows/releases) and run it. 22 | 23 | You'll need to have [ViGEm Bus](https://github.com/ViGEm/ViGEmBus) installed. 24 | 25 | ## Building 26 | ### Prerequisites 27 | You'll need to have the following components installed: 28 | * CMake(tested with 3.14) 29 | * Visual Studio 2019 (Any Edition) 30 | * [ViGEm Bus](https://github.com/ViGEm/ViGEmBus) 31 | 32 | You need to provide library files for the following components used 33 | * [ViGEmClient](https://github.com/ViGEm/ViGEmClient) 34 | * [hidapi](https://github.com/libusb/hidapi) 35 | 36 | ### Build Steps 37 | * First, create a dynamic library build for hidapi and ViGEmClient. 38 | * Place `hidapi.lib`, `ViGEmClient.lib` inside the `lib` folder of the project. 39 | * Also place the corrosponding `.dll` files in the `bin` folder of your project root folder 40 | * Execute `CreateVisualStudioSolution.bat` 41 | * Open `DualSense4Windows.sln` created in your `build` folder 42 | * Create a build 43 | -------------------------------------------------------------------------------- /external/ViGEm/Client.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/broken-bytes/DualSense4Windows/87f376e7b28de3d4aa6287ef05a432131d573e21/external/ViGEm/Client.h -------------------------------------------------------------------------------- /external/ViGEm/Common.h: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2019 Nefarius Software Solutions e.U. and Contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or ell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | 25 | 26 | #pragma once 27 | 28 | // 29 | // Represents the desired target type for the emulated device. 30 | // 31 | typedef enum _VIGEM_TARGET_TYPE 32 | { 33 | // 34 | // Microsoft Xbox 360 Controller (wired) 35 | // 36 | Xbox360Wired = 0, 37 | // 38 | // Sony DualShock 4 (wired) 39 | // 40 | DualShock4Wired = 2 // NOTE: 1 skipped on purpose to maintain compatibility 41 | 42 | } VIGEM_TARGET_TYPE, *PVIGEM_TARGET_TYPE; 43 | 44 | // 45 | // Possible XUSB report buttons. 46 | // 47 | typedef enum _XUSB_BUTTON 48 | { 49 | XUSB_GAMEPAD_DPAD_UP = 0x0001, 50 | XUSB_GAMEPAD_DPAD_DOWN = 0x0002, 51 | XUSB_GAMEPAD_DPAD_LEFT = 0x0004, 52 | XUSB_GAMEPAD_DPAD_RIGHT = 0x0008, 53 | XUSB_GAMEPAD_START = 0x0010, 54 | XUSB_GAMEPAD_BACK = 0x0020, 55 | XUSB_GAMEPAD_LEFT_THUMB = 0x0040, 56 | XUSB_GAMEPAD_RIGHT_THUMB = 0x0080, 57 | XUSB_GAMEPAD_LEFT_SHOULDER = 0x0100, 58 | XUSB_GAMEPAD_RIGHT_SHOULDER = 0x0200, 59 | XUSB_GAMEPAD_GUIDE = 0x0400, 60 | XUSB_GAMEPAD_A = 0x1000, 61 | XUSB_GAMEPAD_B = 0x2000, 62 | XUSB_GAMEPAD_X = 0x4000, 63 | XUSB_GAMEPAD_Y = 0x8000 64 | 65 | } XUSB_BUTTON, *PXUSB_BUTTON; 66 | 67 | // 68 | // Represents an XINPUT_GAMEPAD-compatible report structure. 69 | // 70 | typedef struct _XUSB_REPORT 71 | { 72 | USHORT wButtons; 73 | BYTE bLeftTrigger; 74 | BYTE bRightTrigger; 75 | SHORT sThumbLX; 76 | SHORT sThumbLY; 77 | SHORT sThumbRX; 78 | SHORT sThumbRY; 79 | 80 | } XUSB_REPORT, *PXUSB_REPORT; 81 | 82 | // 83 | // Initializes a _XUSB_REPORT structure. 84 | // 85 | VOID FORCEINLINE XUSB_REPORT_INIT( 86 | _Out_ PXUSB_REPORT Report 87 | ) 88 | { 89 | RtlZeroMemory(Report, sizeof(XUSB_REPORT)); 90 | } 91 | 92 | // 93 | // The color value (RGB) of a DualShock 4 Lightbar 94 | // 95 | typedef struct _DS4_LIGHTBAR_COLOR 96 | { 97 | // 98 | // Red part of the Lightbar (0-255). 99 | // 100 | UCHAR Red; 101 | 102 | // 103 | // Green part of the Lightbar (0-255). 104 | // 105 | UCHAR Green; 106 | 107 | // 108 | // Blue part of the Lightbar (0-255). 109 | // 110 | UCHAR Blue; 111 | 112 | } DS4_LIGHTBAR_COLOR, *PDS4_LIGHTBAR_COLOR; 113 | 114 | // 115 | // DualShock 4 digital buttons 116 | // 117 | typedef enum _DS4_BUTTONS 118 | { 119 | DS4_BUTTON_THUMB_RIGHT = 1 << 15, 120 | DS4_BUTTON_THUMB_LEFT = 1 << 14, 121 | DS4_BUTTON_OPTIONS = 1 << 13, 122 | DS4_BUTTON_SHARE = 1 << 12, 123 | DS4_BUTTON_TRIGGER_RIGHT = 1 << 11, 124 | DS4_BUTTON_TRIGGER_LEFT = 1 << 10, 125 | DS4_BUTTON_SHOULDER_RIGHT = 1 << 9, 126 | DS4_BUTTON_SHOULDER_LEFT = 1 << 8, 127 | DS4_BUTTON_TRIANGLE = 1 << 7, 128 | DS4_BUTTON_CIRCLE = 1 << 6, 129 | DS4_BUTTON_CROSS = 1 << 5, 130 | DS4_BUTTON_SQUARE = 1 << 4 131 | 132 | } DS4_BUTTONS, *PDS4_BUTTONS; 133 | 134 | // 135 | // DualShock 4 special buttons 136 | // 137 | typedef enum _DS4_SPECIAL_BUTTONS 138 | { 139 | DS4_SPECIAL_BUTTON_PS = 1 << 0, 140 | DS4_SPECIAL_BUTTON_TOUCHPAD = 1 << 1 141 | 142 | } DS4_SPECIAL_BUTTONS, *PDS4_SPECIAL_BUTTONS; 143 | 144 | // 145 | // DualShock 4 directional pad (HAT) values 146 | // 147 | typedef enum _DS4_DPAD_DIRECTIONS 148 | { 149 | DS4_BUTTON_DPAD_NONE = 0x8, 150 | DS4_BUTTON_DPAD_NORTHWEST = 0x7, 151 | DS4_BUTTON_DPAD_WEST = 0x6, 152 | DS4_BUTTON_DPAD_SOUTHWEST = 0x5, 153 | DS4_BUTTON_DPAD_SOUTH = 0x4, 154 | DS4_BUTTON_DPAD_SOUTHEAST = 0x3, 155 | DS4_BUTTON_DPAD_EAST = 0x2, 156 | DS4_BUTTON_DPAD_NORTHEAST = 0x1, 157 | DS4_BUTTON_DPAD_NORTH = 0x0 158 | 159 | } DS4_DPAD_DIRECTIONS, *PDS4_DPAD_DIRECTIONS; 160 | 161 | // 162 | // DualShock 4 HID Input report 163 | // 164 | typedef struct _DS4_REPORT 165 | { 166 | BYTE bThumbLX; 167 | BYTE bThumbLY; 168 | BYTE bThumbRX; 169 | BYTE bThumbRY; 170 | USHORT wButtons; 171 | BYTE bSpecial; 172 | BYTE bTriggerL; 173 | BYTE bTriggerR; 174 | 175 | } DS4_REPORT, *PDS4_REPORT; 176 | 177 | // 178 | // Sets the current state of the D-PAD on a DualShock 4 report. 179 | // 180 | VOID FORCEINLINE DS4_SET_DPAD( 181 | _Out_ PDS4_REPORT Report, 182 | _In_ DS4_DPAD_DIRECTIONS Dpad 183 | ) 184 | { 185 | Report->wButtons &= ~0xF; 186 | Report->wButtons |= (USHORT)Dpad; 187 | } 188 | 189 | VOID FORCEINLINE DS4_REPORT_INIT( 190 | _Out_ PDS4_REPORT Report 191 | ) 192 | { 193 | RtlZeroMemory(Report, sizeof(DS4_REPORT)); 194 | 195 | Report->bThumbLX = 0x80; 196 | Report->bThumbLY = 0x80; 197 | Report->bThumbRX = 0x80; 198 | Report->bThumbRY = 0x80; 199 | 200 | DS4_SET_DPAD(Report, DS4_BUTTON_DPAD_NONE); 201 | } 202 | 203 | -------------------------------------------------------------------------------- /external/hidapi/hidapi.h: -------------------------------------------------------------------------------- 1 | /******************************************************* 2 | HIDAPI - Multi-Platform library for 3 | communication with HID devices. 4 | Alan Ott 5 | Signal 11 Software 6 | 8/22/2009 7 | Copyright 2009, All Rights Reserved. 8 | At the discretion of the user of this library, 9 | this software may be licensed under the terms of the 10 | GNU General Public License v3, a BSD-Style license, or the 11 | original HIDAPI license as outlined in the LICENSE.txt, 12 | LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt 13 | files located at the root of the source distribution. 14 | These files may also be found in the public source 15 | code repository located at: 16 | https://github.com/libusb/hidapi . 17 | ********************************************************/ 18 | 19 | /** @file 20 | * @defgroup API hidapi API 21 | */ 22 | 23 | #ifndef HIDAPI_H__ 24 | #define HIDAPI_H__ 25 | 26 | #include 27 | 28 | #ifdef _WIN32 29 | #define HID_API_EXPORT __declspec(dllexport) 30 | #define HID_API_CALL 31 | #else 32 | #define HID_API_EXPORT /**< API export macro */ 33 | #define HID_API_CALL /**< API call macro */ 34 | #endif 35 | 36 | #define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ 37 | 38 | /** @brief Static/compile-time major version of the library. 39 | @ingroup API 40 | */ 41 | #define HID_API_VERSION_MAJOR 0 42 | /** @brief Static/compile-time minor version of the library. 43 | @ingroup API 44 | */ 45 | #define HID_API_VERSION_MINOR 10 46 | /** @brief Static/compile-time patch version of the library. 47 | @ingroup API 48 | */ 49 | #define HID_API_VERSION_PATCH 0 50 | 51 | /* Helper macros */ 52 | #define HID_API_AS_STR_IMPL(x) #x 53 | #define HID_API_AS_STR(x) HID_API_AS_STR_IMPL(x) 54 | #define HID_API_TO_VERSION_STR(v1, v2, v3) HID_API_AS_STR(v1.v2.v3) 55 | 56 | /** @brief Static/compile-time string version of the library. 57 | @ingroup API 58 | */ 59 | #define HID_API_VERSION_STR HID_API_TO_VERSION_STR(HID_API_VERSION_MAJOR, HID_API_VERSION_MINOR, HID_API_VERSION_PATCH) 60 | 61 | #ifdef __cplusplus 62 | extern "C" { 63 | #endif 64 | struct hid_api_version { 65 | int major; 66 | int minor; 67 | int patch; 68 | }; 69 | 70 | struct hid_device_; 71 | typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ 72 | 73 | /** hidapi info structure */ 74 | struct hid_device_info { 75 | /** Platform-specific device path */ 76 | char* path; 77 | /** Device Vendor ID */ 78 | unsigned short vendor_id; 79 | /** Device Product ID */ 80 | unsigned short product_id; 81 | /** Serial Number */ 82 | wchar_t* serial_number; 83 | /** Device Release Number in binary-coded decimal, 84 | also known as Device Version Number */ 85 | unsigned short release_number; 86 | /** Manufacturer String */ 87 | wchar_t* manufacturer_string; 88 | /** Product string */ 89 | wchar_t* product_string; 90 | /** Usage Page for this Device/Interface 91 | (Windows/Mac only). */ 92 | unsigned short usage_page; 93 | /** Usage for this Device/Interface 94 | (Windows/Mac only).*/ 95 | unsigned short usage; 96 | /** The USB interface which this logical device 97 | represents. 98 | * Valid on both Linux implementations in all cases. 99 | * Valid on the Windows implementation only if the device 100 | contains more than one interface. 101 | * Valid on the Mac implementation if and only if the device 102 | is a USB HID device. */ 103 | int interface_number; 104 | 105 | /** Pointer to the next device */ 106 | struct hid_device_info* next; 107 | }; 108 | 109 | 110 | /** @brief Initialize the HIDAPI library. 111 | This function initializes the HIDAPI library. Calling it is not 112 | strictly necessary, as it will be called automatically by 113 | hid_enumerate() and any of the hid_open_*() functions if it is 114 | needed. This function should be called at the beginning of 115 | execution however, if there is a chance of HIDAPI handles 116 | being opened by different threads simultaneously. 117 | 118 | @ingroup API 119 | @returns 120 | This function returns 0 on success and -1 on error. 121 | */ 122 | int HID_API_EXPORT HID_API_CALL hid_init(void); 123 | 124 | /** @brief Finalize the HIDAPI library. 125 | This function frees all of the static data associated with 126 | HIDAPI. It should be called at the end of execution to avoid 127 | memory leaks. 128 | @ingroup API 129 | @returns 130 | This function returns 0 on success and -1 on error. 131 | */ 132 | int HID_API_EXPORT HID_API_CALL hid_exit(void); 133 | 134 | /** @brief Enumerate the HID Devices. 135 | This function returns a linked list of all the HID devices 136 | attached to the system which match vendor_id and product_id. 137 | If @p vendor_id is set to 0 then any vendor matches. 138 | If @p product_id is set to 0 then any product matches. 139 | If @p vendor_id and @p product_id are both set to 0, then 140 | all HID devices will be returned. 141 | @ingroup API 142 | @param vendor_id The Vendor ID (VID) of the types of device 143 | to open. 144 | @param product_id The Product ID (PID) of the types of 145 | device to open. 146 | @returns 147 | This function returns a pointer to a linked list of type 148 | struct #hid_device_info, containing information about the HID devices 149 | attached to the system, or NULL in the case of failure. Free 150 | this linked list by calling hid_free_enumeration(). 151 | */ 152 | struct hid_device_info HID_API_EXPORT* HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); 153 | 154 | /** @brief Free an enumeration Linked List 155 | This function frees a linked list created by hid_enumerate(). 156 | @ingroup API 157 | @param devs Pointer to a list of struct_device returned from 158 | hid_enumerate(). 159 | */ 160 | void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info* devs); 161 | 162 | /** @brief Open a HID device using a Vendor ID (VID), Product ID 163 | (PID) and optionally a serial number. 164 | If @p serial_number is NULL, the first device with the 165 | specified VID and PID is opened. 166 | This function sets the return value of hid_error(). 167 | @ingroup API 168 | @param vendor_id The Vendor ID (VID) of the device to open. 169 | @param product_id The Product ID (PID) of the device to open. 170 | @param serial_number The Serial Number of the device to open 171 | (Optionally NULL). 172 | @returns 173 | This function returns a pointer to a #hid_device object on 174 | success or NULL on failure. 175 | */ 176 | HID_API_EXPORT hid_device* HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t* serial_number); 177 | 178 | /** @brief Open a HID device by its path name. 179 | The path name be determined by calling hid_enumerate(), or a 180 | platform-specific path name can be used (eg: /dev/hidraw0 on 181 | Linux). 182 | This function sets the return value of hid_error(). 183 | @ingroup API 184 | @param path The path name of the device to open 185 | @returns 186 | This function returns a pointer to a #hid_device object on 187 | success or NULL on failure. 188 | */ 189 | HID_API_EXPORT hid_device* HID_API_CALL hid_open_path(const char* path); 190 | 191 | /** @brief Write an Output report to a HID device. 192 | The first byte of @p data[] must contain the Report ID. For 193 | devices which only support a single report, this must be set 194 | to 0x0. The remaining bytes contain the report data. Since 195 | the Report ID is mandatory, calls to hid_write() will always 196 | contain one more byte than the report contains. For example, 197 | if a hid report is 16 bytes long, 17 bytes must be passed to 198 | hid_write(), the Report ID (or 0x0, for devices with a 199 | single report), followed by the report data (16 bytes). In 200 | this example, the length passed in would be 17. 201 | hid_write() will send the data on the first OUT endpoint, if 202 | one exists. If it does not, it will send the data through 203 | the Control Endpoint (Endpoint 0). 204 | This function sets the return value of hid_error(). 205 | @ingroup API 206 | @param dev A device handle returned from hid_open(). 207 | @param data The data to send, including the report number as 208 | the first byte. 209 | @param length The length in bytes of the data to send. 210 | @returns 211 | This function returns the actual number of bytes written and 212 | -1 on error. 213 | */ 214 | int HID_API_EXPORT HID_API_CALL hid_write(hid_device* dev, const unsigned char* data, size_t length); 215 | 216 | /** @brief Read an Input report from a HID device with timeout. 217 | Input reports are returned 218 | to the host through the INTERRUPT IN endpoint. The first byte will 219 | contain the Report number if the device uses numbered reports. 220 | This function sets the return value of hid_error(). 221 | @ingroup API 222 | @param dev A device handle returned from hid_open(). 223 | @param data A buffer to put the read data into. 224 | @param length The number of bytes to read. For devices with 225 | multiple reports, make sure to read an extra byte for 226 | the report number. 227 | @param milliseconds timeout in milliseconds or -1 for blocking wait. 228 | @returns 229 | This function returns the actual number of bytes read and 230 | -1 on error. If no packet was available to be read within 231 | the timeout period, this function returns 0. 232 | */ 233 | int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device* dev, unsigned char* data, size_t length, int milliseconds); 234 | 235 | /** @brief Read an Input report from a HID device. 236 | Input reports are returned 237 | to the host through the INTERRUPT IN endpoint. The first byte will 238 | contain the Report number if the device uses numbered reports. 239 | This function sets the return value of hid_error(). 240 | @ingroup API 241 | @param dev A device handle returned from hid_open(). 242 | @param data A buffer to put the read data into. 243 | @param length The number of bytes to read. For devices with 244 | multiple reports, make sure to read an extra byte for 245 | the report number. 246 | @returns 247 | This function returns the actual number of bytes read and 248 | -1 on error. If no packet was available to be read and 249 | the handle is in non-blocking mode, this function returns 0. 250 | */ 251 | int HID_API_EXPORT HID_API_CALL hid_read(hid_device* dev, unsigned char* data, size_t length); 252 | 253 | /** @brief Set the device handle to be non-blocking. 254 | In non-blocking mode calls to hid_read() will return 255 | immediately with a value of 0 if there is no data to be 256 | read. In blocking mode, hid_read() will wait (block) until 257 | there is data to read before returning. 258 | Nonblocking can be turned on and off at any time. 259 | @ingroup API 260 | @param dev A device handle returned from hid_open(). 261 | @param nonblock enable or not the nonblocking reads 262 | - 1 to enable nonblocking 263 | - 0 to disable nonblocking. 264 | @returns 265 | This function returns 0 on success and -1 on error. 266 | */ 267 | int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device* dev, int nonblock); 268 | 269 | /** @brief Send a Feature report to the device. 270 | Feature reports are sent over the Control endpoint as a 271 | Set_Report transfer. The first byte of @p data[] must 272 | contain the Report ID. For devices which only support a 273 | single report, this must be set to 0x0. The remaining bytes 274 | contain the report data. Since the Report ID is mandatory, 275 | calls to hid_send_feature_report() will always contain one 276 | more byte than the report contains. For example, if a hid 277 | report is 16 bytes long, 17 bytes must be passed to 278 | hid_send_feature_report(): the Report ID (or 0x0, for 279 | devices which do not use numbered reports), followed by the 280 | report data (16 bytes). In this example, the length passed 281 | in would be 17. 282 | This function sets the return value of hid_error(). 283 | @ingroup API 284 | @param dev A device handle returned from hid_open(). 285 | @param data The data to send, including the report number as 286 | the first byte. 287 | @param length The length in bytes of the data to send, including 288 | the report number. 289 | @returns 290 | This function returns the actual number of bytes written and 291 | -1 on error. 292 | */ 293 | int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device* dev, const unsigned char* data, size_t length); 294 | 295 | /** @brief Get a feature report from a HID device. 296 | Set the first byte of @p data[] to the Report ID of the 297 | report to be read. Make sure to allow space for this 298 | extra byte in @p data[]. Upon return, the first byte will 299 | still contain the Report ID, and the report data will 300 | start in data[1]. 301 | This function sets the return value of hid_error(). 302 | @ingroup API 303 | @param dev A device handle returned from hid_open(). 304 | @param data A buffer to put the read data into, including 305 | the Report ID. Set the first byte of @p data[] to the 306 | Report ID of the report to be read, or set it to zero 307 | if your device does not use numbered reports. 308 | @param length The number of bytes to read, including an 309 | extra byte for the report ID. The buffer can be longer 310 | than the actual report. 311 | @returns 312 | This function returns the number of bytes read plus 313 | one for the report ID (which is still in the first 314 | byte), or -1 on error. 315 | */ 316 | int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device* dev, unsigned char* data, size_t length); 317 | 318 | /** @brief Get a input report from a HID device. 319 | Set the first byte of @p data[] to the Report ID of the 320 | report to be read. Make sure to allow space for this 321 | extra byte in @p data[]. Upon return, the first byte will 322 | still contain the Report ID, and the report data will 323 | start in data[1]. 324 | @ingroup API 325 | @param device A device handle returned from hid_open(). 326 | @param data A buffer to put the read data into, including 327 | the Report ID. Set the first byte of @p data[] to the 328 | Report ID of the report to be read, or set it to zero 329 | if your device does not use numbered reports. 330 | @param length The number of bytes to read, including an 331 | extra byte for the report ID. The buffer can be longer 332 | than the actual report. 333 | @returns 334 | This function returns the number of bytes read plus 335 | one for the report ID (which is still in the first 336 | byte), or -1 on error. 337 | */ 338 | int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device* dev, unsigned char* data, size_t length); 339 | 340 | /** @brief Close a HID device. 341 | This function sets the return value of hid_error(). 342 | @ingroup API 343 | @param dev A device handle returned from hid_open(). 344 | */ 345 | void HID_API_EXPORT HID_API_CALL hid_close(hid_device* dev); 346 | 347 | /** @brief Get The Manufacturer String from a HID device. 348 | @ingroup API 349 | @param dev A device handle returned from hid_open(). 350 | @param string A wide string buffer to put the data into. 351 | @param maxlen The length of the buffer in multiples of wchar_t. 352 | @returns 353 | This function returns 0 on success and -1 on error. 354 | */ 355 | int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device* dev, wchar_t* string, size_t maxlen); 356 | 357 | /** @brief Get The Product String from a HID device. 358 | @ingroup API 359 | @param dev A device handle returned from hid_open(). 360 | @param string A wide string buffer to put the data into. 361 | @param maxlen The length of the buffer in multiples of wchar_t. 362 | @returns 363 | This function returns 0 on success and -1 on error. 364 | */ 365 | int HID_API_EXPORT_CALL hid_get_product_string(hid_device* dev, wchar_t* string, size_t maxlen); 366 | 367 | /** @brief Get The Serial Number String from a HID device. 368 | @ingroup API 369 | @param dev A device handle returned from hid_open(). 370 | @param string A wide string buffer to put the data into. 371 | @param maxlen The length of the buffer in multiples of wchar_t. 372 | @returns 373 | This function returns 0 on success and -1 on error. 374 | */ 375 | int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device* dev, wchar_t* string, size_t maxlen); 376 | 377 | /** @brief Get a string from a HID device, based on its string index. 378 | @ingroup API 379 | @param dev A device handle returned from hid_open(). 380 | @param string_index The index of the string to get. 381 | @param string A wide string buffer to put the data into. 382 | @param maxlen The length of the buffer in multiples of wchar_t. 383 | @returns 384 | This function returns 0 on success and -1 on error. 385 | */ 386 | int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device* dev, int string_index, wchar_t* string, size_t maxlen); 387 | 388 | /** @brief Get a string describing the last error which occurred. 389 | Whether a function sets the last error is noted in its 390 | documentation. These functions will reset the last error 391 | to NULL before their execution. 392 | Strings returned from hid_error() must not be freed by the user! 393 | This function is thread-safe, and error messages are thread-local. 394 | @ingroup API 395 | @param dev A device handle returned from hid_open(), 396 | or NULL to get the last non-device-specific error 397 | (e.g. for errors in hid_open() itself). 398 | @returns 399 | This function returns a string containing the last error 400 | which occurred or NULL if none has occurred. 401 | */ 402 | HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device* dev); 403 | 404 | /** @brief Get a runtime version of the library. 405 | @ingroup API 406 | @returns 407 | Pointer to statically allocated struct, that contains version. 408 | */ 409 | HID_API_EXPORT const struct hid_api_version* HID_API_CALL hid_version(); 410 | 411 | 412 | /** @brief Get a runtime version string of the library. 413 | @ingroup API 414 | @returns 415 | Pointer to statically allocated string, that contains version string. 416 | */ 417 | HID_API_EXPORT const char* HID_API_CALL hid_version_str(); 418 | 419 | #ifdef __cplusplus 420 | } 421 | #endif 422 | 423 | #endif 424 | -------------------------------------------------------------------------------- /external/sigslot/signal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #if defined __clang__ || (__GNUC__ > 5) 11 | #define SIGSLOT_MAY_ALIAS __attribute__((__may_alias__)) 12 | #else 13 | #define SIGSLOT_MAY_ALIAS 14 | #endif 15 | 16 | #if defined(__GXX_RTTI) || defined(__cpp_rtti) || defined(_CPPRTTI) 17 | #define SIGSLOT_RTTI_ENABLED 1 18 | #include 19 | #endif 20 | 21 | namespace sigslot { 22 | 23 | namespace detail { 24 | 25 | // Used to detect an object of observer type 26 | struct observer_type {}; 27 | 28 | } // namespace detail 29 | 30 | namespace trait { 31 | 32 | /// represent a list of types 33 | template struct typelist {}; 34 | 35 | /** 36 | * Pointers that can be converted to a weak pointer concept for tracking 37 | * purpose must implement the to_weak() function in order to make use of 38 | * ADL to convert that type and make it usable 39 | */ 40 | 41 | template 42 | std::weak_ptr to_weak(std::weak_ptr w) { 43 | return w; 44 | } 45 | 46 | template 47 | std::weak_ptr to_weak(std::shared_ptr s) { 48 | return s; 49 | } 50 | 51 | // tools 52 | namespace detail { 53 | 54 | template 55 | struct voider { using type = void; }; 56 | 57 | // void_t from c++17 58 | template 59 | using void_t = typename detail::voider::type; 60 | 61 | template 62 | struct has_call_operator : std::false_type {}; 63 | 64 | template 65 | struct has_call_operator::type::operator())>> 66 | : std::true_type {}; 67 | 68 | template 69 | struct is_callable : std::false_type {}; 70 | 71 | template 72 | struct is_callable, 73 | void_t()).*std::declval())(std::declval()...))>> 74 | : std::true_type {}; 75 | 76 | template 77 | struct is_callable, 78 | void_t()(std::declval()...))>> 79 | : std::true_type {}; 80 | 81 | 82 | template 83 | struct is_weak_ptr : std::false_type {}; 84 | 85 | template 86 | struct is_weak_ptr().expired()), 87 | decltype(std::declval().lock()), 88 | decltype(std::declval().reset())>> 89 | : std::true_type {}; 90 | 91 | template 92 | struct is_weak_ptr_compatible : std::false_type {}; 93 | 94 | template 95 | struct is_weak_ptr_compatible()))>> 96 | : is_weak_ptr()))> {}; 97 | 98 | } // namespace detail 99 | 100 | static constexpr bool with_rtti = 101 | #ifdef SIGSLOT_RTTI_ENABLED 102 | true; 103 | #else 104 | false; 105 | #endif 106 | 107 | /// determine if a pointer is convertible into a "weak" pointer 108 | template 109 | constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible>::value; 110 | 111 | /// determine if a type T (Callable or Pmf) is callable with supplied arguments 112 | template 113 | constexpr bool is_callable_v = detail::is_callable::value; 114 | 115 | template 116 | constexpr bool is_weak_ptr_v = detail::is_weak_ptr::value; 117 | 118 | template 119 | constexpr bool has_call_operator_v = detail::has_call_operator::value; 120 | 121 | template 122 | constexpr bool is_pointer_v = std::is_pointer::value; 123 | 124 | template 125 | constexpr bool is_func_v = std::is_function::value; 126 | 127 | template 128 | constexpr bool is_pmf_v = std::is_member_function_pointer::value; 129 | 130 | template 131 | constexpr bool is_observer_v = std::is_base_of<::sigslot::detail::observer_type, 132 | std::remove_pointer_t>::value; 133 | 134 | } // namespace trait 135 | 136 | template 137 | class signal_base; 138 | 139 | /** 140 | * A group_id is used to identify a group of slots 141 | */ 142 | using group_id = std::int32_t; 143 | 144 | namespace detail { 145 | 146 | /** 147 | * The following function_traits and object_pointer series of templates are 148 | * used to circumvent the type-erasing that takes place in the slot_base 149 | * implementations. They are used to compare the stored functions and objects 150 | * with another one for disconnection purpose. 151 | */ 152 | 153 | /* 154 | * Function pointers and member function pointers size differ from compiler to 155 | * compiler, and for virtual members compared to non virtual members. On some 156 | * compilers, multiple inheritance has an impact too. Hence, we form an union 157 | * big enough to store any kind of function pointer. 158 | */ 159 | namespace mock { 160 | 161 | struct a { virtual ~a() = default; void f(); virtual void g(); }; 162 | struct b { virtual ~b() = default; virtual void h(); }; 163 | struct c : a, b { void g() override; }; 164 | 165 | union fun_types { 166 | decltype(&c::g) m; 167 | decltype(&a::g) v; 168 | decltype(&a::f) d; 169 | void (*f)(); 170 | void* o; 171 | }; 172 | 173 | } // namespace mock 174 | 175 | /* 176 | * This union is used to compare function pointers 177 | * Generic callables cannot be compared. Here we compare pointers but there is 178 | * no guarantee that this always works. 179 | */ 180 | union SIGSLOT_MAY_ALIAS func_ptr { 181 | void* value() { 182 | return &data[0]; 183 | } 184 | 185 | const void* value() const { 186 | return &data[0]; 187 | } 188 | 189 | template 190 | T& value() { 191 | return *static_cast(value()); 192 | } 193 | 194 | template 195 | const T& value() const { 196 | return *static_cast(value()); 197 | } 198 | 199 | inline explicit operator bool() const { 200 | return value() != nullptr; 201 | } 202 | 203 | inline bool operator==(const func_ptr& o) const { 204 | return std::equal(std::begin(data), std::end(data), std::begin(o.data)); 205 | } 206 | 207 | mock::fun_types _; 208 | char data[sizeof(mock::fun_types)]; 209 | }; 210 | 211 | 212 | template 213 | struct function_traits { 214 | static void ptr(const T&/*t*/, func_ptr& d) { 215 | d.value() = nullptr; 216 | } 217 | 218 | static constexpr bool is_disconnectable = false; 219 | static constexpr bool must_check_object = true; 220 | }; 221 | 222 | template 223 | struct function_traits>> { 224 | static void ptr(T& t, func_ptr& d) { 225 | d.value() = &t; 226 | } 227 | 228 | static constexpr bool is_disconnectable = true; 229 | static constexpr bool must_check_object = false; 230 | }; 231 | 232 | template 233 | struct function_traits>> { 234 | static void ptr(T* t, func_ptr& d) { 235 | d.value() = t; 236 | } 237 | 238 | static constexpr bool is_disconnectable = true; 239 | static constexpr bool must_check_object = false; 240 | }; 241 | 242 | template 243 | struct function_traits>> { 244 | static void ptr(const T& t, func_ptr& d) { 245 | d.value() = t; 246 | } 247 | 248 | static constexpr bool is_disconnectable = trait::with_rtti; 249 | static constexpr bool must_check_object = true; 250 | }; 251 | 252 | // for function objects, the assumption is that we are looking for the call operator 253 | template 254 | struct function_traits>> { 255 | using call_type = decltype(&std::remove_reference::type::operator()); 256 | 257 | static void ptr(const T&/*t*/, func_ptr& d) { 258 | function_traits::ptr(&T::operator(), d); 259 | } 260 | 261 | static constexpr bool is_disconnectable = function_traits::is_disconnectable; 262 | static constexpr bool must_check_object = function_traits::must_check_object; 263 | }; 264 | 265 | template 266 | func_ptr get_function_ptr(const T& t) { 267 | func_ptr d; 268 | std::uninitialized_fill(std::begin(d.data), std::end(d.data), '\0'); 269 | function_traits>::ptr(t, d); 270 | return d; 271 | } 272 | 273 | /* 274 | * obj_ptr is used to store a pointer to an object. 275 | * The object_pointer traits are needed to handle trackable objects correctly, 276 | * as they are likely to not be pointers. 277 | */ 278 | using obj_ptr = const void*; 279 | 280 | template 281 | obj_ptr get_object_ptr(const T& t); 282 | 283 | template 284 | struct object_pointer { 285 | static obj_ptr get(const T&) { 286 | return nullptr; 287 | } 288 | }; 289 | 290 | template 291 | struct object_pointer>> { 292 | static obj_ptr get(const T* t) { 293 | return reinterpret_cast(t); 294 | } 295 | }; 296 | 297 | template 298 | struct object_pointer>> { 299 | static obj_ptr get(const T& t) { 300 | auto p = t.lock(); 301 | return get_object_ptr(p); 302 | } 303 | }; 304 | 305 | template 306 | struct object_pointer && 307 | !trait::is_weak_ptr_v&& 308 | trait::is_weak_ptr_compatible_v>> 309 | { 310 | static obj_ptr get(const T& t) { 311 | return t ? reinterpret_cast(t.get()) : nullptr; 312 | } 313 | }; 314 | 315 | template 316 | obj_ptr get_object_ptr(const T& t) { 317 | return object_pointer::get(t); 318 | } 319 | 320 | 321 | // noop mutex for thread-unsafe use 322 | struct null_mutex { 323 | null_mutex() noexcept = default; 324 | ~null_mutex() noexcept = default; 325 | null_mutex(const null_mutex&) = delete; 326 | null_mutex& operator=(const null_mutex&) = delete; 327 | null_mutex(null_mutex&&) = delete; 328 | null_mutex& operator=(null_mutex&&) = delete; 329 | 330 | inline bool try_lock() noexcept { return true; } 331 | inline void lock() noexcept {} 332 | inline void unlock() noexcept {} 333 | }; 334 | 335 | /** 336 | * A spin mutex that yields, mostly for use in benchmarks and scenarii that invoke 337 | * slots at a very high pace. 338 | * One should almost always prefer a standard mutex over this. 339 | */ 340 | struct spin_mutex { 341 | spin_mutex() noexcept = default; 342 | ~spin_mutex() noexcept = default; 343 | spin_mutex(spin_mutex const&) = delete; 344 | spin_mutex& operator=(const spin_mutex&) = delete; 345 | spin_mutex(spin_mutex&&) = delete; 346 | spin_mutex& operator=(spin_mutex&&) = delete; 347 | 348 | void lock() noexcept { 349 | while (true) { 350 | while (!state.load(std::memory_order_relaxed)) { 351 | std::this_thread::yield(); 352 | } 353 | 354 | if (try_lock()) { 355 | break; 356 | } 357 | } 358 | } 359 | 360 | bool try_lock() noexcept { 361 | return state.exchange(false, std::memory_order_acquire); 362 | } 363 | 364 | void unlock() noexcept { 365 | state.store(true, std::memory_order_release); 366 | } 367 | 368 | private: 369 | std::atomic state{ true }; 370 | }; 371 | 372 | /** 373 | * A simple copy on write container that will be used to improve slot lists 374 | * access efficiency in a multithreaded context. 375 | */ 376 | template 377 | class copy_on_write { 378 | struct payload { 379 | payload() = default; 380 | 381 | template 382 | explicit payload(Args && ...args) 383 | : value(std::forward(args)...) 384 | {} 385 | 386 | std::atomic count{ 1 }; 387 | T value; 388 | }; 389 | 390 | public: 391 | using element_type = T; 392 | 393 | copy_on_write() 394 | : m_data(new payload) 395 | {} 396 | 397 | template 398 | explicit copy_on_write(U&& x, std::enable_if_t, 399 | copy_on_write>::value>* = nullptr) 400 | : m_data(new payload(std::forward(x))) 401 | {} 402 | 403 | copy_on_write(const copy_on_write& x) noexcept 404 | : m_data(x.m_data) 405 | { 406 | ++m_data->count; 407 | } 408 | 409 | copy_on_write(copy_on_write&& x) noexcept 410 | : m_data(x.m_data) 411 | { 412 | x.m_data = nullptr; 413 | } 414 | 415 | ~copy_on_write() { 416 | if (m_data && (--m_data->count == 0)) { 417 | delete m_data; 418 | } 419 | } 420 | 421 | copy_on_write& operator=(const copy_on_write& x) noexcept { 422 | if (&x != this) { 423 | *this = copy_on_write(x); 424 | } 425 | return *this; 426 | } 427 | 428 | copy_on_write& operator=(copy_on_write&& x) noexcept { 429 | auto tmp = std::move(x); 430 | swap(*this, tmp); 431 | return *this; 432 | } 433 | 434 | element_type& write() { 435 | if (!unique()) { 436 | *this = copy_on_write(read()); 437 | } 438 | return m_data->value; 439 | } 440 | 441 | const element_type& read() const noexcept { 442 | return m_data->value; 443 | } 444 | 445 | friend inline void swap(copy_on_write& x, copy_on_write& y) noexcept { 446 | using std::swap; 447 | swap(x.m_data, y.m_data); 448 | } 449 | 450 | private: 451 | bool unique() const noexcept { 452 | return m_data->count == 1; 453 | } 454 | 455 | private: 456 | payload* m_data; 457 | }; 458 | 459 | /** 460 | * Specializations for thread-safe code path 461 | */ 462 | template 463 | const T& cow_read(const T& v) { 464 | return v; 465 | } 466 | 467 | template 468 | const T& cow_read(copy_on_write& v) { 469 | return v.read(); 470 | } 471 | 472 | template 473 | T& cow_write(T& v) { 474 | return v; 475 | } 476 | 477 | template 478 | T& cow_write(copy_on_write& v) { 479 | return v.write(); 480 | } 481 | 482 | /** 483 | * std::make_shared instantiates a lot a templates, and makes both compilation time 484 | * and executable size far bigger than they need to be. We offer a make_shared 485 | * equivalent that will avoid most instantiations with the following tradeoffs: 486 | * - Not exception safe, 487 | * - Allocates a separate control block, and will thus make the code slower. 488 | */ 489 | #ifdef SIGSLOT_REDUCE_COMPILE_TIME 490 | template 491 | inline std::shared_ptr make_shared(Arg && ... arg) { 492 | return std::shared_ptr(static_cast(new D(std::forward(arg)...))); 493 | } 494 | #else 495 | template 496 | inline std::shared_ptr make_shared(Arg && ... arg) { 497 | return std::static_pointer_cast(std::make_shared(std::forward(arg)...)); 498 | } 499 | #endif 500 | 501 | /* slot_state holds slot type independent state, to be used to interact with 502 | * slots indirectly through connection and scoped_connection objects. 503 | */ 504 | class slot_state { 505 | public: 506 | constexpr slot_state(group_id gid) noexcept 507 | : m_index(0) 508 | , m_group(gid) 509 | , m_connected(true) 510 | , m_blocked(false) 511 | {} 512 | 513 | virtual ~slot_state() = default; 514 | 515 | virtual bool connected() const noexcept { return m_connected; } 516 | 517 | bool disconnect() noexcept { 518 | bool ret = m_connected.exchange(false); 519 | if (ret) { 520 | do_disconnect(); 521 | } 522 | return ret; 523 | } 524 | 525 | bool blocked() const noexcept { return m_blocked.load(); } 526 | void block() noexcept { m_blocked.store(true); } 527 | void unblock() noexcept { m_blocked.store(false); } 528 | 529 | protected: 530 | virtual void do_disconnect() {} 531 | 532 | auto index() const { 533 | return m_index; 534 | } 535 | 536 | auto& index() { 537 | return m_index; 538 | } 539 | 540 | group_id group() const { 541 | return m_group; 542 | } 543 | 544 | private: 545 | template 546 | friend class ::sigslot::signal_base; 547 | 548 | std::size_t m_index; // index into the array of slot pointers inside the signal 549 | const group_id m_group; // slot group this slot belongs to 550 | std::atomic m_connected; 551 | std::atomic m_blocked; 552 | }; 553 | 554 | } // namespace detail 555 | 556 | /** 557 | * connection_blocker is a RAII object that blocks a connection until destruction 558 | */ 559 | class connection_blocker { 560 | public: 561 | connection_blocker() = default; 562 | ~connection_blocker() noexcept { release(); } 563 | 564 | connection_blocker(const connection_blocker&) = delete; 565 | connection_blocker& operator=(const connection_blocker&) = delete; 566 | 567 | connection_blocker(connection_blocker&& o) noexcept 568 | : m_state{ std::move(o.m_state) } 569 | {} 570 | 571 | connection_blocker& operator=(connection_blocker&& o) noexcept { 572 | release(); 573 | m_state.swap(o.m_state); 574 | return *this; 575 | } 576 | 577 | private: 578 | friend class connection; 579 | explicit connection_blocker(std::weak_ptr s) noexcept 580 | : m_state{ std::move(s) } 581 | { 582 | if (auto d = m_state.lock()) { 583 | d->block(); 584 | } 585 | } 586 | 587 | void release() noexcept { 588 | if (auto d = m_state.lock()) { 589 | d->unblock(); 590 | } 591 | } 592 | 593 | private: 594 | std::weak_ptr m_state; 595 | }; 596 | 597 | 598 | /** 599 | * A connection object allows interaction with an ongoing slot connection 600 | * 601 | * It allows common actions such as connection blocking and disconnection. 602 | * Note that connection is not a RAII object, one does not need to hold one 603 | * such object to keep the signal-slot connection alive. 604 | */ 605 | class connection { 606 | public: 607 | connection() = default; 608 | virtual ~connection() = default; 609 | 610 | connection(const connection&) noexcept = default; 611 | connection& operator=(const connection&) noexcept = default; 612 | connection(connection&&) noexcept = default; 613 | connection& operator=(connection&&) noexcept = default; 614 | 615 | bool valid() const noexcept { 616 | return !m_state.expired(); 617 | } 618 | 619 | bool connected() const noexcept { 620 | const auto d = m_state.lock(); 621 | return d && d->connected(); 622 | } 623 | 624 | bool disconnect() noexcept { 625 | auto d = m_state.lock(); 626 | return d && d->disconnect(); 627 | } 628 | 629 | bool blocked() const noexcept { 630 | const auto d = m_state.lock(); 631 | return d && d->blocked(); 632 | } 633 | 634 | void block() noexcept { 635 | if (auto d = m_state.lock()) { 636 | d->block(); 637 | } 638 | } 639 | 640 | void unblock() noexcept { 641 | if (auto d = m_state.lock()) { 642 | d->unblock(); 643 | } 644 | } 645 | 646 | connection_blocker blocker() const noexcept { 647 | return connection_blocker{ m_state }; 648 | } 649 | 650 | protected: 651 | template friend class signal_base; 652 | explicit connection(std::weak_ptr s) noexcept 653 | : m_state{ std::move(s) } 654 | {} 655 | 656 | protected: 657 | std::weak_ptr m_state; 658 | }; 659 | 660 | /** 661 | * scoped_connection is a RAII version of connection 662 | * It disconnects the slot from the signal upon destruction. 663 | */ 664 | class scoped_connection final : public connection { 665 | public: 666 | scoped_connection() = default; 667 | ~scoped_connection() override { 668 | disconnect(); 669 | } 670 | 671 | /*implicit*/ scoped_connection(const connection& c) noexcept : connection(c) {} 672 | /*implicit*/ scoped_connection(connection&& c) noexcept : connection(std::move(c)) {} 673 | 674 | scoped_connection(const scoped_connection&) noexcept = delete; 675 | scoped_connection& operator=(const scoped_connection&) noexcept = delete; 676 | 677 | scoped_connection(scoped_connection&& o) noexcept 678 | : connection{ std::move(o.m_state) } 679 | {} 680 | 681 | scoped_connection& operator=(scoped_connection&& o) noexcept { 682 | disconnect(); 683 | m_state.swap(o.m_state); 684 | return *this; 685 | } 686 | 687 | private: 688 | template friend class signal_base; 689 | explicit scoped_connection(std::weak_ptr s) noexcept 690 | : connection{ std::move(s) } 691 | {} 692 | }; 693 | 694 | /** 695 | * Observer is a base class for intrusive lifetime tracking of objects. 696 | * 697 | * This is an alternative to trackable pointers, such as std::shared_ptr, 698 | * and manual connection management by keeping connection objects in scope. 699 | * Deriving from this class allows automatic disconnection of all the slots 700 | * connected to any signal when an instance is destroyed. 701 | */ 702 | template 703 | struct observer_base : private detail::observer_type { 704 | virtual ~observer_base() = default; 705 | 706 | protected: 707 | /** 708 | * Disconnect all signals connected to this object. 709 | * 710 | * To avoid invocation of slots on a semi-destructed instance, which may happen 711 | * in multi-threaded contexts, derived classes should call this method in their 712 | * destructor. This will ensure proper disconnection prior to the destruction. 713 | */ 714 | void disconnect_all() { 715 | std::unique_lock _{ m_mutex }; 716 | m_connections.clear(); 717 | } 718 | 719 | private: 720 | template 721 | friend class signal_base; 722 | 723 | void add_connection(connection conn) { 724 | std::unique_lock _{ m_mutex }; 725 | m_connections.emplace_back(std::move(conn)); 726 | } 727 | 728 | Lockable m_mutex; 729 | std::vector m_connections; 730 | }; 731 | 732 | /** 733 | * Specialization of observer_base to be used in single threaded contexts. 734 | */ 735 | using observer_st = observer_base; 736 | 737 | /** 738 | * Specialization of observer_base to be used in multi-threaded contexts. 739 | */ 740 | using observer = observer_base; 741 | 742 | 743 | namespace detail { 744 | 745 | // interface for cleanable objects, used to cleanup disconnected slots 746 | struct cleanable { 747 | virtual ~cleanable() = default; 748 | virtual void clean(slot_state*) = 0; 749 | }; 750 | 751 | template 752 | class slot_base; 753 | 754 | template 755 | using slot_ptr = std::shared_ptr>; 756 | 757 | 758 | /* A base class for slot objects. This base type only depends on slot argument 759 | * types, it will be used as an element in an intrusive singly-linked list of 760 | * slots, hence the public next member. 761 | */ 762 | template 763 | class slot_base : public slot_state { 764 | public: 765 | using base_types = trait::typelist; 766 | 767 | explicit slot_base(cleanable& c, group_id gid) 768 | : slot_state(gid) 769 | , cleaner(c) 770 | {} 771 | ~slot_base() override = default; 772 | 773 | // method effectively responsible for calling the "slot" function with 774 | // supplied arguments whenever emission happens. 775 | virtual void call_slot(Args...) = 0; 776 | 777 | template 778 | void operator()(U && ...u) { 779 | if (slot_state::connected() && !slot_state::blocked()) { 780 | call_slot(std::forward(u)...); 781 | } 782 | } 783 | 784 | // check if we are storing callable c 785 | template 786 | bool has_callable(const C& c) const { 787 | auto cp = get_function_ptr(c); 788 | auto p = get_callable(); 789 | return cp && p && cp == p; 790 | } 791 | 792 | template 793 | std::enable_if_t::must_check_object, bool> 794 | has_full_callable(const C& c) const { 795 | return has_callable(c) && check_class_type>(); 796 | } 797 | 798 | template 799 | std::enable_if_t::must_check_object, bool> 800 | has_full_callable(const C& c) const { 801 | return has_callable(c); 802 | } 803 | 804 | // check if we are storing object o 805 | template 806 | bool has_object(const O& o) const { 807 | return get_object() == get_object_ptr(o); 808 | } 809 | 810 | protected: 811 | void do_disconnect() final { 812 | cleaner.clean(this); 813 | } 814 | 815 | // retieve a pointer to the object embedded in the slot 816 | virtual obj_ptr get_object() const noexcept { 817 | return nullptr; 818 | } 819 | 820 | // retieve a pointer to the callable embedded in the slot 821 | virtual func_ptr get_callable() const noexcept { 822 | return get_function_ptr(nullptr); 823 | } 824 | 825 | #ifdef SIGSLOT_RTTI_ENABLED 826 | // retieve a pointer to the callable embedded in the slot 827 | virtual const std::type_info& get_callable_type() const noexcept { 828 | return typeid(nullptr); 829 | } 830 | 831 | private: 832 | template 833 | bool check_class_type() const { 834 | return typeid(U) == get_callable_type(); 835 | } 836 | 837 | #else 838 | template 839 | bool check_class_type() const { 840 | return false; 841 | } 842 | #endif 843 | 844 | private: 845 | cleanable& cleaner; 846 | }; 847 | 848 | /* 849 | * A slot object holds state information, and a callable to to be called 850 | * whenever the function call operator of its slot_base base class is called. 851 | */ 852 | template 853 | class slot final : public slot_base { 854 | public: 855 | template 856 | constexpr slot(cleanable& c, F&& f, Gid gid) 857 | : slot_base(c, gid) 858 | , func{ std::forward(f) } {} 859 | 860 | protected: 861 | void call_slot(Args ...args) override { 862 | func(args...); 863 | } 864 | 865 | func_ptr get_callable() const noexcept override { 866 | return get_function_ptr(func); 867 | } 868 | 869 | #ifdef SIGSLOT_RTTI_ENABLED 870 | const std::type_info& get_callable_type() const noexcept override { 871 | return typeid(func); 872 | } 873 | #endif 874 | 875 | private: 876 | std::decay_t func; 877 | }; 878 | 879 | /* 880 | * Variation of slot that prepends a connection object to the callable 881 | */ 882 | template 883 | class slot_extended final : public slot_base { 884 | public: 885 | template 886 | constexpr slot_extended(cleanable& c, F&& f, group_id gid) 887 | : slot_base(c, gid) 888 | , func{ std::forward(f) } {} 889 | 890 | connection conn; 891 | 892 | protected: 893 | void call_slot(Args ...args) override { 894 | func(conn, args...); 895 | } 896 | 897 | func_ptr get_callable() const noexcept override { 898 | return get_function_ptr(func); 899 | } 900 | 901 | #ifdef SIGSLOT_RTTI_ENABLED 902 | const std::type_info& get_callable_type() const noexcept override { 903 | return typeid(func); 904 | } 905 | #endif 906 | 907 | private: 908 | std::decay_t func; 909 | }; 910 | 911 | /* 912 | * A slot object holds state information, an object and a pointer over member 913 | * function to be called whenever the function call operator of its slot_base 914 | * base class is called. 915 | */ 916 | template 917 | class slot_pmf final : public slot_base { 918 | public: 919 | template 920 | constexpr slot_pmf(cleanable& c, F&& f, P&& p, group_id gid) 921 | : slot_base(c, gid) 922 | , pmf{ std::forward(f) } 923 | , ptr{ std::forward

(p) } {} 924 | 925 | protected: 926 | void call_slot(Args ...args) override { 927 | ((*ptr).*pmf)(args...); 928 | } 929 | 930 | func_ptr get_callable() const noexcept override { 931 | return get_function_ptr(pmf); 932 | } 933 | 934 | obj_ptr get_object() const noexcept override { 935 | return get_object_ptr(ptr); 936 | } 937 | 938 | #ifdef SIGSLOT_RTTI_ENABLED 939 | const std::type_info& get_callable_type() const noexcept override { 940 | return typeid(pmf); 941 | } 942 | #endif 943 | 944 | private: 945 | std::decay_t pmf; 946 | std::decay_t ptr; 947 | }; 948 | 949 | /* 950 | * Variation of slot that prepends a connection object to the callable 951 | */ 952 | template 953 | class slot_pmf_extended final : public slot_base { 954 | public: 955 | template 956 | constexpr slot_pmf_extended(cleanable& c, F&& f, P&& p, group_id gid) 957 | : slot_base(c, gid) 958 | , pmf{ std::forward(f) } 959 | , ptr{ std::forward

(p) } {} 960 | 961 | connection conn; 962 | 963 | protected: 964 | void call_slot(Args ...args) override { 965 | ((*ptr).*pmf)(conn, args...); 966 | } 967 | 968 | func_ptr get_callable() const noexcept override { 969 | return get_function_ptr(pmf); 970 | } 971 | obj_ptr get_object() const noexcept override { 972 | return get_object_ptr(ptr); 973 | } 974 | 975 | #ifdef SIGSLOT_RTTI_ENABLED 976 | const std::type_info& get_callable_type() const noexcept override { 977 | return typeid(pmf); 978 | } 979 | #endif 980 | 981 | private: 982 | std::decay_t pmf; 983 | std::decay_t ptr; 984 | }; 985 | 986 | /* 987 | * An implementation of a slot that tracks the life of a supplied object 988 | * through a weak pointer in order to automatically disconnect the slot 989 | * on said object destruction. 990 | */ 991 | template 992 | class slot_tracked final : public slot_base { 993 | public: 994 | template 995 | constexpr slot_tracked(cleanable& c, F&& f, P&& p, group_id gid) 996 | : slot_base(c, gid) 997 | , func{ std::forward(f) } 998 | , ptr{ std::forward

(p) } 999 | {} 1000 | 1001 | bool connected() const noexcept override { 1002 | return !ptr.expired() && slot_state::connected(); 1003 | } 1004 | 1005 | protected: 1006 | void call_slot(Args ...args) override { 1007 | auto sp = ptr.lock(); 1008 | if (!sp) { 1009 | slot_state::disconnect(); 1010 | return; 1011 | } 1012 | if (slot_state::connected()) { 1013 | func(args...); 1014 | } 1015 | } 1016 | 1017 | func_ptr get_callable() const noexcept override { 1018 | return get_function_ptr(func); 1019 | } 1020 | 1021 | obj_ptr get_object() const noexcept override { 1022 | return get_object_ptr(ptr); 1023 | } 1024 | 1025 | #ifdef SIGSLOT_RTTI_ENABLED 1026 | const std::type_info& get_callable_type() const noexcept override { 1027 | return typeid(func); 1028 | } 1029 | #endif 1030 | 1031 | private: 1032 | std::decay_t func; 1033 | std::decay_t ptr; 1034 | }; 1035 | 1036 | /* 1037 | * An implementation of a slot as a pointer over member function, that tracks 1038 | * the life of a supplied object through a weak pointer in order to automatically 1039 | * disconnect the slot on said object destruction. 1040 | */ 1041 | template 1042 | class slot_pmf_tracked final : public slot_base { 1043 | public: 1044 | template 1045 | constexpr slot_pmf_tracked(cleanable& c, F&& f, P&& p, group_id gid) 1046 | : slot_base(c, gid) 1047 | , pmf{ std::forward(f) } 1048 | , ptr{ std::forward

(p) } 1049 | {} 1050 | 1051 | bool connected() const noexcept override { 1052 | return !ptr.expired() && slot_state::connected(); 1053 | } 1054 | 1055 | protected: 1056 | void call_slot(Args ...args) override { 1057 | auto sp = ptr.lock(); 1058 | if (!sp) { 1059 | slot_state::disconnect(); 1060 | return; 1061 | } 1062 | if (slot_state::connected()) { 1063 | ((*sp).*pmf)(args...); 1064 | } 1065 | } 1066 | 1067 | func_ptr get_callable() const noexcept override { 1068 | return get_function_ptr(pmf); 1069 | } 1070 | 1071 | obj_ptr get_object() const noexcept override { 1072 | return get_object_ptr(ptr); 1073 | } 1074 | 1075 | #ifdef SIGSLOT_RTTI_ENABLED 1076 | const std::type_info& get_callable_type() const noexcept override { 1077 | return typeid(pmf); 1078 | } 1079 | #endif 1080 | 1081 | private: 1082 | std::decay_t pmf; 1083 | std::decay_t ptr; 1084 | }; 1085 | 1086 | } // namespace detail 1087 | 1088 | 1089 | /** 1090 | * signal_base is an implementation of the observer pattern, through the use 1091 | * of an emitting object and slots that are connected to the signal and called 1092 | * with supplied arguments when a signal is emitted. 1093 | * 1094 | * signal_base is the general implementation, whose locking policy must be 1095 | * set in order to decide thread safety guarantees. signal and signal_st 1096 | * are partial specializations for multi-threaded and single-threaded use. 1097 | * 1098 | * It does not allow slots to return a value. 1099 | * 1100 | * Slot execution order can be constrained by assigning group ids to the slots. 1101 | * The execution order of slots in a same group is unspecified and should not be 1102 | * relied upon, however groups are executed in ascending group ids order. When 1103 | * the group id of a slot is not set, it is assigned to the group 0. Group ids 1104 | * can have any value in the range of signed 32 bit integers. 1105 | * 1106 | * @tparam Lockable a lock type to decide the lock policy 1107 | * @tparam T... the argument types of the emitting and slots functions. 1108 | */ 1109 | template 1110 | class signal_base final : public detail::cleanable { 1111 | template 1112 | using is_thread_safe = std::integral_constant::value>; 1113 | 1114 | template 1115 | using cow_type = std::conditional_t::value, 1116 | detail::copy_on_write, U>; 1117 | 1118 | template 1119 | using cow_copy_type = std::conditional_t::value, 1120 | detail::copy_on_write, const U&>; 1121 | 1122 | using lock_type = std::unique_lock; 1123 | using slot_base = detail::slot_base; 1124 | using slot_ptr = detail::slot_ptr; 1125 | using slots_type = std::vector; 1126 | struct group_type { slots_type slts; group_id gid; }; 1127 | using list_type = std::vector; // kept ordered by ascending gid 1128 | 1129 | public: 1130 | using arg_list = trait::typelist; 1131 | using ext_arg_list = trait::typelist; 1132 | 1133 | signal_base() noexcept : m_block(false) {} 1134 | ~signal_base() override { 1135 | disconnect_all(); 1136 | } 1137 | 1138 | signal_base(const signal_base&) = delete; 1139 | signal_base& operator=(const signal_base&) = delete; 1140 | 1141 | signal_base(signal_base&& o) /* not noexcept */ 1142 | : m_block{ o.m_block.load() } 1143 | { 1144 | lock_type lock(o.m_mutex); 1145 | using std::swap; 1146 | swap(m_slots, o.m_slots); 1147 | } 1148 | 1149 | signal_base& operator=(signal_base&& o) /* not noexcept */ { 1150 | lock_type lock1(m_mutex, std::defer_lock); 1151 | lock_type lock2(o.m_mutex, std::defer_lock); 1152 | std::lock(lock1, lock2); 1153 | 1154 | using std::swap; 1155 | swap(m_slots, o.m_slots); 1156 | m_block.store(o.m_block.exchange(m_block.load())); 1157 | return *this; 1158 | } 1159 | 1160 | /** 1161 | * Emit a signal 1162 | * 1163 | * Effect: All non blocked and connected slot functions will be called 1164 | * with supplied arguments. 1165 | * Safety: With proper locking (see pal::signal), emission can happen from 1166 | * multiple threads simultaneously. The guarantees only apply to the 1167 | * signal object, it does not cover thread safety of potentially 1168 | * shared state used in slot functions. 1169 | * 1170 | * @param a... arguments to emit 1171 | */ 1172 | template 1173 | void operator()(U && ...a) { 1174 | if (m_block) { 1175 | return; 1176 | } 1177 | 1178 | // Reference to the slots to execute them out of the lock 1179 | // a copy may occur if another thread writes to it. 1180 | cow_copy_type ref = slots_reference(); 1181 | 1182 | for (const auto& group : detail::cow_read(ref)) { 1183 | for (const auto& s : group.slts) { 1184 | s->operator()(a...); 1185 | } 1186 | } 1187 | } 1188 | 1189 | /** 1190 | * Connect a callable of compatible arguments 1191 | * 1192 | * Effect: Creates and stores a new slot responsible for executing the 1193 | * supplied callable for every subsequent signal emission. 1194 | * Safety: Thread-safety depends on locking policy. 1195 | * 1196 | * @param c a callable 1197 | * @param gid an identifier that can be used to order slot execution 1198 | * @return a connection object that can be used to interact with the slot 1199 | */ 1200 | template 1201 | std::enable_if_t, connection> 1202 | connect(Callable&& c, group_id gid = 0) { 1203 | using slot_t = detail::slot; 1204 | auto s = make_slot(std::forward(c), gid); 1205 | connection conn(s); 1206 | add_slot(std::move(s)); 1207 | return conn; 1208 | } 1209 | 1210 | /** 1211 | * Connect a callable with an additional connection argument 1212 | * 1213 | * The callable's first argument must be of type connection. This overload 1214 | * the callable to manage it's own connection through this argument. 1215 | * 1216 | * @param c a callable 1217 | * @param gid an identifier that can be used to order slot execution 1218 | * @return a connection object that can be used to interact with the slot 1219 | */ 1220 | template 1221 | std::enable_if_t, connection> 1222 | connect_extended(Callable&& c, group_id gid = 0) { 1223 | using slot_t = detail::slot_extended; 1224 | auto s = make_slot(std::forward(c), gid); 1225 | connection conn(s); 1226 | std::static_pointer_cast(s)->conn = conn; 1227 | add_slot(std::move(s)); 1228 | return conn; 1229 | } 1230 | 1231 | /** 1232 | * Overload of connect for pointers over member functions derived from 1233 | * observer 1234 | * 1235 | * @param pmf a pointer over member function 1236 | * @param ptr an object pointer derived from observer 1237 | * @param gid an identifier that can be used to order slot execution 1238 | * @return a connection object that can be used to interact with the slot 1239 | */ 1240 | template 1241 | std::enable_if_t&& 1242 | trait::is_observer_v, connection> 1243 | connect(Pmf&& pmf, Ptr&& ptr, group_id gid = 0) { 1244 | using slot_t = detail::slot_pmf; 1245 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1246 | connection conn(s); 1247 | add_slot(std::move(s)); 1248 | ptr->add_connection(conn); 1249 | return conn; 1250 | } 1251 | 1252 | /** 1253 | * Overload of connect for pointers over member functions 1254 | * 1255 | * @param pmf a pointer over member function 1256 | * @param ptr an object pointer 1257 | * @param gid an identifier that can be used to order slot execution 1258 | * @return a connection object that can be used to interact with the slot 1259 | */ 1260 | template 1261 | std::enable_if_t && 1262 | !trait::is_observer_v && 1263 | !trait::is_weak_ptr_compatible_v, connection> 1264 | connect(Pmf&& pmf, Ptr&& ptr, group_id gid = 0) { 1265 | using slot_t = detail::slot_pmf; 1266 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1267 | connection conn(s); 1268 | add_slot(std::move(s)); 1269 | return conn; 1270 | } 1271 | 1272 | /** 1273 | * Overload of connect for pointer over member functions and 1274 | * 1275 | * @param pmf a pointer over member function 1276 | * @param ptr an object pointer 1277 | * @param gid an identifier that can be used to order slot execution 1278 | * @return a connection object that can be used to interact with the slot 1279 | */ 1280 | template 1281 | std::enable_if_t && 1282 | !trait::is_weak_ptr_compatible_v, connection> 1283 | connect_extended(Pmf&& pmf, Ptr&& ptr, group_id gid = 0) { 1284 | using slot_t = detail::slot_pmf_extended; 1285 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1286 | connection conn(s); 1287 | std::static_pointer_cast(s)->conn = conn; 1288 | add_slot(std::move(s)); 1289 | return conn; 1290 | } 1291 | 1292 | /** 1293 | * Overload of connect for lifetime object tracking and automatic disconnection 1294 | * 1295 | * Ptr must be convertible to an object following a loose form of weak pointer 1296 | * concept, by implementing the ADL-detected conversion function to_weak(). 1297 | * 1298 | * This overload covers the case of a pointer over member function and a 1299 | * trackable pointer of that class. 1300 | * 1301 | * Note: only weak references are stored, a slot does not extend the lifetime 1302 | * of a suppied object. 1303 | * 1304 | * @param pmf a pointer over member function 1305 | * @param ptr a trackable object pointer 1306 | * @param gid an identifier that can be used to order slot execution 1307 | * @return a connection object that can be used to interact with the slot 1308 | */ 1309 | template 1310 | std::enable_if_t&& 1311 | trait::is_weak_ptr_compatible_v, connection> 1312 | connect(Pmf&& pmf, Ptr&& ptr, group_id gid = 0) { 1313 | using trait::to_weak; 1314 | auto w = to_weak(std::forward(ptr)); 1315 | using slot_t = detail::slot_pmf_tracked; 1316 | auto s = make_slot(std::forward(pmf), w, gid); 1317 | connection conn(s); 1318 | add_slot(std::move(s)); 1319 | return conn; 1320 | } 1321 | 1322 | /** 1323 | * Overload of connect for lifetime object tracking and automatic disconnection 1324 | * 1325 | * Trackable must be convertible to an object following a loose form of weak 1326 | * pointer concept, by implementing the ADL-detected conversion function to_weak(). 1327 | * 1328 | * This overload covers the case of a standalone callable and unrelated trackable 1329 | * object. 1330 | * 1331 | * Note: only weak references are stored, a slot does not extend the lifetime 1332 | * of a suppied object. 1333 | * 1334 | * @param c a callable 1335 | * @param ptr a trackable object pointer 1336 | * @param gid an identifier that can be used to order slot execution 1337 | * @return a connection object that can be used to interact with the slot 1338 | */ 1339 | template 1340 | std::enable_if_t&& 1341 | trait::is_weak_ptr_compatible_v, connection> 1342 | connect(Callable&& c, Trackable&& ptr, group_id gid = 0) { 1343 | using trait::to_weak; 1344 | auto w = to_weak(std::forward(ptr)); 1345 | using slot_t = detail::slot_tracked; 1346 | auto s = make_slot(std::forward(c), w, gid); 1347 | connection conn(s); 1348 | add_slot(std::move(s)); 1349 | return conn; 1350 | } 1351 | 1352 | /** 1353 | * Creates a connection whose duration is tied to the return object 1354 | * Use the same semantics as connect 1355 | */ 1356 | template 1357 | scoped_connection connect_scoped(CallArgs && ...args) { 1358 | return connect(std::forward(args)...); 1359 | } 1360 | 1361 | /** 1362 | * Disconnect slots bound to a callable 1363 | * 1364 | * Effect: Disconnects all the slots bound to the callable in argument. 1365 | * Safety: Thread-safety depends on locking policy. 1366 | * 1367 | * If the callable is a free or static member function, this overload is always 1368 | * available. However, RTTI is needed for it to work for pointer to member 1369 | * functions, function objects or and (references to) lambdas, because the 1370 | * C++ spec does not mandate the pointers to member functions to be unique. 1371 | * 1372 | * @param c a callable 1373 | * @return the number of disconnected slots 1374 | */ 1375 | template 1376 | std::enable_if_t<(trait::is_callable_v || 1377 | trait::is_callable_v || 1378 | trait::is_pmf_v) && 1379 | detail::function_traits::is_disconnectable, size_t> 1380 | disconnect(const Callable& c) { 1381 | return disconnect_if([&](const auto& s) { 1382 | return s->has_full_callable(c); 1383 | }); 1384 | } 1385 | 1386 | /** 1387 | * Disconnect slots bound to this object 1388 | * 1389 | * Effect: Disconnects all the slots bound to the object or tracked object 1390 | * in argument. 1391 | * Safety: Thread-safety depends on locking policy. 1392 | * 1393 | * The object may be a pointer or trackable object. 1394 | * 1395 | * @param obj an object 1396 | * @return the number of disconnected slots 1397 | */ 1398 | template 1399 | std::enable_if_t && 1400 | !trait::is_callable_v && 1401 | !trait::is_pmf_v, size_t> 1402 | disconnect(const Obj& obj) { 1403 | return disconnect_if([&](const auto& s) { 1404 | return s->has_object(obj); 1405 | }); 1406 | } 1407 | 1408 | /** 1409 | * Disconnect slots bound both to a callable and object 1410 | * 1411 | * Effect: Disconnects all the slots bound to the callable and object in argument. 1412 | * Safety: Thread-safety depends on locking policy. 1413 | * 1414 | * For naked pointers, the Callable is expected to be a pointer over member 1415 | * function. If obj is trackable, any kind of Callable can be used. 1416 | * 1417 | * @param c a callable 1418 | * @param obj an object 1419 | * @return the number of disconnected slots 1420 | */ 1421 | template 1422 | size_t disconnect(const Callable& c, const Obj& obj) { 1423 | return disconnect_if([&](const auto& s) { 1424 | return s->has_object(obj) && s->has_callable(c); 1425 | }); 1426 | } 1427 | 1428 | /** 1429 | * Disconnect slots in a particular group 1430 | * 1431 | * Effect: Disconnects all the slots in the group id in argument. 1432 | * Safety: Thread-safety depends on locking policy. 1433 | * 1434 | * @param gid a group id 1435 | * @return the number of disconnected slots 1436 | */ 1437 | size_t disconnect(group_id gid) { 1438 | lock_type lock(m_mutex); 1439 | for (auto& group : detail::cow_write(m_slots)) { 1440 | if (group.gid == gid) { 1441 | size_t count = group.slts.size(); 1442 | group.slts.clear(); 1443 | return count; 1444 | } 1445 | } 1446 | return 0; 1447 | } 1448 | 1449 | /** 1450 | * Disconnects all the slots 1451 | * Safety: Thread safety depends on locking policy 1452 | */ 1453 | void disconnect_all() { 1454 | lock_type lock(m_mutex); 1455 | clear(); 1456 | } 1457 | 1458 | /** 1459 | * Blocks signal emission 1460 | * Safety: thread safe 1461 | */ 1462 | void block() noexcept { 1463 | m_block.store(true); 1464 | } 1465 | 1466 | /** 1467 | * Unblocks signal emission 1468 | * Safety: thread safe 1469 | */ 1470 | void unblock() noexcept { 1471 | m_block.store(false); 1472 | } 1473 | 1474 | /** 1475 | * Tests blocking state of signal emission 1476 | */ 1477 | bool blocked() const noexcept { 1478 | return m_block.load(); 1479 | } 1480 | 1481 | /** 1482 | * Get number of connected slots 1483 | * Safety: thread safe 1484 | */ 1485 | size_t slot_count() noexcept { 1486 | cow_copy_type ref = slots_reference(); 1487 | size_t count = 0; 1488 | for (const auto& g : detail::cow_read(ref)) { 1489 | count += g.slts.size(); 1490 | } 1491 | return count; 1492 | } 1493 | 1494 | protected: 1495 | /** 1496 | * remove disconnected slots 1497 | */ 1498 | void clean(detail::slot_state* state) override { 1499 | lock_type lock(m_mutex); 1500 | const auto idx = state->index(); 1501 | const auto gid = state->group(); 1502 | 1503 | // find the group 1504 | for (auto& group : detail::cow_write(m_slots)) { 1505 | if (group.gid == gid) { 1506 | auto& slts = group.slts; 1507 | 1508 | // ensure we have the right slot, in case of concurrent cleaning 1509 | if (idx < slts.size() && slts[idx] && slts[idx].get() == state) { 1510 | std::swap(slts[idx], slts.back()); 1511 | slts[idx]->index() = idx; 1512 | slts.pop_back(); 1513 | } 1514 | 1515 | return; 1516 | } 1517 | } 1518 | } 1519 | 1520 | private: 1521 | // used to get a reference to the slots for reading 1522 | inline cow_copy_type slots_reference() { 1523 | lock_type lock(m_mutex); 1524 | return m_slots; 1525 | } 1526 | 1527 | // create a new slot 1528 | template 1529 | inline auto make_slot(A && ...a) { 1530 | return detail::make_shared(*this, std::forward(a)...); 1531 | } 1532 | 1533 | // add the slot to the list of slots of the right group 1534 | void add_slot(slot_ptr&& s) { 1535 | const group_id gid = s->group(); 1536 | 1537 | lock_type lock(m_mutex); 1538 | auto& groups = detail::cow_write(m_slots); 1539 | 1540 | // find the group 1541 | auto it = groups.begin(); 1542 | while (it != groups.end() && it->gid < gid) { 1543 | it++; 1544 | } 1545 | 1546 | // create a new group if necessary 1547 | if (it == groups.end() || it->gid != gid) { 1548 | it = groups.insert(it, { {}, gid }); 1549 | } 1550 | 1551 | // add the slot 1552 | s->index() = it->slts.size(); 1553 | it->slts.push_back(std::move(s)); 1554 | } 1555 | 1556 | // disconnect a slot if a condition occurs 1557 | template 1558 | size_t disconnect_if(Cond&& cond) { 1559 | lock_type lock(m_mutex); 1560 | auto& groups = detail::cow_write(m_slots); 1561 | 1562 | size_t count = 0; 1563 | 1564 | for (auto& group : groups) { 1565 | auto& slts = group.slts; 1566 | size_t i = 0; 1567 | while (i < slts.size()) { 1568 | if (cond(slts[i])) { 1569 | std::swap(slts[i], slts.back()); 1570 | slts[i]->index() = i; 1571 | slts.pop_back(); 1572 | ++count; 1573 | } 1574 | else { 1575 | ++i; 1576 | } 1577 | } 1578 | } 1579 | 1580 | return count; 1581 | } 1582 | 1583 | // to be called under lock: remove all the slots 1584 | void clear() { 1585 | detail::cow_write(m_slots).clear(); 1586 | } 1587 | 1588 | private: 1589 | Lockable m_mutex; 1590 | cow_type m_slots; 1591 | std::atomic m_block; 1592 | }; 1593 | 1594 | /** 1595 | * Specialization of signal_base to be used in single threaded contexts. 1596 | * Slot connection, disconnection and signal emission are not thread-safe. 1597 | * The performance improvement over the thread-safe variant is not impressive, 1598 | * so this is not very useful. 1599 | */ 1600 | template 1601 | using signal_st = signal_base; 1602 | 1603 | /** 1604 | * Specialization of signal_base to be used in multi-threaded contexts. 1605 | * Slot connection, disconnection and signal emission are thread-safe. 1606 | * 1607 | * Recursive signal emission and emission cycles are supported too. 1608 | */ 1609 | template 1610 | using signal = signal_base; 1611 | 1612 | } // namespace sigslot 1613 | -------------------------------------------------------------------------------- /include/Base64.hxx: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c 2 | 3 | #ifndef _BASE64_H_ 4 | #define _BASE64_H_ 5 | 6 | #include 7 | #include 8 | typedef unsigned char BYTE; 9 | 10 | std::string base64_encode(char const* buf, unsigned int bufLen); 11 | std::vector base64_decode(std::string const&); 12 | 13 | #endif -------------------------------------------------------------------------------- /include/ColorPicker.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include 5 | 6 | #include "Core.hxx" 7 | #include "Window.hxx" 8 | #include "DualSenseInfo.hxx" 9 | 10 | namespace BrokenBytes::DualSense4Windows::UI { 11 | class ColorPicker : public Window { 12 | public: 13 | sigslot::signal ColorChanged; 14 | sigslot::signal ColorApplied; 15 | 16 | ColorPicker(Color initColor); 17 | virtual ~ColorPicker(); 18 | 19 | void Show() override final; 20 | 21 | private: 22 | Color _oldColor; 23 | Color _color; 24 | HWND _rH; 25 | HWND _gH; 26 | HWND _bH; 27 | 28 | void SetColor(uint8_t r, uint8_t g, uint8_t b); 29 | void SetColor(Color c); 30 | LRESULT ProcessEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) override final; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /include/Core.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #define UNICODE 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | #define SLOT void 7 | #define SIGNAL sigslot::signal -------------------------------------------------------------------------------- /include/DualSense.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core.hxx" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Types.hxx" 10 | 11 | 12 | namespace BrokenBytes::DualSense4Windows { 13 | ///

14 | /// The rate at which to send reports to the DS 15 | /// 16 | constexpr uint8_t DS_SENDRATE = 20; 17 | /// 18 | /// The Default mode at which DualSense operates 19 | /// 20 | constexpr ControllerMode DEFAULT_MODE = ControllerMode::XBoxOne; 21 | 22 | struct DS_LIGHTBARCOLOR { 23 | uint8_t R; 24 | uint8_t G; 25 | uint8_t B; 26 | }; 27 | 28 | enum class DS_BUTTON { 29 | LeftStickX, 30 | LeftStickY, 31 | RightStickX, 32 | RightStickY, 33 | Square, 34 | Triangle, 35 | Circle, 36 | Cross, 37 | L1, 38 | R1, 39 | L2, 40 | R2, 41 | L3, 42 | R3, 43 | PS, 44 | Create, 45 | Options, 46 | Mute 47 | }; 48 | 49 | enum class DS_RUMBLE { 50 | Heavy, 51 | Light 52 | }; 53 | 54 | struct DS_INPUT { 55 | char LeftStickX; 56 | char LeftStickY; 57 | char RightStickX; 58 | char RightStickY; 59 | bool Square; 60 | bool Triangle; 61 | bool Circle; 62 | bool Cross; 63 | bool L1; 64 | bool R1; 65 | bool L2; 66 | bool R2; 67 | bool L3; 68 | bool R3; 69 | bool PS; 70 | bool Create; 71 | bool Options; 72 | bool Mute; 73 | DPad DPad; 74 | char LeftTrigger; 75 | char RightTrigger; 76 | }; 77 | 78 | enum class DS_TRIGGERMODE { 79 | Free, 80 | ContinuousResistance, 81 | StaticResistance 82 | }; 83 | 84 | struct DS_TRIGGER { 85 | uint8_t Resistance; 86 | uint8_t ResistanceStart; 87 | uint8_t Rumble; 88 | DS_TRIGGERMODE Mode; 89 | }; 90 | 91 | /// 92 | /// A report for a state of the controller 93 | /// 94 | struct DS_REPORT { 95 | char bThumbLX; 96 | char bThumbLY; 97 | char bThumbRX; 98 | char bThumbRY; 99 | uint16_t wButtons; 100 | char bSpecial; 101 | char bTriggerL; 102 | char bTriggerR; 103 | }; 104 | 105 | class DualSense { 106 | public: 107 | /// 108 | /// Creates a new DS4 instance based on HID path 109 | /// 110 | /// 111 | /// The number of the controller 112 | DualSense(char* path, uint8_t controllerNumber); 113 | 114 | /// 115 | /// Destroys this instance and frees resources 116 | /// 117 | ~DualSense(); 118 | 119 | /// 120 | /// Returns the mode of the controller 121 | /// 122 | /// The mode 123 | [[nodiscard]] ControllerMode Mode() const; 124 | 125 | /// 126 | /// Returns the path of the device 127 | /// 128 | /// The path 129 | [[nodiscard]] char* Path() const; 130 | 131 | [[nodiscard]] PVIGEM_TARGET Target() const; 132 | 133 | /// 134 | /// Gets the MAC address of the device 135 | /// 136 | /// The mac as a string 137 | std::string MAC() const; 138 | 139 | /// 140 | /// Sets the color of the LED 141 | /// 142 | /// The color to use 143 | void SetLEDColor(DS_LIGHTBARCOLOR color); 144 | 145 | /// 146 | /// Sets the trigger state of a given trigger 147 | /// 148 | /// The trigger data 149 | /// The Id of the trigger, 0 = left, right = 1 150 | void SetTrigger(DS_TRIGGER trigger, uint8_t triggerId); 151 | 152 | /// 153 | /// Sets rumble for a motor 154 | /// 155 | /// The strength 156 | /// The motor to use 157 | void SetRumble(uint8_t rumble, DS_RUMBLE type); 158 | 159 | /// 160 | /// Sets the mute state 161 | /// 0 = Unmute 162 | /// 1 = Muted 163 | /// 2 = Deaf 164 | /// 165 | /// The state 166 | void SetMute(uint8_t muted); 167 | 168 | /// 169 | /// Sets the mode of the controller. 170 | /// 171 | /// The mode to use 172 | void SetMode(ControllerMode mode); 173 | 174 | /// 175 | /// Sets the target of this device 176 | /// 177 | /// 178 | void SetTarget(PVIGEM_TARGET target); 179 | 180 | /// 181 | /// Sets the callback to trigger on new report data 182 | /// 183 | /// The callback to use 184 | void SetReportCallback(std::function reportCallback); 185 | 186 | /// 187 | /// Sets the callback to trigger on mode change 188 | /// 189 | /// The callback to use 190 | void SetModeCallback(std::function modeCallback); 191 | 192 | private: 193 | // Device data 194 | char* _path; 195 | hid_device* _device; 196 | PVIGEM_TARGET _target; 197 | ControllerMode _mode; 198 | DS_LIGHTBARCOLOR _color; 199 | uint8_t _controllerNumber; 200 | DS_TRIGGER _triggerLeft; 201 | DS_TRIGGER _triggerRight; 202 | uint8_t _rumbleHeavy; 203 | uint8_t _rumbleLight; 204 | uint8_t _mute; 205 | 206 | // Data 207 | unsigned char _buffer[32]; 208 | std::thread _thread; 209 | std::function _reportCallback; 210 | std::function _modeCallback; 211 | 212 | 213 | /// 214 | /// Runs a thread routine that reads and writes data 215 | /// 216 | void Routine(); 217 | 218 | /// 219 | /// Writes data to the DS. WIP 220 | /// 221 | void WriteReports(); 222 | 223 | /// 224 | /// Reads data from the DS 225 | /// 226 | void ReadReports(); 227 | }; 228 | } 229 | -------------------------------------------------------------------------------- /include/DualSense4Windows.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | namespace BrokenBytes::DualSense4Windows { 3 | /// 4 | /// Inits the program and does initial setup needed 5 | /// 6 | void Init(); 7 | 8 | /// 9 | /// Inits the logger instance 10 | /// 11 | void InitLogger(); 12 | 13 | /// 14 | /// Inits the UI portion of the app 15 | /// 16 | void InitUI(); 17 | 18 | /// 19 | /// Inits the interface for ViGEm and HID 20 | /// 21 | void InitInterface(); 22 | 23 | /// 24 | /// Connects all signals and slots 25 | /// 26 | void ConnectSignals(); 27 | 28 | /// 29 | /// RUns the app loop 30 | /// 31 | void Run(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /include/DualSenseInfo.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Core.hxx" 4 | 5 | 6 | namespace BrokenBytes::DualSense4Windows::UI { 7 | struct Color { 8 | uint8_t R; 9 | uint8_t G; 10 | uint8_t B; 11 | 12 | COLORREF RGB_VALUE() const { 13 | return RGB(R, G, B); 14 | } 15 | }; 16 | 17 | enum class ConnectionType { 18 | USB, 19 | Bluetooth 20 | }; 21 | 22 | enum class Mode { 23 | XBox, 24 | DS4, 25 | Native 26 | }; 27 | 28 | struct DualSenseInfo { 29 | char* Path; 30 | std::wstring Name; 31 | ConnectionType Connection; 32 | Mode Mode; 33 | Color Color; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /include/DualSenseList.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | #include "Core.hxx" 10 | #include "DualSenseListEntry.hxx" 11 | #include "DualSenseListHeader.hxx" 12 | 13 | namespace BrokenBytes::DualSense4Windows::UI { 14 | class DualSenseList : public QWidget { 15 | public: 16 | DualSenseList(QWidget* parent); 17 | SLOT Refresh(std::vector& list); 18 | 19 | private: 20 | std::unique_ptr _layout; 21 | std::unique_ptr _header; 22 | std::vector> _entries; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /include/DualSenseListEntry.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace BrokenBytes::DualSense4Windows::UI { 9 | class DualSenseListEntry : public QWidget { 10 | public: 11 | DualSenseListEntry(); 12 | DualSenseListEntry(std::string name, bool state, float battery); 13 | 14 | private: 15 | std::unique_ptr _layout; 16 | std::unique_ptr _icon; 17 | std::unique_ptr _text; 18 | std::unique_ptr _state; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /include/DualSenseListHeader.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace BrokenBytes::DualSense4Windows::UI { 9 | class DualSenseListHeader : public QWidget { 10 | public: 11 | DualSenseListHeader(QWidget* parent); 12 | 13 | private: 14 | std::unique_ptr _layout; 15 | std::unique_ptr _id; 16 | std::unique_ptr _state; 17 | std::unique_ptr _battery; 18 | std::unique_ptr _profile; 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /include/Interface.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Core.hxx" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Types.hxx" 10 | 11 | namespace BrokenBytes::DualSense4Windows { 12 | struct DS_LIGHTBARCOLOR; 13 | 14 | namespace UI { 15 | struct Color; 16 | } 17 | 18 | class DualSense; 19 | struct DS_REPORT; 20 | 21 | } 22 | 23 | namespace BrokenBytes::DualSense4Windows::IO { 24 | // Hardware IDs 25 | constexpr uint16_t SONY = 0x054C; 26 | constexpr uint16_t DS = 0x0CE6; 27 | constexpr int8_t DS_INTERFACE = 0x03; 28 | 29 | class Interface { 30 | public: 31 | /// 32 | /// The signal used for device change notifications 33 | /// 34 | sigslot::signal < std::vector> DevicesChanged; 35 | 36 | 37 | /// 38 | /// Creates a new object 39 | /// Note: This does not init the interface 40 | /// 41 | Interface(); 42 | /// 43 | /// Inits the Interface 44 | /// 45 | void Init(); 46 | 47 | /// 48 | /// Gets all DualSense devices connected 49 | /// 50 | /// A map of devices, by HID path and instance 51 | std::vector GetDualSenses(); 52 | /// 53 | /// Updates the list of connected DualSenses 54 | /// 55 | void UpdateDualSenseDevices(); 56 | 57 | /// 58 | /// Creates a new virtual device with type 59 | /// 60 | /// 61 | /// The virtual device 62 | PVIGEM_TARGET CreateVirtualDevice(ControllerMode mode); 63 | 64 | /// 65 | /// Removes a virtual device from the bus 66 | /// 67 | /// The device to remove 68 | void RemoveVirtualDevice(PVIGEM_TARGET device); 69 | 70 | /// 71 | /// Binds a virtual device to a physical DualSense 72 | /// 73 | /// The device to bind 74 | /// The target to be bound to 75 | void BindVirtualDevice(DualSense* device, PVIGEM_TARGET target); 76 | 77 | void SetColor(uint8_t ID, DS_LIGHTBARCOLOR c); 78 | 79 | // Slots 80 | 81 | private: 82 | PVIGEM_CLIENT _client; 83 | std::map _devices; 84 | std::map _virtualDevices; 85 | 86 | void InitViGEmClient(); 87 | }; 88 | } -------------------------------------------------------------------------------- /include/Logger.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace BrokenBytes::DualSense4Windows::Logger { 7 | static inline std::fstream LOG; 8 | static inline std::mutex m; 9 | inline void Log(std::string msg) { 10 | const std::lock_guard lock(m); 11 | LOG.open("./log.txt",std::fstream::out | std::fstream::app); 12 | LOG << msg << "\n"; 13 | LOG.close(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /include/MainWindow.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "ColorPicker.hxx" 13 | #include "Core.hxx" 14 | 15 | #include "DualSense.hxx" 16 | #include "DualSenseList.hxx" 17 | #include "Window.hxx" 18 | 19 | namespace BrokenBytes::DualSense4Windows::UI { 20 | constexpr wchar_t* WINDOW_NAME = L"DualSense4Windows"; 21 | constexpr uint32_t WINDOW_WIDTH = 800; 22 | constexpr uint32_t WINDOW_HEIGHT = 600; 23 | 24 | class MainWindow : public QMainWindow { 25 | public: 26 | /// 27 | /// Called when the MainWindow starts 28 | /// 29 | SIGNAL<> MainWindowStarted; 30 | /// 31 | /// Called when the MainWindow receives a device change event 32 | /// 33 | SIGNAL> DevicesChanged; 34 | 35 | /// 36 | /// Called when the MainWindow triggers a color picker change 37 | /// 38 | SIGNAL ColorChanged; 39 | 40 | /// 41 | /// Creates an MainWindow instance 42 | /// 43 | MainWindow(); 44 | ~MainWindow(); 45 | 46 | /// 47 | /// Runs the MainWindow loop 48 | /// 49 | void Run(); 50 | 51 | /// 52 | /// Called on Update 53 | /// 54 | void OnUpdate(); 55 | 56 | /// 57 | /// Called on close of the MainWindow 58 | /// 59 | void OnClose(); 60 | 61 | // Slots 62 | /// 63 | /// Called when the number of DualSense devices connected changes 64 | /// 65 | /// 66 | SLOT DualSenseDevicesChanged(std::vector devices); 67 | 68 | 69 | private: 70 | std::shared_ptr _mainWindow; 71 | std::shared_ptr _window; 72 | std::unique_ptr _layout; 73 | std::unique_ptr _list; 74 | 75 | void RegisterWindows(); 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /include/Math.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace BrokenBytes::DualSense4Windows::Math { 10 | template::value>::type* = nullptr> 12 | struct Vector2 { 13 | T x; 14 | T y; 15 | }; 16 | 17 | template::value>::type* = nullptr> 19 | struct Vector3 { 20 | T x; 21 | T y; 22 | T z; 23 | }; 24 | 25 | template::value>::type* = nullptr> 27 | struct Vector4 { 28 | T x; 29 | T y; 30 | T z; 31 | T w; 32 | }; 33 | 34 | /// 35 | /// Converts a value from uint range to float range from (-1) - 1 36 | /// 37 | /// 38 | /// The float value 39 | inline float ConvertToSignedFloat(uint8_t value) { 40 | auto f = static_cast(value); 41 | f /= 255; 42 | return static_cast((f * 2) - 1.0f); 43 | } 44 | 45 | /// 46 | /// Converts a value from uint range to float range from 0 - 1 47 | /// 48 | /// 49 | /// The float value 50 | inline float ConvertToUnsignedFloat(uint8_t value) { 51 | auto f = static_cast(value); 52 | f /= 255; 53 | return static_cast(f); 54 | } 55 | 56 | 57 | /// 58 | /// Gets the CRC-32 checksum for a range of bytes 59 | /// 60 | /// 61 | /// The CRC Checksum split into 4 bytes 62 | inline std::array GetCRCFromBytes(unsigned char* bytes, size_t length) { 63 | std::uint32_t crc = CRC::Calculate(bytes, length, CRC::CRC_32()); 64 | auto arr = std::array { 65 | static_cast(crc), 66 | static_cast(crc >> 8), 67 | static_cast(crc >> 16), 68 | static_cast(crc >> 24), 69 | }; 70 | 71 | return arr; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /include/Squirrel.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace BrokenBytes::Squirrel { 9 | struct Nut { 10 | std::string module; 11 | std::string event; 12 | std::string message; 13 | }; 14 | 15 | class Squirrel { 16 | public: 17 | /// 18 | /// Creates the singleton instance of the Squirrel logger 19 | /// 20 | /// The instance 21 | static std::shared_ptr Instance(); 22 | 23 | /// 24 | /// Plants a new Nut(A log) 25 | /// 26 | /// The Module 27 | /// The Event 28 | /// The Message 29 | void Plant( 30 | std::string module, 31 | std::string event, 32 | std::string message 33 | ); 34 | 35 | /// 36 | /// Picks the last Nut. This reads the newest log but does not clear it 37 | /// 38 | /// A Nut 39 | [[nodiscard]] std::optional Pick() const; 40 | 41 | /// 42 | /// Consumes the last Nut. This reads the newest log and removes it 43 | /// 44 | /// A Nut 45 | [[nodiscard]] std::optional Consume(); 46 | 47 | private: 48 | Squirrel(); 49 | static inline std::weak_ptr _instance; 50 | std::vector _nuts; 51 | }; 52 | 53 | inline void Squirrel::Plant( 54 | std::string module, 55 | std::string event, 56 | std::string message 57 | ) { 58 | _nuts.emplace_back(Nut{ module, event, message }); 59 | } 60 | 61 | inline Squirrel::Squirrel() { 62 | _nuts = std::vector(); 63 | } 64 | 65 | inline std::shared_ptr Squirrel::Instance() { 66 | if(auto ptr = Squirrel::_instance.lock()) { 67 | return ptr; 68 | } 69 | return std::make_shared(Squirrel()); 70 | } 71 | 72 | inline std::optional Squirrel::Pick() const { 73 | if(_nuts.empty()) { 74 | return std::nullopt; 75 | } 76 | return _nuts.front(); 77 | } 78 | 79 | inline std::optional Squirrel::Consume() { 80 | if(_nuts.empty()) { 81 | return std::nullopt; 82 | } 83 | auto nut = _nuts.front(); 84 | _nuts.erase(_nuts.begin()); 85 | return nut; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /include/Types.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace BrokenBytes::DualSense4Windows { 4 | enum class ControllerMode { 5 | DS4, 6 | XBoxOne 7 | }; 8 | 9 | enum class DPad { 10 | None, 11 | Left, 12 | LeftUp, 13 | Up, 14 | RightUp, 15 | Right, 16 | RightDown, 17 | Down, 18 | LeftDown 19 | }; 20 | } -------------------------------------------------------------------------------- /include/Utils.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "DualSense.hxx" 4 | 5 | namespace BrokenBytes::DualSense4Windows::Utils { 6 | 7 | /// 8 | /// Maps a DualSense report to a DS4 Report 9 | /// 10 | /// The report to be mapped to 11 | /// The mapping report 12 | void MapDS4Report(DS4_REPORT& report, DS_REPORT raw); 13 | 14 | /// 15 | /// Maps a DualSense report to a XUSB Report 16 | /// 17 | /// The report to be mapped to 18 | /// The mapping report 19 | void MapXUSBReport(XUSB_REPORT& report, DS_REPORT raw); 20 | 21 | void GetDSInputFromReport(DS_REPORT report, DS_INPUT& input); 22 | } 23 | -------------------------------------------------------------------------------- /include/Window.hxx: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Core.hxx" 7 | #include "Math.hxx" 8 | 9 | namespace BrokenBytes::DualSense4Windows::UI { 10 | constexpr LPCWSTR WINDOW_DEFAULT = L"WINDOW_DEFAULT"; 11 | 12 | constexpr int FONT_TAB = 19; 13 | constexpr int FONT_HEADLINE = 23; 14 | constexpr int FONT_LABEL = 21; 15 | constexpr int FONT_BTN = 22; 16 | 17 | class Window { 18 | public: 19 | Window( 20 | LPCWSTR title, 21 | uint16_t width, 22 | uint16_t height, 23 | UINT extraStyles = 0 24 | ) { 25 | _handle = nullptr; 26 | _isRegistered = false; 27 | _dimensions = { width, height }; 28 | WNDCLASSW wnd = { }; 29 | wnd.hInstance = GetModuleHandle(nullptr); 30 | wnd.lpszClassName = WINDOW_DEFAULT; 31 | wnd.lpfnWndProc = WindowProcMain; 32 | wnd.style = CS_DBLCLKS | CS_PARENTDC; 33 | wnd.lpszMenuName = nullptr; 34 | Register(&wnd); 35 | _handle = CreateWindow( 36 | WINDOW_DEFAULT, 37 | title, 38 | WS_OVERLAPPEDWINDOW | extraStyles, 39 | 0, 40 | 0, 41 | width, 42 | height, 43 | nullptr, 44 | nullptr, 45 | GetModuleHandle(nullptr), 46 | nullptr 47 | ); 48 | SetWindowLongPtr(_handle, GWLP_USERDATA, reinterpret_cast(this)); 49 | SetFont(Handle(), FONT_HEADLINE); 50 | 51 | if (_handle == nullptr) { 52 | return; 53 | } 54 | } 55 | 56 | /// 57 | /// Wrapper WindowProc 58 | /// 59 | static LRESULT CALLBACK WindowProcMain(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { 60 | auto ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA); 61 | auto w = reinterpret_cast(ptr); 62 | 63 | if (w) { 64 | return w->ProcessEvent(uMsg, wParam, lParam); 65 | } 66 | return DefWindowProc(hwnd, uMsg, wParam, lParam); 67 | } 68 | 69 | /// 70 | /// Shows the window 71 | /// 72 | virtual void Show() { 73 | ShowWindow(_handle, SW_SHOW); 74 | } 75 | /// 76 | /// Hides the window 77 | /// 78 | virtual void Hide() { 79 | ShowWindow(_handle, SW_HIDE); 80 | } 81 | 82 | /// 83 | /// The width of the Window 84 | /// 85 | /// The width 86 | [[nodiscard]] uint16_t Width() const { 87 | return _dimensions.x; 88 | } 89 | 90 | /// 91 | /// The height of the window 92 | /// 93 | /// The height 94 | [[nodiscard]] uint16_t Height() const { 95 | return _dimensions.y; 96 | } 97 | 98 | /// 99 | /// Toggles if this window can be resized 100 | /// 101 | /// Resizable state 102 | void SetResizable(bool resizable) { 103 | if(resizable) { 104 | SetWindowLongPtr( 105 | _handle, 106 | GWL_STYLE, 107 | WS_OVERLAPPEDWINDOW 108 | ); 109 | return; 110 | } 111 | SetWindowLongPtr( 112 | _handle, 113 | GWL_STYLE, 114 | WS_OVERLAPPED| 115 | WS_CAPTION | 116 | WS_SYSMENU | 117 | WS_MINIMIZEBOX | 118 | WS_MAXIMIZEBOX 119 | ); 120 | 121 | } 122 | 123 | protected: 124 | /// 125 | /// Returns the internal handle 126 | /// 127 | /// The handle 128 | HWND Handle() const { 129 | return _handle; 130 | } 131 | 132 | /// 133 | /// Proccess a new event 134 | /// 135 | /// 136 | /// 137 | /// 138 | virtual LRESULT CALLBACK ProcessEvent( 139 | UINT uMsg, 140 | WPARAM wParam, 141 | LPARAM lParam 142 | ) { 143 | return DefWindowProc(Handle(), uMsg, wParam, lParam); 144 | } 145 | 146 | /// 147 | /// Register the class for this window 148 | /// 149 | /// The class 150 | void Register(WNDCLASS* wnd) { 151 | if(_isRegistered) { 152 | return; 153 | } 154 | RegisterClass(wnd); 155 | _class = wnd; 156 | _isRegistered = true; 157 | } 158 | 159 | /// 160 | /// Sets the default font for the given window 161 | /// 162 | /// 163 | void SetFont(HWND handle, int size) { 164 | HFONT hFont = CreateFont(size, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, 165 | OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, HIGH_LEVEL, 166 | DEFAULT_PITCH | FF_DONTCARE, TEXT("Roboto")); 167 | SendMessage( 168 | handle, 169 | WM_SETFONT, 170 | reinterpret_cast(hFont), 171 | true 172 | ); 173 | } 174 | 175 | 176 | /// 177 | /// Sets the handle of this Window 178 | /// 179 | /// The handle 180 | void SetHandle(HWND instance) { 181 | _handle = instance; 182 | } 183 | 184 | private: 185 | HWND _handle; 186 | WNDCLASS* _class; 187 | bool _isRegistered; 188 | Math::Vector2 _dimensions; 189 | }; 190 | } 191 | -------------------------------------------------------------------------------- /src/Base64.cxx: -------------------------------------------------------------------------------- 1 | #include "../include/Base64.hxx" 2 | #include 3 | #include 4 | 5 | static const std::string base64_chars = 6 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 7 | "abcdefghijklmnopqrstuvwxyz" 8 | "0123456789+/"; 9 | 10 | 11 | static inline bool is_base64(char c) { 12 | return (isalnum(c) || (c == '+') || (c == '/')); 13 | } 14 | 15 | std::string base64_encode(char const* buf, unsigned int bufLen) { 16 | std::string ret; 17 | int i = 0; 18 | int j = 0; 19 | char char_array_3[3]; 20 | char char_array_4[4]; 21 | 22 | while (bufLen--) { 23 | char_array_3[i++] = *(buf++); 24 | if (i == 3) { 25 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 26 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 27 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 28 | char_array_4[3] = char_array_3[2] & 0x3f; 29 | 30 | for (i = 0; (i < 4); i++) 31 | ret += base64_chars[char_array_4[i]]; 32 | i = 0; 33 | } 34 | } 35 | 36 | if (i) 37 | { 38 | for (j = i; j < 3; j++) 39 | char_array_3[j] = '\0'; 40 | 41 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 42 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 43 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 44 | char_array_4[3] = char_array_3[2] & 0x3f; 45 | 46 | for (j = 0; (j < i + 1); j++) 47 | ret += base64_chars[char_array_4[j]]; 48 | 49 | while ((i++ < 3)) 50 | ret += '='; 51 | } 52 | 53 | return ret; 54 | } 55 | 56 | std::vector base64_decode(std::string const& encoded_string) { 57 | int in_len = encoded_string.size(); 58 | int i = 0; 59 | int j = 0; 60 | int in_ = 0; 61 | char char_array_4[4], char_array_3[3]; 62 | std::vector ret; 63 | 64 | while (in_len-- && (encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { 65 | char_array_4[i++] = encoded_string[in_]; in_++; 66 | if (i == 4) { 67 | for (i = 0; i < 4; i++) 68 | char_array_4[i] = base64_chars.find(char_array_4[i]); 69 | 70 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 71 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 72 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 73 | 74 | for (i = 0; (i < 3); i++) 75 | ret.push_back(char_array_3[i]); 76 | i = 0; 77 | } 78 | } 79 | 80 | if (i) { 81 | for (j = i; j < 4; j++) 82 | char_array_4[j] = 0; 83 | 84 | for (j = 0; j < 4; j++) 85 | char_array_4[j] = base64_chars.find(char_array_4[j]); 86 | 87 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 88 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 89 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 90 | 91 | for (j = 0; (j < i - 1); j++) ret.push_back(char_array_3[j]); 92 | } 93 | 94 | return ret; 95 | } -------------------------------------------------------------------------------- /src/ColorPicker.cxx: -------------------------------------------------------------------------------- 1 | #include "Core.hxx" 2 | #include 3 | 4 | #include "ColorPicker.hxx" 5 | 6 | namespace BrokenBytes::DualSense4Windows::UI { 7 | constexpr uint16_t WIDTH = 512; 8 | constexpr uint16_t HEIGHT = 250; 9 | 10 | constexpr uint8_t BTN_OK = 0; 11 | constexpr uint8_t BTN_CANCEL = 1; 12 | 13 | constexpr long COLOR_RANGE = MAKELONG(0, 255); 14 | ColorPicker::ColorPicker(Color initColor): 15 | Window(TEXT("Color Picker"), WIDTH, HEIGHT) { 16 | _color = initColor; 17 | _oldColor = initColor; 18 | _rH = CreateWindowEx( 19 | 0, // no extended styles 20 | TRACKBAR_CLASS, // class name 21 | TEXT("R"), // title (caption) 22 | WS_CHILD | 23 | WS_VISIBLE | 24 | TBS_ENABLESELRANGE, // style 25 | 48, 8, // position 26 | WIDTH - 64, 24, // size 27 | Handle(), // parent window 28 | 0, // control identifier 29 | GetModuleHandle(nullptr), // instance 30 | NULL // no WM_CREATE parameter 31 | ); 32 | auto label = CreateWindowEx(0, L"STATIC", L"R", SS_RIGHT | WS_CHILD | WS_VISIBLE, 33 | 0, 8, 48, 16, Handle(), NULL, GetModuleHandle(nullptr), NULL); 34 | 35 | SendMessage(_rH, TBM_SETBUDDY, static_cast(TRUE), reinterpret_cast(label)); 36 | SendMessage(_rH, TBM_SETRANGE, true, COLOR_RANGE); 37 | 38 | 39 | _gH = CreateWindowEx( 40 | 0, // no extended styles 41 | TRACKBAR_CLASS, // class name 42 | TEXT("G"), // title (caption) 43 | WS_CHILD | 44 | WS_VISIBLE | 45 | TBS_ENABLESELRANGE, // style 46 | 48, 40, // position 47 | WIDTH - 64, 24, // size 48 | Handle(), // parent window 49 | 0, // control identifier 50 | GetModuleHandle(nullptr), // instance 51 | NULL // no WM_CREATE parameter 52 | ); 53 | 54 | label = CreateWindowEx(0, L"STATIC", L"G", SS_RIGHT | WS_CHILD | WS_VISIBLE, 55 | 0, 8, 48, 16, Handle(), NULL, GetModuleHandle(nullptr), NULL); 56 | 57 | SendMessage(_gH, TBM_SETBUDDY, static_cast(TRUE), reinterpret_cast(label)); 58 | SendMessage(_gH, TBM_SETRANGE, true, COLOR_RANGE); 59 | 60 | _bH = CreateWindowEx( 61 | 0, // no extended styles 62 | TRACKBAR_CLASS, // class name 63 | TEXT("B"), // title (caption) 64 | WS_CHILD | 65 | WS_VISIBLE | 66 | TBS_ENABLESELRANGE, // style 67 | 48, 72, // position 68 | WIDTH - 64, 24, // size 69 | Handle(), // parent window 70 | 0, // control identifier 71 | GetModuleHandle(nullptr), // instance 72 | NULL // no WM_CREATE parameter 73 | ); 74 | label = CreateWindowEx(0, L"STATIC", L"B", SS_RIGHT | WS_CHILD | WS_VISIBLE, 75 | 0, 8, 48, 16, Handle(), NULL, GetModuleHandle(nullptr), NULL); 76 | 77 | SendMessage(_bH, TBM_SETBUDDY, static_cast(TRUE), reinterpret_cast(label)); 78 | SendMessage(_bH, TBM_SETRANGE, true, COLOR_RANGE); 79 | 80 | CreateWindow( 81 | L"BUTTON", // Predefined class; Unicode assumed 82 | L"Apply", // Button text 83 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles 84 | 32, // x position 85 | 172, // y position 86 | 64, // Button width 87 | 24, // Button height 88 | Handle(), // Parent window 89 | reinterpret_cast(BTN_OK), // No menu. 90 | reinterpret_cast(GetWindowLongPtr(Handle(), GWLP_HINSTANCE)), 91 | NULL); 92 | CreateWindow( 93 | L"BUTTON", // Predefined class; Unicode assumed 94 | L"Cancel", // Button text 95 | WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles 96 | WIDTH - 96 - 32, // x position 97 | 172, // y position 98 | 64, // Button width 99 | 24, // Button height 100 | Handle(), // Parent window 101 | reinterpret_cast(BTN_CANCEL), // No menu. 102 | reinterpret_cast(GetWindowLongPtr(Handle(), GWLP_HINSTANCE)), 103 | NULL); 104 | 105 | SetResizable(false); 106 | } 107 | 108 | ColorPicker::~ColorPicker() { 109 | DestroyWindow(Handle()); 110 | } 111 | 112 | void ColorPicker::Show() { 113 | Window::Show(); 114 | SetWindowPos(Handle(), HWND_TOPMOST, 0, 0, WIDTH, HEIGHT, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); 115 | } 116 | 117 | void ColorPicker::SetColor(uint8_t r, uint8_t g, uint8_t b) { 118 | SetColor(Color{ r, g, b }); 119 | } 120 | void ColorPicker::SetColor(Color c) { 121 | _color = c; 122 | ColorChanged(c); 123 | } 124 | 125 | LRESULT ColorPicker::ProcessEvent(UINT uMsg, WPARAM wParam, LPARAM lParam) { 126 | int wmId, wmEvent; 127 | PAINTSTRUCT ps; 128 | HDC hdc; 129 | RECT r { 0, 164, WIDTH, 104 }; 130 | DWORD dwPos[3]; 131 | switch (uMsg) { 132 | case WM_PAINT: 133 | HBRUSH brush; 134 | hdc = BeginPaint(Handle(), &ps); 135 | brush = CreateSolidBrush(_color.RGB_VALUE()); 136 | BeginPaint(Handle(), &ps); 137 | FillRect(hdc, &r, brush); 138 | DeleteObject(brush); 139 | DeleteObject(&r); 140 | EndPaint(Handle(), &ps); 141 | break; 142 | 143 | case WM_COMMAND: 144 | if (LOWORD(wParam) == BTN_OK) { 145 | ColorChanged(_color); 146 | this->Hide(); 147 | PostMessage(Handle(), WM_CLOSE, 0, 0); 148 | } 149 | if (LOWORD(wParam) == BTN_CANCEL) { 150 | ColorChanged(_oldColor); 151 | this->Hide(); 152 | PostMessage(Handle(), WM_CLOSE, 0, 0); 153 | } 154 | break; 155 | 156 | case WM_HSCROLL: 157 | dwPos[0] = SendMessage(_rH, TBM_GETPOS, 0, 0); 158 | dwPos[1] = SendMessage(_gH, TBM_GETPOS, 0, 0); 159 | dwPos[2] = SendMessage(_bH, TBM_GETPOS, 0, 0); 160 | SetColor(dwPos[0], dwPos[1], dwPos[2]); 161 | RedrawWindow( 162 | Handle(), 163 | nullptr, 164 | nullptr, 165 | RDW_INVALIDATE 166 | ); 167 | break; 168 | 169 | case WM_CLOSE: 170 | this->~ColorPicker(); 171 | break; 172 | default: 173 | return DefWindowProcA(Handle(), uMsg, wParam, lParam); 174 | } 175 | return 0; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/DualSense.cxx: -------------------------------------------------------------------------------- 1 | #define WIN32_LEAN_AND_MEAN 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "DualSense.hxx" 8 | #include "Math.hxx" 9 | #include "Utils.hxx" 10 | 11 | uint8_t reportID = 0x00; 12 | uint8_t protocolID = 0x00; 13 | 14 | namespace BrokenBytes::DualSense4Windows { 15 | DualSense::DualSense(char* path, uint8_t controllerNumber) { 16 | _path = path; 17 | _device = hid_open_path(_path); 18 | _thread = std::thread(&DualSense::Routine, this); 19 | _controllerNumber = controllerNumber; 20 | _mute = 0; 21 | _triggerRight = {0,0,0, DS_TRIGGERMODE::Free}; 22 | _triggerLeft = {0,0,0, DS_TRIGGERMODE::Free }; 23 | _rumbleLight = 0; 24 | _rumbleHeavy = 0; 25 | _mode = DEFAULT_MODE; 26 | } 27 | 28 | DualSense::~DualSense() { 29 | hid_close(_device); 30 | } 31 | 32 | ControllerMode DualSense::Mode() const { 33 | return _mode; 34 | } 35 | 36 | char* DualSense::Path() const { 37 | return _path; 38 | } 39 | 40 | PVIGEM_TARGET DualSense::Target() const { 41 | return _target; 42 | } 43 | 44 | std::string DualSense::MAC() const { 45 | wchar_t* str = nullptr; 46 | 47 | if(hid_get_manufacturer_string(_device, str, 64) != 0) { 48 | str = L""; 49 | } 50 | 51 | auto err = hid_error(_device); 52 | wprintf(err); 53 | std::wstring ws(str); 54 | return std::string(ws.begin(), ws.end()); 55 | } 56 | 57 | void DualSense::SetLEDColor(DS_LIGHTBARCOLOR color) { 58 | _color = color; 59 | } 60 | 61 | void DualSense::SetTrigger(DS_TRIGGER trigger, uint8_t triggerId) { 62 | if(triggerId > 0) { 63 | _triggerRight = trigger; 64 | return; 65 | } 66 | 67 | _triggerLeft = trigger; 68 | } 69 | 70 | void DualSense::SetRumble(uint8_t rumble, DS_RUMBLE type) { 71 | if(type == DS_RUMBLE::Heavy) { 72 | _rumbleHeavy = rumble; 73 | return; 74 | } 75 | _rumbleLight = rumble; 76 | } 77 | 78 | void DualSense::SetMute(uint8_t muted) { 79 | _mute = muted; 80 | } 81 | 82 | void DualSense::SetMode(ControllerMode mode) { 83 | if(mode == _mode) { 84 | return; 85 | } 86 | _mode = mode; 87 | _modeCallback(_mode); 88 | } 89 | 90 | void DualSense::SetTarget(PVIGEM_TARGET target) { 91 | _target = target; 92 | } 93 | 94 | void DualSense::SetReportCallback(std::function reportCallback) { 95 | this->_reportCallback = reportCallback; 96 | } 97 | 98 | void DualSense::SetModeCallback(std::function modeCallback 99 | ) { 100 | this->_modeCallback = modeCallback; 101 | } 102 | 103 | void DualSense::Routine() { 104 | uint8_t ticks = 0; 105 | memset(_buffer, 0, 32); 106 | while (_device != nullptr) { 107 | ticks++; 108 | ReadReports(); 109 | if(ticks < DS_SENDRATE) { 110 | continue; 111 | } else { 112 | ticks = 0; 113 | } 114 | 115 | WriteReports(); 116 | } 117 | } 118 | 119 | void DualSense::ReadReports() { 120 | if(hid_read(_device, _buffer, 32) < 1) { 121 | return; 122 | } 123 | 124 | uint16_t btns = _buffer[8]; 125 | btns += (_buffer[9] << 8); 126 | btns += (_buffer[10] << 16); 127 | 128 | const DS_REPORT rep = DS_REPORT{ 129 | static_cast(_buffer[1]), 130 | static_cast(_buffer[2]), 131 | static_cast(_buffer[3]), 132 | static_cast(_buffer[4]), 133 | btns, 134 | static_cast(_buffer[10]), 135 | static_cast(_buffer[5]), 136 | static_cast(_buffer[6]) 137 | }; 138 | 139 | if (_reportCallback != nullptr) { 140 | _reportCallback(rep); 141 | } 142 | } 143 | 144 | void DualSense::WriteReports() { 145 | unsigned char outputReport[48]; 146 | memset(outputReport, 0, 48); 147 | // Config 148 | // WIP LEDs, Rumble 149 | // Not functional yet 150 | // Contains parts of https://www.reddit.com/r/gamedev/comments/jumvi5/dualsense_haptics_leds_and_more_hid_output_report/ 151 | outputReport[0] = 0x02; // report type 152 | 153 | outputReport[1] = 0xff; // flags determining what changes this packet will perform 154 | // 0x01 ??? used by PS Remote Play on startup 155 | // 0x02 ??? 156 | // 0x04 ??? used by PS Remote Play on startup 157 | // 0x08 ??? used by PS Remote Play on startup 158 | // 0x10 modification of audio volume 159 | // 0x20 toggling of internal speaker while headset is connected 160 | // 0x40 modification of microphone volume 161 | // 0x80 toggling of internal mic or external speaker while headset is connected 162 | outputReport[2] = 0x15; // further flags determining what changes this packet will perform 163 | // 0x01 toggling microphone LED 164 | // 0x02 toggling audio/mic mute 165 | // 0x04 toggling LED strips on the sides of the touchpad 166 | // 0x08 will actively turn all LEDs off? Convenience flag? (if so, third parties might not support it properly) 167 | // 0x10 toggling white player indicator LEDs below touchpad 168 | // 0x20 ??? 169 | // 0x40 ??? used by PS Remote Play on startup and exit 170 | // 0x80 ??? 171 | 172 | // main motors 173 | outputReport[3] = _rumbleLight; // left low freq motor 0-255 174 | outputReport[4] = _rumbleHeavy; // right high freq motor 0-255 175 | 176 | // audio settings requiring volume control flags 177 | outputReport[5] = 0xff; // audio volume of connected headphones (maxes out at about 0x7f) 178 | outputReport[6] = 0xff; // volume of internal speaker (0-255) (ties in with index 38?!?) 179 | outputReport[7] = 0xff; // internal microphone volume (not at all linear; 0-255, appears to max out at about 64; 0 is not fully muted, use audio mute flag instead!) 180 | outputReport[8] = 0x0c; // internal device enablement override flags (0xc default by ps remote play) 181 | // 0x01 = enable internal microphone (in addition to a connected headset) 182 | // 0x04 = ??? set by default via PS Remote Play 183 | // 0x08 = ??? set by default via PS Remote Play 184 | // 0x10 = disable attached headphones (only if 0x20 to enable internal speakers is provided as well) 185 | // 0x20 = enable audio on internal speaker (in addition to a connected headset) 186 | 187 | // audio related LEDs requiring according LED toggle flags 188 | outputReport[9] = _mute; // microphone LED (1 = on, 2 = pulsating / neither does affect the mic) 189 | 190 | // audio settings requiring mute toggling flags 191 | outputReport[10] = 0x00; // 0x10 microphone mute, 0x40 audio mute 192 | 193 | // left trigger motor 194 | outputReport[11] = static_cast(_triggerRight.Mode); // right trigger motor mode (0 = no resistance, 1 = continuous resistance, 2 = section resistance / PS Remote Play defaults this to 5; bit 4 only disables the motor?) 195 | outputReport[12] = _triggerRight.ResistanceStart; // right trigger start of resistance section (0-255; 0 = released state; 0xb0 roughly matches trigger value 0xff) 196 | outputReport[13] = _triggerRight.Resistance; // right trigger 197 | // (mode1) amount of force exerted; 0-255 198 | // (mode2) end of resistance section (>= begin of resistance section is enforced); 0xff makes it behave like mode1 199 | outputReport[14] = 0; // right trigger force exerted in range (mode2), 0-255 200 | 201 | outputReport[22] = static_cast(_triggerLeft.Mode);// left trigger motor mode (0 = no resistance, 1 = continuous resistance, 2 = section resistance / PS Remote Play defaults this to 5; bit 4 only disables the motor?) 202 | outputReport[23] = _triggerLeft.ResistanceStart; // left trigger start of resistance section 0-255 (0 = released state; 0xb0 roughly matches trigger value 0xff) 203 | outputReport[24] = _triggerLeft.Resistance; // left trigger 204 | // (mode1) amount of force exerted; 0-255 205 | // (mode2) end of resistance section (>= begin of resistance section is enforced); 0xff makes it behave like mode1 206 | outputReport[25] = 0; // left trigger: (mode2) amount of force exerted within range; 0-255 207 | 208 | outputReport[38] = 0x07; // volume of internal speaker (0-7; ties in with index 6) 209 | 210 | // Uninterruptable Pulse LED setting (requires LED setting flag) 211 | outputReport[39] = 0; // 2 = blue LED pulse (together with index 42) 212 | outputReport[42] = 0; // pulse option 213 | /*1 = slowly(2s) fade to blue(scheduled to when the regular LED settings are active) 214 | 2 = slowly(2s) fade out(scheduled after fade - in completion) with eventual switch back to configured LED color; only a fade - out can cancel the pulse(neither index 2, 0x08, nor turning this off will cancel it!) 215 | */ 216 | // Regular LED settings (requires LED setting flag) 217 | outputReport[44] = _controllerNumber + 1; // 5 white player indicator LEDs below the touchpad (bitmask 00-1f from left to right with 0x04 being the center LED) 218 | outputReport[45] = _color.R; // Red value of light bars left and right from touchpad 219 | outputReport[46] = _color.G; // Green value of light bars left and right from touchpad 220 | outputReport[47] = _color.B; // Blue value of light bars left and right from touchpad 221 | hid_write(_device, outputReport, 48); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/DualSense4Windows.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "DualSense4Windows.hxx" 8 | 9 | #include "Interface.hxx" 10 | #include "MainWindow.hxx" 11 | #include "Utils.hxx" 12 | 13 | 14 | 15 | using namespace BrokenBytes::DualSense4Windows; 16 | 17 | std::unique_ptr App; 18 | std::unique_ptr Interface; 19 | std::unique_ptr <::UI::MainWindow> Window; 20 | 21 | int main(int argc, char** argv); 22 | 23 | 24 | 25 | int WinMain( 26 | HINSTANCE hInstance, 27 | HINSTANCE hPrevInstance, 28 | LPSTR lpCmdLine, 29 | int nShowCmd 30 | ) { 31 | auto name = new char[] {"DualSense 4 Windows"}; 32 | auto args = new char* [] {name}; 33 | main(1, args); 34 | } 35 | 36 | 37 | int main(int argc, char** argv) { 38 | int args = 0; 39 | std::cout << *argv << std::endl; 40 | App = std::make_unique(argc, argv); 41 | Interface = std::make_unique(); 42 | Window = std::make_unique(); 43 | Interface->DevicesChanged.connect( 44 | &UI::MainWindow::DualSenseDevicesChanged, 45 | Window.get() 46 | ); 47 | Interface->Init(); 48 | Window->setFixedWidth(1025); 49 | Window->setFixedHeight(768); 50 | Window->show(); 51 | return App->exec(); 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/DualSenseList.cxx: -------------------------------------------------------------------------------- 1 | #include "DualSenseList.hxx" 2 | 3 | namespace BrokenBytes::DualSense4Windows::UI { 4 | 5 | DualSenseList::DualSenseList(QWidget* parent) : QWidget(parent) { 6 | _layout = std::make_unique(this); 7 | _header = std::make_unique(this); 8 | _layout->addWidget(_header.get()); 9 | } 10 | 11 | void DualSenseList::Refresh(std::vector& list) { 12 | for (auto& item : _entries) { 13 | _layout->removeWidget(item.get()); 14 | } 15 | _entries.clear(); 16 | _entries.shrink_to_fit(); 17 | for(auto& item: list) { 18 | auto ptr = 19 | _entries.emplace_back(std::make_unique(item, false, 0)).get(); 20 | _layout->addWidget(ptr); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/DualSenseListEntry.cxx: -------------------------------------------------------------------------------- 1 | #include "DualSenseListEntry.hxx" 2 | 3 | namespace BrokenBytes::DualSense4Windows::UI { 4 | DualSenseListEntry::DualSenseListEntry() { 5 | _layout = std::make_unique(this); 6 | _icon = std::make_unique(this); 7 | _text = std::make_unique(this); 8 | _state = std::make_unique(this); 9 | _layout->addWidget(_icon.get()); 10 | _layout->addWidget(_text.get()); 11 | _layout->addWidget(_state.get()); 12 | } 13 | 14 | DualSenseListEntry::DualSenseListEntry(std::string name, bool state, float battery) : DualSenseListEntry() { 15 | _text->setText(name.c_str()); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/DualSenseListHeader.cxx: -------------------------------------------------------------------------------- 1 | #include "DualSenseListHeader.hxx" 2 | 3 | namespace BrokenBytes::DualSense4Windows::UI { 4 | DualSenseListHeader::DualSenseListHeader(QWidget* parent) : QWidget(parent) { 5 | _layout = std::make_unique(this); 6 | _id = std::make_unique(this); 7 | _state = std::make_unique(this); 8 | _battery = std::make_unique(this); 9 | _profile = std::make_unique(this); 10 | 11 | _id->setText("ID"); 12 | _state->setText("State"); 13 | _battery->setText("Battery"); 14 | _profile->setText("Profile"); 15 | 16 | _layout->addWidget(_id.get()); 17 | _layout->addWidget(_state.get()); 18 | _layout->addWidget(_battery.get()); 19 | _layout->addWidget(_profile.get()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Interface.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "Interface.hxx" 7 | #include "ColorPicker.hxx" 8 | #include "Types.hxx" 9 | #include "DualSense.hxx" 10 | #include "Utils.hxx" 11 | 12 | namespace BrokenBytes::DualSense4Windows::IO { 13 | /// 14 | /// Callback used for ViGEm notification when in DS4 Mode. 15 | /// 16 | /// 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | static void __stdcall DS4_Notification( 23 | PVIGEM_CLIENT Client, 24 | PVIGEM_TARGET Target, 25 | UCHAR LargeMotor, 26 | UCHAR SmallMotor, 27 | DS4_LIGHTBAR_COLOR LightbarColor, 28 | LPVOID UserData 29 | ) { 30 | auto device = static_cast(UserData); 31 | 32 | device->SetRumble(LargeMotor, DS_RUMBLE::Heavy); 33 | device->SetRumble(SmallMotor, DS_RUMBLE::Light); 34 | } 35 | 36 | /// 37 | /// Callback used for ViGEm notification when in XBOX Mode. 38 | /// 39 | /// 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | static void __stdcall XBOX_Notification( 46 | PVIGEM_CLIENT Client, 47 | PVIGEM_TARGET Target, 48 | UCHAR LargeMotor, 49 | UCHAR SmallMotor, 50 | UCHAR LedNumber, 51 | LPVOID UserData 52 | ) { 53 | auto device = static_cast(UserData); 54 | device->SetRumble(LargeMotor, DS_RUMBLE::Heavy); 55 | device->SetRumble(SmallMotor, DS_RUMBLE::Light); 56 | if (auto d = device; d->Target() == Target) { 57 | return; 58 | } 59 | } 60 | 61 | Interface::Interface() { 62 | _client = nullptr; 63 | } 64 | 65 | void Interface::Init() { 66 | InitViGEmClient(); 67 | _devices = std::map(); 68 | _virtualDevices = std::map(); 69 | UpdateDualSenseDevices(); 70 | } 71 | 72 | void Interface::InitViGEmClient() { 73 | _client = vigem_alloc(); 74 | if (_client == nullptr) 75 | { 76 | std::cerr << "Uh, not enough memory to do that?!" << std::endl; 77 | return; 78 | } 79 | if (const auto code = vigem_connect(_client); !VIGEM_SUCCESS(code)) 80 | { 81 | std::cerr << "ViGEm Bus connection failed with error code: 0x" << std::hex << code << std::endl; 82 | return; 83 | } 84 | } 85 | 86 | std::vector Interface::GetDualSenses() { 87 | auto devices = std::vector(); 88 | 89 | auto* ds = hid_enumerate(SONY, DS); 90 | while (ds != nullptr) { 91 | if (ds->interface_number != DS_INTERFACE) { 92 | ds = ds->next; 93 | continue; 94 | } 95 | devices.emplace_back(ds->path); 96 | ds = ds->next; 97 | } 98 | hid_free_enumeration(ds); 99 | 100 | return devices; 101 | } 102 | 103 | void Interface::UpdateDualSenseDevices() { 104 | auto dualsenses = GetDualSenses(); 105 | 106 | // Check if a device is no longer present 107 | for (auto ds : _devices) { 108 | bool remove = true; 109 | for (auto d : dualsenses) { 110 | if (strcmp(d, ds.first) == 0) { 111 | remove = false; 112 | break; 113 | } 114 | } 115 | if (remove) { 116 | auto id = ds.first; 117 | delete ds.second; 118 | _devices.erase(ds.first); 119 | RemoveVirtualDevice(_virtualDevices[ds.first]); 120 | } 121 | } 122 | 123 | // Check if a new device has been connected 124 | for (auto ds : dualsenses) { 125 | bool add = true; 126 | for(auto d: _devices) { 127 | if (strcmp(ds, d.first) == 0) { 128 | add = false; 129 | continue; 130 | } 131 | } 132 | if(!add) { 133 | return; 134 | } 135 | 136 | auto dualsense = new DualSense(ds, 0); 137 | _devices.emplace(ds, dualsense); 138 | BindVirtualDevice(dualsense, CreateVirtualDevice(DEFAULT_MODE)); 139 | } 140 | 141 | auto paths = std::vector(); 142 | for(auto item: _devices) { 143 | paths.emplace_back(item.first); 144 | } 145 | DevicesChanged(paths); 146 | } 147 | 148 | PVIGEM_TARGET Interface::CreateVirtualDevice(ControllerMode mode) { 149 | PVIGEM_TARGET target = nullptr; 150 | switch (mode) { 151 | case ControllerMode::DS4: 152 | target = vigem_target_ds4_alloc(); 153 | break; 154 | case ControllerMode::XBoxOne: 155 | target = vigem_target_x360_alloc(); 156 | break; 157 | } 158 | if (auto code = vigem_target_add(_client, target); !VIGEM_SUCCESS(code)) 159 | { 160 | throw std::runtime_error("Could not add target"); 161 | return nullptr; 162 | } 163 | return target; 164 | } 165 | 166 | void Interface::RemoveVirtualDevice(PVIGEM_TARGET device) { 167 | vigem_target_remove(_client, device); 168 | vigem_target_free(device); 169 | } 170 | 171 | void Interface::BindVirtualDevice(DualSense* device, PVIGEM_TARGET target) { 172 | if (device->Mode() == ControllerMode::XBoxOne) { 173 | auto path = device->Path(); 174 | auto code = vigem_target_x360_register_notification( 175 | _client, 176 | target, 177 | &XBOX_Notification, 178 | device); 179 | if (!VIGEM_SUCCESS(code)) { 180 | throw std::runtime_error("Failed to add callback"); 181 | } 182 | } 183 | else { 184 | auto path = device->Path(); 185 | auto code = vigem_target_ds4_register_notification( 186 | _client, 187 | target, 188 | &DS4_Notification, 189 | device); 190 | if (!VIGEM_SUCCESS(code)) { 191 | throw std::runtime_error("Failed to add callback"); 192 | } 193 | } 194 | 195 | device->SetModeCallback([this, device, target](ControllerMode mode) { 196 | _virtualDevices.erase(device->Path()); 197 | const auto newTarget = CreateVirtualDevice(mode); 198 | 199 | _virtualDevices.emplace(device->Path(), newTarget); 200 | BindVirtualDevice(device, newTarget); 201 | }); 202 | device->SetReportCallback([this, device, target](DS_REPORT raw) { 203 | switch (device->Mode()) { 204 | case ControllerMode::DS4: 205 | DS4_REPORT ds4Report; 206 | Utils::MapDS4Report(ds4Report, raw); 207 | vigem_target_ds4_update(_client, target, ds4Report); 208 | break; 209 | case ControllerMode::XBoxOne: 210 | XUSB_REPORT xusbReport; 211 | Utils::MapXUSBReport(xusbReport, raw); 212 | vigem_target_x360_update(_client, target, xusbReport); 213 | break; 214 | } 215 | }); 216 | } 217 | 218 | void Interface::SetColor(uint8_t ID, DS_LIGHTBARCOLOR c) { 219 | for (auto ds: _devices) { 220 | ds.second->SetLEDColor(c); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/MainWindow.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include "MainWindow.hxx" 3 | #include "ColorPicker.hxx" 4 | #include "Window.hxx" 5 | 6 | #include 7 | #include 8 | 9 | namespace BrokenBytes::DualSense4Windows::UI { 10 | MainWindow::MainWindow() : QMainWindow(nullptr, {}) { 11 | _list = std::make_unique(this); 12 | _list->setFixedSize(1024, 256); 13 | setCentralWidget(_list.get()); 14 | DevicesChanged.connect(&DualSenseList::Refresh, _list.get()); 15 | setPalette({ Qt::gray, {35 , 35, 50} }); 16 | } 17 | 18 | MainWindow::~MainWindow() {} 19 | void MainWindow::Run() {} 20 | void MainWindow::OnUpdate() {} 21 | void MainWindow::OnClose() {} 22 | void MainWindow::RegisterWindows() {} 23 | 24 | void MainWindow::DualSenseDevicesChanged(std::vector devices) { 25 | DevicesChanged(devices); 26 | } 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Utils.cxx: -------------------------------------------------------------------------------- 1 | #include "Core.hxx" 2 | #include 3 | #include 4 | 5 | #include "Utils.hxx" 6 | #include "DualSense.hxx" 7 | #include "Types.hxx" 8 | 9 | namespace BrokenBytes::DualSense4Windows::Utils { 10 | void MapDS4Report(DS4_REPORT& report, DS_REPORT raw) { 11 | DS4_REPORT_INIT(&report); 12 | report.bThumbLX = raw.bThumbLX; 13 | report.bThumbLY = raw.bThumbLY; 14 | report.bThumbRX = raw.bThumbRX; 15 | report.bThumbRY = raw.bThumbRY; 16 | report.wButtons = raw.wButtons; 17 | report.bTriggerL = raw.bTriggerL; 18 | report.bTriggerR = raw.bTriggerR; 19 | report.bSpecial = raw.bSpecial; 20 | } 21 | 22 | void MapXUSBReport(XUSB_REPORT& report, DS_REPORT raw) { 23 | // Button Layout 24 | XUSB_REPORT_INIT(&report); 25 | std::bitset<16> buttons; 26 | std::bitset<4> dpad; 27 | 28 | DS_INPUT input; 29 | GetDSInputFromReport(raw, input); 30 | 31 | 32 | switch (input.DPad) { 33 | case DPad::Down: 34 | dpad[1] = true; 35 | case DPad::Left: 36 | dpad[2] = true; 37 | break; 38 | case DPad::None: 39 | break; 40 | case DPad::LeftUp: 41 | dpad[0] = true; 42 | dpad[2] = true; 43 | break; 44 | case DPad::Up: 45 | dpad[0] = true; 46 | break; 47 | case DPad::RightUp: 48 | dpad[0] = true; 49 | dpad[3] = true; 50 | break; 51 | case DPad::Right: 52 | dpad[3] = true; 53 | break; 54 | case DPad::RightDown: 55 | dpad[1] = true; 56 | dpad[3] = true; 57 | break; 58 | case DPad::LeftDown: 59 | dpad[1] = true; 60 | dpad[2] = true; 61 | break; 62 | } 63 | 64 | for (int x = 0; x < 4; x++) { 65 | buttons[x] = dpad[x]; 66 | } 67 | 68 | buttons[4] = input.Options; 69 | buttons[5] = input.Create; 70 | buttons[6] = input.L3; 71 | buttons[7] = input.R3; 72 | buttons[8] = input.L1; 73 | buttons[9] = input.R1; 74 | buttons[10] = input.PS; 75 | buttons[11] = false; 76 | buttons[12] = input.Cross; 77 | buttons[13] = input.Circle; 78 | buttons[14] = input.Square; 79 | buttons[15] = input.Triangle; 80 | 81 | report.wButtons = static_cast(buttons.to_ulong()); 82 | report.bLeftTrigger = raw.bTriggerL; 83 | report.bRightTrigger = raw.bTriggerR; 84 | report.sThumbLX = (((raw.bThumbLX) * 255) - 32768); 85 | report.sThumbLY = ~(((raw.bThumbLY) * 255) - 32768); 86 | report.sThumbRX = (((raw.bThumbRX) * 255) - 32768); 87 | report.sThumbRY = ~(((raw.bThumbRY) * 255) - 32768); 88 | } 89 | 90 | void GetDSInputFromReport(DS_REPORT report, DS_INPUT& input) { 91 | input.L2 = false; 92 | input.R2 = false; 93 | input.Triangle = (report.wButtons & 0x80); 94 | input.Circle = (report.wButtons & 0x40); 95 | input.Cross = (report.wButtons & 0x20); 96 | input.Square = (report.wButtons & 0x10); 97 | input.L1 = (report.wButtons & 0x100); 98 | input.R1 = (report.wButtons & 0x200); 99 | input.Create = (report.wButtons & 0x1000); 100 | input.Options = (report.wButtons & 0x2000); 101 | input.L3 = (report.wButtons & 0x4000); 102 | input.R3 = (report.wButtons & 0x8000); 103 | input.PS = (report.bSpecial & 0x01); 104 | 105 | switch (report.wButtons & 0x000F) { 106 | case 0b0000: 107 | input.DPad = DPad::Up; 108 | break; 109 | case 0b0001: 110 | input.DPad = DPad::RightUp; 111 | break; 112 | case 0b0010: 113 | input.DPad = DPad::Right; 114 | break; 115 | case 0b0011: 116 | input.DPad = DPad::RightDown; 117 | break; 118 | case 0b0100: 119 | input.DPad = DPad::Down; 120 | break; 121 | case 0b0101: 122 | input.DPad = DPad::LeftDown; 123 | break; 124 | case 0b0110: 125 | input.DPad = DPad::Left; 126 | break; 127 | case 0b0111: 128 | input.DPad = DPad::LeftUp; 129 | break; 130 | case 0b1000: 131 | input.DPad = DPad::None; 132 | break; 133 | } 134 | } 135 | } 136 | --------------------------------------------------------------------------------