1 | /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ |
2 | /* If you are missing that file, acquire a complete release at teeworlds.com. */ |
3 | #include <SDL.h> |
4 | |
5 | #include <base/system.h> |
6 | #include <engine/console.h> |
7 | #include <engine/graphics.h> |
8 | #include <engine/input.h> |
9 | #include <engine/keys.h> |
10 | #include <engine/shared/config.h> |
11 | |
12 | #include "input.h" |
13 | |
14 | // this header is protected so you don't include it from anywhere |
15 | #define KEYS_INCLUDE |
16 | #include "keynames.h" |
17 | #undef KEYS_INCLUDE |
18 | |
19 | // support older SDL version (pre 2.0.6) |
20 | #ifndef SDL_JOYSTICK_AXIS_MIN |
21 | #define SDL_JOYSTICK_AXIS_MIN (-32768) |
22 | #endif |
23 | #ifndef SDL_JOYSTICK_AXIS_MAX |
24 | #define SDL_JOYSTICK_AXIS_MAX 32767 |
25 | #endif |
26 | |
27 | #if defined(CONF_FAMILY_WINDOWS) |
28 | #include <windows.h> |
29 | // windows.h must be included before imm.h, but clang-format requires includes to be sorted alphabetically, hence this comment. |
30 | #include <imm.h> |
31 | #endif |
32 | |
33 | // for platform specific features that aren't available or are broken in SDL |
34 | #include <SDL_syswm.h> |
35 | |
36 | void CInput::AddEvent(char *pText, int Key, int Flags) |
37 | { |
38 | if(m_NumEvents != INPUT_BUFFER_SIZE) |
39 | { |
40 | m_aInputEvents[m_NumEvents].m_Key = Key; |
41 | m_aInputEvents[m_NumEvents].m_Flags = Flags; |
42 | if(pText == nullptr) |
43 | m_aInputEvents[m_NumEvents].m_aText[0] = '\0'; |
44 | else |
45 | str_copy(dst&: m_aInputEvents[m_NumEvents].m_aText, src: pText); |
46 | m_aInputEvents[m_NumEvents].m_InputCount = m_InputCounter; |
47 | m_NumEvents++; |
48 | } |
49 | } |
50 | |
51 | CInput::CInput() |
52 | { |
53 | mem_zero(block: m_aInputCount, size: sizeof(m_aInputCount)); |
54 | mem_zero(block: m_aInputState, size: sizeof(m_aInputState)); |
55 | |
56 | m_LastUpdate = 0; |
57 | m_UpdateTime = 0.0f; |
58 | |
59 | m_InputCounter = 1; |
60 | m_InputGrabbed = false; |
61 | |
62 | m_MouseDoubleClick = false; |
63 | |
64 | m_NumEvents = 0; |
65 | m_MouseFocus = true; |
66 | |
67 | m_pClipboardText = nullptr; |
68 | |
69 | m_CompositionLength = COMP_LENGTH_INACTIVE; |
70 | m_CompositionCursor = 0; |
71 | m_CandidateSelectedIndex = -1; |
72 | |
73 | m_aDropFile[0] = '\0'; |
74 | } |
75 | |
76 | void CInput::Init() |
77 | { |
78 | StopTextInput(); |
79 | |
80 | m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>(); |
81 | m_pConsole = Kernel()->RequestInterface<IConsole>(); |
82 | |
83 | MouseModeRelative(); |
84 | |
85 | InitJoysticks(); |
86 | } |
87 | |
88 | void CInput::Shutdown() |
89 | { |
90 | SDL_free(mem: m_pClipboardText); |
91 | CloseJoysticks(); |
92 | } |
93 | |
94 | void CInput::InitJoysticks() |
95 | { |
96 | if(!SDL_WasInit(SDL_INIT_JOYSTICK)) |
97 | { |
98 | if(SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) |
99 | { |
100 | dbg_msg(sys: "joystick" , fmt: "Unable to init SDL joystick system: %s" , SDL_GetError()); |
101 | return; |
102 | } |
103 | } |
104 | |
105 | const int NumJoysticks = SDL_NumJoysticks(); |
106 | dbg_msg(sys: "joystick" , fmt: "%d joystick(s) found" , NumJoysticks); |
107 | for(int i = 0; i < NumJoysticks; i++) |
108 | OpenJoystick(JoystickIndex: i); |
109 | UpdateActiveJoystick(); |
110 | |
111 | Console()->Chain(pName: "inp_controller_guid" , pfnChainFunc: ConchainJoystickGuidChanged, pUser: this); |
112 | } |
113 | |
114 | bool CInput::OpenJoystick(int JoystickIndex) |
115 | { |
116 | SDL_Joystick *pJoystick = SDL_JoystickOpen(device_index: JoystickIndex); |
117 | if(!pJoystick) |
118 | { |
119 | dbg_msg(sys: "joystick" , fmt: "Could not open joystick %d: '%s'" , JoystickIndex, SDL_GetError()); |
120 | return false; |
121 | } |
122 | if(std::find_if(first: m_vJoysticks.begin(), last: m_vJoysticks.end(), pred: [pJoystick](const CJoystick &Joystick) -> bool { return Joystick.m_pDelegate == pJoystick; }) != m_vJoysticks.end()) |
123 | { |
124 | // Joystick has already been added |
125 | return false; |
126 | } |
127 | m_vJoysticks.emplace_back(args: this, args: m_vJoysticks.size(), args&: pJoystick); |
128 | const CJoystick &Joystick = m_vJoysticks[m_vJoysticks.size() - 1]; |
129 | dbg_msg(sys: "joystick" , fmt: "Opened joystick %d '%s' (%d axes, %d buttons, %d balls, %d hats)" , JoystickIndex, Joystick.GetName(), |
130 | Joystick.GetNumAxes(), Joystick.GetNumButtons(), Joystick.GetNumBalls(), Joystick.GetNumHats()); |
131 | return true; |
132 | } |
133 | |
134 | void CInput::UpdateActiveJoystick() |
135 | { |
136 | m_pActiveJoystick = nullptr; |
137 | if(m_vJoysticks.empty()) |
138 | return; |
139 | for(auto &Joystick : m_vJoysticks) |
140 | { |
141 | if(str_comp(a: Joystick.GetGUID(), b: g_Config.m_InpControllerGUID) == 0) |
142 | { |
143 | m_pActiveJoystick = &Joystick; |
144 | return; |
145 | } |
146 | } |
147 | // Fall back to first joystick if no match was found |
148 | if(!m_pActiveJoystick) |
149 | m_pActiveJoystick = &m_vJoysticks.front(); |
150 | } |
151 | |
152 | void CInput::ConchainJoystickGuidChanged(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) |
153 | { |
154 | pfnCallback(pResult, pCallbackUserData); |
155 | if(pResult->NumArguments() == 1) |
156 | { |
157 | static_cast<CInput *>(pUserData)->UpdateActiveJoystick(); |
158 | } |
159 | } |
160 | |
161 | float CInput::GetJoystickDeadzone() |
162 | { |
163 | return minimum(a: g_Config.m_InpControllerTolerance / 50.0f, b: 0.995f); |
164 | } |
165 | |
166 | CInput::CJoystick::CJoystick(CInput *pInput, int Index, SDL_Joystick *pDelegate) |
167 | { |
168 | m_pInput = pInput; |
169 | m_Index = Index; |
170 | m_pDelegate = pDelegate; |
171 | m_NumAxes = SDL_JoystickNumAxes(joystick: pDelegate); |
172 | m_NumButtons = SDL_JoystickNumButtons(joystick: pDelegate); |
173 | m_NumBalls = SDL_JoystickNumBalls(joystick: pDelegate); |
174 | m_NumHats = SDL_JoystickNumHats(joystick: pDelegate); |
175 | str_copy(dst&: m_aName, src: SDL_JoystickName(joystick: pDelegate)); |
176 | SDL_JoystickGetGUIDString(guid: SDL_JoystickGetGUID(joystick: pDelegate), pszGUID: m_aGUID, cbGUID: sizeof(m_aGUID)); |
177 | m_InstanceId = SDL_JoystickInstanceID(joystick: pDelegate); |
178 | } |
179 | |
180 | void CInput::CloseJoysticks() |
181 | { |
182 | if(SDL_WasInit(SDL_INIT_JOYSTICK)) |
183 | SDL_QuitSubSystem(SDL_INIT_JOYSTICK); |
184 | |
185 | m_vJoysticks.clear(); |
186 | m_pActiveJoystick = nullptr; |
187 | } |
188 | |
189 | void CInput::SetActiveJoystick(size_t Index) |
190 | { |
191 | m_pActiveJoystick = &m_vJoysticks[Index]; |
192 | str_copy(dst&: g_Config.m_InpControllerGUID, src: m_pActiveJoystick->GetGUID()); |
193 | } |
194 | |
195 | float CInput::CJoystick::GetAxisValue(int Axis) |
196 | { |
197 | return (SDL_JoystickGetAxis(joystick: m_pDelegate, axis: Axis) - SDL_JOYSTICK_AXIS_MIN) / (float)(SDL_JOYSTICK_AXIS_MAX - SDL_JOYSTICK_AXIS_MIN) * 2.0f - 1.0f; |
198 | } |
199 | |
200 | void CInput::CJoystick::GetJoystickHatKeys(int Hat, int HatValue, int (&HatKeys)[2]) |
201 | { |
202 | if(HatValue & SDL_HAT_UP) |
203 | HatKeys[0] = KEY_JOY_HAT0_UP + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; |
204 | else if(HatValue & SDL_HAT_DOWN) |
205 | HatKeys[0] = KEY_JOY_HAT0_DOWN + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; |
206 | else |
207 | HatKeys[0] = KEY_UNKNOWN; |
208 | |
209 | if(HatValue & SDL_HAT_LEFT) |
210 | HatKeys[1] = KEY_JOY_HAT0_LEFT + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; |
211 | else if(HatValue & SDL_HAT_RIGHT) |
212 | HatKeys[1] = KEY_JOY_HAT0_RIGHT + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; |
213 | else |
214 | HatKeys[1] = KEY_UNKNOWN; |
215 | } |
216 | |
217 | void CInput::CJoystick::GetHatValue(int Hat, int (&HatKeys)[2]) |
218 | { |
219 | return GetJoystickHatKeys(Hat, HatValue: SDL_JoystickGetHat(joystick: m_pDelegate, hat: Hat), HatKeys); |
220 | } |
221 | |
222 | bool CInput::CJoystick::Relative(float *pX, float *pY) |
223 | { |
224 | if(!Input()->m_MouseFocus || !Input()->m_InputGrabbed || !g_Config.m_InpControllerEnable) |
225 | return false; |
226 | |
227 | const vec2 RawJoystickPos = vec2(GetAxisValue(Axis: g_Config.m_InpControllerX), GetAxisValue(Axis: g_Config.m_InpControllerY)); |
228 | const float Len = length(a: RawJoystickPos); |
229 | const float DeadZone = Input()->GetJoystickDeadzone(); |
230 | if(Len > DeadZone) |
231 | { |
232 | const float Factor = 2500.0f * Input()->GetUpdateTime() * maximum(a: (Len - DeadZone) / (1.0f - DeadZone), b: 0.001f) / Len; |
233 | *pX = RawJoystickPos.x * Factor; |
234 | *pY = RawJoystickPos.y * Factor; |
235 | return true; |
236 | } |
237 | return false; |
238 | } |
239 | |
240 | bool CInput::CJoystick::Absolute(float *pX, float *pY) |
241 | { |
242 | if(!Input()->m_MouseFocus || !Input()->m_InputGrabbed || !g_Config.m_InpControllerEnable) |
243 | return false; |
244 | |
245 | const vec2 RawJoystickPos = vec2(GetAxisValue(Axis: g_Config.m_InpControllerX), GetAxisValue(Axis: g_Config.m_InpControllerY)); |
246 | const float DeadZone = Input()->GetJoystickDeadzone(); |
247 | if(dot(a: RawJoystickPos, b: RawJoystickPos) > DeadZone * DeadZone) |
248 | { |
249 | *pX = RawJoystickPos.x; |
250 | *pY = RawJoystickPos.y; |
251 | return true; |
252 | } |
253 | return false; |
254 | } |
255 | |
256 | bool CInput::MouseRelative(float *pX, float *pY) |
257 | { |
258 | if(!m_MouseFocus || !m_InputGrabbed) |
259 | return false; |
260 | |
261 | ivec2 Relative; |
262 | #if defined(CONF_PLATFORM_ANDROID) // No relative mouse on Android |
263 | ivec2 CurrentPos; |
264 | SDL_GetMouseState(&CurrentPos.x, &CurrentPos.y); |
265 | Relative = CurrentPos - m_LastMousePos; |
266 | m_LastMousePos = CurrentPos; |
267 | #else |
268 | SDL_GetRelativeMouseState(x: &Relative.x, y: &Relative.y); |
269 | #endif |
270 | |
271 | *pX = Relative.x; |
272 | *pY = Relative.y; |
273 | return *pX != 0.0f || *pY != 0.0f; |
274 | } |
275 | |
276 | void CInput::MouseModeAbsolute() |
277 | { |
278 | m_InputGrabbed = false; |
279 | SDL_SetRelativeMouseMode(enabled: SDL_FALSE); |
280 | Graphics()->SetWindowGrab(false); |
281 | } |
282 | |
283 | void CInput::MouseModeRelative() |
284 | { |
285 | m_InputGrabbed = true; |
286 | #if !defined(CONF_PLATFORM_ANDROID) // No relative mouse on Android |
287 | SDL_SetRelativeMouseMode(enabled: SDL_TRUE); |
288 | #endif |
289 | Graphics()->SetWindowGrab(true); |
290 | // Clear pending relative mouse motion |
291 | SDL_GetRelativeMouseState(x: nullptr, y: nullptr); |
292 | } |
293 | |
294 | void CInput::NativeMousePos(int *pX, int *pY) const |
295 | { |
296 | SDL_GetMouseState(x: pX, y: pY); |
297 | } |
298 | |
299 | bool CInput::NativeMousePressed(int Index) |
300 | { |
301 | int i = SDL_GetMouseState(x: nullptr, y: nullptr); |
302 | return (i & SDL_BUTTON(Index)) != 0; |
303 | } |
304 | |
305 | bool CInput::MouseDoubleClick() |
306 | { |
307 | if(m_MouseDoubleClick) |
308 | { |
309 | m_MouseDoubleClick = false; |
310 | return true; |
311 | } |
312 | return false; |
313 | } |
314 | |
315 | const char *CInput::GetClipboardText() |
316 | { |
317 | SDL_free(mem: m_pClipboardText); |
318 | m_pClipboardText = SDL_GetClipboardText(); |
319 | return m_pClipboardText; |
320 | } |
321 | |
322 | void CInput::SetClipboardText(const char *pText) |
323 | { |
324 | SDL_SetClipboardText(text: pText); |
325 | } |
326 | |
327 | void CInput::StartTextInput() |
328 | { |
329 | // enable system messages for IME |
330 | SDL_EventState(type: SDL_SYSWMEVENT, SDL_ENABLE); |
331 | SDL_StartTextInput(); |
332 | } |
333 | |
334 | void CInput::StopTextInput() |
335 | { |
336 | SDL_StopTextInput(); |
337 | // disable system messages for performance |
338 | SDL_EventState(type: SDL_SYSWMEVENT, SDL_DISABLE); |
339 | m_CompositionLength = COMP_LENGTH_INACTIVE; |
340 | m_CompositionCursor = 0; |
341 | m_aComposition[0] = '\0'; |
342 | m_vCandidates.clear(); |
343 | } |
344 | |
345 | void CInput::Clear() |
346 | { |
347 | mem_zero(block: m_aInputState, size: sizeof(m_aInputState)); |
348 | mem_zero(block: m_aInputCount, size: sizeof(m_aInputCount)); |
349 | m_NumEvents = 0; |
350 | } |
351 | |
352 | bool CInput::KeyState(int Key) const |
353 | { |
354 | if(Key < KEY_FIRST || Key >= KEY_LAST) |
355 | return false; |
356 | return m_aInputState[Key]; |
357 | } |
358 | |
359 | void CInput::UpdateMouseState() |
360 | { |
361 | const int MouseState = SDL_GetMouseState(x: nullptr, y: nullptr); |
362 | if(MouseState & SDL_BUTTON(SDL_BUTTON_LEFT)) |
363 | m_aInputState[KEY_MOUSE_1] = 1; |
364 | if(MouseState & SDL_BUTTON(SDL_BUTTON_RIGHT)) |
365 | m_aInputState[KEY_MOUSE_2] = 1; |
366 | if(MouseState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) |
367 | m_aInputState[KEY_MOUSE_3] = 1; |
368 | if(MouseState & SDL_BUTTON(SDL_BUTTON_X1)) |
369 | m_aInputState[KEY_MOUSE_4] = 1; |
370 | if(MouseState & SDL_BUTTON(SDL_BUTTON_X2)) |
371 | m_aInputState[KEY_MOUSE_5] = 1; |
372 | if(MouseState & SDL_BUTTON(6)) |
373 | m_aInputState[KEY_MOUSE_6] = 1; |
374 | if(MouseState & SDL_BUTTON(7)) |
375 | m_aInputState[KEY_MOUSE_7] = 1; |
376 | if(MouseState & SDL_BUTTON(8)) |
377 | m_aInputState[KEY_MOUSE_8] = 1; |
378 | if(MouseState & SDL_BUTTON(9)) |
379 | m_aInputState[KEY_MOUSE_9] = 1; |
380 | } |
381 | |
382 | void CInput::UpdateJoystickState() |
383 | { |
384 | if(!g_Config.m_InpControllerEnable) |
385 | return; |
386 | IJoystick *pJoystick = GetActiveJoystick(); |
387 | if(!pJoystick) |
388 | return; |
389 | |
390 | const float DeadZone = GetJoystickDeadzone(); |
391 | for(int Axis = 0; Axis < pJoystick->GetNumAxes(); Axis++) |
392 | { |
393 | const float Value = pJoystick->GetAxisValue(Axis); |
394 | const int LeftKey = KEY_JOY_AXIS_0_LEFT + 2 * Axis; |
395 | const int RightKey = LeftKey + 1; |
396 | m_aInputState[LeftKey] = Value <= -DeadZone; |
397 | m_aInputState[RightKey] = Value >= DeadZone; |
398 | } |
399 | |
400 | for(int Hat = 0; Hat < pJoystick->GetNumHats(); Hat++) |
401 | { |
402 | int HatKeys[2]; |
403 | pJoystick->GetHatValue(Hat, HatKeys); |
404 | for(int Key = KEY_JOY_HAT0_UP + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key <= KEY_JOY_HAT0_DOWN + Hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key++) |
405 | m_aInputState[Key] = HatKeys[0] == Key || HatKeys[1] == Key; |
406 | } |
407 | } |
408 | |
409 | void CInput::HandleJoystickAxisMotionEvent(const SDL_JoyAxisEvent &Event) |
410 | { |
411 | if(!g_Config.m_InpControllerEnable) |
412 | return; |
413 | CJoystick *pJoystick = GetActiveJoystick(); |
414 | if(!pJoystick || pJoystick->GetInstanceId() != Event.which) |
415 | return; |
416 | if(Event.axis >= NUM_JOYSTICK_AXES) |
417 | return; |
418 | |
419 | const int LeftKey = KEY_JOY_AXIS_0_LEFT + 2 * Event.axis; |
420 | const int RightKey = LeftKey + 1; |
421 | const float DeadZone = GetJoystickDeadzone(); |
422 | |
423 | if(Event.value <= SDL_JOYSTICK_AXIS_MIN * DeadZone && !m_aInputState[LeftKey]) |
424 | { |
425 | m_aInputState[LeftKey] = true; |
426 | m_aInputCount[LeftKey] = m_InputCounter; |
427 | AddEvent(pText: nullptr, Key: LeftKey, Flags: IInput::FLAG_PRESS); |
428 | } |
429 | else if(Event.value > SDL_JOYSTICK_AXIS_MIN * DeadZone && m_aInputState[LeftKey]) |
430 | { |
431 | m_aInputState[LeftKey] = false; |
432 | AddEvent(pText: nullptr, Key: LeftKey, Flags: IInput::FLAG_RELEASE); |
433 | } |
434 | |
435 | if(Event.value >= SDL_JOYSTICK_AXIS_MAX * DeadZone && !m_aInputState[RightKey]) |
436 | { |
437 | m_aInputState[RightKey] = true; |
438 | m_aInputCount[RightKey] = m_InputCounter; |
439 | AddEvent(pText: nullptr, Key: RightKey, Flags: IInput::FLAG_PRESS); |
440 | } |
441 | else if(Event.value < SDL_JOYSTICK_AXIS_MAX * DeadZone && m_aInputState[RightKey]) |
442 | { |
443 | m_aInputState[RightKey] = false; |
444 | AddEvent(pText: nullptr, Key: RightKey, Flags: IInput::FLAG_RELEASE); |
445 | } |
446 | } |
447 | |
448 | void CInput::HandleJoystickButtonEvent(const SDL_JoyButtonEvent &Event) |
449 | { |
450 | if(!g_Config.m_InpControllerEnable) |
451 | return; |
452 | CJoystick *pJoystick = GetActiveJoystick(); |
453 | if(!pJoystick || pJoystick->GetInstanceId() != Event.which) |
454 | return; |
455 | if(Event.button >= NUM_JOYSTICK_BUTTONS) |
456 | return; |
457 | |
458 | const int Key = Event.button + KEY_JOYSTICK_BUTTON_0; |
459 | |
460 | if(Event.type == SDL_JOYBUTTONDOWN) |
461 | { |
462 | m_aInputState[Key] = true; |
463 | m_aInputCount[Key] = m_InputCounter; |
464 | AddEvent(pText: nullptr, Key, Flags: IInput::FLAG_PRESS); |
465 | } |
466 | else if(Event.type == SDL_JOYBUTTONUP) |
467 | { |
468 | m_aInputState[Key] = false; |
469 | AddEvent(pText: nullptr, Key, Flags: IInput::FLAG_RELEASE); |
470 | } |
471 | } |
472 | |
473 | void CInput::HandleJoystickHatMotionEvent(const SDL_JoyHatEvent &Event) |
474 | { |
475 | if(!g_Config.m_InpControllerEnable) |
476 | return; |
477 | CJoystick *pJoystick = GetActiveJoystick(); |
478 | if(!pJoystick || pJoystick->GetInstanceId() != Event.which) |
479 | return; |
480 | if(Event.hat >= NUM_JOYSTICK_HATS) |
481 | return; |
482 | |
483 | int HatKeys[2]; |
484 | CJoystick::GetJoystickHatKeys(Hat: Event.hat, HatValue: Event.value, HatKeys); |
485 | |
486 | for(int Key = KEY_JOY_HAT0_UP + Event.hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key <= KEY_JOY_HAT0_DOWN + Event.hat * NUM_JOYSTICK_BUTTONS_PER_HAT; Key++) |
487 | { |
488 | if(Key != HatKeys[0] && Key != HatKeys[1] && m_aInputState[Key]) |
489 | { |
490 | m_aInputState[Key] = false; |
491 | AddEvent(pText: nullptr, Key, Flags: IInput::FLAG_RELEASE); |
492 | } |
493 | } |
494 | |
495 | for(int CurrentKey : HatKeys) |
496 | { |
497 | if(CurrentKey != KEY_UNKNOWN && !m_aInputState[CurrentKey]) |
498 | { |
499 | m_aInputState[CurrentKey] = true; |
500 | m_aInputCount[CurrentKey] = m_InputCounter; |
501 | AddEvent(pText: nullptr, Key: CurrentKey, Flags: IInput::FLAG_PRESS); |
502 | } |
503 | } |
504 | } |
505 | |
506 | void CInput::HandleJoystickAddedEvent(const SDL_JoyDeviceEvent &Event) |
507 | { |
508 | if(OpenJoystick(JoystickIndex: Event.which)) |
509 | { |
510 | UpdateActiveJoystick(); |
511 | } |
512 | } |
513 | |
514 | void CInput::HandleJoystickRemovedEvent(const SDL_JoyDeviceEvent &Event) |
515 | { |
516 | auto RemovedJoystick = std::find_if(first: m_vJoysticks.begin(), last: m_vJoysticks.end(), pred: [Event](const CJoystick &Joystick) -> bool { return Joystick.GetInstanceId() == Event.which; }); |
517 | if(RemovedJoystick != m_vJoysticks.end()) |
518 | { |
519 | dbg_msg(sys: "joystick" , fmt: "Closed joystick %d '%s'" , (*RemovedJoystick).GetIndex(), (*RemovedJoystick).GetName()); |
520 | auto NextJoystick = m_vJoysticks.erase(position: RemovedJoystick); |
521 | // Adjust indices of following joysticks |
522 | while(NextJoystick != m_vJoysticks.end()) |
523 | { |
524 | (*NextJoystick).m_Index--; |
525 | ++NextJoystick; |
526 | } |
527 | UpdateActiveJoystick(); |
528 | } |
529 | } |
530 | |
531 | void CInput::SetCompositionWindowPosition(float X, float Y, float H) |
532 | { |
533 | SDL_Rect Rect; |
534 | Rect.x = X / m_pGraphics->ScreenHiDPIScale(); |
535 | Rect.y = Y / m_pGraphics->ScreenHiDPIScale(); |
536 | Rect.h = H / m_pGraphics->ScreenHiDPIScale(); |
537 | Rect.w = 0; |
538 | SDL_SetTextInputRect(rect: &Rect); |
539 | } |
540 | |
541 | int CInput::Update() |
542 | { |
543 | const int64_t Now = time_get(); |
544 | if(m_LastUpdate != 0) |
545 | { |
546 | const float Diff = (Now - m_LastUpdate) / (float)time_freq(); |
547 | m_UpdateTime = m_UpdateTime == 0.0f ? Diff : (m_UpdateTime * 0.8f + Diff * 0.2f); |
548 | } |
549 | m_LastUpdate = Now; |
550 | |
551 | // keep the counter between 1..0xFFFF, 0 means not pressed |
552 | m_InputCounter = (m_InputCounter % 0xFFFF) + 1; |
553 | |
554 | // Ensure that we have the latest keyboard, mouse and joystick state |
555 | SDL_PumpEvents(); |
556 | |
557 | int NumKeyStates; |
558 | const Uint8 *pState = SDL_GetKeyboardState(numkeys: &NumKeyStates); |
559 | if(NumKeyStates >= KEY_MOUSE_1) |
560 | NumKeyStates = KEY_MOUSE_1; |
561 | mem_copy(dest: m_aInputState, source: pState, size: NumKeyStates); |
562 | mem_zero(block: m_aInputState + NumKeyStates, size: KEY_LAST - NumKeyStates); |
563 | |
564 | // these states must always be updated manually because they are not in the SDL_GetKeyboardState from SDL |
565 | UpdateMouseState(); |
566 | UpdateJoystickState(); |
567 | |
568 | SDL_Event Event; |
569 | bool IgnoreKeys = false; |
570 | while(SDL_PollEvent(event: &Event)) |
571 | { |
572 | int Scancode = 0; |
573 | int Action = IInput::FLAG_PRESS; |
574 | switch(Event.type) |
575 | { |
576 | case SDL_SYSWMEVENT: |
577 | ProcessSystemMessage(pMsg: Event.syswm.msg); |
578 | break; |
579 | |
580 | case SDL_TEXTEDITING: |
581 | { |
582 | m_CompositionLength = str_length(str: Event.edit.text); |
583 | if(m_CompositionLength) |
584 | { |
585 | str_copy(dst&: m_aComposition, src: Event.edit.text); |
586 | m_CompositionCursor = 0; |
587 | for(int i = 0; i < Event.edit.start; i++) |
588 | m_CompositionCursor = str_utf8_forward(str: m_aComposition, cursor: m_CompositionCursor); |
589 | // Event.edit.length is currently unused on Windows and will always be 0, so we don't support selecting composition text |
590 | AddEvent(pText: nullptr, Key: KEY_UNKNOWN, Flags: IInput::FLAG_TEXT); |
591 | } |
592 | else |
593 | { |
594 | m_aComposition[0] = '\0'; |
595 | m_CompositionLength = 0; |
596 | m_CompositionCursor = 0; |
597 | } |
598 | break; |
599 | } |
600 | |
601 | case SDL_TEXTINPUT: |
602 | m_aComposition[0] = '\0'; |
603 | m_CompositionLength = COMP_LENGTH_INACTIVE; |
604 | m_CompositionCursor = 0; |
605 | AddEvent(pText: Event.text.text, Key: KEY_UNKNOWN, Flags: IInput::FLAG_TEXT); |
606 | break; |
607 | |
608 | // handle keys |
609 | case SDL_KEYDOWN: |
610 | // See SDL_Keymod for possible modifiers: |
611 | // NONE = 0 |
612 | // LSHIFT = 1 |
613 | // RSHIFT = 2 |
614 | // LCTRL = 64 |
615 | // RCTRL = 128 |
616 | // LALT = 256 |
617 | // RALT = 512 |
618 | // LGUI = 1024 |
619 | // RGUI = 2048 |
620 | // NUM = 4096 |
621 | // CAPS = 8192 |
622 | // MODE = 16384 |
623 | // Sum if you want to ignore multiple modifiers. |
624 | if(!(Event.key.keysym.mod & g_Config.m_InpIgnoredModifiers)) |
625 | { |
626 | Scancode = g_Config.m_InpTranslatedKeys ? SDL_GetScancodeFromKey(key: Event.key.keysym.sym) : Event.key.keysym.scancode; |
627 | } |
628 | break; |
629 | case SDL_KEYUP: |
630 | Action = IInput::FLAG_RELEASE; |
631 | Scancode = g_Config.m_InpTranslatedKeys ? SDL_GetScancodeFromKey(key: Event.key.keysym.sym) : Event.key.keysym.scancode; |
632 | break; |
633 | |
634 | // handle the joystick events |
635 | case SDL_JOYAXISMOTION: |
636 | HandleJoystickAxisMotionEvent(Event: Event.jaxis); |
637 | break; |
638 | |
639 | case SDL_JOYBUTTONUP: |
640 | case SDL_JOYBUTTONDOWN: |
641 | HandleJoystickButtonEvent(Event: Event.jbutton); |
642 | break; |
643 | |
644 | case SDL_JOYHATMOTION: |
645 | HandleJoystickHatMotionEvent(Event: Event.jhat); |
646 | break; |
647 | |
648 | case SDL_JOYDEVICEADDED: |
649 | HandleJoystickAddedEvent(Event: Event.jdevice); |
650 | break; |
651 | |
652 | case SDL_JOYDEVICEREMOVED: |
653 | HandleJoystickRemovedEvent(Event: Event.jdevice); |
654 | break; |
655 | |
656 | // handle mouse buttons |
657 | case SDL_MOUSEBUTTONUP: |
658 | Action = IInput::FLAG_RELEASE; |
659 | |
660 | [[fallthrough]]; |
661 | case SDL_MOUSEBUTTONDOWN: |
662 | if(Event.button.button == SDL_BUTTON_LEFT) |
663 | Scancode = KEY_MOUSE_1; |
664 | if(Event.button.button == SDL_BUTTON_RIGHT) |
665 | Scancode = KEY_MOUSE_2; |
666 | if(Event.button.button == SDL_BUTTON_MIDDLE) |
667 | Scancode = KEY_MOUSE_3; |
668 | if(Event.button.button == SDL_BUTTON_X1) |
669 | Scancode = KEY_MOUSE_4; |
670 | if(Event.button.button == SDL_BUTTON_X2) |
671 | Scancode = KEY_MOUSE_5; |
672 | if(Event.button.button == 6) |
673 | Scancode = KEY_MOUSE_6; |
674 | if(Event.button.button == 7) |
675 | Scancode = KEY_MOUSE_7; |
676 | if(Event.button.button == 8) |
677 | Scancode = KEY_MOUSE_8; |
678 | if(Event.button.button == 9) |
679 | Scancode = KEY_MOUSE_9; |
680 | if(Event.button.button == SDL_BUTTON_LEFT) |
681 | { |
682 | if(Event.button.clicks % 2 == 0) |
683 | m_MouseDoubleClick = true; |
684 | if(Event.button.clicks == 1) |
685 | m_MouseDoubleClick = false; |
686 | } |
687 | break; |
688 | |
689 | case SDL_MOUSEWHEEL: |
690 | if(Event.wheel.y > 0) |
691 | Scancode = KEY_MOUSE_WHEEL_UP; |
692 | if(Event.wheel.y < 0) |
693 | Scancode = KEY_MOUSE_WHEEL_DOWN; |
694 | if(Event.wheel.x > 0) |
695 | Scancode = KEY_MOUSE_WHEEL_LEFT; |
696 | if(Event.wheel.x < 0) |
697 | Scancode = KEY_MOUSE_WHEEL_RIGHT; |
698 | Action |= IInput::FLAG_RELEASE; |
699 | break; |
700 | |
701 | case SDL_WINDOWEVENT: |
702 | // Ignore keys following a focus gain as they may be part of global |
703 | // shortcuts |
704 | switch(Event.window.event) |
705 | { |
706 | case SDL_WINDOWEVENT_MOVED: |
707 | Graphics()->Move(x: Event.window.data1, y: Event.window.data2); |
708 | break; |
709 | // listen to size changes, this includes our manual changes and the ones by the window manager |
710 | case SDL_WINDOWEVENT_SIZE_CHANGED: |
711 | Graphics()->GotResized(w: Event.window.data1, h: Event.window.data2, RefreshRate: -1); |
712 | break; |
713 | case SDL_WINDOWEVENT_FOCUS_GAINED: |
714 | if(m_InputGrabbed) |
715 | { |
716 | MouseModeRelative(); |
717 | // Clear pending relative mouse motion |
718 | SDL_GetRelativeMouseState(x: nullptr, y: nullptr); |
719 | } |
720 | m_MouseFocus = true; |
721 | IgnoreKeys = true; |
722 | break; |
723 | case SDL_WINDOWEVENT_FOCUS_LOST: |
724 | m_MouseFocus = false; |
725 | IgnoreKeys = true; |
726 | if(m_InputGrabbed) |
727 | { |
728 | MouseModeAbsolute(); |
729 | // Remember that we had relative mouse |
730 | m_InputGrabbed = true; |
731 | } |
732 | break; |
733 | case SDL_WINDOWEVENT_MINIMIZED: |
734 | Graphics()->WindowDestroyNtf(WindowId: Event.window.windowID); |
735 | break; |
736 | |
737 | case SDL_WINDOWEVENT_MAXIMIZED: |
738 | #if defined(CONF_PLATFORM_MACOS) // Todo: remove this when fixed in SDL |
739 | MouseModeAbsolute(); |
740 | MouseModeRelative(); |
741 | #endif |
742 | [[fallthrough]]; |
743 | case SDL_WINDOWEVENT_RESTORED: |
744 | Graphics()->WindowCreateNtf(WindowId: Event.window.windowID); |
745 | break; |
746 | } |
747 | break; |
748 | |
749 | // other messages |
750 | case SDL_QUIT: |
751 | return 1; |
752 | |
753 | case SDL_DROPFILE: |
754 | str_copy(dst&: m_aDropFile, src: Event.drop.file); |
755 | SDL_free(mem: Event.drop.file); |
756 | break; |
757 | } |
758 | |
759 | if(Scancode > KEY_FIRST && Scancode < g_MaxKeys && !IgnoreKeys && !HasComposition()) |
760 | { |
761 | if(Action & IInput::FLAG_PRESS) |
762 | { |
763 | m_aInputState[Scancode] = 1; |
764 | m_aInputCount[Scancode] = m_InputCounter; |
765 | } |
766 | AddEvent(pText: nullptr, Key: Scancode, Flags: Action); |
767 | } |
768 | } |
769 | |
770 | if(m_CompositionLength == 0) |
771 | m_CompositionLength = COMP_LENGTH_INACTIVE; |
772 | |
773 | return 0; |
774 | } |
775 | |
776 | void CInput::ProcessSystemMessage(SDL_SysWMmsg *pMsg) |
777 | { |
778 | #if defined(CONF_FAMILY_WINDOWS) |
779 | // Todo SDL: remove this after SDL2 supports IME candidates |
780 | if(pMsg->subsystem == SDL_SYSWM_WINDOWS && pMsg->msg.win.msg == WM_IME_NOTIFY) |
781 | { |
782 | switch(pMsg->msg.win.wParam) |
783 | { |
784 | case IMN_OPENCANDIDATE: |
785 | case IMN_CHANGECANDIDATE: |
786 | { |
787 | HWND WindowHandle = pMsg->msg.win.hwnd; |
788 | HIMC ImeContext = ImmGetContext(WindowHandle); |
789 | DWORD Size = ImmGetCandidateListW(ImeContext, 0, nullptr, 0); |
790 | LPCANDIDATELIST pCandidateList = nullptr; |
791 | if(Size > 0) |
792 | { |
793 | pCandidateList = (LPCANDIDATELIST)malloc(Size); |
794 | Size = ImmGetCandidateListW(ImeContext, 0, pCandidateList, Size); |
795 | } |
796 | m_vCandidates.clear(); |
797 | if(pCandidateList && Size > 0) |
798 | { |
799 | for(DWORD i = pCandidateList->dwPageStart; i < pCandidateList->dwCount && (int)m_vCandidates.size() < (int)pCandidateList->dwPageSize; i++) |
800 | { |
801 | LPCWSTR pCandidate = (LPCWSTR)((DWORD_PTR)pCandidateList + pCandidateList->dwOffset[i]); |
802 | m_vCandidates.push_back(std::move(windows_wide_to_utf8(pCandidate).value_or("<invalid candidate>" ))); |
803 | } |
804 | m_CandidateSelectedIndex = pCandidateList->dwSelection - pCandidateList->dwPageStart; |
805 | } |
806 | else |
807 | { |
808 | m_CandidateSelectedIndex = -1; |
809 | } |
810 | free(pCandidateList); |
811 | ImmReleaseContext(WindowHandle, ImeContext); |
812 | break; |
813 | } |
814 | case IMN_CLOSECANDIDATE: |
815 | m_vCandidates.clear(); |
816 | m_CandidateSelectedIndex = -1; |
817 | break; |
818 | } |
819 | } |
820 | #endif |
821 | } |
822 | |
823 | bool CInput::GetDropFile(char *aBuf, int Len) |
824 | { |
825 | if(m_aDropFile[0] != '\0') |
826 | { |
827 | str_copy(dst: aBuf, src: m_aDropFile, dst_size: Len); |
828 | m_aDropFile[0] = '\0'; |
829 | return true; |
830 | } |
831 | return false; |
832 | } |
833 | |
834 | IEngineInput *CreateEngineInput() |
835 | { |
836 | return new CInput; |
837 | } |
838 | |