Skip to content

Commit 8b9924d

Browse files
committed
feat: integrate Logitech Steering Wheel SDK for LED control via static linking
1 parent be5b15c commit 8b9924d

File tree

8 files changed

+326
-224
lines changed

8 files changed

+326
-224
lines changed

Common Files/LogitechLED.cpp

Lines changed: 35 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,9 @@
11
#include "LogitechLED.h"
2+
#include "LogitechSDK/LogitechSteeringWheelLib.h"
23

34
#include <stdio.h>
45
#include <stdarg.h>
56

6-
// --- Logitech LED Escape protocol (from SDK "Independent" sample) ---
7-
8-
static const DWORD ESCAPE_COMMAND_LEDS = 0;
9-
static const DWORD LEDS_VERSION_NUMBER = 0x00000001;
10-
11-
struct LedsRpmData
12-
{
13-
FLOAT currentRPM;
14-
FLOAT rpmFirstLedTurnsOn;
15-
FLOAT rpmRedLine;
16-
};
17-
18-
struct WheelData
19-
{
20-
DWORD size;
21-
DWORD versionNbr;
22-
LedsRpmData rpmData;
23-
};
24-
25-
// --- Known Logitech wheel VID/PIDs ---
26-
27-
static const DWORD LOGITECH_VID = 0x046D;
28-
static const DWORD KNOWN_PIDS[] = {
29-
0xC24F, // G29
30-
0xC260, // G920 (no RPM LEDs but detected for completeness)
31-
0xC262, // G920 Xbox
32-
0xC266, // G923 PS
33-
0xC267, // G923 PS (alt)
34-
0xC26D, // G923 Xbox (alt PID)
35-
0xC26E, // G923 Xbox
36-
};
37-
static const int NUM_PIDS = sizeof(KNOWN_PIDS) / sizeof(KNOWN_PIDS[0]);
38-
397
// --- Logging ---
408

419
static FILE* g_logFile = NULL;
@@ -56,20 +24,10 @@ static void Log(const char* fmt, ...)
5624
OutputDebugStringA("\n");
5725
}
5826

59-
static bool IsKnownPID(DWORD pid)
60-
{
61-
for (int i = 0; i < NUM_PIDS; i++)
62-
if (KNOWN_PIDS[i] == pid) return true;
63-
return false;
64-
}
65-
6627
// --- LogitechLED ---
6728

6829
LogitechLED::LogitechLED()
69-
: m_dinputDll(NULL)
70-
, m_pDI(NULL)
71-
, m_pDevice(NULL)
72-
, m_available(false)
30+
: m_available(false)
7331
{
7432
}
7533

@@ -80,196 +38,66 @@ LogitechLED::~LogitechLED()
8038

8139
void LogitechLED::Close()
8240
{
83-
if (m_pDevice)
84-
{
85-
m_pDevice->Unacquire();
86-
m_pDevice->Release();
87-
m_pDevice = NULL;
88-
}
89-
90-
if (m_pDI)
91-
{
92-
m_pDI->Release();
93-
m_pDI = NULL;
94-
}
95-
96-
if (m_dinputDll)
41+
if (m_available)
9742
{
98-
FreeLibrary(m_dinputDll);
99-
m_dinputDll = NULL;
43+
LogiSteeringShutdown();
44+
m_available = false;
45+
Log("LogitechLED shutdown");
10046
}
101-
102-
m_available = false;
10347
}
10448

10549
bool LogitechLED::IsAvailable() const
10650
{
10751
return m_available;
10852
}
10953

110-
// --- Device enumeration callback ---
111-
112-
BOOL CALLBACK LogitechLED::EnumDevicesCallback(LPCDIDEVICEINSTANCEA lpddi, LPVOID pvRef)
113-
{
114-
EnumContext* ctx = (EnumContext*)pvRef;
115-
116-
DWORD vid = LOWORD(lpddi->guidProduct.Data1);
117-
DWORD pid = HIWORD(lpddi->guidProduct.Data1);
118-
119-
Log(" Enum: VID=0x%04X PID=0x%04X Name='%s'", vid, pid, lpddi->tszProductName);
120-
121-
if (vid == LOGITECH_VID && IsKnownPID(pid))
122-
{
123-
Log(" -> Logitech wheel found!");
124-
ctx->deviceGuid = lpddi->guidInstance;
125-
ctx->found = true;
126-
return DIENUM_STOP;
127-
}
128-
129-
return DIENUM_CONTINUE;
130-
}
131-
132-
// --- Init ---
133-
13454
bool LogitechLED::Init()
13555
{
136-
Log("LogitechLED::Init() - DirectInput Escape mode");
56+
Log("LogitechLED::Init() - Logitech SDK (static link)");
13757

13858
if (m_available)
13959
return true;
14060

141-
// Load the REAL dinput8.dll from System32 (not our wrapper)
142-
char sysDir[MAX_PATH];
143-
GetSystemDirectoryA(sysDir, MAX_PATH);
144-
strcat_s(sysDir, "\\dinput8.dll");
145-
146-
m_dinputDll = LoadLibraryA(sysDir);
147-
if (!m_dinputDll)
148-
{
149-
Log("Failed to load real dinput8.dll from %s (err=%lu)", sysDir, GetLastError());
150-
return false;
151-
}
152-
153-
Log("Real dinput8.dll loaded from %s", sysDir);
154-
155-
// Get the real DirectInput8Create
156-
typedef HRESULT(WINAPI* PFN_DirectInput8Create)(HINSTANCE, DWORD, REFIID, LPVOID*, LPUNKNOWN);
157-
PFN_DirectInput8Create pfnCreate = (PFN_DirectInput8Create)GetProcAddress(m_dinputDll, "DirectInput8Create");
158-
if (!pfnCreate)
159-
{
160-
Log("DirectInput8Create not found in real dinput8.dll");
161-
Close();
162-
return false;
163-
}
61+
// Try init with foreground window first, then without
62+
HWND hwnd = GetForegroundWindow();
63+
bool initOk = false;
16464

165-
// Create DirectInput interface
166-
HRESULT hr = pfnCreate(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8A, (LPVOID*)&m_pDI, NULL);
167-
if (FAILED(hr))
65+
if (hwnd)
16866
{
169-
Log("DirectInput8Create failed (hr=0x%08X)", hr);
170-
Close();
171-
return false;
67+
Log("Trying LogiSteeringInitializeWithWindow (hwnd=%p)", hwnd);
68+
initOk = LogiSteeringInitializeWithWindow(false, hwnd);
17269
}
17370

174-
Log("DirectInput8 interface created");
175-
176-
// Enumerate game controllers to find Logitech wheel
177-
EnumContext ctx = {};
178-
ctx.found = false;
179-
180-
Log("Enumerating game controllers...");
181-
m_pDI->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumDevicesCallback, &ctx, DIEDFL_ATTACHEDONLY);
182-
183-
if (!ctx.found)
71+
if (!initOk)
18472
{
185-
Log("No Logitech wheel found");
186-
Close();
187-
return false;
73+
Log("Trying LogiSteeringInitialize (no window)");
74+
initOk = LogiSteeringInitialize(false);
18875
}
18976

190-
// Create device
191-
hr = m_pDI->CreateDevice(ctx.deviceGuid, &m_pDevice, NULL);
192-
if (FAILED(hr))
77+
if (!initOk)
19378
{
194-
Log("CreateDevice failed (hr=0x%08X)", hr);
195-
Close();
79+
Log("SDK init failed - is G Hub running? Is LogitechSteeringWheelEnginesWrapper.dll present?");
19680
return false;
19781
}
19882

199-
// Set data format
200-
hr = m_pDevice->SetDataFormat(&c_dfDIJoystick2);
201-
if (FAILED(hr))
202-
{
203-
Log("SetDataFormat failed (hr=0x%08X)", hr);
204-
Close();
205-
return false;
206-
}
83+
Log("SDK initialized");
20784

208-
// Set cooperative level: background + non-exclusive (don't steal from game)
209-
HWND hwnd = GetForegroundWindow();
210-
if (!hwnd) hwnd = GetDesktopWindow();
85+
// Give SDK time to enumerate
86+
LogiUpdate();
21187

212-
hr = m_pDevice->SetCooperativeLevel(hwnd, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
213-
if (FAILED(hr))
88+
if (LogiIsConnected(0))
21489
{
215-
Log("SetCooperativeLevel failed (hr=0x%08X)", hr);
216-
Close();
217-
return false;
90+
wchar_t name[256] = {};
91+
LogiGetFriendlyProductName(0, name, 256);
92+
Log("Wheel connected at index 0: %ls", name);
21893
}
219-
220-
// Acquire the device
221-
hr = m_pDevice->Acquire();
222-
if (FAILED(hr))
94+
else
22395
{
224-
Log("Acquire failed (hr=0x%08X)", hr);
225-
Close();
226-
return false;
227-
}
228-
229-
Log("Device acquired, testing Escape LED command...");
230-
231-
// Test: try to clear LEDs to verify Escape() works
232-
if (!PlayLedsEscape(0.0f, 200.0f, 1000.0f))
233-
{
234-
Log("Escape LED command not supported on this device");
235-
Close();
236-
return false;
96+
Log("No wheel detected yet (will retry on first LED update)");
23797
}
23898

23999
m_available = true;
240-
Log("LogitechLED ready (DirectInput Escape mode)");
241-
return true;
242-
}
243-
244-
// --- LED control via Escape ---
245-
246-
bool LogitechLED::PlayLedsEscape(float currentRPM, float rpmFirstLed, float rpmRedLine)
247-
{
248-
if (!m_pDevice)
249-
return false;
250-
251-
WheelData wheelData;
252-
ZeroMemory(&wheelData, sizeof(wheelData));
253-
wheelData.size = sizeof(WheelData);
254-
wheelData.versionNbr = LEDS_VERSION_NUMBER;
255-
wheelData.rpmData.currentRPM = currentRPM;
256-
wheelData.rpmData.rpmFirstLedTurnsOn = rpmFirstLed;
257-
wheelData.rpmData.rpmRedLine = rpmRedLine;
258-
259-
DIEFFESCAPE escape;
260-
ZeroMemory(&escape, sizeof(escape));
261-
escape.dwSize = sizeof(DIEFFESCAPE);
262-
escape.dwCommand = ESCAPE_COMMAND_LEDS;
263-
escape.lpvInBuffer = &wheelData;
264-
escape.cbInBuffer = sizeof(wheelData);
265-
266-
HRESULT hr = m_pDevice->Escape(&escape);
267-
if (FAILED(hr))
268-
{
269-
Log("Escape failed (hr=0x%08X)", hr);
270-
return false;
271-
}
272-
100+
Log("LogitechLED ready");
273101
return true;
274102
}
275103

@@ -281,9 +109,13 @@ bool LogitechLED::SetLEDsFromPercent(double percent)
281109
if (percent < 0.0) percent = 0.0;
282110
if (percent > 1.0) percent = 1.0;
283111

112+
LogiUpdate();
113+
284114
// Map FFB strength (0.0-1.0) to RPM values
115+
// rpmFirstLed=200: first LED at ~20% strength
116+
// rpmRedLine=1000: all LEDs at 100% strength
285117
float currentRPM = (float)(percent * 1000.0);
286-
return PlayLedsEscape(currentRPM, 200.0f, 1000.0f);
118+
return LogiPlayLeds(0, currentRPM, 200.0f, 1000.0f);
287119
}
288120

289121
bool LogitechLED::SetLEDs(BYTE ledMask)
@@ -303,5 +135,6 @@ bool LogitechLED::ClearLEDs()
303135
if (!m_available)
304136
return false;
305137

306-
return PlayLedsEscape(0.0f, 200.0f, 1000.0f);
138+
LogiUpdate();
139+
return LogiPlayLeds(0, 0.0f, 200.0f, 1000.0f);
307140
}

Common Files/LogitechLED.h

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
#pragma once
22

3-
#define DIRECTINPUT_VERSION 0x0800
43
#include <windows.h>
5-
#include <dinput.h>
64

7-
// Logitech G923/G29 RPM LED controller via DirectInput Escape().
8-
// Uses the same mechanism as the Logitech SDK "Independent" sample.
9-
// Requires Logitech G Hub running (provides the FF driver that handles Escape).
10-
// No external DLL dependency.
5+
// Logitech G923/G29 RPM LED controller via Logitech Steering Wheel SDK.
6+
// Statically linked against LogitechSteeringWheelLib.lib.
7+
// Requires LogitechSteeringWheelEnginesWrapper.dll alongside the game
8+
// and Logitech G Hub running.
119

1210
class LogitechLED
1311
{
@@ -24,17 +22,5 @@ class LogitechLED
2422
bool IsAvailable() const;
2523

2624
private:
27-
HMODULE m_dinputDll;
28-
LPDIRECTINPUT8A m_pDI;
29-
LPDIRECTINPUTDEVICE8A m_pDevice;
3025
bool m_available;
31-
32-
struct EnumContext
33-
{
34-
GUID deviceGuid;
35-
bool found;
36-
};
37-
38-
bool PlayLedsEscape(float currentRPM, float rpmFirstLed, float rpmRedLine);
39-
static BOOL CALLBACK EnumDevicesCallback(LPCDIDEVICEINSTANCEA lpddi, LPVOID pvRef);
4026
};

0 commit comments

Comments
 (0)