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
36void 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
47void 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
57CInput::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
80void 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
92void CInput::Shutdown()
93{
94 SDL_free(mem: m_pClipboardText);
95 CloseJoysticks();
96}
97
98void 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
118bool 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
138void 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
156void 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
165float CInput::GetJoystickDeadzone()
166{
167 return minimum(a: g_Config.m_InpControllerTolerance / 50.0f, b: 0.995f);
168}
169
170CInput::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
184void 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
193void 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
199float 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
204void 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
221void CInput::CJoystick::GetHatValue(int Hat, int (&HatKeys)[2])
222{
223 GetJoystickHatKeys(Hat, HatValue: SDL_JoystickGetHat(joystick: m_pDelegate, hat: Hat), HatKeys);
224}
225
226bool 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
244bool 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
260bool 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
280void CInput::MouseModeAbsolute()
281{
282 m_InputGrabbed = false;
283 SDL_SetRelativeMouseMode(enabled: SDL_FALSE);
284 Graphics()->SetWindowGrab(false);
285}
286
287void 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
298void CInput::NativeMousePos(int *pX, int *pY) const
299{
300 SDL_GetMouseState(x: pX, y: pY);
301}
302
303bool CInput::NativeMousePressed(int Index)
304{
305 int i = SDL_GetMouseState(x: nullptr, y: nullptr);
306 return (i & SDL_BUTTON(Index)) != 0;
307}
308
309const char *CInput::GetClipboardText()
310{
311 SDL_free(mem: m_pClipboardText);
312 m_pClipboardText = SDL_GetClipboardText();
313 return m_pClipboardText;
314}
315
316void CInput::SetClipboardText(const char *pText)
317{
318 SDL_SetClipboardText(text: pText);
319}
320
321void CInput::StartTextInput()
322{
323 // enable system messages for IME
324 SDL_EventState(type: SDL_SYSWMEVENT, SDL_ENABLE);
325 SDL_StartTextInput();
326}
327
328void 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
339void 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
351void 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
358float CInput::GetUpdateTime() const
359{
360 return m_UpdateTime;
361}
362
363bool CInput::KeyState(int Key) const
364{
365 if(Key < KEY_FIRST || Key >= KEY_LAST)
366 return false;
367 return m_aInputState[Key];
368}
369
370void 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
393void 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
420void 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
459void 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
484void 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
517void CInput::HandleJoystickAddedEvent(const SDL_JoyDeviceEvent &Event)
518{
519 if(OpenJoystick(JoystickIndex: Event.which))
520 {
521 UpdateActiveJoystick();
522 }
523}
524
525void 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
542void 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
552static 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
586int 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
823void 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
870bool 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
881IEngineInput *CreateEngineInput()
882{
883 return new CInput;
884}
885