├── ds4wizard-cpp ├── pch.cpp ├── ds4wizard-cpp.rc ├── ds4wizard-cpp.ico ├── Resources │ └── race_q00.ico ├── pathutil.h ├── MacAddress.h ├── ds4wizardcpp.qrc ├── ConnectionType.h ├── lock.h ├── XInputGamepad.h ├── Bluetooth.h ├── ProfileEditorDialog.h ├── ISimulator.cpp ├── ISimulator.h ├── JsonData.h ├── Latency.h ├── ProfileEditorDialog.cpp ├── Ds4Color.h ├── Stopwatch.h ├── XInputRumbleSimulator.h ├── pathutil.cpp ├── Ds4Output.h ├── circular_buffer.h ├── Stopwatch.cpp ├── stringutil.h ├── Ds4AutoLightColor.h ├── Settings.h ├── DeviceIdleOptions.cpp ├── Pressable.cpp ├── KeyboardSimulator.cpp ├── DeviceProfileModel.h ├── Ds4Color.cpp ├── XInputRumbleSimulator.cpp ├── busenum.h ├── Settings.cpp ├── Ds4LightOptions.h ├── Logger.cpp ├── ViGEmDriver.cpp ├── ViGEmDriver.h ├── DeviceProfileItemModel.h ├── Ds4Output.cpp ├── Latency.cpp ├── RumbleSequence.h ├── DeviceSettingsCommon.cpp ├── DeviceSettingsCommon.h ├── KeyboardSimulator.h ├── Ds4LightOptions.cpp ├── DeviceIdleOptions.h ├── DeviceSettings.cpp ├── stringutil.cpp ├── Ds4ItemModel.h ├── MouseSimulator.h ├── DeviceSettings.h ├── XInputGamepad.cpp ├── DevicePropertiesDialog.h ├── Logger.h ├── program.h ├── Ds4AutoLightColor.cpp ├── MouseSimulator.cpp ├── MainWindow.h ├── main.cpp ├── Vector2.h ├── Vector3.h ├── DeviceProfile.h ├── Bluetooth.cpp ├── DeviceProfileModel.cpp ├── average.h ├── Ds4Input.h ├── ViGEmTarget.h ├── RumbleSequence.cpp ├── Pressable.h ├── Event.h ├── pch.h ├── Ds4InputData.h ├── program.cpp ├── ViGEmTarget.cpp ├── Trackball.h ├── Ds4DeviceManager.h ├── gmath.h ├── DeviceProfileItemModel.cpp ├── DeviceProfileCache.h ├── Ds4ItemModel.cpp ├── Ds4InputData.cpp ├── Vector2.cpp ├── InputMap.h ├── Ds4Device.h ├── Vector3.cpp └── ProfileEditorDialog.ui ├── libdevicetoggle ├── devicetoggle.h ├── libdevicetoggle.vcxproj.filters └── libdevicetoggle.vcxproj ├── README.md ├── .gitmodules ├── setup.bat ├── ds4wizard-device-toggle ├── main.cpp ├── ds4wizard-device-toggle.vcxproj.filters └── ds4wizard-device-toggle.vcxproj ├── ds4wizard-cpp.cppcheck ├── libhid ├── hid_handle.h ├── hid_util.h ├── hid_handle.cpp ├── libhid.vcxproj.filters ├── hid_instance.h ├── hid_util.cpp └── libhid.vcxproj ├── hid-tester ├── hid-tester.vcxproj.filters ├── main.cpp └── hid-tester.vcxproj ├── LICENSE ├── SingleApplication └── SingleApplication.vcxproj.filters ├── .gitattributes ├── ds4wizard-cpp.sln ├── .gitignore └── ds4wizard-cpp.sln.DotSettings /ds4wizard-cpp/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ds4wizard-cpp.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "ds4wizard-cpp.ico" 2 | 3 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ds4wizard-cpp.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michael-fadely/ds4wizard/HEAD/ds4wizard-cpp/ds4wizard-cpp.ico -------------------------------------------------------------------------------- /ds4wizard-cpp/Resources/race_q00.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michael-fadely/ds4wizard/HEAD/ds4wizard-cpp/Resources/race_q00.ico -------------------------------------------------------------------------------- /libdevicetoggle/devicetoggle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void toggleDevice(const std::wstring& instanceId); 6 | -------------------------------------------------------------------------------- /ds4wizard-cpp/pathutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | void makeValidFileName(std::string& str); 5 | std::string validatedFileName(std::string str); 6 | -------------------------------------------------------------------------------- /ds4wizard-cpp/MacAddress.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | constexpr size_t macAddressLength = 6; 6 | using MacAddress = std::array; 7 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ds4wizardcpp.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Resources/Dualshock_4_Layout manually colored.svg 4 | Resources/race_q00.ico 5 | 6 | 7 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ConnectionType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * \brief The preferred connection type to use for all wired/wireless devices. 7 | */ 8 | BETTER_ENUM(ConnectionType, int, usb, bluetooth) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ds4wizard 2 | *ds4wizard* is an in-development C++/Qt alternative to DS4Windows with additional functionality. 3 | 4 | ## Note 5 | This code was ported from a C# codebase and is consequently not up to my standards for C++ code. It will be cleaned up over time. 6 | -------------------------------------------------------------------------------- /ds4wizard-cpp/lock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define MAKE_GUARD(M) std::lock_guard M ## _guard(M) 6 | 7 | // Create a std::lock_guard for a symbol that has an associated *_lock 8 | #define LOCK(NAME) MAKE_GUARD(NAME ## _lock) 9 | -------------------------------------------------------------------------------- /ds4wizard-cpp/XInputGamepad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | /** 6 | * \brief Simple wrapper for \c XINPUT_GAMEPAD with comparison operators and method for outputting bytes. 7 | */ 8 | struct XInputGamepad : XINPUT_GAMEPAD 9 | { 10 | bool operator==(const XInputGamepad& r) const; 11 | bool operator!=(const XInputGamepad& r) const; 12 | 13 | void toBytes(uint8_t* buffer, int offset) const; 14 | }; 15 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Bluetooth.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | /** 5 | * \brief Class for managing Bluetooth state. 6 | */ 7 | class Bluetooth 8 | { 9 | public: 10 | /** 11 | * \brief Disconnects a device with matching MAC address from 12 | * the first Bluetooth radio it is connected to. 13 | * \param macAddress The MAC address to search for. 14 | * \return \c true on success. 15 | */ 16 | static bool disconnectDevice(std::span macAddress); 17 | }; 18 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ProfileEditorDialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_ProfileEditorDialog.h" 5 | 6 | class DeviceProfileModel; 7 | 8 | class ProfileEditorDialog : public QDialog 9 | { 10 | Q_OBJECT 11 | 12 | DeviceProfileModel* profileModel = nullptr; 13 | 14 | public: 15 | explicit ProfileEditorDialog(DeviceProfileModel* profileModel_, QWidget* parent = Q_NULLPTR); 16 | ~ProfileEditorDialog() override; 17 | 18 | private: 19 | Ui::ProfileEditorDialog ui; 20 | }; 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "json"] 2 | path = dependencies/json 3 | url = https://github.com/nlohmann/json.git 4 | [submodule "dependencies/ViGEmClient"] 5 | path = dependencies/ViGEmClient 6 | url = https://github.com/ViGEm/ViGEmClient.git 7 | [submodule "dependencies/better-enums"] 8 | path = dependencies/better-enums 9 | url = https://github.com/aantron/better-enums.git 10 | [submodule "dependencies/SingleApplication"] 11 | path = dependencies/SingleApplication 12 | url = https://github.com/itay-grudev/SingleApplication.git 13 | -------------------------------------------------------------------------------- /setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal EnableDelayedExpansion 3 | 4 | :MAIN 5 | pushd "%~dp0\dependencies" 6 | 7 | call :MAKE_CMAKE_PROJECT "ViGEmClient" 8 | 9 | popd 10 | goto :EOF 11 | 12 | @rem %~1 = Source directory name (assumes working directory is the dependencies directory) 13 | :MAKE_CMAKE_PROJECT 14 | if exist "%~1-build" ( 15 | rmdir /s /q "%~1-build" 16 | ) 17 | 18 | mkdir "%~1-build" 19 | pushd "%~1-build" 20 | 21 | cmake -G "Visual Studio 17 2022" -A "x64" -D "CMAKE_CONFIGURATION_TYPES=Debug;Release" "..\%~1" 22 | 23 | popd 24 | exit /b %ERRORLEVEL% -------------------------------------------------------------------------------- /ds4wizard-cpp/ISimulator.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ISimulator.h" 3 | 4 | ISimulator::ISimulator(InputSimulator* parent) 5 | : state(SimulatorState::inactive), 6 | parent(parent) 7 | { 8 | } 9 | 10 | void ISimulator::activate(float deltaTime) 11 | { 12 | if (state == SimulatorState::inactive) 13 | { 14 | state = SimulatorState::active; 15 | onActivate(deltaTime); 16 | } 17 | } 18 | 19 | void ISimulator::deactivate(float deltaTime) 20 | { 21 | if (state == SimulatorState::active) 22 | { 23 | state = SimulatorState::inactive; 24 | onDeactivate(deltaTime); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ISimulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class InputSimulator; 4 | 5 | enum class SimulatorState 6 | { 7 | inactive, 8 | active 9 | }; 10 | 11 | class ISimulator 12 | { 13 | public: 14 | SimulatorState state; 15 | InputSimulator* parent; 16 | 17 | explicit ISimulator(InputSimulator* parent); 18 | virtual ~ISimulator() = default; 19 | 20 | void activate(float deltaTime); 21 | virtual void update(float deltaTime) = 0; 22 | void deactivate(float deltaTime); 23 | 24 | private: 25 | virtual void onActivate(float deltaTime) {} 26 | virtual void onDeactivate(float deltaTime) {} 27 | }; 28 | -------------------------------------------------------------------------------- /ds4wizard-device-toggle/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char** argv) 6 | { 7 | if (argc < 3) 8 | { 9 | return -1; 10 | } 11 | 12 | if (strcmp(argv[1], "--toggle-device") != 0) 13 | { 14 | return -2; 15 | } 16 | 17 | const std::string instanceIdA = argv[2]; 18 | const std::wstring instanceIdW(instanceIdA.begin(), instanceIdA.end()); 19 | 20 | try 21 | { 22 | toggleDevice(instanceIdW); 23 | } 24 | catch (const std::exception& ex) 25 | { 26 | std::cout << ex.what() << std::endl; 27 | return -3; 28 | } 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /ds4wizard-cpp.cppcheck: -------------------------------------------------------------------------------- 1 | 2 | 3 | cppcheck-build-dir 4 | win64 5 | ds4wizard-cpp.sln 6 | true 7 | true 8 | false 9 | 10 10 | 11 | 12 | 13 | 14 | qt 15 | windows 16 | 17 | 18 | -------------------------------------------------------------------------------- /ds4wizard-cpp/JsonData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * \brief A simple interface for de/serializable JSON objects. 7 | */ 8 | struct JsonData 9 | { 10 | virtual ~JsonData() = default; 11 | virtual void readJson(const nlohmann::json& json) = 0; 12 | virtual void writeJson(nlohmann::json& json) const = 0; 13 | 14 | nlohmann::json toJson() const 15 | { 16 | nlohmann::json object; 17 | writeJson(object); 18 | return object; 19 | } 20 | 21 | template 22 | static T fromJson(const nlohmann::json& json) 23 | { 24 | T result {}; 25 | result.readJson(json); 26 | return result; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Latency.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Stopwatch.h" 4 | #include "average.h" 5 | 6 | class Latency 7 | { 8 | Stopwatch stopwatch; 9 | Stopwatch::Duration lastValue_ {}; 10 | Stopwatch::Duration peak_ {}; 11 | timed_average average_; 12 | 13 | public: 14 | Latency(); 15 | 16 | void start(); 17 | Stopwatch::Duration stop(); 18 | [[nodiscard]] Stopwatch::Duration elapsed() const; 19 | [[nodiscard]] bool running() const; 20 | 21 | [[nodiscard]] Stopwatch::Duration lastValue() const; 22 | [[nodiscard]] Stopwatch::Duration peak(); 23 | [[nodiscard]] Stopwatch::Duration average(); 24 | void resetPeak(); 25 | }; 26 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ProfileEditorDialog.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ProfileEditorDialog.h" 3 | #include "DeviceProfileModel.h" 4 | 5 | // TODO: maybe https://doc.qt.io/qt-5/qdatawidgetmapper.html#details 6 | 7 | ProfileEditorDialog::ProfileEditorDialog(DeviceProfileModel* profileModel_, QWidget* parent) 8 | : QDialog(parent), 9 | profileModel(profileModel_) 10 | { 11 | ui.setupUi(this); 12 | 13 | ui.lineEdit_ProfileName->setText(QString::fromStdString(profileModel_->profile.name)); 14 | ui.comboBox_ModifierList->setModel(profileModel->makeModifierModel(ui.comboBox_ModifierList)); 15 | } 16 | 17 | ProfileEditorDialog::~ProfileEditorDialog() = default; 18 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4Color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "JsonData.h" 5 | 6 | /** 7 | * \brief PoD describing \c Ds4Device lightbar color. 8 | */ 9 | struct Ds4Color : JsonData 10 | { 11 | uint8_t red = 0; 12 | uint8_t green = 0; 13 | uint8_t blue = 0; 14 | 15 | Ds4Color() = default; 16 | Ds4Color(uint8_t r, uint8_t g, uint8_t b); 17 | 18 | bool operator==(const Ds4Color& other) const; 19 | bool operator!=(const Ds4Color& other) const; 20 | 21 | static Ds4Color lerp(const Ds4Color& a, const Ds4Color& b, float f); 22 | void readJson(const nlohmann::json& json) override; 23 | void writeJson(nlohmann::json& json) const override; 24 | }; 25 | -------------------------------------------------------------------------------- /libhid/hid_handle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Handle 6 | { 7 | public: 8 | bool owner = false; 9 | HANDLE nativeHandle = INVALID_HANDLE_VALUE; 10 | 11 | Handle() = default; 12 | Handle(const Handle& other) = delete; 13 | 14 | Handle(Handle&& other) noexcept; 15 | explicit Handle(HANDLE h, bool owner = false); 16 | ~Handle(); 17 | 18 | [[nodiscard]] bool isValid() const; 19 | 20 | void close(); 21 | 22 | [[nodiscard]] bool operator==(const Handle& rhs) const; 23 | [[nodiscard]] bool operator!=(const Handle& rhs) const; 24 | 25 | Handle& operator=(const Handle& rhs) = delete; 26 | Handle& operator=(Handle&& rhs) noexcept; 27 | }; 28 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Stopwatch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Stopwatch 6 | { 7 | public: 8 | using Clock = std::chrono::high_resolution_clock; 9 | using TimePoint = Clock::time_point; 10 | using Duration = Clock::duration; 11 | 12 | protected: 13 | bool running_ = false; 14 | TimePoint start_time_; 15 | TimePoint end_time_; 16 | 17 | public: 18 | Stopwatch() = default; 19 | explicit Stopwatch(bool start_now); 20 | 21 | void start(); 22 | Duration stop(); 23 | [[nodiscard]] Duration elapsed() const; 24 | [[nodiscard]] bool running() const; 25 | [[nodiscard]] TimePoint start_time() const; 26 | [[nodiscard]] TimePoint end_time() const; 27 | }; 28 | -------------------------------------------------------------------------------- /ds4wizard-cpp/XInputRumbleSimulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #include "ISimulator.h" 6 | #include "ViGEmTarget.h" 7 | #include "Event.h" 8 | 9 | class XInputRumbleSimulator : public ISimulator 10 | { 11 | XINPUT_VIBRATION xinputVibration; 12 | EventToken xinputNotification; 13 | 14 | public: 15 | std::shared_ptr xinputTarget; 16 | 17 | explicit XInputRumbleSimulator(InputSimulator* parent); 18 | ~XInputRumbleSimulator() override = default; 19 | 20 | void update(float deltaTime) override; 21 | 22 | private: 23 | void onActivate(float deltaTime) override; 24 | void onDeactivate(float deltaTime) override; 25 | }; 26 | -------------------------------------------------------------------------------- /ds4wizard-cpp/pathutil.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include 4 | 5 | #include "pathutil.h" 6 | 7 | // TODO: separate function for validating Windows paths. See: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file 8 | 9 | static const std::string INVALID_CHARS(R"(\/:*?"<>|)"); 10 | 11 | static char replace_invalid(char c) 12 | { 13 | if (std::ranges::find(INVALID_CHARS, c) != INVALID_CHARS.cend()) 14 | { 15 | return '_'; 16 | } 17 | 18 | return c; 19 | } 20 | 21 | void makeValidFileName(std::string& str) 22 | { 23 | std::ranges::transform(str, str.begin(), replace_invalid); 24 | } 25 | 26 | std::string validatedFileName(std::string str) 27 | { 28 | makeValidFileName(str); 29 | return str; 30 | } 31 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4Output.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Ds4Color.h" 3 | #include 4 | 5 | /** 6 | * \brief PoD for setting \c Ds4Device parameters like motor speed, light color, etc. 7 | */ 8 | struct Ds4Output 9 | { 10 | uint8_t rightMotor = 0; 11 | uint8_t leftMotor = 0; 12 | Ds4Color lightColor = {}; 13 | uint8_t flashOnDur = 0; 14 | uint8_t flashOffDur = 0; 15 | uint8_t volumeLeft = 50; 16 | uint8_t volumeRight = 50; 17 | uint8_t volumeMic = 50; 18 | uint8_t volumeSpeaker = 50; 19 | 20 | /** 21 | * \brief Updates a buffer with the stored device parameters. 22 | * \param buffer Buffer to update. 23 | * \return \c true if changes have been made to \a buffer. 24 | */ 25 | bool update(std::span buffer) const; 26 | }; 27 | -------------------------------------------------------------------------------- /ds4wizard-cpp/circular_buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | struct circular_buffer 5 | { 6 | private: 7 | std::array points; 8 | 9 | ptrdiff_t l = n - 1; 10 | ptrdiff_t i = 0; 11 | 12 | public: 13 | const ptrdiff_t count = n; 14 | 15 | void insert(const T& value) 16 | { 17 | l = i; 18 | points[i++] = value; 19 | i %= n; 20 | } 21 | 22 | void fill(const T& value) 23 | { 24 | for (auto& p : points) 25 | { 26 | p = value; 27 | } 28 | } 29 | 30 | [[nodiscard]] T newest() const 31 | { 32 | return points[l]; 33 | } 34 | 35 | [[nodiscard]] T oldest() const 36 | { 37 | return points[i]; 38 | } 39 | 40 | const T& operator[](ptrdiff_t index) const 41 | { 42 | return points[(index + n + i) % n]; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Stopwatch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Stopwatch.h" 3 | 4 | Stopwatch::Stopwatch(bool start_now) 5 | { 6 | if (start_now) 7 | { 8 | start(); 9 | } 10 | } 11 | 12 | void Stopwatch::start() 13 | { 14 | running_ = true; 15 | start_time_ = Clock::now(); 16 | } 17 | 18 | Stopwatch::Duration Stopwatch::stop() 19 | { 20 | end_time_ = Clock::now(); 21 | running_ = false; 22 | return elapsed(); 23 | } 24 | 25 | Stopwatch::Duration Stopwatch::elapsed() const 26 | { 27 | return (running_ ? Clock::now() : end_time_) - start_time_; 28 | } 29 | 30 | bool Stopwatch::running() const 31 | { 32 | return running_; 33 | } 34 | 35 | Stopwatch::TimePoint Stopwatch::start_time() const 36 | { 37 | return start_time_; 38 | } 39 | 40 | Stopwatch::TimePoint Stopwatch::end_time() const 41 | { 42 | return end_time_; 43 | } 44 | -------------------------------------------------------------------------------- /ds4wizard-cpp/stringutil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | bool iequalsw(const wchar_t& a, const wchar_t& b); 6 | 7 | bool iequals(const std::wstring& a, const std::wstring& b); 8 | 9 | bool iequalsc(const char& a, const char& b); 10 | 11 | bool iequals(const std::string& a, const std::string& b); 12 | 13 | // source: https://stackoverflow.com/a/217605 14 | 15 | // trim from start (in place) 16 | void triml(std::string& s); 17 | 18 | // trim from end (in place) 19 | void trimr(std::string& s); 20 | 21 | // trim from both ends (in place) 22 | void trim(std::string& s); 23 | 24 | // trim from start (copying) 25 | std::string triml_copy(std::string s); 26 | 27 | // trim from end (copying) 28 | std::string trimr_copy(std::string s); 29 | 30 | // trim from both ends (copying) 31 | std::string trim_copy(std::string s); 32 | -------------------------------------------------------------------------------- /libhid/hid_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Windows 4 | #include 5 | #include 6 | 7 | // STL 8 | #include 9 | #include 10 | 11 | // libhid 12 | #include "hid_instance.h" 13 | 14 | namespace hid 15 | { 16 | std::wstring getDevicePath(HDEVINFO devInfoSet, SP_DEVICE_INTERFACE_DATA* interface, SP_DEVINFO_DATA* data = nullptr) noexcept; 17 | std::wstring getInstanceId(HDEVINFO devInfoSet, SP_DEVINFO_DATA* devInfoData) noexcept; 18 | bool enumerateGuid(const std::function& fn, const GUID& guid) noexcept; 19 | void enumerateHid(const std::function instance)>& fn) noexcept; 20 | void enumerateUsb(const std::function& fn) noexcept; 21 | } 22 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4AutoLightColor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Ds4Color.h" 7 | 8 | /** 9 | * \brief Class for managing automatic light color selection for a \c Ds4Device 10 | * \sa Ds4Device 11 | */ 12 | class Ds4AutoLightColor 13 | { 14 | struct Pair 15 | { 16 | const Ds4Color color; 17 | ptrdiff_t references = 0; 18 | 19 | explicit Pair(const Ds4Color& color); 20 | }; 21 | 22 | inline static std::recursive_mutex lock; 23 | 24 | static std::array colors; 25 | 26 | public: 27 | /** 28 | * \brief Get an automatically assigned color. 29 | * \param index Assigned color index. 30 | * \return Automatically assigned color. 31 | */ 32 | static Ds4Color getColor(ptrdiff_t& index); 33 | 34 | /** 35 | * \brief Release an assigned color by index. 36 | * \param index The index to release. 37 | */ 38 | static void releaseColor(ptrdiff_t index); 39 | }; 40 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ConnectionType.h" 3 | #include "JsonData.h" 4 | 5 | /** 6 | * \brief Structure describing program configuration. 7 | */ 8 | struct Settings : JsonData 9 | { 10 | Settings() = default; 11 | Settings(const Settings&) = default; 12 | 13 | /** 14 | * \brief Specifies the preferred connection mode for all wired/wireless devices. Default is \c ConnectionType::usb 15 | */ 16 | ConnectionType preferredConnection = ConnectionType::usb; 17 | 18 | /** 19 | * \brief If \c true, starts the program minimized. 20 | */ 21 | bool startMinimized = false; 22 | 23 | /** 24 | * \brief If \c true, minimizes the program to the system tray. 25 | */ 26 | bool minimizeToTray = true; 27 | 28 | Settings& operator=(const Settings& rhs) = default; 29 | bool operator==(const Settings& rhs) const; 30 | bool operator!=(const Settings& rhs) const; 31 | 32 | void readJson(const nlohmann::json& json) override; 33 | void writeJson(nlohmann::json& json) const override; 34 | }; 35 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceIdleOptions.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DeviceIdleOptions.h" 3 | 4 | const DeviceIdleOptions DeviceIdleOptions::defaultIdleOptions(std::chrono::minutes(5), true); 5 | 6 | DeviceIdleOptions::DeviceIdleOptions(std::chrono::seconds timeout, bool disconnect) 7 | : timeout(timeout), 8 | disconnect(disconnect) 9 | { 10 | } 11 | 12 | bool DeviceIdleOptions::operator==(const DeviceIdleOptions& other) const 13 | { 14 | return disconnect == other.disconnect && timeout == other.timeout; 15 | } 16 | 17 | bool DeviceIdleOptions::operator!=(const DeviceIdleOptions& other) const 18 | { 19 | return !(*this == other); 20 | } 21 | 22 | void DeviceIdleOptions::readJson(const nlohmann::json& json) 23 | { 24 | this->timeout = std::chrono::seconds(json["timeout"].get()); 25 | this->disconnect = json["disconnect"]; 26 | } 27 | 28 | void DeviceIdleOptions::writeJson(nlohmann::json& json) const 29 | { 30 | json["timeout"] = this->timeout.count(); 31 | json["disconnect"] = this->disconnect; 32 | } 33 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Pressable.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Pressable.h" 3 | 4 | bool Pressable::isActive() const 5 | { 6 | return isActiveState(pressedState); 7 | } 8 | 9 | void Pressable::press(PressedState& state) 10 | { 11 | switch (state) 12 | { 13 | case PressedState::off: 14 | case PressedState::released: 15 | state = PressedState::pressed; 16 | break; 17 | 18 | default: 19 | state = PressedState::on; 20 | break; 21 | } 22 | } 23 | 24 | void Pressable::release(PressedState& state) 25 | { 26 | switch (state) 27 | { 28 | case PressedState::on: 29 | case PressedState::pressed: 30 | state = PressedState::released; 31 | break; 32 | 33 | default: 34 | state = PressedState::off; 35 | break; 36 | } 37 | } 38 | 39 | bool Pressable::isActiveState(PressedState state) 40 | { 41 | return state == PressedState::on || state == PressedState::pressed; 42 | } 43 | 44 | void Pressable::press() 45 | { 46 | press(pressedState); 47 | } 48 | 49 | void Pressable::release() 50 | { 51 | release(pressedState); 52 | } 53 | -------------------------------------------------------------------------------- /hid-tester/hid-tester.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /ds4wizard-cpp/KeyboardSimulator.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | #include "KeyboardSimulator.h" 4 | 5 | KeyboardSimulator::~KeyboardSimulator() 6 | { 7 | for (auto keyCode : pressedKeys) 8 | { 9 | keyUp(keyCode); 10 | } 11 | } 12 | 13 | void KeyboardSimulator::keyUp(int keyCode) 14 | { 15 | pressedKeys.erase(keyCode); 16 | press(keyCode, false); 17 | } 18 | 19 | void KeyboardSimulator::keyDown(int keyCode) 20 | { 21 | pressedKeys.insert(keyCode); 22 | press(keyCode, true); 23 | } 24 | 25 | void KeyboardSimulator::press(int keyCode, bool down) 26 | { 27 | INPUT input; 28 | 29 | input.type = INPUT_KEYBOARD; 30 | input.ki.wScan = static_cast(MapVirtualKey(keyCode, MAPVK_VK_TO_VSC)); 31 | input.ki.dwFlags = KEYEVENTF_SCANCODE; 32 | 33 | if ((keyCode > 32 && keyCode < 47) || (keyCode > 90 && keyCode < 94)) 34 | { 35 | input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; 36 | } 37 | 38 | if (!down) 39 | { 40 | input.ki.dwFlags |= KEYEVENTF_KEYUP; // 0 indicates pressed 41 | } 42 | 43 | SendInput(1, &input, sizeof(INPUT)); 44 | } 45 | -------------------------------------------------------------------------------- /ds4wizard-device-toggle/ds4wizard-device-toggle.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceProfileModel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "DeviceProfile.h" 4 | 5 | class ModifierListModel final : public QAbstractListModel 6 | { 7 | Q_OBJECT 8 | 9 | DeviceProfile* profile = nullptr; 10 | bool includeNone = false; 11 | 12 | public: 13 | ModifierListModel(QObject* parent, DeviceProfile* profile_); 14 | 15 | int rowCount(const QModelIndex& parent) const override; 16 | 17 | int columnCount(const QModelIndex& parent) const override; 18 | 19 | QVariant data(const QModelIndex& index, int role) const override; 20 | QVariant headerData(int section, Qt::Orientation orientation, int role) const override; 21 | }; 22 | 23 | class DeviceProfileModel final : public QObject 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | DeviceProfile profile; // WIP: shouldn't be public 29 | 30 | DeviceProfileModel(QObject* parent, DeviceProfile&& profile_); 31 | DeviceProfileModel(QObject* parent, const DeviceProfile& profile_); 32 | ~DeviceProfileModel() override = default; 33 | 34 | [[nodiscard]] ModifierListModel* makeModifierModel(QObject* parent); 35 | }; 36 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4Color.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Ds4Color.h" 3 | #include "gmath.h" 4 | 5 | Ds4Color::Ds4Color(uint8_t r, uint8_t g, uint8_t b) 6 | : red(r), 7 | green(g), 8 | blue(b) 9 | { 10 | } 11 | 12 | bool Ds4Color::operator==(const Ds4Color& other) const 13 | { 14 | return red == other.red && green == other.green && blue == other.blue; 15 | } 16 | 17 | bool Ds4Color::operator!=(const Ds4Color& other) const 18 | { 19 | return !(*this == other); 20 | } 21 | 22 | Ds4Color Ds4Color::lerp(const Ds4Color& a, const Ds4Color& b, float f) 23 | { 24 | Ds4Color dest; 25 | 26 | dest.red = gmath::lerp(a.red, b.red, f); 27 | dest.green = gmath::lerp(a.green, b.green, f); 28 | dest.blue = gmath::lerp(a.blue, b.blue, f); 29 | 30 | return dest; 31 | } 32 | 33 | void Ds4Color::readJson(const nlohmann::json& json) 34 | { 35 | red = json["red"].get(); 36 | green = json["green"].get(); 37 | blue = json["blue"].get(); 38 | } 39 | 40 | void Ds4Color::writeJson(nlohmann::json& json) const 41 | { 42 | json["red"] = red; 43 | json["green"] = green; 44 | json["blue"] = blue; 45 | } 46 | -------------------------------------------------------------------------------- /ds4wizard-cpp/XInputRumbleSimulator.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "ViGEmTarget.h" 3 | #include "XInputRumbleSimulator.h" 4 | 5 | XInputRumbleSimulator::XInputRumbleSimulator(InputSimulator* parent) 6 | : ISimulator(parent), 7 | xinputVibration({}) 8 | { 9 | } 10 | 11 | void XInputRumbleSimulator::update(float deltaTime) 12 | { 13 | parent->setRumble(static_cast(xinputVibration.wLeftMotorSpeed >> 8) / 255.0f, 14 | static_cast(xinputVibration.wRightMotorSpeed >> 8) / 255.0f); 15 | } 16 | 17 | void XInputRumbleSimulator::onActivate(float deltaTime) 18 | { 19 | if (xinputTarget) 20 | { 21 | this->xinputNotification = xinputTarget->notification.add( 22 | [&](auto sender, auto large, auto small, auto led) -> void 23 | { 24 | this->xinputVibration.wLeftMotorSpeed = (large << 8) | large; 25 | this->xinputVibration.wRightMotorSpeed = (small << 8) | small; 26 | }); 27 | } 28 | } 29 | 30 | void XInputRumbleSimulator::onDeactivate(float deltaTime) 31 | { 32 | if (xinputTarget) 33 | { 34 | xinputTarget->notification.remove(this->xinputNotification); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ds4wizard-cpp/busenum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define FILE_DEVICE_BUSENUM FILE_DEVICE_BUS_EXTENDER 6 | 7 | #ifndef BUSENUM_IOCTL 8 | #define BUSENUM_IOCTL(_index_) \ 9 | CTL_CODE (FILE_DEVICE_BUSENUM, _index_, METHOD_BUFFERED, FILE_READ_DATA) 10 | #endif 11 | 12 | #define IOCTL_BUSENUM_PLUGIN_HARDWARE BUSENUM_IOCTL (0x0) 13 | #define IOCTL_BUSENUM_UNPLUG_HARDWARE BUSENUM_IOCTL (0x1) 14 | #define IOCTL_BUSENUM_EJECT_HARDWARE BUSENUM_IOCTL (0x2) 15 | #define IOCTL_TOASTER_DONT_DISPLAY_IN_UI_DEVICE BUSENUM_IOCTL (0x3) 16 | 17 | typedef struct _BUSENUM_UNPLUG_HARDWARE { 18 | 19 | IN ULONG Size; 20 | 21 | IN ULONG SerialNo; 22 | 23 | IN ULONG Flags; 24 | 25 | ULONG Reserved[1]; 26 | 27 | static _BUSENUM_UNPLUG_HARDWARE create(int i) 28 | { 29 | _BUSENUM_UNPLUG_HARDWARE result {}; 30 | 31 | result.Size = sizeof(_BUSENUM_UNPLUG_HARDWARE); 32 | result.SerialNo = i + 1; 33 | result.Flags = 0; 34 | result.Reserved[0] = 0; 35 | 36 | return result; 37 | } 38 | 39 | } BUSENUM_UNPLUG_HARDWARE, *PBUSENUM_UNPLUG_HARDWARE; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Michael Fadely 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 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Settings.h" 3 | 4 | bool Settings::operator==(const Settings& rhs) const 5 | { 6 | return preferredConnection == rhs.preferredConnection && 7 | startMinimized == rhs.startMinimized && 8 | minimizeToTray == rhs.minimizeToTray; 9 | } 10 | 11 | bool Settings::operator!=(const Settings& rhs) const 12 | { 13 | return !(*this == rhs); 14 | } 15 | 16 | void Settings::readJson(const nlohmann::json& json) 17 | { 18 | if (json.find("preferredConnection") != json.end()) 19 | { 20 | preferredConnection = ConnectionType::_from_string(json["preferredConnection"].get().c_str()); 21 | } 22 | 23 | if (json.find("startMinimized") != json.end()) 24 | { 25 | startMinimized = json["startMinimized"]; 26 | } 27 | 28 | if (json.find("minimizeToTray") != json.end()) 29 | { 30 | minimizeToTray = json["minimizeToTray"]; 31 | } 32 | } 33 | 34 | void Settings::writeJson(nlohmann::json& json) const 35 | { 36 | json["preferredConnection"] = preferredConnection._to_string(); 37 | json["startMinimized"] = startMinimized; 38 | json["minimizeToTray"] = minimizeToTray; 39 | } 40 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4LightOptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Ds4Color.h" 4 | #include "JsonData.h" 5 | 6 | /** 7 | * \brief Configuration for the \c Ds4Device light bar. 8 | * \sa Ds4Device, Ds4Color 9 | */ 10 | class Ds4LightOptions : public JsonData 11 | { 12 | public: 13 | /** 14 | * \brief If \c true, a color is automatically assigned based on device connection order. 15 | * \sa Ds4AutoLightColor 16 | */ 17 | bool automaticColor = true; 18 | 19 | /** 20 | * \brief The user-defined light bar color. 21 | */ 22 | Ds4Color color; 23 | 24 | // TODO: move to idle options? 25 | /** 26 | * \brief If \c true, the light fades out proportionally to its idle time (elapsed / threshold). 27 | */ 28 | bool idleFade = true; 29 | 30 | explicit Ds4LightOptions(const Ds4Color& color); 31 | Ds4LightOptions(const Ds4LightOptions& other); 32 | Ds4LightOptions() = default; 33 | 34 | bool operator==(const Ds4LightOptions& other) const; 35 | bool operator!=(const Ds4LightOptions& other) const; 36 | Ds4LightOptions& operator=(const Ds4LightOptions& other) = default; 37 | 38 | void readJson(const nlohmann::json& json) override; 39 | void writeJson(nlohmann::json& json) const override; 40 | }; 41 | -------------------------------------------------------------------------------- /libdevicetoggle/libdevicetoggle.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Logger.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | #include 4 | #include 5 | #include "Logger.h" 6 | 7 | using namespace std::chrono; 8 | 9 | LineLoggedEventArgs::LineLoggedEventArgs(LogLevel level, std::string line) 10 | : time(system_clock::now()), 11 | level(level), 12 | line(std::move(line)) 13 | { 14 | } 15 | 16 | void Logger::writeLine(LogLevel level, const std::string& line) 17 | { 18 | const auto now = system_clock::now(); 19 | std::time_t t = system_clock::to_time_t(now); 20 | 21 | std::stringstream message; 22 | message << std::put_time(std::localtime(&t), "%F %T") << " [" << level._to_string() << "] " << line; 23 | 24 | qDebug() << message.str().c_str(); 25 | onLineLogged(level, line); 26 | } 27 | 28 | void Logger::writeLine(LogLevel level, const std::string& context, const std::string& line) 29 | { 30 | std::stringstream message; 31 | message << "[" << context << "] " << line; 32 | 33 | writeLine(level, message.str()); 34 | } 35 | 36 | void Logger::onLineLogged(LogLevel level, const std::string& line) 37 | { 38 | MAKE_GUARD(lock); 39 | 40 | auto args = std::make_shared(level, line); 41 | lineLogged.invoke(nullptr, args); 42 | } 43 | -------------------------------------------------------------------------------- /libhid/hid_handle.cpp: -------------------------------------------------------------------------------- 1 | #include "hid_handle.h" 2 | #include 3 | 4 | Handle::Handle(Handle&& other) noexcept 5 | : owner(std::exchange(other.owner, false)), 6 | nativeHandle(std::exchange(other.nativeHandle, INVALID_HANDLE_VALUE)) 7 | { 8 | } 9 | 10 | Handle::Handle(HANDLE h, bool owner) 11 | : owner(owner), 12 | nativeHandle(h) 13 | { 14 | } 15 | 16 | Handle::~Handle() 17 | { 18 | close(); 19 | } 20 | 21 | bool Handle::isValid() const 22 | { 23 | return nativeHandle && nativeHandle != INVALID_HANDLE_VALUE; 24 | } 25 | 26 | void Handle::close() 27 | { 28 | if (owner && isValid()) 29 | { 30 | CloseHandle(nativeHandle); 31 | nativeHandle = INVALID_HANDLE_VALUE; 32 | } 33 | } 34 | 35 | bool Handle::operator==(const Handle& rhs) const 36 | { 37 | return nativeHandle == rhs.nativeHandle; 38 | } 39 | 40 | bool Handle::operator!=(const Handle& rhs) const 41 | { 42 | return !(*this == rhs); 43 | } 44 | 45 | Handle& Handle::operator=(Handle&& rhs) noexcept 46 | { 47 | if (this != &rhs && *this != rhs) 48 | { 49 | close(); 50 | 51 | owner = std::exchange(rhs.owner, false); 52 | nativeHandle = std::exchange(rhs.nativeHandle, INVALID_HANDLE_VALUE); 53 | } 54 | 55 | return *this; 56 | } 57 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ViGEmDriver.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "ViGEmDriver.h" 7 | 8 | namespace vigem 9 | { 10 | Driver::Driver(Driver&& rhs) noexcept 11 | : client(std::exchange(rhs.client, nullptr)) 12 | { 13 | } 14 | 15 | Driver::~Driver() 16 | { 17 | close(); 18 | } 19 | 20 | VIGEM_ERROR Driver::open() 21 | { 22 | if (isOpen()) 23 | { 24 | return VIGEM_ERROR_NONE; 25 | } 26 | 27 | auto guard = lock(); 28 | client = vigem_alloc(); 29 | const VIGEM_ERROR result = vigem_connect(client); 30 | 31 | if (!VIGEM_SUCCESS(result)) 32 | { 33 | vigem_free(client); 34 | client = nullptr; 35 | } 36 | 37 | return result; 38 | } 39 | 40 | bool Driver::isOpen() const 41 | { 42 | return client != nullptr; 43 | } 44 | 45 | void Driver::close() 46 | { 47 | if (isOpen()) 48 | { 49 | auto guard = lock(); 50 | vigem_disconnect(client); 51 | vigem_free(client); 52 | client = nullptr; 53 | } 54 | } 55 | 56 | Driver& Driver::operator=(Driver&& rhs) noexcept 57 | { 58 | if (this != &rhs) 59 | { 60 | close(); 61 | client = std::exchange(rhs.client, nullptr); 62 | } 63 | 64 | return *this; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ViGEmDriver.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vigem 7 | { 8 | class XInputTarget; 9 | 10 | class Driver 11 | { 12 | friend class XInputTarget; 13 | PVIGEM_CLIENT client = nullptr; 14 | std::recursive_mutex vigem_mutex; 15 | 16 | public: 17 | /** 18 | * \brief Explicitly disallow copying. 19 | */ 20 | Driver(const Driver&) = delete; 21 | 22 | Driver() = default; 23 | Driver(Driver&& rhs) noexcept; 24 | ~Driver(); 25 | 26 | /** 27 | * \brief Opens a handle to the ViGEm driver. 28 | * \return \c VIGEM_ERROR_NONE on success. 29 | */ 30 | VIGEM_ERROR open(); 31 | 32 | /** 33 | * \brief Indicates the opened state of the ViGEm driver handle. 34 | * \return \c true if the handle is open. 35 | */ 36 | [[nodiscard]] bool isOpen() const; 37 | 38 | /** 39 | * \brief If open, closes the handle to the ViGEm driver. 40 | */ 41 | void close(); 42 | 43 | /** 44 | * \brief Explicitly disallow copying. 45 | */ 46 | Driver& operator=(const Driver&) = delete; 47 | Driver& operator=(Driver&& rhs) noexcept; 48 | 49 | auto lock() 50 | { 51 | return std::unique_lock(vigem_mutex); 52 | } 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceProfileItemModel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "DeviceProfileCache.h" 5 | 6 | class DeviceProfileItemModel final : public QAbstractListModel 7 | { 8 | Q_OBJECT 9 | 10 | DeviceProfileCache& profileCache; 11 | EventToken onProfileAdded_; 12 | EventToken onProfileChanged_; 13 | EventToken onProfileRemoved_; 14 | 15 | bool includeDefault; 16 | 17 | public: 18 | DeviceProfileItemModel(QObject* parent, DeviceProfileCache& profileCache, bool includeDefault_); 19 | 20 | int rowCount(const QModelIndex& parent) const override; 21 | 22 | int columnCount(const QModelIndex& parent) const override; 23 | 24 | QVariant data(const QModelIndex& index, int role) const override; 25 | QVariant headerData(int section, Qt::Orientation orientation, int role) const override; 26 | 27 | DeviceProfile getProfile(int index) const; 28 | 29 | private: 30 | void onProfileAdded(DeviceProfileCache* sender, const DeviceProfile& profile, int index); 31 | void onProfileChanged(DeviceProfileCache* sender, const DeviceProfile& oldProfile, const DeviceProfile& newProfile, int oldIndex, int newIndex); 32 | void onProfileRemoved(DeviceProfileCache* sender, const DeviceProfile& profile, int index); 33 | }; 34 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4Output.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Ds4Output.h" 3 | 4 | bool Ds4Output::update(std::span buffer) const 5 | { 6 | size_t i = 0; 7 | 8 | bool result = buffer[i] != rightMotor; 9 | buffer[i++] = rightMotor; 10 | 11 | result = result || buffer[i] != leftMotor; 12 | buffer[i++] = leftMotor; 13 | 14 | result = result || buffer[i] != lightColor.red; 15 | buffer[i++] = lightColor.red; 16 | 17 | result = result || buffer[i] != lightColor.green; 18 | buffer[i++] = lightColor.green; 19 | 20 | result = result || buffer[i] != lightColor.blue; 21 | buffer[i++] = lightColor.blue; 22 | 23 | result = result || buffer[i] != flashOnDur; 24 | buffer[i++] = flashOnDur; 25 | 26 | result = result || buffer[i] != flashOffDur; 27 | buffer[i] = flashOffDur; 28 | 29 | i = 17; 30 | 31 | result = result || buffer[i] != volumeLeft; 32 | buffer[i++] = volumeLeft; 33 | 34 | result = result || buffer[i] != volumeRight; 35 | buffer[i++] = volumeRight; 36 | 37 | result = result || buffer[i] != volumeMic; 38 | buffer[i++] = volumeMic; 39 | 40 | result = result || buffer[i] != volumeSpeaker; 41 | buffer[i] = volumeSpeaker; 42 | 43 | return result; 44 | } 45 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Latency.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Latency.h" 3 | 4 | #include 5 | 6 | using namespace std::chrono; 7 | 8 | Latency::Latency() 9 | : average_(1000ms) 10 | { 11 | } 12 | 13 | void Latency::start() 14 | { 15 | stopwatch.start(); 16 | } 17 | 18 | Stopwatch::Duration Latency::stop() 19 | { 20 | if (!stopwatch.running()) 21 | { 22 | return lastValue_; 23 | } 24 | 25 | lastValue_ = stopwatch.stop(); 26 | 27 | average_.push(lastValue_); 28 | 29 | if (lastValue_ > peak_) 30 | { 31 | peak_ = lastValue_; 32 | } 33 | 34 | return lastValue_; 35 | } 36 | 37 | Stopwatch::Duration Latency::elapsed() const 38 | { 39 | return stopwatch.elapsed(); 40 | } 41 | 42 | bool Latency::running() const 43 | { 44 | return stopwatch.running(); 45 | } 46 | 47 | Stopwatch::Duration Latency::lastValue() const 48 | { 49 | return lastValue_; 50 | } 51 | 52 | Stopwatch::Duration Latency::peak() 53 | { 54 | if (stopwatch.running()) 55 | { 56 | if (stopwatch.elapsed() > peak_) 57 | { 58 | peak_ = stopwatch.elapsed(); 59 | } 60 | } 61 | 62 | return peak_; 63 | } 64 | 65 | Stopwatch::Duration Latency::average() 66 | { 67 | return average_.value(); 68 | } 69 | 70 | void Latency::resetPeak() 71 | { 72 | peak_ = 0ms; 73 | } 74 | -------------------------------------------------------------------------------- /ds4wizard-cpp/RumbleSequence.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "ISimulator.h" 7 | #include "Stopwatch.h" 8 | 9 | enum class RumbleSequenceBlending 10 | { 11 | none, 12 | linear 13 | }; 14 | 15 | struct RumbleSequenceElement 16 | { 17 | RumbleSequenceBlending blending = RumbleSequenceBlending::none; 18 | int64_t durationMilliseconds = 0; 19 | float leftMotor = 0.0f; 20 | float rightMotor = 0.0f; 21 | }; 22 | 23 | class InputSimulator; 24 | 25 | class RumbleSequence : public ISimulator 26 | { 27 | std::queue sequence; 28 | std::optional currentElement; 29 | Stopwatch stopwatch; 30 | 31 | public: 32 | explicit RumbleSequence(InputSimulator* parent); 33 | 34 | void update(float deltaTime) override; 35 | void add(const RumbleSequenceElement& element); 36 | }; 37 | 38 | class RumbleTimer : public ISimulator 39 | { 40 | Stopwatch stopwatch; 41 | 42 | public: 43 | RumbleTimer(InputSimulator* parent, Stopwatch::Duration duration, 44 | float left, float right); 45 | 46 | Stopwatch::Duration duration; 47 | float left; 48 | float right; 49 | 50 | void reset(); 51 | 52 | void update(float deltaTime) override; 53 | void onActivate(float deltaTime) override; 54 | }; 55 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceSettingsCommon.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DeviceSettingsCommon.h" 3 | 4 | DeviceSettingsCommon::DeviceSettingsCommon() 5 | : idle({}), 6 | notifyFullyCharged(true), 7 | notifyBatteryLow(2) 8 | { 9 | } 10 | 11 | bool DeviceSettingsCommon::operator==(const DeviceSettingsCommon& other) const 12 | { 13 | return light == other.light && 14 | idle == other.idle && 15 | notifyFullyCharged == other.notifyFullyCharged && 16 | notifyBatteryLow == other.notifyBatteryLow; 17 | } 18 | 19 | bool DeviceSettingsCommon::operator!=(const DeviceSettingsCommon& other) const 20 | { 21 | return !(*this == other); 22 | } 23 | 24 | void DeviceSettingsCommon::readJson(const nlohmann::json& json) 25 | { 26 | light = fromJson(json["light"]); 27 | idle = fromJson(json["idle"]); 28 | notifyFullyCharged = json.value("notifyFullyCharged", true); 29 | notifyBatteryLow = static_cast(json.value("notifyBatteryLow", 2)); 30 | } 31 | 32 | void DeviceSettingsCommon::writeJson(nlohmann::json& json) const 33 | { 34 | json["light"] = light.toJson(); 35 | json["idle"] = idle.toJson(); 36 | json["notifyFullyCharged"] = notifyFullyCharged; 37 | json["notifyBatteryLow"] = notifyBatteryLow; 38 | } 39 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceSettingsCommon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Ds4LightOptions.h" 6 | #include "DeviceIdleOptions.h" 7 | 8 | /** 9 | * \brief Represents common device configuration for a \c Ds4Device. 10 | * \sa Ds4Device 11 | */ 12 | class DeviceSettingsCommon : public JsonData 13 | { 14 | public: 15 | /** \brief Options for the light bar. */ 16 | Ds4LightOptions light; 17 | 18 | /** 19 | * \brief Options for handling idle state of a \c Ds4Device 20 | * \sa Ds4Device 21 | */ 22 | DeviceIdleOptions idle; 23 | 24 | /** 25 | * \brief If \c true, the program will display a notification when 26 | * a \c Ds4Device reaches full charge. 27 | */ 28 | bool notifyFullyCharged; 29 | 30 | /** 31 | * \brief Threshold for low charge level. When met, the program will 32 | * display a notification. 33 | */ 34 | uint8_t notifyBatteryLow; 35 | 36 | DeviceSettingsCommon(); 37 | DeviceSettingsCommon(const DeviceSettingsCommon&) = default; 38 | DeviceSettingsCommon& operator=(const DeviceSettingsCommon&) = default; 39 | 40 | bool operator==(const DeviceSettingsCommon& other) const; 41 | bool operator!=(const DeviceSettingsCommon& other) const; 42 | 43 | void readJson(const nlohmann::json& json) override; 44 | void writeJson(nlohmann::json& json) const override; 45 | }; 46 | -------------------------------------------------------------------------------- /ds4wizard-cpp/KeyboardSimulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | * \brief Object used for simulating keyboard input. 7 | */ 8 | class KeyboardSimulator 9 | { 10 | std::unordered_set pressedKeys; 11 | 12 | public: 13 | KeyboardSimulator() = default; 14 | KeyboardSimulator(KeyboardSimulator&&) = default; 15 | 16 | ~KeyboardSimulator(); 17 | 18 | KeyboardSimulator& operator=(KeyboardSimulator&&) = default; 19 | 20 | /** 21 | * \brief Explicitly disallow copying. 22 | */ 23 | KeyboardSimulator(const KeyboardSimulator&) = delete; 24 | 25 | /** 26 | * \brief Explicitly disallow copying. 27 | */ 28 | KeyboardSimulator& operator=(const KeyboardSimulator&) = delete; 29 | 30 | /** 31 | * \brief Releases a key specified by \a keyCode. 32 | * \param keyCode The key code to release. 33 | */ 34 | void keyUp(int keyCode); 35 | 36 | /** 37 | * \brief Presses a key specified by \a keyCode. 38 | * \param keyCode The key code to press. 39 | */ 40 | void keyDown(int keyCode); 41 | 42 | /** 43 | * \brief Presses or releases a key specified by \a keyCode. 44 | * \param keyCode The key code to press or release. 45 | * \param down If \c true, the key is pressed. If \c false, the key is released. 46 | */ 47 | static void press(int keyCode, bool down); 48 | }; 49 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4LightOptions.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Ds4LightOptions.h" 3 | 4 | Ds4LightOptions::Ds4LightOptions(const Ds4Color& color) 5 | : color(color) 6 | { 7 | } 8 | 9 | Ds4LightOptions::Ds4LightOptions(const Ds4LightOptions& other) 10 | : automaticColor(other.automaticColor), 11 | color(other.color), 12 | idleFade(other.idleFade) 13 | { 14 | } 15 | 16 | // TODO: replace with IComparable (if this were C#) 17 | // TODO: or perhaps more preferably, don't store the automatic light color in here? 18 | bool Ds4LightOptions::operator==(const Ds4LightOptions& other) const 19 | { 20 | bool result = automaticColor == other.automaticColor && idleFade == other.idleFade; 21 | 22 | if (result && (!automaticColor || !other.automaticColor)) 23 | { 24 | result = result && color == other.color; 25 | } 26 | 27 | return result; 28 | } 29 | 30 | bool Ds4LightOptions::operator!=(const Ds4LightOptions& other) const 31 | { 32 | return !(*this == other); 33 | } 34 | 35 | void Ds4LightOptions::readJson(const nlohmann::json& json) 36 | { 37 | automaticColor = json["automaticColor"]; 38 | color = fromJson(json["color"]); 39 | idleFade = json["idleFade"]; 40 | } 41 | 42 | void Ds4LightOptions::writeJson(nlohmann::json& json) const 43 | { 44 | json["automaticColor"] = automaticColor; 45 | json["color"] = color.toJson(); 46 | json["idleFade"] = idleFade; 47 | } 48 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceIdleOptions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "JsonData.h" 5 | 6 | /** 7 | * \brief Configuration for handling device idle states. 8 | */ 9 | struct DeviceIdleOptions : JsonData 10 | { 11 | using clock = std::chrono::high_resolution_clock; 12 | 13 | /** 14 | * \brief Default idle state configuration. 15 | */ 16 | static const DeviceIdleOptions defaultIdleOptions; 17 | 18 | /** 19 | * \brief Time threshold for taking action on idle state. 20 | */ 21 | std::chrono::seconds timeout = std::chrono::minutes(5); 22 | 23 | /** 24 | * \brief If \c true, wireless devices will be disconnected upon idle detection. 25 | */ 26 | bool disconnect = true; 27 | 28 | DeviceIdleOptions() = default; 29 | 30 | /** 31 | * \brief Constructs with essential values provided. 32 | * \param timeout Time threshold for taking action on idle state. 33 | * \param disconnect Disconnect wireless devices on idle. 34 | */ 35 | DeviceIdleOptions(std::chrono::seconds timeout, bool disconnect); 36 | 37 | DeviceIdleOptions(const DeviceIdleOptions& other) = default; 38 | 39 | DeviceIdleOptions& operator=(const DeviceIdleOptions& rhs) = default; 40 | 41 | bool operator==(const DeviceIdleOptions& other) const; 42 | bool operator!=(const DeviceIdleOptions& other) const; 43 | void readJson(const nlohmann::json& json) override; 44 | void writeJson(nlohmann::json& json) const override; 45 | }; 46 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceSettings.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DeviceSettings.h" 3 | 4 | using namespace std::chrono; 5 | 6 | DeviceSettings::DeviceSettings() 7 | : latencyThreshold(17) 8 | { 9 | } 10 | 11 | bool DeviceSettings::operator==(const DeviceSettings& other) const 12 | { 13 | return DeviceSettingsCommon::operator==(other) && 14 | name == other.name && 15 | profile == other.profile && 16 | useProfileLight == other.useProfileLight && 17 | useProfileIdle == other.useProfileIdle; 18 | } 19 | 20 | bool DeviceSettings::operator!=(const DeviceSettings& other) const 21 | { 22 | return !(*this == other); 23 | } 24 | 25 | void DeviceSettings::readJson(const nlohmann::json& json) 26 | { 27 | DeviceSettingsCommon::readJson(json); 28 | 29 | name = json["name"]; 30 | profile = json.value("profile", ""); 31 | useProfileLight = json["useProfileLight"]; 32 | useProfileIdle = json["useProfileIdle"]; 33 | latencyThreshold = milliseconds(json.value("latencyThreshold", latencyThreshold.count())); 34 | } 35 | 36 | void DeviceSettings::writeJson(nlohmann::json& json) const 37 | { 38 | DeviceSettingsCommon::writeJson(json); 39 | 40 | json["name"] = name; 41 | json["profile"] = profile; 42 | json["useProfileLight"] = useProfileLight; 43 | json["useProfileIdle"] = useProfileIdle; 44 | json["latencyThreshold"] = latencyThreshold.count(); 45 | } 46 | -------------------------------------------------------------------------------- /ds4wizard-cpp/stringutil.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "stringutil.h" 9 | 10 | bool iequalsw(const wchar_t& a, const wchar_t& b) 11 | { 12 | return std::towupper(a) == std::towupper(b); 13 | } 14 | 15 | bool iequals(const std::wstring& a, const std::wstring& b) 16 | { 17 | return a.size() == b.size() && std::ranges::equal(a, b, &iequalsw); 18 | } 19 | 20 | bool iequalsc(const char& a, const char& b) 21 | { 22 | return std::toupper(a) == std::toupper(b); 23 | } 24 | 25 | bool iequals(const std::string& a, const std::string& b) 26 | { 27 | return a.size() == b.size() && std::ranges::equal(a, b, &iequalsc); 28 | } 29 | 30 | void triml(std::string& s) 31 | { 32 | s.erase(s.begin(), std::ranges::find_if(s, [](int ch) 33 | { 34 | return !std::isspace(ch); 35 | })); 36 | } 37 | 38 | void trimr(std::string& s) 39 | { 40 | std::ranges::reverse_view rs(s); 41 | s.erase(std::ranges::find_if(rs, [](int ch) 42 | { 43 | return !std::isspace(ch); 44 | }).base(), s.end()); 45 | } 46 | 47 | void trim(std::string& s) 48 | { 49 | triml(s); 50 | trimr(s); 51 | } 52 | 53 | std::string triml_copy(std::string s) 54 | { 55 | triml(s); 56 | return s; 57 | } 58 | 59 | std::string trimr_copy(std::string s) 60 | { 61 | trimr(s); 62 | return s; 63 | } 64 | 65 | std::string trim_copy(std::string s) 66 | { 67 | trim(s); 68 | return s; 69 | } 70 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4ItemModel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | class Ds4ItemModel : public QAbstractListModel 9 | { 10 | Q_OBJECT 11 | 12 | std::set> devices; 13 | std::unordered_map, EventToken> tokens; 14 | EventToken deviceOpened_; 15 | EventToken deviceClosed_; 16 | 17 | public: 18 | Ds4ItemModel(QObject* parent, const std::shared_ptr& deviceManager); 19 | 20 | int rowCount(const QModelIndex& parent) const override; 21 | 22 | int columnCount(const QModelIndex& parent) const override; 23 | 24 | QVariant data(const QModelIndex& index, int role) const override; 25 | QVariant headerData(int section, Qt::Orientation orientation, int role) const override; 26 | 27 | std::shared_ptr getDevice(int row) const; 28 | 29 | private: 30 | int getRow(const std::shared_ptr& device) const; 31 | int getRow(const Ds4Device* device) const; 32 | 33 | signals: 34 | void s_onDeviceOpened(std::shared_ptr a); 35 | void s_onDeviceClosed(std::shared_ptr a); 36 | void s_onDeviceBatteryChanged(const Ds4Device* sender); 37 | 38 | protected slots: 39 | void onDeviceOpened(std::shared_ptr a); 40 | void onDeviceClosed(std::shared_ptr a); 41 | void onDeviceBatteryChanged(const Ds4Device* sender); 42 | }; 43 | -------------------------------------------------------------------------------- /ds4wizard-cpp/MouseSimulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "enums.h" 5 | 6 | /** 7 | * \brief An object for simulating mouse input. 8 | */ 9 | class MouseSimulator 10 | { 11 | std::unordered_set pressedButtons; 12 | 13 | public: 14 | MouseSimulator() = default; 15 | MouseSimulator(MouseSimulator&&) = default; 16 | 17 | ~MouseSimulator(); 18 | 19 | /** 20 | * \brief Explicitly disallow copying. 21 | */ 22 | MouseSimulator(const MouseSimulator&) = delete; 23 | 24 | /** 25 | * \brief Explicitly disallow copying. 26 | */ 27 | MouseSimulator& operator=(const MouseSimulator&) = delete; 28 | 29 | MouseSimulator& operator=(MouseSimulator&&) = default; 30 | 31 | /** 32 | * \brief Releases a mouse button. 33 | * \param button The button to release. 34 | */ 35 | void buttonUp(MouseButton button); 36 | 37 | /** 38 | * \brief Presses a mouse button. 39 | * \param button The button to press. 40 | */ 41 | void buttonDown(MouseButton button); 42 | 43 | /** 44 | * \brief Moves the cursor relative to its current position. 45 | * \param dx X delta to move by, in pixels. 46 | * \param dy Y delta to move by, in pixels. 47 | */ 48 | static void moveBy(int dx, int dy); 49 | 50 | /** 51 | * \brief Presses or releases a mouse button. 52 | * \param button The button to press or release. 53 | * \param down If \c true, the button is pressed. If \c false, the button is released. 54 | */ 55 | static void press(MouseButton button, bool down); 56 | }; 57 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceSettings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "DeviceSettingsCommon.h" 6 | 7 | /** 8 | * \brief Represents device-specific configuration for a \c Ds4Device. 9 | * \sa Ds4Device 10 | */ 11 | class DeviceSettings : public DeviceSettingsCommon 12 | { 13 | public: 14 | /** 15 | * \brief User-configured name for the device. 16 | */ 17 | std::string name; 18 | 19 | /** 20 | * \brief User-configured profile applied to the device. If none, empty string. 21 | */ 22 | std::string profile; 23 | 24 | /** 25 | * \brief If \c true, light settings from the profile specified by \c profile 26 | * will be used instead of those configured in \c DeviceSettingsCommon. 27 | * \sa DeviceSettingsCommon 28 | */ 29 | bool useProfileLight = false; 30 | 31 | /** 32 | * \brief If \c true, idle settings from the profile specified by \c profile 33 | * will be used instead of those configured in \c DeviceSettingsCommon. 34 | * \sa DeviceSettingsCommon 35 | */ 36 | bool useProfileIdle = false; 37 | 38 | /** 39 | * \brief Threshold for latency at which the program will notify the user or begin mitigation. 40 | */ 41 | std::chrono::milliseconds latencyThreshold; 42 | 43 | DeviceSettings(); 44 | DeviceSettings(const DeviceSettings& s) = default; 45 | 46 | bool operator==(const DeviceSettings& other) const; 47 | bool operator!=(const DeviceSettings& other) const; 48 | 49 | void readJson(const nlohmann::json& json) override; 50 | void writeJson(nlohmann::json& json) const override; 51 | }; 52 | -------------------------------------------------------------------------------- /ds4wizard-cpp/XInputGamepad.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "XInputGamepad.h" 3 | 4 | bool XInputGamepad::operator==(const XInputGamepad& r) const 5 | { 6 | return wButtons == r.wButtons 7 | && bLeftTrigger == r.bLeftTrigger 8 | && bRightTrigger == r.bRightTrigger 9 | && sThumbLX == r.sThumbLX 10 | && sThumbLY == r.sThumbLY 11 | && sThumbRX == r.sThumbRX 12 | && sThumbRY == r.sThumbRY; 13 | } 14 | 15 | bool XInputGamepad::operator!=(const XInputGamepad& r) const 16 | { 17 | return wButtons != r.wButtons 18 | || bLeftTrigger != r.bLeftTrigger 19 | || bRightTrigger != r.bRightTrigger 20 | || sThumbLX != r.sThumbLX 21 | || sThumbLY != r.sThumbLY 22 | || sThumbRX != r.sThumbRX 23 | || sThumbRY != r.sThumbRY; 24 | } 25 | 26 | void XInputGamepad::toBytes(uint8_t* buffer, int offset) const 27 | { 28 | buffer[offset + 0] = (uint8_t)((int)wButtons & 0xFF); 29 | buffer[offset + 1] = (uint8_t)(((int)wButtons >> 8) & 0xFF); 30 | buffer[offset + 2] = bLeftTrigger; 31 | buffer[offset + 3] = bRightTrigger; 32 | 33 | buffer[offset + 4] = (uint8_t)(sThumbLX & 0xFF); 34 | buffer[offset + 5] = (uint8_t)((sThumbLX >> 8) & 0xFF); 35 | 36 | buffer[offset + 6] = (uint8_t)(sThumbLY & 0xFF); 37 | buffer[offset + 7] = (uint8_t)((sThumbLY >> 8) & 0xFF); 38 | 39 | buffer[offset + 8] = (uint8_t)(sThumbRX & 0xFF); 40 | buffer[offset + 9] = (uint8_t)((sThumbRX >> 8) & 0xFF); 41 | 42 | buffer[offset + 10] = (uint8_t)(sThumbRY & 0xFF); 43 | buffer[offset + 11] = (uint8_t)((sThumbRY >> 8) & 0xFF); 44 | } 45 | -------------------------------------------------------------------------------- /libhid/libhid.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DevicePropertiesDialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "ui_DevicePropertiesDialog.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class DevicePropertiesDialog : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | private: 16 | DeviceSettings oldSettings; 17 | DeviceSettings newSettings; 18 | std::wstring deviceKey; 19 | std::shared_ptr device; 20 | std::atomic_bool doReadout = false; 21 | std::unique_ptr readoutThread; 22 | QColor lightColor; 23 | int lastUnit = 0; 24 | 25 | public: 26 | DevicePropertiesDialog(QWidget* parent, std::shared_ptr device_); 27 | ~DevicePropertiesDialog() override; 28 | void setColorPickerColor() const; 29 | void populateForm(); 30 | 31 | signals: 32 | void settingsChanged(const DeviceSettings& oldSettings, const DeviceSettings& newSettings); 33 | 34 | private: 35 | Ui::DevicePropertiesDialog ui; 36 | void readoutMethod(); 37 | void stopReadout(); 38 | void startReadout(); 39 | void applySettings(); 40 | 41 | signals: 42 | void readoutChanged(Ds4Buttons_t heldButtons, Ds4InputData data); 43 | 44 | private slots: 45 | void tabChanged(int index); 46 | void updateReadout(Ds4Buttons_t heldButtons, Ds4InputData data) const; 47 | void resetPeakLatency() const; 48 | void profileEditClicked(bool checked); 49 | void colorEditClicked(bool checked); 50 | void buttonBoxAccepted(); 51 | void applyButtonClicked(bool checked); 52 | std::chrono::high_resolution_clock::duration getGuiIdleTime() const; 53 | void timeUnitChanged(int index); 54 | }; 55 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "Event.h" 8 | 9 | BETTER_ENUM(LogLevel, int, info, warning, error); 10 | 11 | /** 12 | * \brief Event arguments for \c Logger.LineLogged. 13 | * \sa Logger.LineLogged 14 | */ 15 | class LineLoggedEventArgs 16 | { 17 | public: 18 | /** 19 | * \brief The time that this line was submitted. 20 | */ 21 | const std::chrono::system_clock::time_point time; 22 | 23 | /** 24 | * \brief Error level. 25 | * \sa LogLevel 26 | */ 27 | const LogLevel level; 28 | 29 | /** 30 | * \brief The line of text submitted. 31 | */ 32 | const std::string line; 33 | 34 | LineLoggedEventArgs(LogLevel level, std::string line); 35 | }; 36 | 37 | class Logger 38 | { 39 | inline static std::recursive_mutex lock; 40 | 41 | public: 42 | static inline Event> lineLogged; 43 | 44 | /** 45 | * \brief Submits a line to the logger and notifies all registered events. 46 | * \param level Error level. 47 | * \param line Line of text to be submitted. 48 | */ 49 | static void writeLine(LogLevel level, const std::string& line); 50 | 51 | /** 52 | * \brief Submits a line (with context) to the logger and notifies all registered events. 53 | * The submitted line will be in the format "[context] line". 54 | * \param level Error level. 55 | * \param context Context of this line. 56 | * \param line Line of text to be submitted. 57 | */ 58 | static void writeLine(LogLevel level, const std::string& context, const std::string& line); 59 | 60 | private: 61 | static void onLineLogged(LogLevel level, const std::string& line); 62 | }; 63 | -------------------------------------------------------------------------------- /ds4wizard-cpp/program.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Settings.h" 3 | #include "ViGEmDriver.h" 4 | 5 | class DeviceProfileCache; 6 | 7 | class Program 8 | { 9 | static Settings lastSettings; 10 | static QString settingsPath; 11 | static QString settingsFilePath; 12 | static std::string profilesPath_; 13 | static std::string devicesFilePath_; 14 | inline static bool isElevated_ = false; 15 | 16 | public: 17 | /** 18 | * \brief ViGEm driver instance used for endpoint emulation (XInput, DualShock 4). 19 | * \sa vigem::Driver 20 | */ 21 | static vigem::Driver driver; 22 | 23 | /** 24 | * \brief Profiles currently tracked and managed by the program. 25 | * \sa DeviceProfileCache 26 | */ 27 | static DeviceProfileCache profileCache; 28 | 29 | /** 30 | * \brief Currently active program settings. 31 | * \sa Settings 32 | */ 33 | static Settings settings; 34 | 35 | /** 36 | * \brief Filesystem path to search for profiles. 37 | */ 38 | static const std::string& profilesPath(); 39 | 40 | /** 41 | * \brief Filesystem path to the per-device configuration file. 42 | */ 43 | static const std::string& devicesFilePath(); 44 | 45 | /** 46 | * \brief Indicates whether or not the program is running with elevated privileges. 47 | * \return \c true if the application is running with elevated privileges. 48 | */ 49 | static bool isElevated(); 50 | 51 | /** 52 | * \brief Performs startup initialization. 53 | */ 54 | static void initialize(); 55 | 56 | /** 57 | * \brief Loads program settings. 58 | * \sa Settings, settings 59 | */ 60 | static void loadSettings(); 61 | 62 | /** 63 | * \brief Saves program settings to disk if changed. 64 | * \sa Settings, settings 65 | */ 66 | static void saveSettings(); 67 | }; 68 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4AutoLightColor.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Ds4AutoLightColor.h" 3 | #include "lock.h" 4 | 5 | std::array Ds4AutoLightColor::colors = 6 | { 7 | Pair(Ds4Color(0, 0, 64)), 8 | Pair(Ds4Color(64, 0, 0)), 9 | Pair(Ds4Color(0, 64, 0)), 10 | Pair(Ds4Color(32, 0, 32)), 11 | Pair(Ds4Color(12, 23, 29)), 12 | Pair(Ds4Color(0, 86, 0)), 13 | Pair(Ds4Color(105, 0, 98)), 14 | Pair(Ds4Color(0, 114, 0)), 15 | Pair(Ds4Color(97, 0, 116)), 16 | Pair(Ds4Color(0, 105, 0)), 17 | Pair(Ds4Color(111, 0, 110)), 18 | Pair(Ds4Color(0, 32, 0)), 19 | Pair(Ds4Color(84, 0, 105)), 20 | Pair(Ds4Color(0, 109, 0)), 21 | Pair(Ds4Color(101, 0, 111)), 22 | Pair(Ds4Color(0, 117, 0)), 23 | Pair(Ds4Color(116, 0, 0)) 24 | }; 25 | 26 | Ds4AutoLightColor::Pair::Pair(const Ds4Color& color) 27 | : color(color) 28 | { 29 | } 30 | 31 | Ds4Color Ds4AutoLightColor::getColor(ptrdiff_t& index) 32 | { 33 | MAKE_GUARD(lock); 34 | 35 | ptrdiff_t minIndex = 0; 36 | ptrdiff_t refCount = std::numeric_limits::max(); 37 | 38 | for (size_t i = 0; i < colors.size(); i++) 39 | { 40 | const Pair& c = colors[i]; 41 | 42 | if (c.references == 0) 43 | { 44 | minIndex = i; 45 | break; 46 | } 47 | 48 | if (c.references < refCount) 49 | { 50 | minIndex = i; 51 | refCount = c.references; 52 | } 53 | } 54 | 55 | Pair& pair = colors[minIndex]; 56 | ++pair.references; 57 | index = minIndex; 58 | return Ds4Color(pair.color); 59 | } 60 | 61 | void Ds4AutoLightColor::releaseColor(ptrdiff_t index) 62 | { 63 | if (index < 0) 64 | { 65 | return; 66 | } 67 | 68 | MAKE_GUARD(lock); 69 | 70 | Pair& pair = colors[index]; 71 | 72 | if (pair.references > 0) 73 | { 74 | --pair.references; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /ds4wizard-cpp/MouseSimulator.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | #include "MouseSimulator.h" 4 | 5 | MouseSimulator::~MouseSimulator() 6 | { 7 | for (auto button : pressedButtons) 8 | { 9 | buttonUp(MouseButton::_from_integral(button)); 10 | } 11 | } 12 | 13 | void MouseSimulator::buttonUp(MouseButton button) 14 | { 15 | pressedButtons.erase(button); 16 | press(button, false); 17 | } 18 | 19 | void MouseSimulator::buttonDown(MouseButton button) 20 | { 21 | pressedButtons.insert(button); 22 | press(button, true); 23 | } 24 | 25 | void MouseSimulator::moveBy(int dx, int dy) 26 | { 27 | INPUT input; 28 | 29 | input.type = INPUT_MOUSE; 30 | input.mi.dwFlags = MOUSEEVENTF_MOVE; 31 | input.mi.dx = dx; 32 | input.mi.dy = dy; 33 | 34 | SendInput(1, &input, sizeof(INPUT)); 35 | } 36 | 37 | void MouseSimulator::press(MouseButton button, bool down) 38 | { 39 | INPUT input; 40 | 41 | input.type = INPUT_MOUSE; 42 | 43 | switch (button) 44 | { 45 | case MouseButton::left: 46 | input.mi.dwFlags = (down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP); 47 | break; 48 | 49 | case MouseButton::right: 50 | input.mi.dwFlags = (down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP); 51 | break; 52 | 53 | case MouseButton::middle: 54 | input.mi.dwFlags = (down ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP); 55 | break; 56 | 57 | case MouseButton::ex1: 58 | input.mi.dwFlags = (down ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP); 59 | input.mi.mouseData = XBUTTON1; 60 | break; 61 | 62 | case MouseButton::ex2: 63 | input.mi.dwFlags = (down ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP); 64 | input.mi.mouseData = XBUTTON2; 65 | break; 66 | 67 | default: 68 | return; 69 | } 70 | 71 | SendInput(1, &input, sizeof(INPUT)); 72 | } 73 | -------------------------------------------------------------------------------- /ds4wizard-cpp/MainWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_MainWindow.h" 5 | #include "Ds4DeviceManager.h" 6 | #include "Logger.h" 7 | #include "Ds4ItemModel.h" 8 | #include "DeviceProfileItemModel.h" 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget* parent = nullptr); 16 | ~MainWindow() override; 17 | 18 | void changeEvent(QEvent* e) override; 19 | bool wndProc(tagMSG* msg) const; 20 | 21 | private: 22 | std::shared_ptr deviceManager; 23 | Ds4ItemModel* ds4Items = nullptr; 24 | DeviceProfileItemModel* profileItems = nullptr; 25 | 26 | Ui::MainWindow ui; 27 | bool supportsSystemTray = false; 28 | QSystemTrayIcon* trayIcon = nullptr; 29 | PVOID notificationHandle = nullptr; 30 | std::future startupTask; 31 | EventToken onLineLogged_; 32 | void onLineLogged(void* sender, std::shared_ptr args) const; 33 | 34 | void registerDeviceNotification(); 35 | void unregisterDeviceNotification(); 36 | void findController(const std::wstring& name) const; 37 | 38 | protected: 39 | 40 | #if !defined(QT_IS_BROKEN) 41 | bool nativeEvent(const QByteArray& eventType, void* message, long* result) override; 42 | #endif 43 | 44 | protected slots: 45 | void closeEvent(QCloseEvent* event) override; 46 | void toggleHide(QSystemTrayIcon::ActivationReason reason); 47 | void devicePropertiesClicked(); 48 | static void startMinimizedToggled(bool value); 49 | static void minimizeToTrayToggled(bool value); 50 | static void preferredConnectionChanged(int value); 51 | void systemTrayShowHide(bool checked); 52 | void systemTrayExit(bool checked); 53 | 54 | void onProfilesLoaded(); 55 | void deviceSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) const; 56 | 57 | signals: 58 | void s_onProfilesLoaded(); 59 | }; 60 | -------------------------------------------------------------------------------- /ds4wizard-cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "MainWindow.h" 3 | #include 4 | #include 5 | #include "program.h" 6 | 7 | #ifdef QT_IS_BROKEN 8 | #include 9 | #endif 10 | 11 | static MainWindow* window = nullptr; 12 | 13 | #ifdef QT_IS_BROKEN 14 | 15 | static WNDPROC lpPrevWndFunc = nullptr; 16 | 17 | LRESULT CALLBACK windowProc( 18 | _In_ HWND hwnd, 19 | _In_ UINT uMsg, 20 | _In_ WPARAM wParam, 21 | _In_ LPARAM lParam 22 | ) 23 | { 24 | if (uMsg == WM_DEVICECHANGE) 25 | { 26 | MSG msg {}; 27 | msg.hwnd = hwnd; 28 | msg.message = uMsg; 29 | msg.wParam = wParam; 30 | msg.lParam = lParam; 31 | 32 | if (window->wndProc(&msg)) 33 | { 34 | return TRUE; 35 | } 36 | } 37 | 38 | return CallWindowProc(lpPrevWndFunc, hwnd, uMsg, wParam, lParam); 39 | } 40 | 41 | #endif 42 | 43 | int main(int argc, char** argv) 44 | { 45 | #ifdef Q_OS_WIN 46 | SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS); 47 | #endif 48 | 49 | SingleApplication application(argc, argv, false, 50 | SingleApplication::Mode::ExcludeAppPath | SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppVersion); 51 | 52 | Program::initialize(); 53 | Program::loadSettings(); 54 | 55 | window = new MainWindow(); 56 | 57 | QObject::connect(&application, &SingleApplication::instanceStarted, window, [&]() 58 | { 59 | window->setWindowState(Qt::WindowActive); 60 | window->show(); 61 | window->raise(); 62 | window->setFocus(); 63 | }); 64 | 65 | #ifdef QT_IS_BROKEN 66 | auto hWnd = reinterpret_cast(window->winId()); 67 | 68 | lpPrevWndFunc = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_WNDPROC)); 69 | SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast(&windowProc)); 70 | #endif 71 | 72 | int result = application.exec(); 73 | 74 | Program::saveSettings(); 75 | delete window; 76 | 77 | return result; 78 | } 79 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Vector2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Vector2 4 | { 5 | public: 6 | static const Vector2 zero; 7 | static const Vector2 one; 8 | 9 | union 10 | { 11 | #pragma pack(push, 1) 12 | struct 13 | { 14 | float x, y; 15 | }; 16 | 17 | float array[2]; 18 | #pragma pack(pop) 19 | }; 20 | 21 | Vector2() = default; 22 | Vector2(float x, float y); 23 | 24 | explicit Vector2(float f); 25 | 26 | float operator[](size_t i) const; 27 | 28 | Vector2 operator+() const; 29 | Vector2 operator-() const; 30 | 31 | Vector2 operator+(const Vector2& rhs) const; 32 | Vector2 operator-(const Vector2& rhs) const; 33 | Vector2 operator*(const Vector2& rhs) const; 34 | Vector2 operator/(const Vector2& rhs) const; 35 | 36 | void operator+=(const Vector2& rhs); 37 | void operator-=(const Vector2& rhs); 38 | void operator/=(const Vector2& rhs); 39 | void operator*=(const Vector2& rhs); 40 | 41 | Vector2 operator+(float rhs) const; 42 | Vector2 operator-(float rhs) const; 43 | Vector2 operator*(float rhs) const; 44 | Vector2 operator/(float rhs) const; 45 | 46 | void operator+=(float rhs); 47 | void operator-=(float rhs); 48 | void operator/=(float rhs); 49 | void operator*=(float rhs); 50 | 51 | explicit operator float*(); 52 | explicit operator const float*() const; 53 | 54 | [[nodiscard]] float lengthSquared() const; 55 | [[nodiscard]] float length() const; 56 | void normalize(); 57 | [[nodiscard]] Vector2 normalized() const; 58 | bool operator==(const Vector2& rhs) const; 59 | bool operator!=(const Vector2& rhs) const; 60 | [[nodiscard]] bool isNormalized() const; 61 | 62 | [[nodiscard]] float dot(const Vector2& rhs) const; 63 | [[nodiscard]] bool nearEqual(const Vector2& rhs) const; 64 | 65 | static Vector2 lerp(const Vector2& start, const Vector2& end, float amount); 66 | static Vector2 clamp(const Vector2& v, float lower, float upper); 67 | }; 68 | 69 | Vector2 operator*(float lhs, const Vector2& rhs); 70 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Vector3.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class Vector3 4 | { 5 | public: 6 | static const Vector3 zero; 7 | static const Vector3 one; 8 | 9 | union 10 | { 11 | #pragma pack(push, 1) 12 | struct 13 | { 14 | float x, y, z; 15 | }; 16 | 17 | float array[3]; 18 | #pragma pack(pop) 19 | }; 20 | 21 | Vector3() = default; 22 | Vector3(float x, float y, float z); 23 | 24 | explicit Vector3(float f); 25 | 26 | float operator[](size_t i) const; 27 | 28 | Vector3 operator+() const; 29 | Vector3 operator-() const; 30 | 31 | Vector3 operator+(const Vector3& rhs) const; 32 | Vector3 operator-(const Vector3& rhs) const; 33 | Vector3 operator*(const Vector3& rhs) const; 34 | Vector3 operator/(const Vector3& rhs) const; 35 | 36 | void operator+=(const Vector3& rhs); 37 | void operator-=(const Vector3& rhs); 38 | void operator/=(const Vector3& rhs); 39 | void operator*=(const Vector3& rhs); 40 | 41 | Vector3 operator+(float rhs) const; 42 | Vector3 operator-(float rhs) const; 43 | Vector3 operator*(float rhs) const; 44 | Vector3 operator/(float rhs) const; 45 | 46 | void operator+=(float rhs); 47 | void operator-=(float rhs); 48 | void operator/=(float rhs); 49 | void operator*=(float rhs); 50 | 51 | explicit operator float*(); 52 | explicit operator const float*() const; 53 | 54 | [[nodiscard]] float lengthSquared() const; 55 | [[nodiscard]] float length() const; 56 | void normalize(); 57 | [[nodiscard]] Vector3 normalized() const; 58 | bool operator==(const Vector3& rhs) const; 59 | bool operator!=(const Vector3& rhs) const; 60 | [[nodiscard]] bool isNormalized() const; 61 | 62 | [[nodiscard]] float dot(const Vector3& rhs) const; 63 | [[nodiscard]] bool nearEqual(const Vector3& rhs) const; 64 | 65 | static Vector3 lerp(const Vector3& start, const Vector3& end, float amount); 66 | static Vector3 clamp(const Vector3& v, float lower, float upper); 67 | }; 68 | 69 | Vector3 operator*(float lhs, const Vector3& rhs); 70 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceProfile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "DeviceSettingsCommon.h" 8 | #include "Ds4TouchRegion.h" 9 | #include "InputMap.h" 10 | 11 | /** 12 | * \brief A class which represents a collection of input-to-output simulation bindings. 13 | */ 14 | class DeviceProfile : public DeviceSettingsCommon 15 | { 16 | public: 17 | /** 18 | * \brief Gets a filesystem-friendly filename representation of \c name. 19 | * \sa name 20 | */ 21 | [[nodiscard]] std::string fileName() const; 22 | 23 | /** 24 | * \brief User-defined name for the profile. 25 | */ 26 | std::string name; 27 | 28 | /** 29 | * \brief If \c true, opens an exclusive handle to the device, (mostly) hiding it from other applications. 30 | */ 31 | bool exclusiveMode = true; 32 | 33 | /** 34 | * \brief If \c true, enables XInput simulation. 35 | */ 36 | bool useXInput = true; 37 | 38 | // TODO: make ordered with std::map 39 | /** 40 | * \brief Map of touchpad regions referenced by name. 41 | */ 42 | std::unordered_map touchRegions; 43 | 44 | /** 45 | * \brief Input-to-output bindings managed by this profile. 46 | * \sa InputMap 47 | */ 48 | std::deque bindings; 49 | 50 | /** 51 | * \brief Modifier sets managed by this profile. 52 | * \sa InputModifier 53 | */ 54 | std::deque modifiers; 55 | 56 | DeviceProfile() = default; 57 | DeviceProfile(const DeviceProfile&) = default; 58 | DeviceProfile(DeviceProfile&& other) noexcept; 59 | 60 | DeviceProfile& operator=(const DeviceProfile&) = default; 61 | DeviceProfile& operator=(DeviceProfile&& other) noexcept; 62 | 63 | bool operator==(const DeviceProfile& other) const; 64 | 65 | void readJson(const nlohmann::json& json) override; 66 | void writeJson(nlohmann::json& json) const override; 67 | 68 | /** 69 | * \brief The default representation of a \c DeviceProfile. 70 | */ 71 | static DeviceProfile defaultProfile(); 72 | }; 73 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Bluetooth.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Bluetooth.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | bool Bluetooth::disconnectDevice(std::span macAddress) 10 | { 11 | const size_t macAddressSize = macAddress.size(); 12 | 13 | if (macAddressSize != macAddressLength) 14 | { 15 | return false; 16 | } 17 | 18 | // The buffer which will hold the reversed MAC address. 19 | // A length of 8 is required; otherwise DeviceIoControl fails. 20 | std::array buffer {}; 21 | 22 | // Store reversed MAC address. 23 | for (size_t i = 0; i < macAddressSize; i++) 24 | { 25 | buffer[macAddressSize - 1 - i] = macAddress[i]; 26 | } 27 | 28 | BLUETOOTH_FIND_RADIO_PARAMS findParams {}; 29 | findParams.dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS); 30 | 31 | Handle phRadio(nullptr, true); 32 | 33 | const HBLUETOOTH_RADIO_FIND hFind = BluetoothFindFirstRadio(&findParams, &phRadio.nativeHandle); 34 | 35 | if (hFind == nullptr) 36 | { 37 | return false; 38 | } 39 | 40 | bool result = false; 41 | 42 | while (phRadio.nativeHandle != nullptr) 43 | { 44 | const bool success = DeviceIoControl(phRadio.nativeHandle, IOCTL_BTH_DISCONNECT_DEVICE, 45 | // Input buffer. 46 | buffer.data(), static_cast(buffer.size()), 47 | // Output buffer (ignored in our case). 48 | nullptr, 0, 49 | // Bytes returned in output buffer (ignored in our case). 50 | nullptr, 51 | nullptr); 52 | 53 | phRadio.close(); 54 | 55 | if (success) 56 | { 57 | result = true; 58 | break; 59 | } 60 | 61 | if (!BluetoothFindNextRadio(hFind, &phRadio.nativeHandle)) 62 | { 63 | break; 64 | } 65 | } 66 | 67 | BluetoothFindRadioClose(hFind); 68 | return result; 69 | } 70 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceProfileModel.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DeviceProfileModel.h" 3 | 4 | ModifierListModel::ModifierListModel(QObject* parent, DeviceProfile* profile_) 5 | : QAbstractListModel(parent), 6 | profile(profile_) 7 | { 8 | } 9 | 10 | int ModifierListModel::rowCount(const QModelIndex& /*parent*/) const 11 | { 12 | return static_cast(profile->modifiers.size()) + static_cast(includeNone); 13 | } 14 | 15 | int ModifierListModel::columnCount(const QModelIndex& /*parent*/) const 16 | { 17 | return 1; 18 | } 19 | 20 | QVariant ModifierListModel::data(const QModelIndex& index, int role) const 21 | { 22 | if (!index.isValid() || index.row() < 0 || role != Qt::DisplayRole) 23 | { 24 | return QVariant(); 25 | } 26 | 27 | if (includeNone) 28 | { 29 | if (static_cast(index.row()) > profile->modifiers.size()) 30 | { 31 | return QVariant(); 32 | } 33 | } 34 | else if (static_cast(index.row()) >= profile->modifiers.size()) 35 | { 36 | return QVariant(); 37 | } 38 | 39 | if (includeNone && !index.row()) 40 | { 41 | return tr("[None]"); 42 | } 43 | 44 | const auto row = index.row() - static_cast(includeNone); 45 | 46 | return QString::number(row); // TODO: names for modifier sets? use button images? 47 | } 48 | 49 | QVariant ModifierListModel::headerData(int section, Qt::Orientation orientation, int role) const 50 | { 51 | if (section > 0) 52 | { 53 | return QVariant(); 54 | } 55 | 56 | if (orientation != Qt::Horizontal) 57 | { 58 | return QVariant(); 59 | } 60 | 61 | switch (static_cast(role)) 62 | { 63 | case Qt::DisplayRole: 64 | return tr("Modifier"); 65 | 66 | default: 67 | return QVariant(); 68 | } 69 | } 70 | 71 | DeviceProfileModel::DeviceProfileModel(QObject* parent, DeviceProfile&& profile_) 72 | : QObject(parent), profile(std::move(profile_)) 73 | { 74 | } 75 | 76 | DeviceProfileModel::DeviceProfileModel(QObject* parent, const DeviceProfile& profile_) 77 | : QObject(parent), profile(profile_) 78 | { 79 | } 80 | 81 | ModifierListModel* DeviceProfileModel::makeModifierModel(QObject* parent) 82 | { 83 | return new ModifierListModel(parent, &profile); 84 | } 85 | -------------------------------------------------------------------------------- /ds4wizard-cpp/average.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Stopwatch.h" 6 | 7 | template 8 | class timed_average 9 | { 10 | private: 11 | std::vector points; 12 | Stopwatch stopwatch; 13 | Stopwatch::Duration target_duration; 14 | T last_average {}; 15 | bool dirty = false; 16 | 17 | public: 18 | explicit timed_average(Stopwatch::Duration dur) 19 | : target_duration(dur) 20 | { 21 | } 22 | 23 | void push(const T& value) 24 | { 25 | if (!stopwatch.running()) 26 | { 27 | stopwatch.start(); 28 | } 29 | 30 | dirty = true; 31 | points.push_back(value); 32 | 33 | // if we've met our target, cache the result now and clear the buffer 34 | if (stopwatch.elapsed() >= target_duration) 35 | { 36 | this->value(); 37 | } 38 | } 39 | 40 | T value() 41 | { 42 | if (!dirty || stopwatch.elapsed() < target_duration) 43 | { 44 | return last_average; 45 | } 46 | 47 | if (dirty) 48 | { 49 | T temp {}; 50 | 51 | for (const auto& p : points) 52 | { 53 | temp += p / points.size(); 54 | } 55 | 56 | last_average = temp; 57 | dirty = false; 58 | } 59 | 60 | if (stopwatch.elapsed() >= target_duration) 61 | { 62 | points.clear(); 63 | points.push_back(last_average); 64 | stopwatch.start(); 65 | } 66 | 67 | return last_average; 68 | } 69 | }; 70 | 71 | template 72 | class average 73 | { 74 | private: 75 | bool dirty = false; 76 | size_t point_i = 0; 77 | size_t point_count = 0; 78 | std::array points; 79 | T last_average {}; 80 | 81 | public: 82 | void push(const T& value) 83 | { 84 | dirty = true; 85 | points[point_i] = value; 86 | ++point_i %= points.size(); 87 | 88 | if (point_count < points.size()) 89 | { 90 | ++point_count; 91 | } 92 | } 93 | 94 | T value() 95 | { 96 | if (!dirty || point_count < points.size()) 97 | { 98 | return last_average; 99 | } 100 | 101 | dirty = false; 102 | T temp {}; 103 | 104 | for (const auto& p : points) 105 | { 106 | temp += p / points.size(); 107 | } 108 | 109 | last_average = temp; 110 | return temp; 111 | } 112 | }; 113 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4Input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "Ds4InputData.h" 7 | 8 | /** 9 | * \brief Serialized input report from a \c Ds4Device 10 | * \sa Ds4Device 11 | */ 12 | class Ds4Input 13 | { 14 | public: 15 | Ds4Input() = default; 16 | 17 | /** 18 | * \brief Indicates if button press state has changed since the last poll. 19 | */ 20 | bool buttonsChanged = false; // TODO: private set 21 | 22 | /** 23 | * \brief Indicates if touch state has changed since last poll. 24 | */ 25 | bool touchChanged = false; // TODO: private set 26 | 27 | /** 28 | * \brief Buttons currently held. 29 | * \sa Ds4Buttons, Ds4Buttons_t 30 | */ 31 | Ds4Buttons_t heldButtons = 0; // TODO: private set 32 | 33 | /** 34 | * \brief Buttons pressed since last poll. 35 | * \sa Ds4Buttons, Ds4Buttons_t 36 | */ 37 | Ds4Buttons_t pressedButtons = 0; // TODO: private set 38 | 39 | /** 40 | * \brief Buttons released since last poll. 41 | * \sa Ds4Buttons, Ds4Buttons_t 42 | */ 43 | Ds4Buttons_t releasedButtons = 0; // TODO: private set 44 | 45 | /** 46 | * \brief Each axis that has changed since the last poll. 47 | * \sa Ds4Axes, Ds4Axes_t 48 | */ 49 | Ds4Axes_t axes = 0; // TODO: private set 50 | 51 | Ds4InputData data {}; 52 | 53 | /** 54 | * \brief Updates serialized data using the given buffer. 55 | * \param buffer Buffer containing raw input report data. 56 | */ 57 | void update(std::span buffer); 58 | 59 | /** 60 | * \brief Updates button change states since last poll. 61 | */ 62 | void updateChangedState(); 63 | 64 | /** 65 | * \brief Get the magnitude of an axis. 66 | * \param axis The axis to retrieve. 67 | * \param polarity The desired polarity of the axis, or \c std::nullopt for both positive and negative. 68 | * \return The magnitude of the axis. If it does not align with the desired \a polarity, \c 0.0f is returned. 69 | */ 70 | [[nodiscard]] float getAxis(Ds4Axes_t axis, const std::optional& polarity) const; 71 | 72 | private: 73 | Ds4Buttons_t lastHeldButtons = 0; 74 | uint8_t lastTouchFrame {}; 75 | 76 | void addButton(bool pressed, Ds4Buttons_t buttons); 77 | void updateButtons(); 78 | void updateAxes(const Ds4InputData& last); 79 | }; 80 | -------------------------------------------------------------------------------- /SingleApplication/SingleApplication.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} 14 | qrc;* 15 | false 16 | 17 | 18 | {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} 19 | qrc;* 20 | false 21 | 22 | 23 | {f1668895-f128-437e-9a50-08c9e81e47d6} 24 | 25 | 26 | {e4712046-266e-4aaa-83bb-c4eabed10aac} 27 | 28 | 29 | 30 | 31 | Source Files\..%255cdependencies%255csingleapplication 32 | 33 | 34 | Source Files\..%255cdependencies%255csingleapplication 35 | 36 | 37 | 38 | 39 | Header Files\..%255cdependencies%255csingleapplication 40 | 41 | 42 | Header Files\..%255cdependencies%255csingleapplication 43 | 44 | 45 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ViGEmTarget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Event.h" 6 | 7 | namespace vigem 8 | { 9 | class Driver; 10 | 11 | class XInputTarget 12 | { 13 | friend class Driver; 14 | 15 | Driver* parent; 16 | PVIGEM_TARGET target = nullptr; 17 | bool connected_ = false; 18 | 19 | public: 20 | // large motor, small motor, led number 21 | Event notification; 22 | 23 | /** 24 | * \brief Explicitly disallow default construction. 25 | */ 26 | XInputTarget() = delete; 27 | 28 | /** 29 | * \brief Explicitly disallow copying. 30 | */ 31 | XInputTarget(const XInputTarget&) = delete; 32 | 33 | /** 34 | * \brief Constructs an XInput endpoint using the given driver instance. 35 | * \param parent \c Driver instance to use for configuring the endpoint. 36 | */ 37 | explicit XInputTarget(Driver* parent); 38 | XInputTarget(XInputTarget&& rhs) noexcept; 39 | ~XInputTarget(); 40 | 41 | /** 42 | * \brief Connects a virtual XInput device to the system and registers event handlers. 43 | * \return \c VIGEM_ERROR_NONE on success. 44 | */ 45 | VIGEM_ERROR connect(); 46 | 47 | /** 48 | * \brief Specifies connected state of the virtual XInput device to the system. 49 | * \return \c true if the device is connected. 50 | */ 51 | bool connected() const; 52 | 53 | /** 54 | * \brief Disconnects the virtual XInput device from the system and unregisters event handlers. 55 | * \return \c VIGEM_ERROR_NONE on success. 56 | */ 57 | VIGEM_ERROR disconnect(); 58 | 59 | /** 60 | * \brief Writes input data to the virtual XInput device. 61 | * \param data The data to write to the virtual XInput device. 62 | */ 63 | void update(const XInputGamepad& data) const; 64 | 65 | /** 66 | * \brief Explicitly disallow copying. 67 | */ 68 | XInputTarget& operator=(const XInputTarget&) = delete; 69 | 70 | private: 71 | void close(); 72 | VIGEM_ERROR registerNotification(); 73 | void unregisterNotification(); 74 | 75 | static void raiseEvent(PVIGEM_CLIENT client, PVIGEM_TARGET target, 76 | uint8_t largeMotor, uint8_t smallMotor, uint8_t ledNumber, 77 | LPVOID userData); 78 | }; 79 | } 80 | -------------------------------------------------------------------------------- /ds4wizard-cpp/RumbleSequence.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include 3 | #include "RumbleSequence.h" 4 | 5 | using namespace std::chrono; 6 | 7 | RumbleSequence::RumbleSequence(InputSimulator* parent) 8 | : ISimulator(parent), 9 | currentElement(std::nullopt) 10 | { 11 | } 12 | 13 | void RumbleSequence::update(float deltaTime) 14 | { 15 | if (sequence.empty()) 16 | { 17 | deactivate(deltaTime); 18 | return; 19 | } 20 | 21 | const auto elapsed = stopwatch.elapsed(); 22 | 23 | if (!currentElement.has_value() || 24 | elapsed >= milliseconds(currentElement->durationMilliseconds)) 25 | { 26 | stopwatch.start(); 27 | currentElement = sequence.front(); 28 | sequence.pop(); 29 | } 30 | 31 | switch (currentElement.value().blending) 32 | { 33 | case RumbleSequenceBlending::none: 34 | parent->setRumble(currentElement->leftMotor, currentElement->rightMotor); 35 | break; 36 | 37 | case RumbleSequenceBlending::linear: 38 | { 39 | float left = 0.0f; 40 | float right = 0.0f; 41 | 42 | if (!sequence.empty()) 43 | { 44 | const auto& front = sequence.front(); 45 | 46 | left = front.leftMotor; 47 | right = front.rightMotor; 48 | } 49 | 50 | int64_t elapsedMilliseconds = duration_cast(elapsed).count(); 51 | double f = static_cast(elapsedMilliseconds) / static_cast(currentElement->durationMilliseconds); 52 | 53 | left = gmath::lerp(currentElement->leftMotor, left, f); 54 | right = gmath::lerp(currentElement->rightMotor, right, f); 55 | 56 | parent->setRumble(left, right); 57 | break; 58 | } 59 | 60 | default: 61 | break; 62 | } 63 | } 64 | 65 | void RumbleSequence::add(const RumbleSequenceElement& element) 66 | { 67 | sequence.push(element); 68 | } 69 | 70 | RumbleTimer::RumbleTimer(InputSimulator* parent, Stopwatch::Duration duration, float left, float right) 71 | : ISimulator(parent), 72 | duration(duration), 73 | left(left), 74 | right(right) 75 | { 76 | } 77 | 78 | void RumbleTimer::reset() 79 | { 80 | stopwatch.start(); 81 | } 82 | 83 | void RumbleTimer::update(float deltaTime) 84 | { 85 | if (stopwatch.elapsed() >= duration) 86 | { 87 | deactivate(deltaTime); 88 | return; 89 | } 90 | 91 | parent->setRumble(left, right); 92 | } 93 | 94 | void RumbleTimer::onActivate(float deltaTime) 95 | { 96 | reset(); 97 | } 98 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Pressable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "enums.h" 4 | 5 | /** 6 | * \brief An object representing a digital button's pressed state. 7 | * \sa PressedState 8 | */ 9 | class Pressable 10 | { 11 | public: 12 | virtual ~Pressable() = default; 13 | 14 | /** 15 | * \brief Indicates the pressed state of this pressable instance. 16 | * \sa PressedState 17 | */ 18 | PressedState pressedState = PressedState::off; 19 | 20 | /** 21 | * \brief Indicates that this instance is in an active state, that is: just pressed or held. 22 | * \return \c true if active. 23 | */ 24 | [[nodiscard]] virtual bool isActive() const; 25 | 26 | /** 27 | * \brief Changes a \c PressedState from an inactive to active state. 28 | * If \c PressedState::off or \c PressedState::released, the state will be changed to \c PressedState::pressed. 29 | * If \c PressedState::pressed, the state is changed to \c PressedState::on 30 | * \param state The state to modify. 31 | * \sa PressedState 32 | */ 33 | static void press(PressedState& state); 34 | 35 | /** 36 | * \brief Changes a \c PressedState from an active to inactive state. 37 | * If \c PressedState::on or \c PressedState::pressed, the state will be changed to \c PressedState::released. 38 | * If \c PressedState::released, the state is changed to \c PressedState::off 39 | * \param state The state to modify. 40 | * \sa PressedState 41 | */ 42 | static void release(PressedState& state); 43 | 44 | /** 45 | * \brief Indicates that this instance is in an active state, that is: just pressed or held. 46 | * \param state The state to check. 47 | * \return \c true if active. 48 | * \sa PressedState 49 | */ 50 | static bool isActiveState(PressedState state); 51 | 52 | /** 53 | * \brief Changes the state of this instance from an inactive to active state. 54 | * If \c PressedState::off or \c PressedState::released, the state will be changed to \c PressedState::pressed. 55 | * If \c PressedState::pressed, the state is changed to \c PressedState::on 56 | * \sa PressedState 57 | */ 58 | virtual void press(); 59 | 60 | /** 61 | * \brief Changes the state of this instance from an active to inactive state. 62 | * If \c PressedState::on or \c PressedState::pressed, the state will be changed to \c PressedState::released. 63 | * If \c PressedState::released, the state is changed to \c PressedState::off 64 | * \sa PressedState 65 | */ 66 | virtual void release(); 67 | }; 68 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Event.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * \brief An event token to be used with \c Event. 10 | * When the \c EventToken is destroyed, it is unregistered from the event automatically. 11 | */ 12 | using EventToken = std::shared_ptr; 13 | 14 | /** 15 | * \brief An object which notifies listeners of an event. 16 | * \tparam sender_t The type of the sender. 17 | * \tparam args_t Arguments that will be passed to the listeners. 18 | */ 19 | template 20 | class Event 21 | { 22 | public: 23 | using callback_t = std::function; 24 | 25 | private: 26 | using weak_callback = std::weak_ptr; 27 | 28 | std::deque callbacks; 29 | std::recursive_mutex mutex; 30 | 31 | public: 32 | /** 33 | * \brief Registers a listener callback function with the event. 34 | * \param callback Function to execute. 35 | * \return An \c EventToken to maintain connection to the event. 36 | */ 37 | [[nodiscard]] EventToken add(callback_t callback) 38 | { 39 | std::lock_guard lock(mutex); 40 | 41 | auto token = std::make_shared(std::move(callback)); 42 | callbacks.push_back(token); 43 | return token; 44 | } 45 | 46 | /** 47 | * \brief Unregisters a listener from the event. 48 | * \param token The \c EventToken to unregister from the event. 49 | */ 50 | void remove(EventToken token) 51 | { 52 | std::lock_guard lock(mutex); 53 | 54 | callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), [token](auto ptr) -> bool 55 | { 56 | return ptr.lock() == token; 57 | })); 58 | } 59 | 60 | /** 61 | * \brief Raises the event and notifies all listeners. 62 | * \param sender The object invoking the event, or \c nullptr. 63 | * \param args The arguments to pass to the listeners. 64 | */ 65 | void invoke(sender_t* sender, args_t... args) 66 | { 67 | std::lock_guard lock(mutex); 68 | 69 | auto predicate = [](weak_callback t) -> bool 70 | { 71 | return t.expired(); 72 | }; 73 | 74 | callbacks.erase(std::remove_if(callbacks.begin(), callbacks.end(), predicate), callbacks.end()); 75 | 76 | // Copied so that callbacks can register new callbacks or unregister callbacks. 77 | auto callbacksCopy = callbacks; 78 | 79 | for (auto weak : callbacksCopy) 80 | { 81 | if (auto shared = weak.lock()) 82 | { 83 | (*shared)(sender, args...); 84 | } 85 | } 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /ds4wizard-cpp/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define QT_IS_BROKEN 4 | #define QAPPLICATION_CLASS QApplication // for SingleApplication 5 | 6 | // Windows 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // STL 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | // Qt 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include 46 | 47 | // better-enums 48 | #include 49 | 50 | // libhid 51 | #include 52 | #include 53 | #include 54 | 55 | // ViGEm 56 | #include 57 | #include 58 | #include 59 | 60 | #include "MapCache.h" 61 | #include "average.h" 62 | #include "AxisOptions.h" 63 | #include "Bluetooth.h" 64 | #include "busenum.h" 65 | #include "ConnectionType.h" 66 | #include "DeviceIdleOptions.h" 67 | #include "DeviceProfile.h" 68 | #include "DeviceProfileCache.h" 69 | #include "DevicePropertiesDialog.h" 70 | #include "DeviceSettings.h" 71 | #include "DeviceSettingsCommon.h" 72 | #include "Ds4AutoLightColor.h" 73 | #include "Ds4Color.h" 74 | #include "Ds4Device.h" 75 | #include "Ds4DeviceManager.h" 76 | #include "Ds4Input.h" 77 | #include "Ds4InputData.h" 78 | #include "Ds4ItemModel.h" 79 | #include "Ds4LightOptions.h" 80 | #include "Ds4Output.h" 81 | #include "Ds4TouchRegion.h" 82 | #include "enums.h" 83 | #include "Event.h" 84 | #include "gmath.h" 85 | #include "InputMap.h" 86 | #include "InputSimulator.h" 87 | #include "JsonData.h" 88 | #include "KeyboardSimulator.h" 89 | #include "Latency.h" 90 | #include "lock.h" 91 | #include "Logger.h" 92 | #include "MainWindow.h" 93 | #include "MouseSimulator.h" 94 | #include "pathutil.h" 95 | #include "Pressable.h" 96 | #include "ProfileEditorDialog.h" 97 | #include "program.h" 98 | #include "Settings.h" 99 | #include "Stopwatch.h" 100 | #include "stringutil.h" 101 | #include "Trackball.h" 102 | #include "Vector2.h" 103 | #include "Vector3.h" 104 | #include "XInputGamepad.h" 105 | #include "MacAddress.h" 106 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4InputData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "enums.h" 6 | 7 | /** 8 | * \brief A structure defining a DualShock 4 analog stick. 9 | */ 10 | struct Ds4Stick 11 | { 12 | /** 13 | * \brief X axis of the DualShock 4 analog stick. 14 | * Note that the center of the axis rests at 127, not 0. 15 | */ 16 | uint8_t x; 17 | 18 | /** 19 | * \brief Y axis of the DualShock 4 analog stick. 20 | * Note that the center of the axis rests at 127, not 0. 21 | */ 22 | uint8_t y; 23 | 24 | bool operator==(const Ds4Stick& other) const; 25 | bool operator!=(const Ds4Stick& right) const; 26 | 27 | int lengthSquared() const; 28 | float length() const; 29 | }; 30 | 31 | /** 32 | * \brief A structure defining a DualShock 4 vector with 2 signed short components. 33 | * Used for points on the touch pad. 34 | */ 35 | struct Ds4Vector2 36 | { 37 | /** 38 | * \brief The X component of the point. 39 | * Left is \c 0, center is \c 959, right is \c 1919. 40 | */ 41 | short x; 42 | 43 | /** 44 | * \brief The Y component of the point. 45 | * Top is \c 0, center is \c 470, bottom is \c 942. 46 | */ 47 | short y; 48 | 49 | bool operator==(const Ds4Vector2& other) const; 50 | bool operator!=(const Ds4Vector2& other) const; 51 | 52 | int lengthSquared() const; 53 | float length() const; 54 | }; 55 | 56 | /** 57 | * \brief A structure defining a DualShock 4 vector with 3 signed \c short components. 58 | * Used for the gyroscope and accelerometer. 59 | */ 60 | struct Ds4Vector3 61 | { 62 | short x, y, z; 63 | 64 | bool operator==(const Ds4Vector3& other) const; 65 | bool operator!=(const Ds4Vector3& other) const; 66 | 67 | int lengthSquared() const; 68 | float length() const; 69 | }; 70 | 71 | struct Ds4InputData 72 | { 73 | Ds4ButtonsRaw_t activeButtons; 74 | Ds4Stick leftStick; 75 | Ds4Stick rightStick; 76 | 77 | [[nodiscard]] Hat dPad() const; 78 | [[nodiscard]] bool square() const; 79 | [[nodiscard]] bool cross() const; 80 | [[nodiscard]] bool circle() const; 81 | [[nodiscard]] bool triangle() const; 82 | [[nodiscard]] bool l1() const; 83 | [[nodiscard]] bool r1() const; 84 | [[nodiscard]] bool l2() const; 85 | [[nodiscard]] bool r2() const; 86 | [[nodiscard]] bool share() const; 87 | [[nodiscard]] bool options() const; 88 | [[nodiscard]] bool l3() const; 89 | [[nodiscard]] bool r3() const; 90 | [[nodiscard]] bool ps() const; 91 | [[nodiscard]] bool touchButton() const; 92 | 93 | uint8_t frameCount; 94 | uint8_t leftTrigger; 95 | uint8_t rightTrigger; 96 | uint8_t battery; 97 | Ds4Vector3 accel; 98 | Ds4Vector3 gyro; 99 | uint8_t extensions; 100 | uint8_t touchEvent; 101 | uint8_t touchFrame; 102 | bool touch1; 103 | uint8_t touch1Id; 104 | Ds4Vector2 touchPoint1; 105 | bool touch2; 106 | uint8_t touch2Id; 107 | Ds4Vector2 touchPoint2; 108 | Ds4Vector2 lastTouchPoint1; 109 | Ds4Vector2 lastTouchPoint2; 110 | 111 | bool operator==(const Ds4InputData& other) const; 112 | bool operator!=(const Ds4InputData& other) const; 113 | }; 114 | -------------------------------------------------------------------------------- /ds4wizard-cpp/program.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "program.h" 3 | #include "DeviceProfileCache.h" 4 | 5 | BOOL IsElevated() 6 | { 7 | BOOL fRet = FALSE; 8 | HANDLE hToken = nullptr; 9 | 10 | if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) 11 | { 12 | TOKEN_ELEVATION elevation; 13 | DWORD cbSize = sizeof(TOKEN_ELEVATION); 14 | 15 | if (GetTokenInformation(hToken, TokenElevation, &elevation, sizeof(elevation), &cbSize)) 16 | { 17 | fRet = elevation.TokenIsElevated; 18 | } 19 | } 20 | 21 | if (hToken) 22 | { 23 | CloseHandle(hToken); 24 | } 25 | 26 | return fRet; 27 | } 28 | 29 | DeviceProfileCache Program::profileCache {}; 30 | Settings Program::settings {}; 31 | Settings Program::lastSettings {}; 32 | QString Program::settingsPath; 33 | QString Program::settingsFilePath; 34 | std::string Program::profilesPath_; 35 | std::string Program::devicesFilePath_; 36 | 37 | vigem::Driver Program::driver; 38 | 39 | const std::string& Program::profilesPath() 40 | { 41 | return profilesPath_; 42 | } 43 | 44 | const std::string& Program::devicesFilePath() 45 | { 46 | return devicesFilePath_; 47 | } 48 | 49 | bool Program::isElevated() 50 | { 51 | return isElevated_; 52 | } 53 | 54 | void Program::initialize() 55 | { 56 | // TODO: check for and display error opening ViGEm driver handle 57 | driver.open(); 58 | 59 | // HACK: don't do this? 60 | QDir dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); 61 | dir.cdUp(); 62 | 63 | const QString appDataLocation = dir.path(); 64 | 65 | settingsPath = appDataLocation + "/ds4wizard"; 66 | settingsFilePath = settingsPath + "/settings.json"; 67 | profilesPath_ = (settingsPath + "/profiles").toStdString(); 68 | devicesFilePath_ = (settingsPath + "/devices.json").toStdString(); 69 | 70 | isElevated_ = IsElevated() == TRUE; 71 | } 72 | 73 | void Program::loadSettings() 74 | { 75 | QDir settingsDir(settingsPath); 76 | 77 | if (!settingsDir.exists()) 78 | { 79 | qDebug() << "no settings to load"; 80 | return; 81 | } 82 | 83 | QFile file(settingsFilePath); 84 | 85 | if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) 86 | { 87 | qDebug() << "failed to open " << settingsFilePath << "for reading.\n"; 88 | return; 89 | } 90 | 91 | QString val = file.readAll(); 92 | const auto json = nlohmann::json::parse(val.toStdString()); 93 | settings = JsonData::fromJson(json); 94 | lastSettings = settings; 95 | 96 | file.close(); 97 | } 98 | 99 | void Program::saveSettings() 100 | { 101 | if (lastSettings == settings) 102 | { 103 | return; 104 | } 105 | 106 | QDir settingsDir(settingsPath); 107 | 108 | if (!settingsDir.exists()) 109 | { 110 | // there's evidently a good reason for mkpath 111 | // to be non-static, but it still feels weird. 112 | settingsDir.mkpath(settingsPath); 113 | } 114 | 115 | QFile file(settingsFilePath); 116 | 117 | if (!file.open(QIODevice::WriteOnly)) 118 | { 119 | qDebug() << "failed to open " << settingsFilePath << "for writing.\n"; 120 | return; 121 | } 122 | 123 | nlohmann::json obj; 124 | settings.writeJson(obj); 125 | 126 | file.write(QByteArray::fromStdString(obj.dump(4))); 127 | file.close(); 128 | 129 | lastSettings = settings; 130 | } 131 | -------------------------------------------------------------------------------- /ds4wizard-cpp/ViGEmTarget.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | #include 4 | 5 | #include "ViGEmTarget.h" 6 | #include "ViGEmDriver.h" 7 | 8 | namespace vigem 9 | { 10 | XInputTarget::XInputTarget(Driver* parent) 11 | : parent(parent) 12 | { 13 | target = vigem_target_x360_alloc(); 14 | 15 | if (target == nullptr) 16 | { 17 | throw std::runtime_error("Unable to allocate XInput ViGEm target."); 18 | } 19 | } 20 | 21 | XInputTarget::XInputTarget(XInputTarget&& rhs) noexcept 22 | : parent(rhs.parent), 23 | target(rhs.target), 24 | connected_(rhs.connected_) 25 | { 26 | rhs.unregisterNotification(); 27 | rhs.target = nullptr; 28 | rhs.connected_ = false; 29 | registerNotification(); 30 | } 31 | 32 | XInputTarget::~XInputTarget() 33 | { 34 | close(); 35 | } 36 | 37 | VIGEM_ERROR XInputTarget::connect() 38 | { 39 | if (connected()) 40 | { 41 | return VIGEM_ERROR_NONE; 42 | } 43 | 44 | auto guard = parent->lock(); 45 | 46 | const VIGEM_ERROR result = vigem_target_add(parent->client, target); 47 | 48 | if (!VIGEM_SUCCESS(result)) 49 | { 50 | return result; 51 | } 52 | 53 | connected_ = true; 54 | 55 | return registerNotification(); 56 | } 57 | 58 | bool XInputTarget::connected() const 59 | { 60 | return connected_; 61 | } 62 | 63 | VIGEM_ERROR XInputTarget::disconnect() 64 | { 65 | if (!connected()) 66 | { 67 | return VIGEM_ERROR_NONE; 68 | } 69 | 70 | auto guard = parent->lock(); 71 | const VIGEM_ERROR result = vigem_target_remove(parent->client, target); 72 | 73 | if (VIGEM_SUCCESS(result)) 74 | { 75 | connected_ = false; 76 | unregisterNotification(); 77 | } 78 | 79 | return result; 80 | } 81 | 82 | void XInputTarget::update(const XInputGamepad& data) const 83 | { 84 | auto guard = parent->lock(); 85 | vigem_target_x360_update(parent->client, this->target, 86 | *reinterpret_cast(&data)); 87 | } 88 | 89 | void XInputTarget::close() 90 | { 91 | auto guard = parent->lock(); 92 | disconnect(); 93 | 94 | if (target) 95 | { 96 | vigem_target_free(target); 97 | target = nullptr; 98 | } 99 | } 100 | 101 | VIGEM_ERROR XInputTarget::registerNotification() 102 | { 103 | const VIGEM_ERROR result = vigem_target_x360_register_notification(parent->client, target, 104 | &XInputTarget::raiseEvent, 105 | this); 106 | 107 | if (!VIGEM_SUCCESS(result)) 108 | { 109 | disconnect(); 110 | } 111 | 112 | return result; 113 | } 114 | 115 | void XInputTarget::unregisterNotification() 116 | { 117 | vigem_target_x360_unregister_notification(target); 118 | } 119 | 120 | void XInputTarget::raiseEvent(PVIGEM_CLIENT client, PVIGEM_TARGET target, 121 | uint8_t largeMotor, uint8_t smallMotor, uint8_t ledNumber, 122 | LPVOID userData) 123 | { 124 | auto* xinput_target = static_cast(userData); 125 | 126 | if (xinput_target->parent->client == client) 127 | { 128 | xinput_target->notification.invoke(xinput_target, largeMotor, smallMotor, ledNumber); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /hid-tester/main.cpp: -------------------------------------------------------------------------------- 1 | #define WIN32_LEAN_AND_MEAN 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | static std::shared_ptr device = nullptr; 13 | static std::optional vendorId; 14 | static std::optional productId; 15 | 16 | bool enumFunc(std::shared_ptr inst) 17 | { 18 | const auto& attr = inst->attributes(); 19 | 20 | if ((vendorId.has_value() && attr.vendorId != *vendorId) || 21 | (productId.has_value() && attr.productId != *productId)) 22 | { 23 | return false; 24 | } 25 | 26 | const auto& caps = inst->caps(); 27 | 28 | std::wcout << L"Device found: " << inst->path << std::endl; 29 | std::cout << "Diagnostics: " << std::endl; 30 | 31 | std::cout << "\tusage: " << caps.usage << std::endl; 32 | std::cout << "\tusagePage: " << caps.usagePage << std::endl; 33 | std::cout << "\tinputReportSize: " << caps.inputReportSize << std::endl; 34 | std::cout << "\toutputReportSize: " << caps.outputReportSize << std::endl; 35 | std::cout << "\tfeatureReportSize: " << caps.featureReportSize << std::endl; 36 | std::cout << "\tlinkCollectionNodes: " << caps.linkCollectionNodes << std::endl; 37 | std::cout << "\tinputButtonCaps: " << caps.inputButtonCaps << std::endl; 38 | std::cout << "\tinputValueCaps: " << caps.inputValueCaps << std::endl; 39 | std::cout << "\tinputDataIndices: " << caps.inputDataIndices << std::endl; 40 | std::cout << "\toutputButtonCaps: " << caps.outputButtonCaps << std::endl; 41 | std::cout << "\toutputValueCaps: " << caps.outputValueCaps << std::endl; 42 | std::cout << "\toutputDataIndices: " << caps.outputDataIndices << std::endl; 43 | std::cout << "\tfeatureButtonCaps: " << caps.featureButtonCaps << std::endl; 44 | std::cout << "\tfeatureValueCaps: " << caps.featureValueCaps << std::endl; 45 | std::cout << "\tfeatureDataIndices: " << caps.featureDataIndices << std::endl; 46 | 47 | device = std::move(inst); 48 | return true; 49 | } 50 | 51 | int main(int argc, char** argv) 52 | { 53 | for (int i = 1; i < argc; ++i) 54 | { 55 | std::string arg(argv[i]); 56 | 57 | if (i + 1 >= argc) 58 | { 59 | break; 60 | } 61 | 62 | if (arg == "--vendor-id") 63 | { 64 | vendorId = static_cast(std::stoi(argv[++i], nullptr, 16)); 65 | } 66 | else if (arg == "--product-id") 67 | { 68 | productId = static_cast(std::stoi(argv[++i], nullptr, 16)); 69 | } 70 | } 71 | 72 | if (!vendorId.has_value() && !productId.has_value()) 73 | { 74 | printf("fam look you gotta give me --vendor-id and/or --product-id (hex without 0x because I'm lazy)\n"); 75 | return -1; 76 | } 77 | 78 | hid::enumerateHid(enumFunc); 79 | 80 | if (device == nullptr) 81 | { 82 | printf("couldn't find shit bye\n"); 83 | return 0; 84 | } 85 | 86 | auto size = device->caps().inputReportSize; 87 | std::vector buffer(size); 88 | 89 | device->open(false); 90 | 91 | while (device->read(buffer)) 92 | { 93 | for (int i = 0; i < size; i++) 94 | { 95 | printf("%02X ", buffer[i]); 96 | } 97 | 98 | printf("\r"); 99 | } 100 | 101 | device = nullptr; 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Trackball.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Vector2.h" 3 | #include "ISimulator.h" 4 | #include "RumbleSequence.h" 5 | 6 | class Ds4TouchRegion; 7 | 8 | struct TrackballVibration : JsonData 9 | { 10 | bool enabled = false; 11 | float factor = 1.0f; 12 | 13 | bool operator==(TrackballVibration& other) const; 14 | bool operator!=(TrackballVibration& other) const; 15 | 16 | void readJson(const nlohmann::json& json) override; 17 | void writeJson(nlohmann::json& json) const override; 18 | }; 19 | 20 | // TODO: /!\ configuration for simulated axis activation range (> lower bound, < upper bound; basically two dead zones) 21 | // TODO: /!\ configuration for flick threshold (milliseconds, microseconds, whatever) 22 | struct TrackballSettings : JsonData 23 | { 24 | TrackballVibration touchVibration; 25 | TrackballVibration ballVibration; 26 | 27 | float touchFriction = 1.0f; 28 | float ballFriction = 0.025f; 29 | 30 | float ballSpeed = 100.0f; 31 | 32 | bool operator==(TrackballSettings& other) const; 33 | bool operator!=(TrackballSettings& other) const; 34 | 35 | void readJson(const nlohmann::json& json) override; 36 | void writeJson(nlohmann::json& json) const override; 37 | }; 38 | 39 | class TrackballSimulator : public ISimulator 40 | { 41 | Ds4TouchRegion* region; 42 | std::shared_ptr rumbleTimer; 43 | 44 | public: 45 | TrackballSettings settings; 46 | 47 | TrackballSimulator(const TrackballSettings& settings, Ds4TouchRegion* region, InputSimulator* parent); 48 | 49 | Vector2 velocity {}; 50 | [[nodiscard]] bool rolling() const; 51 | 52 | /** 53 | * \brief Indicates the state of the emulated trackball. 54 | */ 55 | enum class TrackballState 56 | { 57 | /** \brief Ball is not moving. */ 58 | stopped, 59 | /** \brief Ball is being slowed by a touch. */ 60 | slowing, 61 | /** \brief Ball is naturally decelerating without external influence. */ 62 | decelerating, 63 | /** \brief Ball is accelerating! */ 64 | accelerating 65 | }; 66 | 67 | /** 68 | * \brief Apply a force to the ball in a given direction. 69 | * If \p force is zero, the ball will slow down according to \p touching. 70 | * If \p touching is \c false, standard friction is applied. 71 | * Otherwise, touch friction is applied. 72 | * \param touching Indicates that the ball is being touched. 73 | * \param targetDirection The direction to apply the force. 74 | * \param force The amount of \c settings.ballSpeed to apply. 75 | * \param deltaTime Delta time to be used in acceleration and deceleration. 76 | * \return A \c TrackballState indicating the state of the ball after the call. 77 | */ 78 | TrackballState applyDirectionalForce(bool touching, Vector2 targetDirection, float force, float deltaTime); 79 | 80 | /** 81 | * \brief Update the ball without any external influence. 82 | * \param deltaTime Delta time to be used in acceleration and deceleration. 83 | * \return A \c TrackballState indicating the state of the ball after the call. 84 | */ 85 | void update(float deltaTime) override; 86 | 87 | private: 88 | /** \brief Accelerate the ball! */ 89 | void accelerate(const Vector2& direction, float factor, float deltaTime); 90 | /** \brief Assume ball is being touched and slow the ball to a stop. */ 91 | void slow(float deltaTime); 92 | /** \brief Allow the ball to naturally decelerate with nothing but friction. */ 93 | void decelerate(float deltaTime); 94 | 95 | void simulate(float deltaTime, Ds4Buttons_t touchId); 96 | }; 97 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4DeviceManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include "Ds4Device.h" 10 | #include "Event.h" 11 | 12 | class DeviceOpenedEventArgs 13 | { 14 | public: 15 | /** 16 | * \brief The device that triggered the event. 17 | */ 18 | const std::shared_ptr device; 19 | 20 | /** 21 | * \brief \c true if this is the first connection for this 22 | * device, \c false if an additional connection has 23 | * been added to this device. 24 | */ 25 | const bool unique; 26 | 27 | DeviceOpenedEventArgs(std::shared_ptr device, bool unique) 28 | : device(std::move(device)), 29 | unique(unique) 30 | { 31 | } 32 | }; 33 | 34 | class DeviceClosedEventArgs 35 | { 36 | public: 37 | /** 38 | * \brief The device that triggered the event. 39 | */ 40 | const std::shared_ptr device; 41 | 42 | explicit DeviceClosedEventArgs(std::shared_ptr device) 43 | : device(std::move(device)) 44 | { 45 | } 46 | }; 47 | 48 | class Ds4DeviceManager 49 | { 50 | std::recursive_mutex sync_lock, devices_lock; 51 | std::unordered_map> tokens; 52 | 53 | public: 54 | std::map> devices; 55 | 56 | /** 57 | * \brief Defines the DualShock 4 Vendor ID. 58 | */ 59 | static const short vendorId = 0x54c; 60 | 61 | /** 62 | * \brief Defines validated DualShock 4 Product IDs. 63 | */ 64 | inline static const std::array productIds = { 0xba0, 0x5c4, 0x9cc }; 65 | 66 | /** 67 | * \brief Fired when a device is opened. 68 | * \sa DeviceOpenedEventArgs 69 | */ 70 | Event> deviceOpened; 71 | 72 | /** 73 | * \brief Fired when a device is closed. 74 | * \sa DeviceClosedEventArgs 75 | */ 76 | Event> deviceClosed; 77 | 78 | ~Ds4DeviceManager(); 79 | 80 | /** 81 | * \brief Checks if a device is a DualShock 4. 82 | * \param hid The HID instance to be checked. 83 | * \return \c true if this device is a DualShock 4. 84 | */ 85 | static bool isDs4(const hid::HidInstance& hid); 86 | 87 | /** 88 | * \brief Checks if a device is a DualShock 4. 89 | * \param devicePath The path to the device to be checked. 90 | * \return \c true if this device is a DualShock 4. 91 | */ 92 | static bool isDs4(const std::wstring& devicePath); 93 | 94 | /** 95 | * \brief Discovers and connects to all DualShock 4 devices. 96 | */ 97 | void findControllers(); 98 | 99 | /** 100 | * \brief Finds and connects the first controller matching \c devicePath 101 | */ 102 | void findController(const std::wstring& devicePath); 103 | 104 | /** 105 | * \brief Number of devices currently being managed. 106 | */ 107 | size_t deviceCount(); 108 | 109 | std::unique_lock lockDevices(); 110 | void registerDeviceCallbacks(const std::wstring& serialString, const std::shared_ptr& device); 111 | 112 | private: 113 | bool handleDevice(const std::shared_ptr& hid); 114 | void onDs4DeviceClose(Ds4Device* sender); 115 | 116 | public: 117 | /** 118 | * \brief Closes and removes all managed devices. 119 | */ 120 | void close(); 121 | 122 | /** 123 | * \brief Automatically prompts for elevation and toggles a device. 124 | * \param instanceId The instance ID of the device to toggle. 125 | */ 126 | static void toggleDevice(const std::wstring& instanceId); 127 | }; 128 | -------------------------------------------------------------------------------- /libhid/hid_instance.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "hid_handle.h" 11 | 12 | namespace hid 13 | { 14 | struct HidOpenFlags 15 | { 16 | enum T : uint32_t 17 | { 18 | none, 19 | exclusive = 1u << 0u, 20 | async = 1u << 1u 21 | }; 22 | }; 23 | 24 | using HidOpenFlags_t = uint32_t; 25 | 26 | struct HidCaps 27 | { 28 | uint16_t usage; 29 | uint16_t usagePage; 30 | uint16_t inputReportSize; 31 | uint16_t outputReportSize; 32 | uint16_t featureReportSize; 33 | uint16_t linkCollectionNodes; 34 | uint16_t inputButtonCaps; 35 | uint16_t inputValueCaps; 36 | uint16_t inputDataIndices; 37 | uint16_t outputButtonCaps; 38 | uint16_t outputValueCaps; 39 | uint16_t outputDataIndices; 40 | uint16_t featureButtonCaps; 41 | uint16_t featureValueCaps; 42 | uint16_t featureDataIndices; 43 | }; 44 | 45 | struct HidAttributes 46 | { 47 | uint16_t vendorId; 48 | uint16_t productId; 49 | uint16_t versionNumber; 50 | }; 51 | 52 | class HidInstance 53 | { 54 | HidOpenFlags_t flags_ = HidOpenFlags::none; 55 | 56 | Handle handle_ = Handle(nullptr, true); 57 | 58 | HidCaps caps_ {}; 59 | HidAttributes attributes_ {}; 60 | 61 | OVERLAPPED overlappedIn_ = {}; 62 | OVERLAPPED overlappedOut_ = {}; 63 | 64 | bool pendingRead_ = false; 65 | bool pendingWrite_ = false; 66 | 67 | size_t nativeError_ = ERROR_SUCCESS; 68 | 69 | public: 70 | std::wstring path; 71 | std::wstring instanceId; 72 | std::wstring serialString; 73 | 74 | std::vector inputBuffer; 75 | std::vector outputBuffer; 76 | 77 | HidInstance(const HidInstance&) = delete; 78 | HidInstance& operator=(const HidInstance&) = delete; 79 | 80 | HidInstance() = default; 81 | 82 | HidInstance(std::wstring path, std::wstring instanceId); 83 | explicit HidInstance(std::wstring path); 84 | HidInstance(HidInstance&& other) noexcept; 85 | 86 | ~HidInstance(); 87 | 88 | HidInstance& operator=(HidInstance&& other) noexcept; 89 | 90 | [[nodiscard]] bool isOpen() const; 91 | [[nodiscard]] bool isExclusive() const; 92 | [[nodiscard]] bool isAsync() const; 93 | [[nodiscard]] const HidCaps& caps() const; 94 | [[nodiscard]] const HidAttributes& attributes() const; 95 | 96 | bool readMetadata(); 97 | bool readCaps(); 98 | bool readSerial(); 99 | bool readAttributes(); 100 | bool getFeature(std::span buffer); 101 | bool setFeature(std::span buffer); 102 | 103 | bool open(HidOpenFlags_t openFlags); 104 | void close(); 105 | 106 | [[nodiscard]] inline auto nativeError() const 107 | { 108 | return nativeError_; 109 | } 110 | 111 | bool read(void* buffer, size_t size); 112 | bool read(std::span buffer); 113 | bool read(); 114 | 115 | bool readAsync(); 116 | 117 | bool write(const void* buffer, size_t size); 118 | bool write(std::span buffer); 119 | bool write(); 120 | 121 | bool writeAsync(); 122 | 123 | bool asyncReadPending() const; 124 | bool asyncReadInProgress(); 125 | bool asyncWritePending() const; 126 | bool asyncWriteInProgress(); 127 | 128 | void cancelAsyncReadAndWait(); 129 | void cancelAsyncWriteAndWait(); 130 | 131 | bool setOutputReport(std::span buffer); 132 | bool setOutputReport(); 133 | 134 | private: 135 | void cancelAsyncAndWait(OVERLAPPED* overlapped); 136 | bool asyncInProgress(OVERLAPPED* overlapped); 137 | 138 | bool readCaps(HANDLE h); 139 | bool readSerial(HANDLE h); 140 | bool readAttributes(HANDLE h); 141 | }; 142 | } 143 | -------------------------------------------------------------------------------- /ds4wizard-cpp/gmath.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace gmath 8 | { 9 | using namespace std; 10 | 11 | constexpr double pi = M_PI; 12 | constexpr double two_pi = 2.0 * pi; 13 | constexpr double pi_over_two = M_PI_2; 14 | constexpr double pi_over_four = M_PI_4; 15 | constexpr double one_over_pi = M_1_PI; 16 | constexpr double two_over_pi = M_2_PI; 17 | 18 | constexpr float pi_f = static_cast(pi); 19 | constexpr float two_pi_f = static_cast(two_pi); 20 | constexpr float pi_over_two_f = static_cast(pi_over_two); 21 | constexpr float pi_over_four_f = static_cast(pi_over_four); 22 | constexpr float one_over_pi_f = static_cast(one_over_pi); 23 | constexpr float two_over_pi_f = static_cast(two_over_pi); 24 | 25 | template 26 | constexpr T epsilon() 27 | { 28 | return numeric_limits::epsilon(); 29 | } 30 | 31 | template 32 | constexpr T wrap_e(T value, T low, T high) 33 | { 34 | if (value > high) 35 | { 36 | return low + (value - high); 37 | } 38 | 39 | if (value < low) 40 | { 41 | return high - (low - value); 42 | } 43 | 44 | return value; 45 | } 46 | 47 | template 48 | constexpr T wrap_i(T value, T low, T high) 49 | { 50 | if (value >= high) 51 | { 52 | return low + (value - high); 53 | } 54 | 55 | if (value <= low) 56 | { 57 | return high - (low - value); 58 | } 59 | 60 | return value; 61 | } 62 | 63 | inline bool is_one(double value) 64 | { 65 | return abs(value - 1.0) < gmath::epsilon(); 66 | } 67 | 68 | inline bool is_one(float value) 69 | { 70 | return abs(value - 1.0f) < gmath::epsilon(); 71 | } 72 | 73 | inline bool is_zero(double value) 74 | { 75 | return abs(value) < gmath::epsilon(); 76 | } 77 | 78 | inline bool is_zero(float value) 79 | { 80 | return abs(value) < gmath::epsilon(); 81 | } 82 | 83 | inline bool near_equal(double a, double b) 84 | { 85 | return abs(a - b) < gmath::epsilon(); 86 | } 87 | 88 | inline bool near_equal(float a, float b) 89 | { 90 | return abs(a - b) < gmath::epsilon(); 91 | } 92 | 93 | template 94 | T sign(T val) 95 | { 96 | return static_cast(static_cast((T(0) < val) - (val < T(0)))); 97 | } 98 | 99 | template 100 | T lerp(const T& a, const T& b, float factor) 101 | { 102 | return (1.0f - factor) * a + factor * b; 103 | } 104 | 105 | template 106 | T lerp(const T& a, const T& b, double factor) 107 | { 108 | return (1.0 - factor) * a + factor * b; 109 | } 110 | 111 | /** 112 | * \brief Performs smooth (cubic Hermite) interpolation between 0 and 1. 113 | * \remarks See https://en.wikipedia.org/wiki/Smoothstep 114 | * \param amount Value between 0 and 1 indicating interpolation amount. 115 | */ 116 | inline float smoothstep(float amount) 117 | { 118 | if (amount <= 0) 119 | { 120 | return 0; 121 | } 122 | 123 | return amount >= 1 ? 1 : amount * amount * (3 - 2 * amount); 124 | } 125 | 126 | /** 127 | * \brief Performs a smooth(er) interpolation between 0 and 1 with 1st and 2nd order derivatives of zero at endpoints. 128 | * \remarks See https://en.wikipedia.org/wiki/Smoothstep 129 | * \param amount Value between 0 and 1 indicating interpolation amount. 130 | */ 131 | inline float smootherstep(float amount) 132 | { 133 | if (amount <= 0) 134 | { 135 | return 0; 136 | } 137 | 138 | return amount >= 1 ? 1 : amount * amount * amount * (amount * (amount * 6 - 15) + 10); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceProfileItemModel.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "DeviceProfileCache.h" 3 | #include "DeviceProfileItemModel.h" 4 | 5 | DeviceProfileItemModel::DeviceProfileItemModel(QObject* parent, DeviceProfileCache& profileCache, bool includeDefault_) 6 | : QAbstractListModel(parent), profileCache(profileCache), includeDefault(includeDefault_) 7 | { 8 | this->onProfileAdded_ = profileCache.profileAdded.add( 9 | [this](DeviceProfileCache* sender, const DeviceProfile& profile, int index) 10 | { 11 | onProfileAdded(sender, profile, index); 12 | }); 13 | 14 | this->onProfileChanged_ = profileCache.profileChanged.add( 15 | [this](DeviceProfileCache* sender, const DeviceProfile& oldProfile, const DeviceProfile& newProfile, int oldIndex, int newIndex) 16 | { 17 | onProfileChanged(sender, oldProfile, newProfile, oldIndex, newIndex); 18 | }); 19 | 20 | this->onProfileRemoved_ = profileCache.profileRemoved.add( 21 | [this](DeviceProfileCache* sender, const DeviceProfile& profile, int index) 22 | { 23 | onProfileRemoved(sender, profile, index); 24 | }); 25 | } 26 | 27 | int DeviceProfileItemModel::rowCount(const QModelIndex& parent) const 28 | { 29 | return static_cast(profileCache.profiles.size()) + static_cast(includeDefault); 30 | } 31 | 32 | int DeviceProfileItemModel::columnCount(const QModelIndex& parent) const 33 | { 34 | return 1; 35 | } 36 | 37 | QVariant DeviceProfileItemModel::data(const QModelIndex& index, int role) const 38 | { 39 | if (!index.isValid() || index.row() < 0 || role != Qt::DisplayRole) 40 | { 41 | return QVariant(); 42 | } 43 | 44 | if (includeDefault) 45 | { 46 | if (static_cast(index.row()) > profileCache.profiles.size()) 47 | { 48 | return QVariant(); 49 | } 50 | } 51 | else if (static_cast(index.row()) >= profileCache.profiles.size()) 52 | { 53 | return QVariant(); 54 | } 55 | 56 | if (includeDefault && !index.row()) 57 | { 58 | return tr("[Default]"); 59 | } 60 | 61 | const auto row = index.row() - static_cast(includeDefault); 62 | 63 | return QString::fromStdString(profileCache.profiles[row].name); 64 | } 65 | 66 | QVariant DeviceProfileItemModel::headerData(int section, Qt::Orientation orientation, int role) const 67 | { 68 | if (section > 0) 69 | { 70 | return QVariant(); 71 | } 72 | 73 | if (orientation != Qt::Horizontal) 74 | { 75 | return QVariant(); 76 | } 77 | 78 | switch (static_cast(role)) 79 | { 80 | case Qt::DisplayRole: 81 | return tr("Profile"); 82 | 83 | default: 84 | return QVariant(); 85 | } 86 | } 87 | 88 | DeviceProfile DeviceProfileItemModel::getProfile(int index) const 89 | { 90 | std::lock_guard guard(this->profileCache.profiles_lock); 91 | return this->profileCache.profiles[index]; 92 | } 93 | 94 | void DeviceProfileItemModel::onProfileAdded(DeviceProfileCache* sender, const DeviceProfile& profile, int index) 95 | { 96 | index += static_cast(includeDefault); 97 | beginInsertRows({}, index, index); 98 | endInsertRows(); 99 | } 100 | 101 | void DeviceProfileItemModel::onProfileChanged(DeviceProfileCache* sender, const DeviceProfile& oldProfile, const DeviceProfile& newProfile, int oldIndex, int newIndex) 102 | { 103 | oldIndex += static_cast(includeDefault); 104 | newIndex += static_cast(includeDefault); 105 | 106 | if (oldIndex >= 0) 107 | { 108 | beginRemoveRows({}, oldIndex, oldIndex); 109 | endRemoveRows(); 110 | } 111 | 112 | beginInsertRows({}, newIndex, newIndex); 113 | endInsertRows(); 114 | } 115 | 116 | void DeviceProfileItemModel::onProfileRemoved(DeviceProfileCache* sender, const DeviceProfile& profile, int index) 117 | { 118 | index += static_cast(includeDefault); 119 | beginRemoveRows({}, index, index); 120 | endRemoveRows(); 121 | } 122 | -------------------------------------------------------------------------------- /ds4wizard-cpp/DeviceProfileCache.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "DeviceSettings.h" 10 | #include "DeviceProfile.h" 11 | #include "Ds4DeviceManager.h" 12 | 13 | /** 14 | * \brief A class which manages device profiles, changes to those profiles, and notifying relevant devices of those changes. 15 | */ 16 | class DeviceProfileCache 17 | { 18 | std::shared_ptr deviceManager; 19 | std::unordered_map deviceSettings; 20 | 21 | public: 22 | // TODO: all of these should be private 23 | std::recursive_mutex deviceManager_lock; 24 | std::recursive_mutex profiles_lock; 25 | std::recursive_mutex deviceSettings_lock; 26 | std::recursive_mutex devices_lock; 27 | std::deque profiles; 28 | 29 | /** 30 | * \brief Event raised when a new profile is added. 31 | * Parameters are: profile, index 32 | */ 33 | Event profileAdded; 34 | 35 | // TODO: modify in-place 36 | /** 37 | * \brief Event raised when a profile is modified. 38 | * Parameters are: old, new, old index, new index 39 | */ 40 | Event profileChanged; 41 | 42 | /** 43 | * \brief Event raised when a profile is permanently removed. 44 | * Parameters are: profile, index 45 | */ 46 | Event profileRemoved; 47 | 48 | DeviceProfileCache() = default; 49 | 50 | /** 51 | * \brief Sets the device manager instance to be used when notifying devices of profile changes. 52 | * \param deviceManager The device manager instance. 53 | */ 54 | void setDevices(const std::shared_ptr& deviceManager); 55 | 56 | /** 57 | * \brief Load profiles from disk. 58 | */ 59 | void load(); 60 | 61 | /** 62 | * \brief Get a profile copy by name. 63 | * \param profileName The name of the profile to get. 64 | * \return A copy of the profile if found, else \c std::nullopt 65 | */ 66 | std::optional getProfile(const std::string& profileName); 67 | 68 | /** 69 | * \brief Returns a copy of the cached settings for the specified MAC address. 70 | * \param id The MAC address of the device whose settings are to be copied. 71 | * \return The settings associated with the MAC address, or \c std::nullopt if none. 72 | */ 73 | std::optional getSettings(const std::string& id); 74 | 75 | /** 76 | * \brief Adds (or replaces) settings for the specified MAC address, then saves changes to disk. 77 | * \param id The MAC address of the device whose settings are being stored. 78 | * \param settings The settings to be stored. 79 | */ 80 | void saveSettings(const std::string& id, const DeviceSettings& settings); 81 | 82 | /** 83 | * \brief Adds a profile. 84 | * \param current The new profile. 85 | * \return \c true on success, \c false if, for example, a profile with that name exists. 86 | */ 87 | bool addProfile(const DeviceProfile& current); 88 | 89 | /** 90 | * \brief Removes a profile from the profile cache. 91 | * \param profile The profile to be removed. 92 | */ 93 | void removeProfile(const DeviceProfile& profile); 94 | 95 | /** 96 | * \brief Updates a profile and notifies all devices of the change. 97 | * \param last The profile to be replaced. 98 | * \param current The new profile. 99 | */ 100 | void updateProfile(const DeviceProfile& last, const DeviceProfile& current); 101 | 102 | private: 103 | std::optional findProfile(const std::string& profileName); 104 | void loadImpl(); 105 | void onProfileChanged(const std::string& oldName, const std::string& newName); 106 | }; 107 | -------------------------------------------------------------------------------- /libhid/hid_util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // for GUID_DEVINTERFACE_USB_HUB 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "hid_instance.h" 9 | #include "hid_util.h" 10 | 11 | // TODO: handle *A and *W variants of these methods and structures! 12 | 13 | template 14 | void set_cbsize(T* pod) 15 | { 16 | if (pod) 17 | { 18 | pod->cbSize = sizeof(T); 19 | } 20 | } 21 | 22 | template 23 | void set_cbsize(T& pod) 24 | { 25 | pod.cbSize = sizeof(T); 26 | } 27 | 28 | template 29 | T cbsize_t() 30 | { 31 | T result {}; 32 | set_cbsize(result); 33 | return result; 34 | } 35 | 36 | std::wstring hid::getDevicePath(const HDEVINFO devInfoSet, SP_DEVICE_INTERFACE_DATA* interface, SP_DEVINFO_DATA* data) noexcept 37 | { 38 | DWORD size = 0; 39 | 40 | SetupDiGetDeviceInterfaceDetail(devInfoSet, interface, nullptr, 0, &size, data); 41 | 42 | auto* detail = static_cast(malloc(offsetof(SP_DEVICE_INTERFACE_DETAIL_DATA, DevicePath) + size + sizeof(TCHAR))); 43 | 44 | if (detail == nullptr) 45 | { 46 | return std::wstring(); 47 | } 48 | 49 | set_cbsize(detail); 50 | 51 | const bool success = SetupDiGetDeviceInterfaceDetail(devInfoSet, interface, detail, size, &size, data); 52 | 53 | std::wstring result; 54 | 55 | if (success) 56 | { 57 | result = std::wstring(detail->DevicePath); 58 | } 59 | 60 | free(detail); 61 | return result; 62 | } 63 | 64 | std::wstring hid::getInstanceId(const HDEVINFO devInfoSet, SP_DEVINFO_DATA* devInfoData) noexcept 65 | { 66 | std::wstring result; 67 | DWORD required = 0; 68 | SetupDiGetDeviceInstanceId(devInfoSet, devInfoData, nullptr, 0, &required); 69 | 70 | if (!required) 71 | { 72 | return result; 73 | } 74 | 75 | auto* buffer = new wchar_t[required]; 76 | 77 | if (SetupDiGetDeviceInstanceId(devInfoSet, devInfoData, buffer, required, &required)) 78 | { 79 | result = std::wstring(buffer); 80 | } 81 | 82 | delete[] buffer; 83 | return result; 84 | } 85 | 86 | bool hid::enumerateGuid(const std::function& fn, const GUID& guid) noexcept 87 | { 88 | const HDEVINFO dev_info = SetupDiGetClassDevs(&guid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); 89 | 90 | if (dev_info == INVALID_HANDLE_VALUE) 91 | { 92 | return true; 93 | } 94 | 95 | bool done = false; 96 | auto dev_info_data = cbsize_t(); 97 | 98 | for (size_t i = 0; SetupDiEnumDeviceInfo(dev_info, static_cast(i), &dev_info_data); i++) 99 | { 100 | auto interface_data = cbsize_t(); 101 | 102 | for (size_t j = 0; SetupDiEnumDeviceInterfaces(dev_info, &dev_info_data, &guid, static_cast(j), &interface_data); j++) 103 | { 104 | const std::wstring path(getDevicePath(dev_info, &interface_data)); 105 | const std::wstring instance_id(getInstanceId(dev_info, &dev_info_data)); 106 | 107 | if (fn(path, instance_id)) 108 | { 109 | done = true; 110 | break; 111 | } 112 | } 113 | 114 | if (done) 115 | { 116 | break; 117 | } 118 | } 119 | 120 | SetupDiDestroyDeviceInfoList(dev_info); 121 | return false; 122 | } 123 | 124 | void hid::enumerateHid(const std::function instance)>& fn) noexcept 125 | { 126 | GUID guid = {}; 127 | HidD_GetHidGuid(&guid); 128 | 129 | const auto callback = [fn](const std::wstring& path, const std::wstring& instanceId) -> bool 130 | { 131 | auto hid = std::make_shared(path, instanceId); 132 | 133 | if (hid->readMetadata()) 134 | { 135 | return fn(hid); 136 | } 137 | 138 | return false; 139 | }; 140 | 141 | enumerateGuid(callback, guid); 142 | } 143 | 144 | void hid::enumerateUsb(const std::function& fn) noexcept 145 | { 146 | enumerateGuid(fn, GUID_DEVINTERFACE_USB_HUB); 147 | } 148 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4ItemModel.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Ds4ItemModel.h" 3 | #include 4 | 5 | Ds4ItemModel::Ds4ItemModel(QObject* parent, const std::shared_ptr& deviceManager) 6 | : QAbstractListModel(parent) 7 | { 8 | qRegisterMetaType>("std::shared_ptr"); 9 | qRegisterMetaType>("std::shared_ptr"); 10 | 11 | connect(this, &Ds4ItemModel::s_onDeviceOpened, this, &Ds4ItemModel::onDeviceOpened); 12 | connect(this, &Ds4ItemModel::s_onDeviceClosed, this, &Ds4ItemModel::onDeviceClosed); 13 | connect(this, &Ds4ItemModel::s_onDeviceBatteryChanged, this, &Ds4ItemModel::onDeviceBatteryChanged); 14 | 15 | this->deviceOpened_ = 16 | deviceManager->deviceOpened.add( 17 | [this](auto, auto args) 18 | { 19 | emit s_onDeviceOpened(args); 20 | }); 21 | 22 | this->deviceClosed_ = 23 | deviceManager->deviceClosed.add( 24 | [this](auto, auto args) 25 | { 26 | emit s_onDeviceClosed(args); 27 | }); 28 | } 29 | 30 | int Ds4ItemModel::rowCount(const QModelIndex& /*parent*/) const 31 | { 32 | return static_cast(devices.size()); 33 | } 34 | 35 | int Ds4ItemModel::columnCount(const QModelIndex& /*parent*/) const 36 | { 37 | return 2; // device name, battery 38 | } 39 | 40 | QVariant Ds4ItemModel::data(const QModelIndex& index, int role) const 41 | { 42 | if (!index.isValid() || index.row() < 0 || static_cast(index.row()) >= devices.size()) 43 | { 44 | return QVariant(); 45 | } 46 | 47 | if (role != Qt::DisplayRole) 48 | { 49 | return QVariant(); 50 | } 51 | 52 | const std::shared_ptr device = getDevice(index.row()); 53 | 54 | switch (index.column()) 55 | { 56 | case 0: 57 | return QString::fromStdString(device->name()); 58 | 59 | case 1: 60 | if (device->charging()) 61 | { 62 | return QString("+%1%").arg(device->battery() * 10); 63 | } 64 | else 65 | { 66 | return QString("%1%").arg(device->battery() * 10); 67 | } 68 | 69 | default: 70 | return QVariant(); 71 | } 72 | } 73 | 74 | QVariant Ds4ItemModel::headerData(int section, Qt::Orientation orientation, int role) const 75 | { 76 | if (section > 1) 77 | { 78 | return QVariant(); 79 | } 80 | 81 | if (orientation != Qt::Horizontal) 82 | { 83 | return QVariant(); 84 | } 85 | 86 | switch (static_cast(role)) 87 | { 88 | case Qt::DisplayRole: 89 | return tr(!section ? "Device" : "Battery"); 90 | 91 | default: 92 | return QVariant(); 93 | } 94 | } 95 | 96 | void Ds4ItemModel::onDeviceOpened(std::shared_ptr a) 97 | { 98 | devices.insert(a->device); 99 | const auto row = getRow(a->device); 100 | 101 | if (a->unique) 102 | { 103 | auto device = a->device; 104 | 105 | tokens[device] = device->onBatteryLevelChanged.add( 106 | [this](auto sender) -> void 107 | { 108 | emit s_onDeviceBatteryChanged(sender); 109 | }); 110 | 111 | beginInsertRows({}, row, row); 112 | endInsertRows(); 113 | } 114 | else 115 | { 116 | const auto index = createIndex(row, 0); 117 | const auto index2 = createIndex(row, columnCount({}) - 1); 118 | emit dataChanged(index, index2); 119 | } 120 | } 121 | 122 | void Ds4ItemModel::onDeviceClosed(std::shared_ptr a) 123 | { 124 | const auto row = getRow(a->device); 125 | 126 | beginRemoveRows({}, row, row); 127 | devices.erase(a->device); 128 | tokens.erase(a->device); 129 | endRemoveRows(); 130 | } 131 | 132 | void Ds4ItemModel::onDeviceBatteryChanged(const Ds4Device* sender) 133 | { 134 | const auto index = createIndex(getRow(sender), 1); 135 | emit dataChanged(index, index); 136 | } 137 | 138 | std::shared_ptr Ds4ItemModel::getDevice(int row) const 139 | { 140 | auto it = devices.cbegin(); 141 | 142 | for (int i = 0; i < row && it != devices.cend(); ++i) 143 | { 144 | ++it; 145 | } 146 | 147 | if (it == devices.cend()) 148 | { 149 | return nullptr; 150 | } 151 | 152 | return *it; 153 | } 154 | 155 | int Ds4ItemModel::getRow(const std::shared_ptr& device) const 156 | { 157 | return getRow(device.get()); 158 | } 159 | 160 | int Ds4ItemModel::getRow(const Ds4Device* device) const 161 | { 162 | auto it = devices.cbegin(); 163 | 164 | int row = -1; 165 | 166 | while (it != devices.cend()) 167 | { 168 | ++row; 169 | 170 | if ((*it).get() == device) 171 | { 172 | break; 173 | } 174 | 175 | ++it; 176 | } 177 | 178 | return row; 179 | } 180 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4InputData.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Ds4InputData.h" 3 | #include 4 | 5 | bool Ds4Stick::operator==(const Ds4Stick& other) const 6 | { 7 | return x == other.x && y == other.y; 8 | } 9 | 10 | bool Ds4Stick::operator!=(const Ds4Stick& right) const 11 | { 12 | return !(*this == right); 13 | } 14 | 15 | int Ds4Stick::lengthSquared() const 16 | { 17 | const auto xi = static_cast(x); 18 | const auto yi = static_cast(y); 19 | return xi * xi + yi * yi; 20 | } 21 | 22 | float Ds4Stick::length() const 23 | { 24 | return std::sqrt(static_cast(lengthSquared())); 25 | } 26 | 27 | bool Ds4Vector2::operator==(const Ds4Vector2& other) const 28 | { 29 | return x == other.x && y == other.y; 30 | } 31 | 32 | bool Ds4Vector2::operator!=(const Ds4Vector2& other) const 33 | { 34 | return !(*this == other); 35 | } 36 | 37 | int Ds4Vector2::lengthSquared() const 38 | { 39 | const auto xi = static_cast(x); 40 | const auto yi = static_cast(y); 41 | return xi * xi + yi * yi; 42 | } 43 | 44 | float Ds4Vector2::length() const 45 | { 46 | return std::sqrt(static_cast(lengthSquared())); 47 | } 48 | 49 | bool Ds4Vector3::operator==(const Ds4Vector3& other) const 50 | { 51 | return x == other.x && y == other.y && z == other.z; 52 | } 53 | 54 | bool Ds4Vector3::operator!=(const Ds4Vector3& other) const 55 | { 56 | return !(*this == other); 57 | } 58 | 59 | int Ds4Vector3::lengthSquared() const 60 | { 61 | const auto xi = static_cast(x); 62 | const auto yi = static_cast(y); 63 | const auto zi = static_cast(z); 64 | 65 | return xi * xi + yi * yi + zi * zi; 66 | } 67 | 68 | float Ds4Vector3::length() const 69 | { 70 | return static_cast(std::sqrt(static_cast(lengthSquared()))); 71 | } 72 | 73 | Hat Ds4InputData::dPad() const 74 | { 75 | return static_cast(activeButtons & Ds4ButtonsRaw::hat); 76 | } 77 | 78 | bool Ds4InputData::square() const 79 | { 80 | return !!(activeButtons & Ds4ButtonsRaw::square); 81 | } 82 | 83 | bool Ds4InputData::cross() const 84 | { 85 | return !!(activeButtons & Ds4ButtonsRaw::cross); 86 | } 87 | 88 | bool Ds4InputData::circle() const 89 | { 90 | return !!(activeButtons & Ds4ButtonsRaw::circle); 91 | } 92 | 93 | bool Ds4InputData::triangle() const 94 | { 95 | return !!(activeButtons & Ds4ButtonsRaw::triangle); 96 | } 97 | 98 | bool Ds4InputData::l1() const 99 | { 100 | return !!(activeButtons & Ds4ButtonsRaw::l1); 101 | } 102 | 103 | bool Ds4InputData::r1() const 104 | { 105 | return !!(activeButtons & Ds4ButtonsRaw::r1); 106 | } 107 | 108 | bool Ds4InputData::l2() const 109 | { 110 | return !!(activeButtons & Ds4ButtonsRaw::l2); 111 | } 112 | 113 | bool Ds4InputData::r2() const 114 | { 115 | return !!(activeButtons & Ds4ButtonsRaw::r2); 116 | } 117 | 118 | bool Ds4InputData::share() const 119 | { 120 | return !!(activeButtons & Ds4ButtonsRaw::share); 121 | } 122 | 123 | bool Ds4InputData::options() const 124 | { 125 | return !!(activeButtons & Ds4ButtonsRaw::options); 126 | } 127 | 128 | bool Ds4InputData::l3() const 129 | { 130 | return !!(activeButtons & Ds4ButtonsRaw::l3); 131 | } 132 | 133 | bool Ds4InputData::r3() const 134 | { 135 | return !!(activeButtons & Ds4ButtonsRaw::r3); 136 | } 137 | 138 | bool Ds4InputData::ps() const 139 | { 140 | return !!(activeButtons & Ds4ButtonsRaw::ps); 141 | } 142 | 143 | bool Ds4InputData::touchButton() const 144 | { 145 | return !!(activeButtons & Ds4ButtonsRaw::touchButton); 146 | } 147 | 148 | bool Ds4InputData::operator==(const Ds4InputData& other) const 149 | { 150 | return frameCount == other.frameCount && 151 | activeButtons == other.activeButtons && 152 | leftStick == other.leftStick && 153 | rightStick == other.rightStick && 154 | leftTrigger == other.leftTrigger && 155 | rightTrigger == other.rightTrigger && 156 | battery == other.battery && 157 | accel == other.accel && 158 | gyro == other.gyro && 159 | extensions == other.extensions && 160 | touchEvent == other.touchEvent && 161 | touchFrame == other.touchFrame && 162 | touch1 == other.touch1 && 163 | touch1Id == other.touch1Id && 164 | touchPoint1 == other.touchPoint1 && 165 | touch2 == other.touch2 && 166 | touch2Id == other.touch2Id && 167 | touchPoint2 == other.touchPoint2 && 168 | lastTouchPoint1 == other.lastTouchPoint1 && 169 | lastTouchPoint2 == other.lastTouchPoint2; 170 | } 171 | 172 | bool Ds4InputData::operator!=(const Ds4InputData& other) const 173 | { 174 | return !(*this == other); 175 | } 176 | -------------------------------------------------------------------------------- /libdevicetoggle/libdevicetoggle.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {3292BD3C-C649-46B0-9C60-545B31F2B169} 16 | libdevicetoggle 17 | 10.0 18 | 19 | 20 | 21 | StaticLibrary 22 | true 23 | v143 24 | MultiByte 25 | 26 | 27 | StaticLibrary 28 | false 29 | v143 30 | true 31 | MultiByte 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | $(SolutionDir)bin\$(Configuration)\ 47 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 48 | 49 | 50 | $(SolutionDir)bin\$(Configuration)\ 51 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 52 | 53 | 54 | 55 | Level3 56 | MaxSpeed 57 | true 58 | true 59 | true 60 | true 61 | UNICODE;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) 62 | true 63 | stdcpp20 64 | 65 | 66 | true 67 | true 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Level3 77 | Disabled 78 | true 79 | true 80 | UNICODE;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) 81 | true 82 | stdcpp20 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /ds4wizard-cpp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32210.238 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ds4wizard-cpp", "ds4wizard-cpp\ds4wizard-cpp.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {7FFC69CC-E15A-4470-9EC4-7477FD662C89} = {7FFC69CC-E15A-4470-9EC4-7477FD662C89} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libhid", "libhid\libhid.vcxproj", "{03F69A1B-6D18-43F5-B71F-B3A3018F12F0}" 12 | EndProject 13 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ds4wizard-device-toggle", "ds4wizard-device-toggle\ds4wizard-device-toggle.vcxproj", "{7FFC69CC-E15A-4470-9EC4-7477FD662C89}" 14 | EndProject 15 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libdevicetoggle", "libdevicetoggle\libdevicetoggle.vcxproj", "{3292BD3C-C649-46B0-9C60-545B31F2B169}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencies", "dependencies", "{EFC8667D-50C4-4A83-A819-B56998FFF2AA}" 18 | EndProject 19 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SingleApplication", "SingleApplication\SingleApplication.vcxproj", "{7ECA4674-EE2E-434E-BA80-BD026783A13D}" 20 | EndProject 21 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "hid-tester", "hid-tester\hid-tester.vcxproj", "{A1449C88-190B-4F6B-9B13-BA8EDB7E2807}" 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "internal libraries", "internal libraries", "{66D229C9-532C-4984-A907-945CED26F85A}" 24 | EndProject 25 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ViGEmClient", "dependencies\ViGEmClient-build\ViGEmClient.vcxproj", "{CA4A140F-336B-3D68-8097-383DA83B9D01}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|x64 = Debug|x64 30 | Release|x64 = Release|x64 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.ActiveCfg = Debug|x64 34 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x64.Build.0 = Debug|x64 35 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.ActiveCfg = Release|x64 36 | {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x64.Build.0 = Release|x64 37 | {03F69A1B-6D18-43F5-B71F-B3A3018F12F0}.Debug|x64.ActiveCfg = Debug|x64 38 | {03F69A1B-6D18-43F5-B71F-B3A3018F12F0}.Debug|x64.Build.0 = Debug|x64 39 | {03F69A1B-6D18-43F5-B71F-B3A3018F12F0}.Release|x64.ActiveCfg = Release|x64 40 | {03F69A1B-6D18-43F5-B71F-B3A3018F12F0}.Release|x64.Build.0 = Release|x64 41 | {7FFC69CC-E15A-4470-9EC4-7477FD662C89}.Debug|x64.ActiveCfg = Debug|x64 42 | {7FFC69CC-E15A-4470-9EC4-7477FD662C89}.Debug|x64.Build.0 = Debug|x64 43 | {7FFC69CC-E15A-4470-9EC4-7477FD662C89}.Release|x64.ActiveCfg = Release|x64 44 | {7FFC69CC-E15A-4470-9EC4-7477FD662C89}.Release|x64.Build.0 = Release|x64 45 | {3292BD3C-C649-46B0-9C60-545B31F2B169}.Debug|x64.ActiveCfg = Debug|x64 46 | {3292BD3C-C649-46B0-9C60-545B31F2B169}.Debug|x64.Build.0 = Debug|x64 47 | {3292BD3C-C649-46B0-9C60-545B31F2B169}.Release|x64.ActiveCfg = Release|x64 48 | {3292BD3C-C649-46B0-9C60-545B31F2B169}.Release|x64.Build.0 = Release|x64 49 | {7ECA4674-EE2E-434E-BA80-BD026783A13D}.Debug|x64.ActiveCfg = Debug|x64 50 | {7ECA4674-EE2E-434E-BA80-BD026783A13D}.Debug|x64.Build.0 = Debug|x64 51 | {7ECA4674-EE2E-434E-BA80-BD026783A13D}.Release|x64.ActiveCfg = Release|x64 52 | {7ECA4674-EE2E-434E-BA80-BD026783A13D}.Release|x64.Build.0 = Release|x64 53 | {A1449C88-190B-4F6B-9B13-BA8EDB7E2807}.Debug|x64.ActiveCfg = Debug|x64 54 | {A1449C88-190B-4F6B-9B13-BA8EDB7E2807}.Debug|x64.Build.0 = Debug|x64 55 | {A1449C88-190B-4F6B-9B13-BA8EDB7E2807}.Release|x64.ActiveCfg = Release|x64 56 | {A1449C88-190B-4F6B-9B13-BA8EDB7E2807}.Release|x64.Build.0 = Release|x64 57 | {CA4A140F-336B-3D68-8097-383DA83B9D01}.Debug|x64.ActiveCfg = Debug|x64 58 | {CA4A140F-336B-3D68-8097-383DA83B9D01}.Debug|x64.Build.0 = Debug|x64 59 | {CA4A140F-336B-3D68-8097-383DA83B9D01}.Release|x64.ActiveCfg = Release|x64 60 | {CA4A140F-336B-3D68-8097-383DA83B9D01}.Release|x64.Build.0 = Release|x64 61 | EndGlobalSection 62 | GlobalSection(SolutionProperties) = preSolution 63 | HideSolutionNode = FALSE 64 | EndGlobalSection 65 | GlobalSection(NestedProjects) = preSolution 66 | {03F69A1B-6D18-43F5-B71F-B3A3018F12F0} = {66D229C9-532C-4984-A907-945CED26F85A} 67 | {3292BD3C-C649-46B0-9C60-545B31F2B169} = {66D229C9-532C-4984-A907-945CED26F85A} 68 | {7ECA4674-EE2E-434E-BA80-BD026783A13D} = {EFC8667D-50C4-4A83-A819-B56998FFF2AA} 69 | {CA4A140F-336B-3D68-8097-383DA83B9D01} = {EFC8667D-50C4-4A83-A819-B56998FFF2AA} 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {7F2C3D6C-DAE3-426B-861F-DC54077524D2} 73 | Qt5Version = $(DefaultQtVersion) 74 | EndGlobalSection 75 | EndGlobal 76 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Vector2.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Vector2.h" 3 | #include "gmath.h" 4 | 5 | const Vector2 Vector2::zero = { 0.0f, 0.0f }; 6 | const Vector2 Vector2::one = { 1.0f, 1.0f }; 7 | 8 | Vector2::Vector2(float f) // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) 9 | : x(f), 10 | y(f) 11 | { 12 | } 13 | 14 | Vector2::Vector2(float x, float y) // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) 15 | : x(x), 16 | y(y) 17 | { 18 | } 19 | 20 | float Vector2::operator[](size_t i) const 21 | { 22 | if (i >= 2) 23 | { 24 | throw; 25 | } 26 | 27 | return array[i]; 28 | } 29 | 30 | Vector2 Vector2::operator+() const 31 | { 32 | const Vector2 result = { +x, +y }; 33 | return result; 34 | } 35 | 36 | Vector2 Vector2::operator-() const 37 | { 38 | const Vector2 result = { -x, -y }; 39 | return result; 40 | } 41 | 42 | Vector2 Vector2::operator+(const Vector2& rhs) const 43 | { 44 | const Vector2 result = { 45 | x + rhs.x, 46 | y + rhs.y 47 | }; 48 | 49 | return result; 50 | } 51 | 52 | Vector2 Vector2::operator-(const Vector2& rhs) const 53 | { 54 | const Vector2 result = { 55 | x - rhs.x, 56 | y - rhs.y 57 | }; 58 | 59 | return result; 60 | } 61 | 62 | Vector2 Vector2::operator*(const Vector2& rhs) const 63 | { 64 | const Vector2 result = { 65 | x * rhs.x, 66 | y * rhs.y 67 | }; 68 | 69 | return result; 70 | } 71 | 72 | Vector2 Vector2::operator/(const Vector2& rhs) const 73 | { 74 | const Vector2 result = { 75 | x / rhs.x, 76 | y / rhs.y 77 | }; 78 | 79 | return result; 80 | } 81 | 82 | void Vector2::operator+=(const Vector2& rhs) 83 | { 84 | x += rhs.x; 85 | y += rhs.y; 86 | } 87 | 88 | void Vector2::operator-=(const Vector2& rhs) 89 | { 90 | x -= rhs.x; 91 | y -= rhs.y; 92 | } 93 | 94 | void Vector2::operator/=(const Vector2& rhs) 95 | { 96 | x /= rhs.x; 97 | y /= rhs.y; 98 | } 99 | 100 | void Vector2::operator*=(const Vector2& rhs) 101 | { 102 | x *= rhs.x; 103 | y *= rhs.y; 104 | } 105 | 106 | Vector2 Vector2::operator+(const float rhs) const 107 | { 108 | const Vector2 result = { 109 | x + rhs, 110 | y + rhs 111 | }; 112 | 113 | return result; 114 | } 115 | 116 | Vector2 Vector2::operator-(const float rhs) const 117 | { 118 | const Vector2 result = { 119 | x - rhs, 120 | y - rhs 121 | }; 122 | 123 | return result; 124 | } 125 | 126 | Vector2 Vector2::operator*(const float rhs) const 127 | { 128 | const Vector2 result = { 129 | x * rhs, 130 | y * rhs 131 | }; 132 | 133 | return result; 134 | } 135 | 136 | Vector2 Vector2::operator/(const float rhs) const 137 | { 138 | const Vector2 result = { 139 | x / rhs, 140 | y / rhs 141 | }; 142 | 143 | return result; 144 | } 145 | 146 | void Vector2::operator+=(const float rhs) 147 | { 148 | x += rhs; 149 | y += rhs; 150 | } 151 | 152 | void Vector2::operator-=(const float rhs) 153 | { 154 | x -= rhs; 155 | y -= rhs; 156 | } 157 | 158 | void Vector2::operator/=(const float rhs) 159 | { 160 | x /= rhs; 161 | y /= rhs; 162 | } 163 | 164 | void Vector2::operator*=(const float rhs) 165 | { 166 | x *= rhs; 167 | y *= rhs; 168 | } 169 | 170 | Vector2::operator float*() 171 | { 172 | return array; 173 | } 174 | 175 | Vector2::operator const float*() const 176 | { 177 | return array; 178 | } 179 | 180 | float Vector2::lengthSquared() const 181 | { 182 | return x * x + y * y; 183 | } 184 | 185 | float Vector2::length() const 186 | { 187 | return sqrt(lengthSquared()); 188 | } 189 | 190 | void Vector2::normalize() 191 | { 192 | const auto l = length(); 193 | 194 | if (l == 0.0f) 195 | { 196 | x = y = 0.0f; 197 | } 198 | else 199 | { 200 | x = x / l; 201 | y = y / l; 202 | } 203 | } 204 | 205 | Vector2 Vector2::normalized() const 206 | { 207 | Vector2 result = *this; 208 | result.normalize(); 209 | return result; 210 | } 211 | 212 | bool Vector2::operator==(const Vector2& rhs) const 213 | { 214 | return nearEqual(rhs); 215 | } 216 | 217 | bool Vector2::operator!=(const Vector2& rhs) const 218 | { 219 | return !(*this == rhs); 220 | } 221 | 222 | bool Vector2::isNormalized() const 223 | { 224 | return gmath::is_one(lengthSquared()); 225 | } 226 | 227 | float Vector2::dot(const Vector2& rhs) const 228 | { 229 | return (x * rhs.x) + (y * rhs.y); 230 | } 231 | 232 | bool Vector2::nearEqual(const Vector2& rhs) const 233 | { 234 | return gmath::near_equal(x, rhs.x) && 235 | gmath::near_equal(y, rhs.y); 236 | } 237 | 238 | Vector2 Vector2::lerp(const Vector2& start, const Vector2& end, float amount) 239 | { 240 | return 241 | { 242 | gmath::lerp(start.x, end.x, amount), 243 | gmath::lerp(start.y, end.y, amount) 244 | }; 245 | } 246 | 247 | Vector2 Vector2::clamp(const Vector2& v, float lower, float upper) 248 | { 249 | return 250 | { 251 | std::clamp(v.x, lower, upper), 252 | std::clamp(v.y, lower, upper) 253 | }; 254 | } 255 | 256 | Vector2 operator*(float lhs, const Vector2& rhs) 257 | { 258 | return rhs * lhs; 259 | } 260 | -------------------------------------------------------------------------------- /ds4wizard-device-toggle/ds4wizard-device-toggle.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {7FFC69CC-E15A-4470-9EC4-7477FD662C89} 16 | ds4wizarddevicetoggle 17 | 10.0 18 | 19 | 20 | 21 | Application 22 | true 23 | v143 24 | MultiByte 25 | 26 | 27 | Application 28 | false 29 | v143 30 | true 31 | MultiByte 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | $(SolutionDir)bin\$(Configuration)\ 47 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 48 | 49 | 50 | $(SolutionDir)bin\$(Configuration)\ 51 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 52 | 53 | 54 | 55 | Level3 56 | MaxSpeed 57 | true 58 | true 59 | true 60 | true 61 | ..\libdevicetoggle 62 | true 63 | stdcpp20 64 | 65 | 66 | true 67 | true 68 | RequireAdministrator 69 | Hid.lib;SetupAPI.lib;%(AdditionalDependencies) 70 | 71 | 72 | 73 | 74 | Level3 75 | Disabled 76 | true 77 | true 78 | ..\libdevicetoggle 79 | true 80 | stdcpp20 81 | 82 | 83 | RequireAdministrator 84 | Hid.lib;SetupAPI.lib;%(AdditionalDependencies) 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | {3292bd3c-c649-46b0-9c60-545b31f2b169} 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /ds4wizard-cpp/InputMap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "Pressable.h" 8 | #include "Stopwatch.h" 9 | #include "AxisOptions.h" 10 | 11 | class InputMapBase : public Pressable, public JsonData 12 | { 13 | public: 14 | bool isToggled = false; 15 | bool performRapidFire() const; 16 | 17 | private: 18 | bool rapidFiring = false; 19 | PressedState rapidState = PressedState::off; 20 | Stopwatch rapidStopwatch; 21 | 22 | public: 23 | ~InputMapBase() override = default; 24 | 25 | /** 26 | * \brief 27 | * The pressed state of the underlying emulated mapping. 28 | * For example if \c toggle and \c isToggled are \c true, 29 | * this function will return \c PressedState::pressed or \c PressedState::on. 30 | * \sa PressedState 31 | */ 32 | [[nodiscard]] PressedState simulatedState() const; 33 | 34 | [[nodiscard]] bool isActive() const override; 35 | 36 | /** 37 | * \brief Indicates if this instance has a persistent state 38 | * which is actively simulated. 39 | */ 40 | [[nodiscard]] bool isPersistent() const; 41 | 42 | InputType_t inputType = 0; 43 | 44 | std::optional inputButtons; 45 | std::optional inputAxes; 46 | std::string inputTouchRegion; 47 | std::optional inputTouchDirection; 48 | 49 | std::optional toggle; 50 | std::optional rapidFire; 51 | 52 | std::optional rapidFireInterval; 53 | 54 | std::unordered_map inputAxisOptions; 55 | 56 | InputMapBase() = default; 57 | InputMapBase(const InputMapBase& other); 58 | InputMapBase(InputMapBase&& other) noexcept; 59 | 60 | explicit InputMapBase(InputType_t inputType); 61 | InputMapBase(InputType_t inputType, Ds4Buttons::T input); 62 | InputMapBase(InputType_t inputType, Ds4Axes::T input); 63 | InputMapBase(InputType_t inputType, std::string input); 64 | 65 | InputMapBase& operator=(const InputMapBase&) = default; 66 | InputMapBase& operator=(InputMapBase&& other) noexcept; 67 | 68 | void press() override; 69 | 70 | protected: 71 | void updateRapidState(); 72 | 73 | public: 74 | void release() override; 75 | InputAxisOptions getAxisOptions(Ds4Axes_t axis) const; 76 | 77 | bool operator==(const InputMapBase& other) const; 78 | bool operator!=(const InputMapBase& other) const; 79 | 80 | void readJson(const nlohmann::json& json) override; 81 | void writeJson(nlohmann::json& json) const override; 82 | }; 83 | 84 | class InputModifier; 85 | 86 | using VirtualKeyCode = int; 87 | 88 | class InputMap : public InputMapBase 89 | { 90 | public: 91 | SimulatorType simulatorType = SimulatorType::none; 92 | OutputType_t outputType = 0; 93 | 94 | std::optional action; 95 | 96 | #pragma region Keyboard 97 | 98 | std::optional keyCode; 99 | std::vector keyCodeModifiers; 100 | 101 | #pragma endregion 102 | 103 | #pragma region Mouse 104 | 105 | std::optional mouseAxes; 106 | std::optional mouseButton; 107 | 108 | #pragma endregion 109 | 110 | #pragma region XInput 111 | 112 | std::optional xinputButtons; 113 | std::optional xinputAxes; 114 | 115 | #pragma endregion 116 | 117 | InputMap() = default; 118 | ~InputMap() override = default; 119 | 120 | InputMap(const InputMap& other) = default; 121 | InputMap(InputMap&& other) noexcept; 122 | 123 | InputMap(SimulatorType simulatorType, InputType_t inputType, OutputType::T outputType); 124 | 125 | InputMap& operator=(const InputMap& other) = default; 126 | InputMap& operator=(InputMap&& other) noexcept; 127 | 128 | /** 129 | * \brief Activates a press state on this instance. 130 | * If a modifier is provided, it must be active for the press to succeed. 131 | * \param modifier The parent modifier, if any. 132 | */ 133 | void pressWithModifier(const InputModifier* modifier); 134 | 135 | bool operator==(const InputMap& other) const; 136 | bool operator!=(const InputMap& other) const; 137 | 138 | void readJson(const nlohmann::json& json) override; 139 | void writeJson(nlohmann::json& json) const override; 140 | }; 141 | 142 | /** 143 | * \brief A modifier set that controls a collection of input bindings. 144 | */ 145 | class InputModifier : public InputMapBase 146 | { 147 | public: 148 | /** 149 | * \brief The bindings associated with this modifier set. 150 | */ 151 | std::deque bindings; 152 | 153 | InputModifier() = default; 154 | ~InputModifier() override = default; 155 | 156 | InputModifier(InputType_t type, Ds4Buttons::T buttons); 157 | InputModifier(InputType_t type, Ds4Axes::T axis); 158 | InputModifier(InputType_t type, const std::string& region); 159 | 160 | // Copy constructor 161 | InputModifier(const InputModifier& other); 162 | 163 | InputModifier(InputModifier&& other) noexcept; 164 | 165 | InputModifier& operator=(const InputModifier&) = default; 166 | InputModifier& operator=(InputModifier&& other) noexcept; 167 | 168 | bool operator==(const InputModifier& other) const; 169 | bool operator!=(const InputModifier& other) const; 170 | void readJson(const nlohmann::json& json) override; 171 | void writeJson(nlohmann::json& json) const override; 172 | }; 173 | -------------------------------------------------------------------------------- /hid-tester/hid-tester.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | {A1449C88-190B-4F6B-9B13-BA8EDB7E2807} 16 | hidtester 17 | 10.0 18 | 19 | 20 | 21 | Application 22 | true 23 | v143 24 | Unicode 25 | 26 | 27 | Application 28 | false 29 | v143 30 | true 31 | Unicode 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | false 47 | $(SolutionDir)bin\$(Configuration)\ 48 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 49 | 50 | 51 | true 52 | $(SolutionDir)bin\$(Configuration)\ 53 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 54 | 55 | 56 | 57 | Level3 58 | true 59 | true 60 | true 61 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 62 | true 63 | ..\libhid 64 | stdcpp20 65 | 66 | 67 | Console 68 | true 69 | true 70 | true 71 | SetupAPI.lib;Bthprops.lib;Hid.lib;%(AdditionalDependencies) 72 | 73 | 74 | 75 | 76 | Level3 77 | true 78 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 79 | true 80 | ..\libhid 81 | stdcpp20 82 | 83 | 84 | Console 85 | true 86 | SetupAPI.lib;Bthprops.lib;Hid.lib;%(AdditionalDependencies) 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | {03f69a1b-6d18-43f5-b71f-b3a3018f12f0} 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /libhid/libhid.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {03F69A1B-6D18-43F5-B71F-B3A3018F12F0} 16 | Win32Proj 17 | libhid 18 | 10.0 19 | 20 | 21 | 22 | StaticLibrary 23 | true 24 | v143 25 | Unicode 26 | 27 | 28 | StaticLibrary 29 | false 30 | v143 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | $(SolutionDir)bin\$(Configuration)\ 48 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 49 | 50 | 51 | $(SolutionDir)bin\$(Configuration)\ 52 | $(SolutionDir)obj\$(Platform)\$(Configuration)\$(ProjectName)\ 53 | 54 | 55 | 56 | NotUsing 57 | Level3 58 | Disabled 59 | WIN32_LEAN_AND_MEAN;_DEBUG;_LIB;%(PreprocessorDefinitions) 60 | 61 | 62 | true 63 | stdcpp20 64 | 65 | 66 | Windows 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | NotUsing 77 | MaxSpeed 78 | true 79 | true 80 | WIN32_LEAN_AND_MEAN;NDEBUG;_LIB;%(PreprocessorDefinitions) 81 | 82 | 83 | true 84 | stdcpp20 85 | 86 | 87 | Windows 88 | true 89 | true 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Ds4Device.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "DeviceSettings.h" 9 | #include "DeviceProfile.h" 10 | #include "hid_instance.h" 11 | #include "Stopwatch.h" 12 | #include "Ds4Input.h" 13 | #include "Ds4Output.h" 14 | #include "Event.h" 15 | 16 | #include "Latency.h" 17 | #include "InputSimulator.h" 18 | #include "MacAddress.h" 19 | 20 | class Ds4ConnectEvent 21 | { 22 | public: 23 | enum class Status 24 | { 25 | opened, 26 | toggleFailed, 27 | exclusiveFailed, 28 | openFailed 29 | }; 30 | 31 | ConnectionType connectionType; 32 | Status status; 33 | std::optional nativeError; 34 | 35 | Ds4ConnectEvent() = delete; 36 | 37 | Ds4ConnectEvent(ConnectionType connectionType_, Status status_, std::optional nativeError_ = std::nullopt); 38 | }; 39 | 40 | class Ds4DisconnectEvent 41 | { 42 | public: 43 | enum class Reason 44 | { 45 | dropped, 46 | closed, 47 | idle, 48 | error 49 | }; 50 | 51 | ConnectionType connectionType; 52 | Reason reason; 53 | std::optional nativeError; 54 | 55 | Ds4DisconnectEvent() = delete; 56 | 57 | Ds4DisconnectEvent(ConnectionType connectionType_, Reason reason_, std::optional nativeError_ = std::nullopt); 58 | }; 59 | 60 | class Ds4Device 61 | { 62 | private: 63 | bool peakedLatencyThreshold = false; 64 | std::string macAddress_; 65 | std::string safeMacAddress_; 66 | 67 | bool running = false; 68 | std::recursive_mutex sync_lock; 69 | 70 | Stopwatch idleTime {}; 71 | Stopwatch writeTime {}; 72 | 73 | inline static const Ds4Color fadeColor {}; 74 | 75 | ptrdiff_t colorIndex = -1; 76 | 77 | Latency readLatency; 78 | Latency writeLatency; 79 | 80 | std::unique_ptr deviceThread = nullptr; 81 | 82 | std::shared_ptr usbDevice; 83 | std::shared_ptr bluetoothDevice; 84 | MacAddress macAddressBytes {}; 85 | 86 | Ds4LightOptions activeLight; 87 | 88 | bool disconnectOnIdle() const; 89 | std::chrono::microseconds idleTimeout() const; 90 | bool isIdle() const; 91 | 92 | InputSimulator simulator; 93 | 94 | // TODO: rather than storing a boolean, implement a run-once, resettable callback 95 | bool notifiedLow = false; 96 | // TODO: rather than storing a boolean, implement a run-once, resettable callback 97 | bool notifiedCharged = true; 98 | 99 | public: 100 | enum class BluetoothDisconnectReason 101 | { 102 | none, 103 | idle 104 | }; 105 | 106 | static constexpr size_t usbInputReportSize = 64; 107 | 108 | Event onDeviceClose; 109 | Event onConnect; 110 | Event onWirelessOperationalModeFailure; 111 | Event onConnectFailure; 112 | Event onDisconnect; 113 | 114 | Event onBatteryLevelChanged; 115 | // current battery level 116 | Event onBatteryLevelLow; 117 | Event onBatteryFullyCharged; 118 | 119 | // value, threshold 120 | Event onLatencyThresholdExceeded; 121 | 122 | DeviceSettings settings; 123 | DeviceProfile profile; 124 | 125 | Ds4Input input {}; 126 | Ds4Output output {}; 127 | 128 | bool bluetoothConnected(); 129 | bool usbConnected(); 130 | bool connected(); 131 | 132 | const std::string& macAddress() const; 133 | const std::string& safeMacAddress() const; 134 | 135 | /** 136 | * \brief Gets the battery charge level in the range 1 to 10. 137 | */ 138 | uint8_t battery() const; 139 | 140 | /** 141 | * \brief Indicates if the controller is charging. 142 | */ 143 | bool charging() const; 144 | 145 | const std::string& name() const; 146 | 147 | Ds4Device(); 148 | explicit Ds4Device(std::shared_ptr device); 149 | ~Ds4Device(); 150 | 151 | static MacAddress getMacAddress(const std::shared_ptr& device); 152 | 153 | void open(std::shared_ptr device); 154 | 155 | std::unique_lock lock(); 156 | 157 | Latency getReadLatency(); 158 | Latency getWriteLatency(); 159 | 160 | void resetReadLatencyPeak(); 161 | void resetWriteLatencyPeak(); 162 | 163 | private: 164 | void closeImpl(); 165 | 166 | public: 167 | void saveSettings(); 168 | void applySettings(const DeviceSettings& newSettings); 169 | 170 | /** 171 | * \brief Applies changes made to the device profile and opens device handles. 172 | */ 173 | void applyProfile(); 174 | 175 | private: 176 | void releaseAutoColor(); 177 | void closeDeviceAndResetIdle(const std::shared_ptr& device); 178 | 179 | public: 180 | void onProfileChanged(const std::string& newName); 181 | void close(); 182 | 183 | void disconnectBluetooth(BluetoothDisconnectReason reason); 184 | 185 | static bool openDevice(const std::shared_ptr& hid, bool exclusive); 186 | 187 | bool openBluetoothDevice(std::shared_ptr hid); 188 | bool openUsbDevice(std::shared_ptr hid); 189 | 190 | private: 191 | void setupBluetoothOutputBuffer() const; 192 | void setupUsbOutputBuffer() const; 193 | void writeUsbAsync(); 194 | void writeBluetooth(); 195 | void onDisconnectError(const std::shared_ptr& device, ConnectionType connectionType); 196 | bool run(); 197 | void controllerThread(); 198 | 199 | public: 200 | void start(); 201 | }; 202 | -------------------------------------------------------------------------------- /ds4wizard-cpp/Vector3.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "Vector3.h" 3 | #include "gmath.h" 4 | 5 | const Vector3 Vector3::zero = { 0.0f, 0.0f, 0.0f }; 6 | const Vector3 Vector3::one = { 1.0f, 1.0f, 1.0f }; 7 | 8 | Vector3::Vector3(float f) // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) 9 | : x(f), 10 | y(f), 11 | z(f) 12 | { 13 | } 14 | 15 | Vector3::Vector3(float x, float y, float z) // NOLINT(cppcoreguidelines-pro-type-member-init, hicpp-member-init) 16 | : x(x), 17 | y(y), 18 | z(z) 19 | { 20 | } 21 | 22 | float Vector3::operator[](size_t i) const 23 | { 24 | if (i >= 3) 25 | { 26 | throw; 27 | } 28 | 29 | return array[i]; 30 | } 31 | 32 | Vector3 Vector3::operator+() const 33 | { 34 | const Vector3 result = { +x, +y, +z }; 35 | return result; 36 | } 37 | 38 | Vector3 Vector3::operator-() const 39 | { 40 | const Vector3 result = { -x, -y, -z }; 41 | return result; 42 | } 43 | 44 | Vector3 Vector3::operator+(const Vector3& rhs) const 45 | { 46 | const Vector3 result = { 47 | x + rhs.x, 48 | y + rhs.y, 49 | z + rhs.z 50 | }; 51 | 52 | return result; 53 | } 54 | 55 | Vector3 Vector3::operator-(const Vector3& rhs) const 56 | { 57 | const Vector3 result = { 58 | x - rhs.x, 59 | y - rhs.y, 60 | z - rhs.z 61 | }; 62 | 63 | return result; 64 | } 65 | 66 | Vector3 Vector3::operator*(const Vector3& rhs) const 67 | { 68 | const Vector3 result = { 69 | x * rhs.x, 70 | y * rhs.y, 71 | z * rhs.z 72 | }; 73 | 74 | return result; 75 | } 76 | 77 | Vector3 Vector3::operator/(const Vector3& rhs) const 78 | { 79 | const Vector3 result = { 80 | x / rhs.x, 81 | y / rhs.y, 82 | z / rhs.z 83 | }; 84 | 85 | return result; 86 | } 87 | 88 | void Vector3::operator+=(const Vector3& rhs) 89 | { 90 | x += rhs.x; 91 | y += rhs.y; 92 | z += rhs.z; 93 | } 94 | 95 | void Vector3::operator-=(const Vector3& rhs) 96 | { 97 | x -= rhs.x; 98 | y -= rhs.y; 99 | z -= rhs.z; 100 | } 101 | 102 | void Vector3::operator/=(const Vector3& rhs) 103 | { 104 | x /= rhs.x; 105 | y /= rhs.y; 106 | z /= rhs.z; 107 | } 108 | 109 | void Vector3::operator*=(const Vector3& rhs) 110 | { 111 | x *= rhs.x; 112 | y *= rhs.y; 113 | z *= rhs.z; 114 | } 115 | 116 | Vector3 Vector3::operator+(const float rhs) const 117 | { 118 | const Vector3 result = { 119 | x + rhs, 120 | y + rhs, 121 | z + rhs 122 | }; 123 | 124 | return result; 125 | } 126 | 127 | Vector3 Vector3::operator-(const float rhs) const 128 | { 129 | const Vector3 result = { 130 | x - rhs, 131 | y - rhs, 132 | z - rhs 133 | }; 134 | 135 | return result; 136 | } 137 | 138 | Vector3 Vector3::operator*(const float rhs) const 139 | { 140 | const Vector3 result = { 141 | x * rhs, 142 | y * rhs, 143 | z * rhs 144 | }; 145 | 146 | return result; 147 | } 148 | 149 | Vector3 Vector3::operator/(const float rhs) const 150 | { 151 | const Vector3 result = { 152 | x / rhs, 153 | y / rhs, 154 | z / rhs 155 | }; 156 | 157 | return result; 158 | } 159 | 160 | void Vector3::operator+=(const float rhs) 161 | { 162 | x += rhs; 163 | y += rhs; 164 | z += rhs; 165 | } 166 | 167 | void Vector3::operator-=(const float rhs) 168 | { 169 | x -= rhs; 170 | y -= rhs; 171 | z -= rhs; 172 | } 173 | 174 | void Vector3::operator/=(const float rhs) 175 | { 176 | x /= rhs; 177 | y /= rhs; 178 | z /= rhs; 179 | } 180 | 181 | void Vector3::operator*=(const float rhs) 182 | { 183 | x *= rhs; 184 | y *= rhs; 185 | z *= rhs; 186 | } 187 | 188 | Vector3::operator float*() 189 | { 190 | return array; 191 | } 192 | 193 | Vector3::operator const float*() const 194 | { 195 | return array; 196 | } 197 | 198 | float Vector3::lengthSquared() const 199 | { 200 | return x * x + y * y + z * z; 201 | } 202 | 203 | float Vector3::length() const 204 | { 205 | return sqrt(lengthSquared()); 206 | } 207 | 208 | void Vector3::normalize() 209 | { 210 | const auto l = length(); 211 | 212 | if (l == 0.0f) 213 | { 214 | x = y = z = 0.0f; 215 | } 216 | else 217 | { 218 | x = x / l; 219 | y = y / l; 220 | z = z / l; 221 | } 222 | } 223 | 224 | Vector3 Vector3::normalized() const 225 | { 226 | Vector3 result = *this; 227 | result.normalize(); 228 | return result; 229 | } 230 | 231 | bool Vector3::operator==(const Vector3& rhs) const 232 | { 233 | return gmath::near_equal(x, rhs.x) && 234 | gmath::near_equal(y, rhs.y) && 235 | gmath::near_equal(z, rhs.z); 236 | } 237 | 238 | bool Vector3::operator!=(const Vector3& rhs) const 239 | { 240 | return !(*this == rhs); 241 | } 242 | 243 | bool Vector3::isNormalized() const 244 | { 245 | return gmath::is_one(lengthSquared()); 246 | } 247 | 248 | float Vector3::dot(const Vector3& rhs) const 249 | { 250 | return (x * rhs.x) + (y * rhs.y) + (z * rhs.z); 251 | } 252 | 253 | bool Vector3::nearEqual(const Vector3& rhs) const 254 | { 255 | return gmath::near_equal(x, rhs.x) && 256 | gmath::near_equal(y, rhs.y) && 257 | gmath::near_equal(z, rhs.z); 258 | } 259 | 260 | Vector3 Vector3::lerp(const Vector3& start, const Vector3& end, float amount) 261 | { 262 | return 263 | { 264 | gmath::lerp(start.x, end.x, amount), 265 | gmath::lerp(start.y, end.y, amount), 266 | gmath::lerp(start.z, end.z, amount) 267 | }; 268 | } 269 | 270 | Vector3 Vector3::clamp(const Vector3& v, float lower, float upper) 271 | { 272 | return 273 | { 274 | std::clamp(v.x, lower, upper), 275 | std::clamp(v.y, lower, upper), 276 | std::clamp(v.z, lower, upper) 277 | }; 278 | } 279 | 280 | Vector3 operator*(float lhs, const Vector3& rhs) 281 | { 282 | return rhs * lhs; 283 | } 284 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | ds4wizard-cpp/GeneratedFiles/* 264 | /cppcheck-build-dir 265 | dependencies/ViGEmClient-build/* 266 | -------------------------------------------------------------------------------- /ds4wizard-cpp.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | SUGGESTION 3 | <NamingElement Priority="10"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 4 | <NamingElement Priority="9"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="member function" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 5 | <NamingElement Priority="11"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="PUBLIC"><type Name="class field" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 6 | <NamingElement Priority="13"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="enumerator" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 7 | <NamingElement Priority="8"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global function" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 8 | <NamingElement Priority="7"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="global variable" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 9 | <NamingElement Priority="6"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="local variable" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 10 | <NamingElement Priority="14"><Descriptor Static="True" Constexpr="Indeterminate" Const="True" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="class field" /><type Name="local variable" /><type Name="struct field" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 11 | <NamingElement Priority="5"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="parameter" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 12 | <NamingElement Priority="19"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="property" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_AaBb" /></NamingElement> 13 | <NamingElement Priority="17"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="False" Prefix="" Suffix="" Style="aa_bb" /></NamingElement> 14 | <NamingElement Priority="12"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union member" /></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb_aaBb" /></NamingElement> 15 | True -------------------------------------------------------------------------------- /ds4wizard-cpp/ProfileEditorDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ProfileEditorDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 892 10 | 462 11 | 12 | 13 | 14 | 15 | Segoe UI 16 | 17 | 18 | 19 | Profile editor 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | Modifier set: 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | a 40 | 41 | 42 | 43 | 44 | 45 | 46 | e 47 | 48 | 49 | 50 | 51 | 52 | 53 | d 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 512 64 | 340 65 | 66 | 67 | 68 | background-color: rgb(0, 0, 0); 69 | image: url(:/ds4wizardcpp/Resources/Dualshock_4_Layout manually colored.svg); 70 | 71 | 72 | QFrame::StyledPanel 73 | 74 | 75 | QFrame::Raised 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 0 84 | 0 85 | 86 | 87 | 88 | 89 | 220 90 | 340 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | Qt::Horizontal 101 | 102 | 103 | 104 | 40 105 | 20 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | a 114 | 115 | 116 | 117 | 118 | 119 | 120 | e 121 | 122 | 123 | 124 | 125 | 126 | 127 | d 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Profile name: 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | Exclusive mode 151 | 152 | 153 | 154 | 155 | 156 | 157 | Use XInput 158 | 159 | 160 | 161 | 162 | 163 | 164 | Qt::Vertical 165 | 166 | 167 | 168 | 20 169 | 40 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | Qt::Horizontal 180 | 181 | 182 | QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok 183 | 184 | 185 | false 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | --------------------------------------------------------------------------------