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