Merge pull request #20620 from hrydgard/ui-fixes

Control the LEDs on DualShock/DualSense gamepads
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2025-07-11 16:32:59 +02:00
commit 7b7a23733f
4 files changed
+142 -58

No files matched your search

+7 -7
View File
@@ -56,27 +56,27 @@ static const char * const condnames[16] = {
"al", // Always (unconditional) 14
};
int SignExtend26(int x) {
static int SignExtend26(int x) {
return (x & 0x02000000) ? (0xFC000000 | x) : (x & 0x3FFFFFF);
}
int SignExtend19(int x) {
static int SignExtend19(int x) {
return (x & 0x00040000) ? (0xFFF80000 | x) : (x & 0x7FFFF);
}
int SignExtend9(int x) {
static int SignExtend9(int x) {
return (x & 0x00000100) ? (0xFFFFFE00 | x) : (x & 0x1FF);
}
int SignExtend7(int x) {
static int SignExtend7(int x) {
return (x & 0x00000040) ? (0xFFFFFF80 | x) : (x & 0x7F);
}
int SignExtend12(int x) {
static int SignExtend12(int x) {
return (x & 0x00000800) ? (0xFFFFF000 | x) : (x & 0xFFF);
}
int HighestSetBit(int value) {
static int HighestSetBit(int value) {
int highest = 0;
for (int i = 0; i < 32; i++) {
if (value & (1 << i))
@@ -85,7 +85,7 @@ int HighestSetBit(int value) {
return highest;
}
int LowestSetBit(int value, int maximum = 32) {
static int LowestSetBit(int value, int maximum = 32) {
for (int i = 0; i < maximum; i++) {
if (value & (1 << i))
return i;
+131 -49
View File
@@ -7,6 +7,7 @@
#include <vector>
#include "Windows/HidInputDevice.h"
#include "Common/CommonTypes.h"
#include "Common/TimeUtil.h"
#include "Common/Log.h"
#include "Common/Input/InputState.h"
@@ -14,6 +15,10 @@
#include "Common/System/NativeApp.h"
#include "Common/System/OSD.h"
constexpr u8 LED_R = 0x05;
constexpr u8 LED_G = 0x10;
constexpr u8 LED_B = 0x40;
enum PSButton : u32 {
PS_DPAD_UP = 1, // These dpad ones are not real, we convert from hat switch format.
PS_DPAD_DOWN = 2,
@@ -135,41 +140,15 @@ static bool IsSupportedGamepad(HANDLE handle, USHORT *pidOut, PSSubType *subType
return false;
}
struct DualSenseOutputReport{
uint8_t reportId;
uint8_t flags1;
uint8_t flags2;
uint8_t headphone[4];
uint8_t muteLED;
uint8_t micMute;
};
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs#L905
// Sends initialization packet to DualSense
static bool InitializeDualSense(HANDLE handle, int outReportSize) {
if (outReportSize != 48) {
return false;
}
// Output report (0x05) – sets lightbar, enables full report mode
uint8_t reportData[48] = {0};
DualSenseOutputReport report;
report.reportId = 2;
report.flags1 = 0x0C;
report.flags2 = 0x15;
report.muteLED = 1;
memcpy(reportData, &report, sizeof(report));
template<class T>
static bool WriteReport(HANDLE handle, const T &report) {
DWORD written;
bool result = WriteFile(handle, reportData, sizeof(reportData), &written, NULL);
bool result = WriteFile(handle, &report, sizeof(report), &written, NULL);
if (!result) {
u32 errorCode = GetLastError();
if (errorCode == ERROR_INVALID_PARAMETER) {
if (!HidD_SetOutputReport(handle, reportData, outReportSize)) {
if (!HidD_SetOutputReport(handle, (PVOID)&report, sizeof(T))) {
errorCode = GetLastError();
}
}
@@ -180,6 +159,67 @@ static bool InitializeDualSense(HANDLE handle, int outReportSize) {
return true;
}
struct DualSenseOutputReport {
u8 reportId;
u8 flags1;
u8 flags2;
u8 rumbleRight;
u8 rumbleLeft;
u8 pad[2];
u8 muteLED;
u8 micMute;
u8 other[32];
u8 enableBrightness;
u8 fade;
u8 brightness;
u8 playerLights;
u8 lightbarRed;
u8 lightbarGreen;
u8 lightbarBlue;
};
static_assert(sizeof(DualSenseOutputReport) == 48);
// https://github.com/ds4windowsapp/DS4Windows/blob/65609b470f53a4f832fb07ac24085d3e28ec15bd/DS4Windows/DS4Library/InputDevices/DualSenseDevice.cs#L905
// Sends initialization packet to DualSense
static bool InitializeDualSense(HANDLE handle, int outReportSize) {
if (outReportSize != sizeof(DualSenseOutputReport)) {
return false;
}
DualSenseOutputReport report{};
report.reportId = 2;
report.flags1 = 0x0C;
report.flags2 = 0x15;
report.muteLED = 1;
report.playerLights = 1;
report.enableBrightness = 1;
report.brightness = 0; // 0 = high, 1 = medium, 2 = low
report.lightbarRed = LED_R;
report.lightbarGreen = LED_G;
report.lightbarBlue = LED_B;
return WriteReport(handle, report);
}
static bool ShutdownDualsense(HANDLE handle, int outReportSize) {
if (outReportSize != sizeof(DualSenseOutputReport)) {
return false;
}
DualSenseOutputReport report{};
report.reportId = 2;
report.flags1 = 0x0C;
report.flags2 = 0x15;
report.muteLED = 1;
report.playerLights = 0;
report.enableBrightness = 1;
report.brightness = 2; // 0 = high, 1 = medium, 2 = low
report.lightbarRed = 0;
report.lightbarGreen = 0;
report.lightbarBlue = 0;
return WriteReport(handle, report);
}
enum class DS4FeatureBits : u8 {
VOL_L = 0x10,
VOL_R = 0x20,
@@ -191,33 +231,67 @@ enum class DS4FeatureBits : u8 {
};
ENUM_CLASS_BITOPS(DS4FeatureBits);
struct DualshockOutputReport {
u8 reportID;
u8 featureBits;
u8 two;
u8 pad;
u8 rumbleRight;
u8 rumbleLeft;
u8 ledR;
u8 ledG;
u8 ledB;
u8 padding[23];
};
static_assert(sizeof(DualshockOutputReport) == 32);
static bool InitializeDualShock(HANDLE handle, int outReportSize) {
if (outReportSize != 32) {
if (outReportSize != sizeof(DualshockOutputReport)) {
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
return false;
}
// DS4 USB output report (report ID 0x05)
// Total size: 32 bytes (must match device buffer size)
uint8_t report[32] = {0};
DualshockOutputReport report{};
report.reportID = 0x05; // Report ID (DS4 output)
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
report.two = 2;
report[0] = 0x05; // Report ID (DS4 output)
report[1] = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
report[2] = 0x02;
// Rumble
report[4] = 0x00; // Right (weak)
report[5] = 0x00; // Left (strong)
report.rumbleRight = 0x00; // Right (weak)
report.rumbleLeft = 0x00; // Left (strong)
// Lightbar (RGB)
report[6] = 0x00; // Red
report[7] = 0x10; // Green
report[8] = 0x40; // Blue (dim blue)
report.ledR = LED_R;
report.ledG = LED_G;
report.ledB = LED_B;
DWORD written;
return WriteFile(handle, report, sizeof(report), &written, NULL);
return WriteReport(handle, report);
}
HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize) {
static bool ShutdownDualShock(HANDLE handle, int outReportSize) {
if (outReportSize != sizeof(DualshockOutputReport)) {
WARN_LOG(Log::UI, "DS4 unexpected report size %d", outReportSize);
return false;
}
DualshockOutputReport report{};
report.reportID = 0x05; // Report ID (DS4 output)
report.featureBits = (u8)(DS4FeatureBits::RUMBLE | DS4FeatureBits::LIGHTBAR | DS4FeatureBits::FLASH); // Flags: enable lightbar, rumble, etc.
report.two = 2;
// Rumble
report.rumbleRight = 0x00; // Right (weak)
report.rumbleLeft = 0x00; // Left (strong)
// Lightbar (RGB)
report.ledR = 0;
report.ledG = 0;
report.ledB = 0;
return WriteReport(handle, report);
}
HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize, int *outReportSize) {
GUID hidGuid;
HidD_GetHidGuid(&hidGuid);
@@ -251,14 +325,14 @@ HANDLE OpenFirstDualShockOrSense(PSSubType *subType, int *reportSize) {
HidD_FreePreparsedData(preparsedData);
*reportSize = caps.InputReportByteLength;
int outReportSize = caps.OutputReportByteLength;
*outReportSize = caps.OutputReportByteLength;
INFO_LOG(Log::UI, "Initializing gamepad. out report size=%d", outReportSize);
bool result;
if (*subType == PSSubType::DS5) {
result = InitializeDualSense(handle, outReportSize);
result = InitializeDualSense(handle, *outReportSize);
} else {
result = InitializeDualShock(handle, outReportSize);
result = InitializeDualShock(handle, *outReportSize);
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
@@ -428,6 +502,14 @@ bool HidInputDevice::ReadDualSenseInput(HANDLE handle, PSControllerState *state)
void HidInputDevice::Init() {}
void HidInputDevice::Shutdown() {
if (controller_) {
switch (subType_) {
case PSSubType::DS4:
ShutdownDualShock(controller_, outReportSize_);
break;
case PSSubType::DS5:
ShutdownDualsense(controller_, outReportSize_);
break;
}
CloseHandle(controller_);
controller_ = nullptr;
}
@@ -452,7 +534,7 @@ int HidInputDevice::UpdateState() {
// Poll for controllers from time to time.
if (pollCount_ == 0) {
pollCount_ = POLL_FREQ;
HANDLE newController = OpenFirstDualShockOrSense(&subType_, &reportSize_);
HANDLE newController = OpenFirstDualShockOrSense(&subType_, &reportSize_, &outReportSize_);
if (newController) {
controller_ = newController;
}
+1
View File
@@ -41,6 +41,7 @@ private:
int pad_ = 0;
int pollCount_ = 0;
int reportSize_ = 0;
int outReportSize_ = 0;
enum {
POLL_FREQ = 283, // a prime number.
};
@@ -37,7 +37,6 @@ public class TextRenderer {
}
private static Point measureLine(String string, double textSize) {
textPaint.setTextSize((float) textSize);
int w = (int) textPaint.measureText(string);
// Round width up to even already here to avoid annoyances from odd-width 16-bit textures
// which OpenGL does not like - each line must be 4-byte aligned
@@ -73,11 +72,13 @@ public class TextRenderer {
}
public static int measureText(String string, double textSize) {
textPaint.setTextSize((float) textSize);
Point s = measure(string, textSize);
return (s.x << 16) | s.y;
}
public static int[] renderText(String string, double textSize) {
textPaint.setTextSize((float) textSize);
Point s = measure(string, textSize);
int w = s.x;
@@ -87,7 +88,7 @@ public class TextRenderer {
Canvas canvas = new Canvas(bmp);
canvas.drawRect(0.0f, 0.0f, w, h, bg);
String lines[] = string.replaceAll("\\r", "").split("\n");
String [] lines = string.replaceAll("\\r", "").split("\n");
float y = 1.0f;
Path path = null;