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