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