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