1// tContGamepad.cpp 
2// 
3// This file implements a gamepad controller. Controllers represent physical input devices. 
4// 
5// Copyright (c) 2025 Tristan Grimmer. 
6// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
7// granted, provided that the above copyright notice and this permission notice appear in all copies. 
8// 
9// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
10// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
11// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
12// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
13// PERFORMANCE OF THIS SOFTWARE. 
14 
15#include <chrono> 
16#ifdef PLATFORM_WINDOWS 
17#include <windows.h> 
18#include <xinput.h> 
19#endif 
20#include "Foundation/tPlatform.h" 
21#include "Input/tContGamepad.h" 
22#include "System/tPrint.h" 
23namespace tInput 
24
25 
26 
27void tContGamepad::StartPolling(int pollingPeriod_ns
28
29 // If it's already running do nothing. We also don't update the period if we're already running. 
30 if (IsPolling()) 
31 return
32 
33 // Set the controller definition. 
34 SetDefinition(); 
35 
36 // PollingPeriod is only ever set before the thread starts and doesn't change. We don't need to Mutex protect it. 
37 if (pollingPeriod_ns <= 0
38 PollingPeriod_ns = int(1000000.0f/Definition.MaxPollingFreq); 
39 else 
40 PollingPeriod_ns = pollingPeriod_ns
41 
42 PollingThread = std::thread(&tContGamepad::Poll, this); 
43
44 
45 
46void tContGamepad::StopPolling() 
47
48 // If it's already stopped so do nothing. 
49 if (!IsPolling()) 
50 return
51 
52 // We don't want to wait around to exit the polling thread to finish its sleep cycle so we use a condition_variable 
53 // that uses PollingExitRequested as the predecate. 
54
55 const std::lock_guard<std::mutex> lock(Mutex); 
56 PollingExitRequested = true
57
58 
59 // Joins back up to the detection thread. 
60 PollingThread.join(); 
61 ClearDefinition(); 
62
63 
64 
65void tContGamepad::Poll() 
66
67 while (true
68
69 #ifdef PLATFORM_WINDOWS 
70 WinXInputState state; 
71 tStd::tMemclr(&state, sizeof(WinXInputState)); 
72 
73 // Get the state of the controller from XInput. From what I can tell reading different gamepads on different 
74 // threads does not require a mutex to protect this call. Only this thread instance will read this particular 
75 // controller and so two calls to XInputGetState for the same controller will never happed at the same time. 
76 WinDWord result = XInputGetState(int(GamepadID), &state); 
77 
78 if (result == WinErrorSuccess) 
79
80 // Controller connected. We can read its state and update components. 
81 if (state.dwPacketNumber != PollingPacketNumber) 
82
83 // The set calls to the components below are all mutex protected internally. Main thread may read input 
84 // unit values so we require the protection. 
85 
86 // Note there is a bit more precision for the neg integral values. -32768 to 32767 maps to [-1.0, 1.0] 
87 int16 rawLX = state.Gamepad.sThumbLX; 
88 float axisLXNorm = (rawLX < 0) ? float(rawLX)/32768.0f : float(rawLX)/32767.0f
89 tPrintf("LX: %05.3f ", axisLXNorm); 
90 // @todo Set LStick X. 
91 
92 int16 rawLY = state.Gamepad.sThumbLY; 
93 float axisLYNorm = (rawLY < 0) ? float(rawLY)/32768.0f : float(rawLY)/32767.0f
94 tPrintf("LY: %05.3f ", axisLXNorm); 
95 // @todo Set LStick Y. 
96 
97 bool rawLB = (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false
98 tPrintf("LB: %s\n", rawLB ? "down" : "up"); 
99 // @todo Set LStick B. 
100 
101 int16 rawRX = state.Gamepad.sThumbRX; 
102 float axisRXNorm = (rawRX < 0) ? float(rawRX)/32768.0f : float(rawRX)/32767.0f
103 // @todo Set RStick X. 
104 
105 int16 rawRY = state.Gamepad.sThumbRY; 
106 float axisRYNorm = (rawRY < 0) ? float(rawRY)/32768.0f : float(rawRY)/32767.0f
107 // @todo Set RStick Y. 
108 
109 bool rawRB = (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false
110 
111 float leftTriggerNorm = float(state.Gamepad.bLeftTrigger) / 255.0f
112 LTrigger.SetDisplacementRaw(leftTriggerNorm); 
113 
114 float rightTriggerNorm = float(state.Gamepad.bRightTrigger) / 255.0f
115 RTrigger.SetDisplacementRaw(rightTriggerNorm); 
116 
117 PollingPacketNumber = state.dwPacketNumber; 
118
119
120 else 
121
122 // Controller is not connected. This can happen if the detection thread of the controller system has not 
123 // realized yet that the controller was disconnected (and stopped the polling thread). In this case we can 
124 // just stop polling. Note that exiting the loop does not stop the thread from being joinable. It is still 
125 // considered connected until the detection thread calls StopPolling. In the mean time, it only makes sense 
126 // to reset the components. 
127 LTrigger.Reset(); 
128 RTrigger.Reset(); 
129 break
130
131 #endif 
132 
133 // This unique_lock is just a more powerful version of lock_guard. Supports subsequent unlocking/locking which 
134 // is presumably needed by wait_for. In any case, wait_for needs this type of lock. 
135 std::unique_lock<std::mutex> lock(Mutex); 
136 bool exitRequested = PollingExitCondition.wait_for(lock&: lock, rtime: std::chrono::nanoseconds(PollingPeriod_ns), p: [this]{ return PollingExitRequested; }); 
137 if (exitRequested
138 break
139
140
141 
142 
143void tContGamepad::Update() 
144
145 LStick.Update(); 
146 RStick.Update(); 
147 DPad.Update(); 
148 LTrigger.Update(); 
149 RTrigger.Update(); 
150 LViewButton.Update(); 
151 RMenuButton.Update(); 
152 LBumperButton.Update(); 
153 RBumperButton.Update(); 
154 XButton.Update(); 
155 YButton.Update(); 
156 AButton.Update(); 
157 BButton.Update(); 
158
159 
160 
161void tContGamepad::SetDefinition() 
162
163 #ifdef PLATFORM_WINDOWS 
164 
165 tPrintf("Gamepad %d Set Definition", int(GamepadID)); 
166 WinXInputCapabilitiesEx capsEx; 
167 tStd::tMemclr(&capsEx, sizeof(WinXInputCapabilitiesEx)); 
168 if (XInputGetCapabilitiesEx(1, int(GamepadID), 0, &capsEx) == WinErrorSuccess) 
169
170 tVidPid vidpid(uint16(capsEx.vendorId), uint16(capsEx.productId)); 
171 const tContDefn* defn = tLookupContDefn(vidpid); 
172 const char* vendor = defn ? defn->Vendor : "unknown"
173 const char* product = defn ? defn->Product : "unknown"
174 tPrintf 
175
176 "Gamepad vid = 0x%04X pid = 0x%04X Vendor:%s Product:%s\n"
177 int(capsEx.vendorId), int(capsEx.productId), vendor, product 
178 ); 
179 if (defn) 
180 Definition = *defn; 
181 else 
182 Definition.SetGeneric(); 
183
184 else 
185
186 tPrintf("Using generic definition\n"); 
187 Definition.SetGeneric(); 
188
189 
190 #else 
191 Definition.SetGeneric(); 
192 #endif 
193
194 
195 
196void tContGamepad::ClearDefinition() 
197
198 Definition.Clear(); 
199
200 
201 
202
203