engine: reject mbf21 and shit24 wads. there is no way to know if it is safe to ignore...
[k8vavoom.git] / source / in_sdl.cpp
blob9929e93864963d49659b64b146579c42425c969e
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
16 //**
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
21 //**
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //**
25 //**************************************************************************
26 #ifdef VAVOOM_CUSTOM_SPECIAL_SDL
27 # include <SDL.h>
28 #else
29 # include <SDL2/SDL.h>
30 #endif
31 #include "gamedefs.h"
32 #include "drawer.h"
33 #include "input.h"
34 #include "touch.h"
35 #include "widgets/ui.h"
36 #include "psim/p_player.h"
37 #include "client/client.h"
38 #include "filesys/files.h"
40 //#define SDL_MOUSE_CAPTURE_DEBUG
43 // k8: joysticks have 16 buttons; no, really, you don't need more
45 #define VSDL_JINIT (SDL_INIT_JOYSTICK|SDL_INIT_GAMECONTROLLER)
48 static int cli_NoMouse = 0;
49 static int cli_NoJoy = 0;
50 static int cli_NoCotl = 0;
52 /*static*/ bool cliRegister_input_args =
53 VParsedArgs::RegisterFlagSet("-nomouse", "Disable mouse controls", &cli_NoMouse) &&
54 VParsedArgs::RegisterFlagSet("-nojoystick", "Disable joysticks", &cli_NoJoy) &&
55 VParsedArgs::RegisterFlagReset("-usejoystick", "Enable joysticks", &cli_NoJoy) &&
56 VParsedArgs::RegisterFlagSet("-nocontroller", "Disable controllers", &cli_NoCotl) &&
57 VParsedArgs::RegisterFlagReset("-usecontroller", "Enable controllers", &cli_NoCotl) &&
58 VParsedArgs::RegisterAlias("-nojoy", "-nojoystick") &&
59 VParsedArgs::RegisterAlias("-joy", "-usejoystick") &&
60 VParsedArgs::RegisterAlias("-noctrl", "-nocontroller") &&
61 VParsedArgs::RegisterAlias("-ctrl", "-usecontroller");
64 // ////////////////////////////////////////////////////////////////////////// //
65 class VSdlInputDevice : public VInputDevice {
66 public:
67 VSdlInputDevice ();
68 ~VSdlInputDevice ();
70 virtual void ReadInput () override;
71 virtual void RegrabMouse () override; // called by UI when mouse cursor is turned off
73 virtual void SetClipboardText (VStr text) override;
74 virtual bool HasClipboardText () override;
75 virtual VStr GetClipboardText () override;
77 private:
78 bool mouse; // is mouse enabled?
79 bool winactive; // is current window active?
80 bool firsttime; // when we just grabbed the mouse, we should ignore the first mouse event
81 bool uiwasactive; // was UI active last time we polled the input?
82 bool uimouselast; // was mouse "logically grabbed" last time we polled the input?
83 bool curHidden; // is real mouse cursor hidden?
85 bool currDoGrab; // should we grab the mouse? (cached "m_grab" value)
86 bool currRelative; // should we use relative mode? (cached "m_relative" value)
87 bool relativeFailed; // did setting relative mode failed at least once?
89 float hlRemX;
90 float hlRemY;
92 int mouseLastX;
93 int mouseLastY;
95 vuint32 curmodflags;
97 double timeKbdAllow;
98 bool kbdSuspended; // if set to `true`, wait until `timeKbdAllow` to resume keyboard processing
99 bool hyperDown;
101 SDL_Joystick *joystick;
102 SDL_GameController *controller;
103 SDL_Haptic *haptic;
104 SDL_JoystickID jid;
105 bool joystick_started;
106 bool joystick_controller;
107 bool has_haptic;
108 int joy_num_buttons;
109 int joy_x[2];
110 int joy_y[2];
111 uint32_t joy_newb; // bitmask
112 int joy_oldx[2];
113 int joy_oldy[2];
114 uint32_t joy_oldb; // bitmask
115 uint32_t ctl_trigger; // bitmask
116 int joynum; // also, controller number too
118 // deletes stream; it is ok to pass `nullptr`
119 void LoadControllerMappings (VStream *st);
121 void StartupJoystick ();
122 void ShutdownJoystick (bool closesubsys);
123 void PostJoystick ();
125 void HideRealMouse ();
126 void ShowRealMouse ();
128 void DoUnpress ();
129 void CtlTriggerButton (int idx, bool down);
131 // must be called after `StartupJoystick()`
132 void OpenJoystick (int jnum);
134 void OwnMouse ();
135 void DisownMouse ();
137 public:
138 bool bGotCloseRequest; // used in `CheckForEscape()`
140 bool CheckForEscape ();
144 //==========================================================================
146 // GNetCheckForUserAbortCB
148 //==========================================================================
149 static bool GNetCheckForUserAbortCB (void *udata) {
150 VSdlInputDevice *drv = (VSdlInputDevice *)udata;
151 return drv->CheckForEscape();
155 // ////////////////////////////////////////////////////////////////////////// //
156 VCvarB ui_freemouse("ui_freemouse", false, "Don't pass mouse movement to the camera. Used in various debug modes.", CVAR_Hidden|CVAR_NoShadow);
157 VCvarB ui_want_mouse_at_zero("ui_want_mouse_at_zero", false, "Move real mouse cursor to (0,0) when UI activated?", CVAR_Archive|CVAR_NoShadow);
158 VCvarB ui_mouse_forced("ui_mouse_forced", false, "Forge-grab mouse for UI?", CVAR_Hidden|CVAR_NoShadow);
159 static VCvarB ui_mouse("ui_mouse", false, "Allow using mouse in UI?", CVAR_Archive|CVAR_NoShadow);
160 static VCvarB ui_active("ui_active", false, "Is UI active (used to stop mouse warping if \"ui_mouse\" is false)?", CVAR_Hidden|CVAR_NoShadow);
161 static VCvarB ui_control_waiting("ui_control_waiting", false, "Waiting for new control key (pass mouse buttons)?", CVAR_Hidden|CVAR_NoShadow);
163 static VCvarB m_dbg_cursor("m_dbg_cursor", false, "Do not hide (true) mouse cursor on startup?", CVAR_PreInit|CVAR_Hidden|CVAR_NoShadow);
164 static VCvarB m_dbg_motion("m_dbg_motion", false, "Dump motion events?", CVAR_PreInit|CVAR_Hidden|CVAR_NoShadow);
166 static VCvarB m_grab("m_grab", true, "Grab mouse?", CVAR_Archive|CVAR_NoShadow);
167 static VCvarB m_relative("m_relative", true, "Use relative mouse motion events?", CVAR_Archive|CVAR_NoShadow);
169 static VCvarI ms_rel_mode("ms_rel_mode", "1", "Relative acceleration mode (0: none; 1: hl-like).", CVAR_Archive|CVAR_NoShadow);
171 static VCvarF ms_rel_exponent("ms_rel_exponent", "1", "Relative acceletation distance exponent.", CVAR_Archive|CVAR_NoShadow);
172 static VCvarF ms_rel_senscap("ms_rel_senscap", "2.7", "Relative acceletation sensitivity cap.", CVAR_Archive|CVAR_NoShadow);
173 static VCvarF ms_rel_sensitivity("ms_rel_sensitivity", "0.8", "Relative acceletation sensitivity.", CVAR_Archive|CVAR_NoShadow);
174 static VCvarF ms_rel_sensscale("ms_rel_sensscale", "0.2", "Relative acceletation sensitivity scale.", CVAR_Archive|CVAR_NoShadow);
176 static VCvarB ms_rel_squaredist("ms_rel_squaredist", false, "Use squared distance for relative acceletation?", CVAR_Archive|CVAR_NoShadow);
177 static VCvarB ms_rel_expweird("ms_rel_expweird", false, "Use different exponent formula for relative acceleration?", CVAR_Archive|CVAR_NoShadow);
179 static VCvarF ctl_deadzone_leftstick_x("ctl_deadzone_leftstick_x", "0.08", "Dead zone for left stick (horizontal motion) -- [0..1].", CVAR_Archive|CVAR_NoShadow);
180 static VCvarF ctl_deadzone_leftstick_y("ctl_deadzone_leftstick_y", "0.08", "Dead zone for left stick (vertical motion) -- [0..1].", CVAR_Archive|CVAR_NoShadow);
181 static VCvarF ctl_deadzone_rightstick_x("ctl_deadzone_rightstick_x", "0.08", "Dead zone for right stick (horizontal motion) -- [0..1].", CVAR_Archive|CVAR_NoShadow);
182 static VCvarF ctl_deadzone_rightstick_y("ctl_deadzone_rightstick_y", "0.08", "Dead zone for right stick (vertical motion) -- [0..1].", CVAR_Archive|CVAR_NoShadow);
183 static VCvarF ctl_trigger_left_edge("ctl_trigger_left_edge", "0.8", "Minimal level for registering trigger A -- [0..1].", CVAR_Archive|CVAR_NoShadow);
184 static VCvarF ctl_trigger_right_edge("ctl_trigger_right_edge", "0.8", "Minimal level for registering trigger B -- [0..1].", CVAR_Archive|CVAR_NoShadow);
186 static inline bool IsUIMouse () noexcept { return (ui_mouse.asBool() || ui_mouse_forced.asBool()); }
188 #ifdef VAVOOM_K8_DEVELOPER
189 # define IN_HYPER_BLOCK_DEFAULT true
190 # define IN_FOCUSGAIN_DELAY_DEFAULT "0.05"
191 #else
192 # define IN_HYPER_BLOCK_DEFAULT false
193 # define IN_FOCUSGAIN_DELAY_DEFAULT "0"
194 #endif
195 static VCvarF in_focusgain_delay("in_focusgain_delay", IN_FOCUSGAIN_DELAY_DEFAULT, "Delay before resume keyboard processing after focus gain (seconds).", CVAR_Archive|CVAR_NoShadow);
196 static VCvarB in_hyper_block("in_hyper_block", IN_HYPER_BLOCK_DEFAULT, "Block keyboard input when `Hyper` is pressed.", CVAR_Archive|CVAR_NoShadow);
198 //extern VCvarB screen_fsmode;
199 extern VCvarB gl_current_screen_fsmode;
202 // ////////////////////////////////////////////////////////////////////////// //
203 static int sdl2TranslateKey (SDL_Scancode scan) {
204 if (scan >= SDL_SCANCODE_A && scan <= SDL_SCANCODE_Z) return (int)(scan-SDL_SCANCODE_A+'a');
205 if (scan >= SDL_SCANCODE_1 && scan <= SDL_SCANCODE_9) return (int)(scan-SDL_SCANCODE_1+'1');
207 switch (scan) {
208 case SDL_SCANCODE_0: return '0';
209 case SDL_SCANCODE_SPACE: return ' ';
210 case SDL_SCANCODE_MINUS: return '-';
211 case SDL_SCANCODE_EQUALS: return '=';
212 case SDL_SCANCODE_LEFTBRACKET: return '[';
213 case SDL_SCANCODE_RIGHTBRACKET: return ']';
214 case SDL_SCANCODE_BACKSLASH: return '\\';
215 case SDL_SCANCODE_SEMICOLON: return ';';
216 case SDL_SCANCODE_APOSTROPHE: return '\'';
217 case SDL_SCANCODE_COMMA: return ',';
218 case SDL_SCANCODE_PERIOD: return '.';
219 case SDL_SCANCODE_SLASH: return '/';
221 case SDL_SCANCODE_UP: return K_UPARROW;
222 case SDL_SCANCODE_LEFT: return K_LEFTARROW;
223 case SDL_SCANCODE_RIGHT: return K_RIGHTARROW;
224 case SDL_SCANCODE_DOWN: return K_DOWNARROW;
225 case SDL_SCANCODE_INSERT: return K_INSERT;
226 case SDL_SCANCODE_DELETE: return K_DELETE;
227 case SDL_SCANCODE_HOME: return K_HOME;
228 case SDL_SCANCODE_END: return K_END;
229 case SDL_SCANCODE_PAGEUP: return K_PAGEUP;
230 case SDL_SCANCODE_PAGEDOWN: return K_PAGEDOWN;
232 case SDL_SCANCODE_KP_0: return K_PAD0;
233 case SDL_SCANCODE_KP_1: return K_PAD1;
234 case SDL_SCANCODE_KP_2: return K_PAD2;
235 case SDL_SCANCODE_KP_3: return K_PAD3;
236 case SDL_SCANCODE_KP_4: return K_PAD4;
237 case SDL_SCANCODE_KP_5: return K_PAD5;
238 case SDL_SCANCODE_KP_6: return K_PAD6;
239 case SDL_SCANCODE_KP_7: return K_PAD7;
240 case SDL_SCANCODE_KP_8: return K_PAD8;
241 case SDL_SCANCODE_KP_9: return K_PAD9;
243 case SDL_SCANCODE_NUMLOCKCLEAR: return K_NUMLOCK;
244 case SDL_SCANCODE_KP_DIVIDE: return K_PADDIVIDE;
245 case SDL_SCANCODE_KP_MULTIPLY: return K_PADMULTIPLE;
246 case SDL_SCANCODE_KP_MINUS: return K_PADMINUS;
247 case SDL_SCANCODE_KP_PLUS: return K_PADPLUS;
248 case SDL_SCANCODE_KP_ENTER: return K_PADENTER;
249 case SDL_SCANCODE_KP_PERIOD: return K_PADDOT;
251 case SDL_SCANCODE_ESCAPE: return K_ESCAPE;
252 case SDL_SCANCODE_RETURN: return K_ENTER;
253 case SDL_SCANCODE_TAB: return K_TAB;
254 case SDL_SCANCODE_BACKSPACE: return K_BACKSPACE;
256 case SDL_SCANCODE_GRAVE: return K_BACKQUOTE;
257 case SDL_SCANCODE_CAPSLOCK: return K_CAPSLOCK;
259 case SDL_SCANCODE_F1: return K_F1;
260 case SDL_SCANCODE_F2: return K_F2;
261 case SDL_SCANCODE_F3: return K_F3;
262 case SDL_SCANCODE_F4: return K_F4;
263 case SDL_SCANCODE_F5: return K_F5;
264 case SDL_SCANCODE_F6: return K_F6;
265 case SDL_SCANCODE_F7: return K_F7;
266 case SDL_SCANCODE_F8: return K_F8;
267 case SDL_SCANCODE_F9: return K_F9;
268 case SDL_SCANCODE_F10: return K_F10;
269 case SDL_SCANCODE_F11: return K_F11;
270 case SDL_SCANCODE_F12: return K_F12;
272 case SDL_SCANCODE_LSHIFT: return K_LSHIFT;
273 case SDL_SCANCODE_RSHIFT: return K_RSHIFT;
274 case SDL_SCANCODE_LCTRL: return K_LCTRL;
275 case SDL_SCANCODE_RCTRL: return K_RCTRL;
276 case SDL_SCANCODE_LALT: return K_LALT;
277 case SDL_SCANCODE_RALT: return K_RALT;
279 case SDL_SCANCODE_LGUI: return K_LWIN;
280 case SDL_SCANCODE_RGUI: return K_RWIN;
281 case SDL_SCANCODE_MENU: return K_MENU;
283 case SDL_SCANCODE_PRINTSCREEN: return K_PRINTSCRN;
284 case SDL_SCANCODE_SCROLLLOCK: return K_SCROLLLOCK;
285 case SDL_SCANCODE_PAUSE: return K_PAUSE;
287 default:
288 //if (scan >= ' ' && scan < 127) return (vuint8)scan;
289 break;
292 return 0;
296 //==========================================================================
298 // VSdlInputDevice::VSdlInputDevice
300 //==========================================================================
301 VSdlInputDevice::VSdlInputDevice ()
302 : mouse(false)
303 , winactive(false)
304 , firsttime(true)
305 , uiwasactive(false)
306 , uimouselast(false)
307 , curHidden(false)
308 , currDoGrab(false)
309 , currRelative(false)
310 , relativeFailed(false)
311 , hlRemX(0.0f)
312 , hlRemY(0.0f)
313 , mouseLastX(0)
314 , mouseLastY(0)
315 , curmodflags(0)
316 , timeKbdAllow(0)
317 , kbdSuspended(false)
318 , hyperDown(false)
319 , joystick(nullptr)
320 , controller(nullptr)
321 , haptic(nullptr)
322 , jid(0)
323 , joystick_started(false)
324 , joystick_controller(false)
325 , has_haptic(false)
326 , joy_num_buttons(0)
327 , bGotCloseRequest(false)
329 memset(&joy_x[0], 0, 2*sizeof(joy_x[0]));
330 memset(&joy_y[0], 0, 2*sizeof(joy_y[0]));
331 memset(&joy_oldx[0], 0, 2*sizeof(joy_oldx[0]));
332 memset(&joy_oldy[0], 0, 2*sizeof(joy_oldy[0]));
333 joy_newb = joy_oldb = 0;
334 ctl_trigger = 0;
335 joynum = -1;
336 // mouse and keyboard are setup using SDL's video interface
337 mouse = true;
338 if (cli_NoMouse) {
339 SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
340 SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_IGNORE);
341 SDL_EventState(SDL_MOUSEBUTTONUP, SDL_IGNORE);
342 mouse = false;
343 } else {
344 if (Drawer) {
345 OwnMouse();
346 } else {
347 // do it later
348 mouseLastX = mouseLastY = 0;
349 currDoGrab = !m_grab.asBool();
350 currRelative = !m_relative.asBool();
354 // always off
355 HideRealMouse();
357 // initialise joystick
358 StartupJoystick();
360 CL_SetNetAbortCallback(&GNetCheckForUserAbortCB, (void *)this);
364 //==========================================================================
366 // VSdlInputDevice::~VSdlInputDevice
368 //==========================================================================
369 VSdlInputDevice::~VSdlInputDevice () {
370 CL_SetNetAbortCallback(nullptr, nullptr);
371 //SDL_SetRelativeMouseMode(SDL_FALSE);
372 SDL_ShowCursor(1); // on
373 ShutdownJoystick(true);
377 //==========================================================================
379 // VSdlInputDevice::OwnMouse
381 //==========================================================================
382 void VSdlInputDevice::OwnMouse () {
383 if (Drawer) {
384 currDoGrab = m_grab.asBool();
385 if (currDoGrab) {
386 if (SDL_CaptureMouse(SDL_TRUE) != 0) {
387 #ifdef SDL_MOUSE_CAPTURE_DEBUG
388 GCon->Log(NAME_Debug, "SDL: cannot capture mouse.");
389 #endif
390 } else {
391 #ifdef SDL_MOUSE_CAPTURE_DEBUG
392 GCon->Log(NAME_Debug, "SDL: mouse captured.");
393 #endif
396 currRelative = (!relativeFailed && m_relative.asBool());
397 if (currRelative) {
398 if (SDL_SetRelativeMouseMode(SDL_TRUE) != 0) {
399 GCon->Log(NAME_Warning, "SDL: cannot switch mouse to relative mode.");
400 currRelative = false;
401 relativeFailed = true;
402 } else {
403 #ifdef SDL_MOUSE_CAPTURE_DEBUG
404 GCon->Log(NAME_Debug, "SDL: switched mouse to relative mode.");
405 #endif
409 // we don't need relative mouse motion in non-relative mode
410 SDL_EventState(SDL_MOUSEMOTION, (currRelative ? SDL_ENABLE : SDL_IGNORE));
411 firsttime = true;
412 if (!currRelative && Drawer) {
413 Drawer->WarpMouseToWindowCenter();
414 Drawer->GetMousePosition(&mouseLastX, &mouseLastY);
419 //==========================================================================
421 // VSdlInputDevice::DisownMouse
423 //==========================================================================
424 void VSdlInputDevice::DisownMouse () {
425 // it is ok to release everything, why not
426 SDL_SetRelativeMouseMode(SDL_FALSE);
427 SDL_CaptureMouse(SDL_FALSE);
428 SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
429 firsttime = true;
430 currDoGrab = false;
431 currRelative = false;
435 //==========================================================================
437 // VSdlInputDevice::DoUnpress
439 //==========================================================================
440 void VSdlInputDevice::DoUnpress () {
441 memset(&joy_x[0], 0, 2*sizeof(joy_x[0]));
442 memset(&joy_y[0], 0, 2*sizeof(joy_y[0]));
443 memset(&joy_oldx[0], 0, 2*sizeof(joy_oldx[0]));
444 memset(&joy_oldy[0], 0, 2*sizeof(joy_oldy[0]));
445 joy_newb = joy_oldb = 0;
446 ctl_trigger = 0;
447 VInputPublic::UnpressAll();
448 curmodflags = 0; // just in case
452 //==========================================================================
454 // VSdlInputDevice::CtlTriggerButton
456 //==========================================================================
457 void VSdlInputDevice::CtlTriggerButton (int idx, bool down) {
458 if (idx < 0 || idx > 1) return;
459 const uint32_t mask = 1u<<idx;
460 if (down) {
461 // pressed
462 if ((ctl_trigger&mask) == 0) {
463 ctl_trigger |= mask;
464 GInput->PostKeyEvent(K_BUTTON_TRIGGER_LEFT+idx, true, curmodflags);
466 } else {
467 // released
468 if ((ctl_trigger&mask) != 0) {
469 ctl_trigger &= ~mask;
470 GInput->PostKeyEvent(K_BUTTON_TRIGGER_LEFT+idx, false, curmodflags);
476 //==========================================================================
478 // VSdlInputDevice::HideRealMouse
480 //==========================================================================
481 void VSdlInputDevice::HideRealMouse () {
482 if (!curHidden) {
483 // real mouse cursor is visible
484 if (m_dbg_cursor || !mouse) return;
485 curHidden = true;
486 SDL_ShowCursor(0);
487 } else {
488 // real mouse cursor is invisible
489 if (m_dbg_cursor || !mouse) {
490 curHidden = false;
491 SDL_ShowCursor(1);
497 //==========================================================================
499 // VSdlInputDevice::ShowRealMouse
501 //==========================================================================
502 void VSdlInputDevice::ShowRealMouse () {
503 if (curHidden) {
504 // real mouse cursor is invisible
505 curHidden = false;
506 SDL_ShowCursor(1);
511 //==========================================================================
513 // VSdlInputDevice::RegrabMouse
515 // Called by UI when mouse cursor is turned off.
517 //==========================================================================
518 void VSdlInputDevice::RegrabMouse () {
519 //FIXME: ignore winactive here, 'cause when mouse is off-window, it may be `false`
520 if (mouse) {
521 firsttime = true;
522 if (Drawer) {
523 if (relativeFailed || !m_relative.asBool()) {
524 Drawer->WarpMouseToWindowCenter();
525 Drawer->GetMousePosition(&mouseLastX, &mouseLastY);
527 } else {
528 SDL_GetMouseState(&mouseLastX, &mouseLastY);
534 //==========================================================================
536 // VSdlInputDevice::SetClipboardText
538 //==========================================================================
539 void VSdlInputDevice::SetClipboardText (VStr text) {
540 if (text.length() && !text.IsValidUtf8()) {
541 VStr s2 = text.Latin1ToUtf8();
542 SDL_SetClipboardText(s2.getCStr());
543 } else {
544 SDL_SetClipboardText(text.getCStr());
549 //==========================================================================
551 // VSdlInputDevice::HasClipboardText
553 //==========================================================================
554 bool VSdlInputDevice::HasClipboardText () {
555 return !!SDL_HasClipboardText();
559 //==========================================================================
561 // VSdlInputDevice::GetClipboardText
563 //==========================================================================
564 VStr VSdlInputDevice::GetClipboardText () {
565 char *text = SDL_GetClipboardText();
566 if (!text || !text[0]) return VStr::EmptyString;
567 for (char *p = text; *p; ++p) {
568 const char ch = *p;
569 if ((unsigned)(ch&0xff) <= 0 || (unsigned)(ch&0xff) > 127) *p = '?';
570 else if (ch == '\t' || ch == '\n') *p = ' ';
571 else if (ch > 0 && ch < 32) *p = ' ';
573 VStr str(text);
574 SDL_free(text);
575 return str;
579 //==========================================================================
581 // calcStickTriggerValue
583 //==========================================================================
584 static int calcStickTriggerValue (const int axis, int value) noexcept {
585 if (value == 0) return 0; // just in case
586 value = clampval(value, -32767, 32767);
587 float dead = 0.0f;
588 switch (axis) {
589 case SDL_CONTROLLER_AXIS_LEFTX: dead = ctl_deadzone_leftstick_x.asFloat(); break;
590 case SDL_CONTROLLER_AXIS_LEFTY: dead = ctl_deadzone_leftstick_y.asFloat(); break;
591 case SDL_CONTROLLER_AXIS_RIGHTX: dead = ctl_deadzone_rightstick_x.asFloat(); break;
592 case SDL_CONTROLLER_AXIS_RIGHTY: dead = ctl_deadzone_rightstick_y.asFloat(); break;
593 case SDL_CONTROLLER_AXIS_TRIGGERLEFT: dead = ctl_trigger_left_edge.asFloat(); break;
594 case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: dead = ctl_trigger_right_edge.asFloat(); break;
595 default: return 0;
597 if (dead >= 1.0f) return 0;
598 dead = min2(0.0f, dead);
599 const int minval = (int)(32767*dead);
600 if (abs(value) < minval) return 0;
601 return clampval(value*127/32767, -127, 127);
605 //==========================================================================
607 // VSdlInputDevice::ReadInput
609 // Reads input from the input devices.
611 //==========================================================================
612 void VSdlInputDevice::ReadInput () {
613 SDL_Event ev;
614 event_t vev;
615 int normal_value;
616 int mouseCurrX = mouseLastX, mouseCurrY = mouseLastY;
617 int rel_x = 0, rel_y = 0;
618 bool firstDump = true;
619 //bool wasMouseButtons = false; // if `true`, prepend mouse movement
621 //SDL_PumpEvents();
622 while (SDL_PollEvent(&ev)) {
623 vev.clear();
624 vev.modflags = curmodflags;
625 switch (ev.type) {
626 case SDL_KEYDOWN:
627 case SDL_KEYUP:
628 // "hyper down" flag
629 switch (ev.key.keysym.scancode) {
630 case SDL_SCANCODE_LGUI:
631 case SDL_SCANCODE_RGUI:
632 hyperDown = (ev.key.state == SDL_PRESSED);
633 break;
634 default: break;
636 // "kbd suspended" check
637 if (kbdSuspended) {
638 if (timeKbdAllow > Sys_Time()) break;
639 kbdSuspended = false;
640 if (ev.key.state != SDL_PRESSED) break; // just in case
642 // translate key, and post keyboard event
643 if (!hyperDown || !in_hyper_block) {
644 int kk = sdl2TranslateKey(ev.key.keysym.scancode);
645 if (kk > 0) {
646 //GCon->Logf(NAME_Debug, "***KEY%s; kk=%d", (ev.key.state == SDL_PRESSED ? "DOWN" : "UP"), kk);
647 GInput->PostKeyEvent(kk, (ev.key.state == SDL_PRESSED ? 1 : 0), vev.modflags);
650 // now fix the flags
651 switch (ev.key.keysym.sym) {
652 case SDLK_LSHIFT: if (ev.type == SDL_KEYDOWN) curmodflags |= bShiftLeft; else curmodflags &= ~bShiftLeft; break;
653 case SDLK_RSHIFT: if (ev.type == SDL_KEYDOWN) curmodflags |= bShiftRight; else curmodflags &= ~bShiftRight; break;
654 case SDLK_LCTRL: if (ev.type == SDL_KEYDOWN) curmodflags |= bCtrlLeft; else curmodflags &= ~bCtrlLeft; break;
655 case SDLK_RCTRL: if (ev.type == SDL_KEYDOWN) curmodflags |= bCtrlRight; else curmodflags &= ~bCtrlRight; break;
656 case SDLK_LALT: if (ev.type == SDL_KEYDOWN) curmodflags |= bAltLeft; else curmodflags &= ~bAltLeft; break;
657 case SDLK_RALT: if (ev.type == SDL_KEYDOWN) curmodflags |= bAltRight; else curmodflags &= ~bAltRight; break;
658 case SDLK_LGUI: if (ev.type == SDL_KEYDOWN) curmodflags |= bHyper; else curmodflags &= ~bHyper; break;
659 case SDLK_RGUI: if (ev.type == SDL_KEYDOWN) curmodflags |= bHyper; else curmodflags &= ~bHyper; break;
660 default: break;
662 if (curmodflags&(bShiftLeft|bShiftRight)) curmodflags |= bShift; else curmodflags &= ~bShift;
663 if (curmodflags&(bCtrlLeft|bCtrlRight)) curmodflags |= bCtrl; else curmodflags &= ~bCtrl;
664 if (curmodflags&(bAltLeft|bAltRight)) curmodflags |= bAlt; else curmodflags &= ~bAlt;
665 break;
666 case SDL_MOUSEMOTION:
667 if (currRelative && winactive) {
668 if (!firsttime) {
669 // motion values
670 int dx = +ev.motion.xrel;
671 int dy = -ev.motion.yrel;
672 if (ms_rel_mode.asInt() > 0) {
673 float mx = (float)dx;
674 float my = (float)dy;
675 float effsens = 1.0f;
677 float movedist = mx*mx+my*my;
678 if (!ms_rel_squaredist.asBool()) movedist = sqrtf(movedist);
680 if (movedist > 0.0f) {
681 float dexp = ms_rel_exponent.asFloat();
682 if (!isFiniteF(dexp)) dexp = 0.0f;
683 if (dexp > 4.0f) dexp = 4.0f;
684 if (dexp > 0.0f) {
685 if (ms_rel_expweird.asBool()) {
686 const float expaccel = max2(0.0f, (dexp-1.0f)*0.5f);
687 effsens = powf(movedist, expaccel)*ms_rel_sensitivity.asFloat();
688 } else {
689 effsens = powf(movedist, dexp)*ms_rel_sensscale.asFloat()+ms_rel_sensitivity.asFloat();
694 float finsens = effsens;
695 if (ms_rel_senscap.asFloat() > 0.0f && finsens > ms_rel_senscap.asFloat()) finsens = ms_rel_senscap.asFloat();
697 if (m_dbg_motion.asBool()) {
698 if (firstDump) { firstDump = false; GCon->Log(NAME_Debug, "=== SDLMOTION ==="); }
699 GCon->Logf(NAME_Debug, "SDLMOTION: dx=%d; dy=%d; movedist=%g; finsens=%g (%g); sqdist=%d; weirdexp=%d; mx=%d; my=%d", dx, dy, movedist, finsens, effsens, (int)ms_rel_squaredist.asBool(), (int)ms_rel_expweird.asBool(), (int)floorf(mx*finsens+hlRemX), (int)floorf(my*finsens+hlRemY));
702 mx *= finsens;
703 my *= finsens;
705 // hold the remainder and pass it into the next round
706 mx += hlRemX;
707 my += hlRemY;
709 hlRemX = mx-floorf(mx);
710 hlRemY = my-floorf(my);
712 rel_x += (int)floorf(mx);
713 rel_y += (int)floorf(my);
714 } else {
715 // no acceleration
716 rel_x += dx;
717 rel_y += dy;
719 // temp, for button events
720 if (Drawer) {
721 mouseCurrX = clampval(mouseLastX+rel_x, 0, Drawer->getWidth()-1);
722 mouseCurrY = clampval(mouseLastY+rel_y, 0, Drawer->getHeight()-1);
724 } else {
725 firsttime = false;
728 break;
729 case SDL_MOUSEBUTTONDOWN:
730 case SDL_MOUSEBUTTONUP:
731 vev.type = (ev.button.state == SDL_PRESSED ? ev_keydown : ev_keyup);
732 if (ev.button.button == SDL_BUTTON_LEFT) vev.keycode = K_MOUSE1;
733 else if (ev.button.button == SDL_BUTTON_RIGHT) vev.keycode = K_MOUSE2;
734 else if (ev.button.button == SDL_BUTTON_MIDDLE) vev.keycode = K_MOUSE3;
735 else if (ev.button.button == SDL_BUTTON_X1) vev.keycode = K_MOUSE4;
736 else if (ev.button.button == SDL_BUTTON_X2) vev.keycode = K_MOUSE5;
737 else break;
738 if (IsUIMouse() || !ui_active || ui_control_waiting || ui_freemouse) {
739 if (currRelative) {
740 vev.x = mouseCurrX;
741 vev.y = mouseCurrY;
742 } else {
743 if (Drawer) Drawer->GetMousePosition(&vev.x, &vev.y);
745 VObject::PostEvent(vev);
746 //wasMouseButtons = true;
748 // now fix the flags
749 if (ev.button.button == SDL_BUTTON_LEFT) { if (ev.button.state == SDL_PRESSED) curmodflags |= bLMB; else curmodflags &= ~bLMB; }
750 else if (ev.button.button == SDL_BUTTON_RIGHT) { if (ev.button.state == SDL_PRESSED) curmodflags |= bRMB; else curmodflags &= ~bRMB; }
751 else if (ev.button.button == SDL_BUTTON_MIDDLE) { if (ev.button.state == SDL_PRESSED) curmodflags |= bMMB; else curmodflags &= ~bMMB; }
752 break;
753 case SDL_MOUSEWHEEL:
754 vev.type = ev_keydown;
755 if (ev.wheel.y > 0) vev.keycode = K_MWHEELUP;
756 else if (ev.wheel.y < 0) vev.keycode = K_MWHEELDOWN;
757 else break;
758 if (IsUIMouse() || !ui_active || ui_control_waiting || ui_freemouse) {
759 if (currRelative) {
760 vev.x = mouseCurrX;
761 vev.y = mouseCurrY;
762 } else {
763 if (Drawer) Drawer->GetMousePosition(&vev.x, &vev.y);
765 VObject::PostEvent(vev);
766 //wasMouseButtons = true;
768 break;
769 // joysticks
770 case SDL_JOYAXISMOTION:
771 if (joystick) {
772 //GCon->Logf(NAME_Debug, "JOY AXIS %d: motion=%d", ev.jaxis.axis, ev.jaxis.value);
773 normal_value = clampval(ev.jaxis.value*127/32767, -127, 127);
774 if (ev.jaxis.axis == 0) joy_x[0] = normal_value;
775 else if (ev.jaxis.axis == 1) joy_y[0] = normal_value;
777 break;
778 case SDL_JOYBALLMOTION:
779 if (joystick) {
780 //GCon->Logf(NAME_Debug, "JOY BALL");
782 break;
783 case SDL_JOYHATMOTION:
784 if (joystick) {
785 //GCon->Logf(NAME_Debug, "JOY HAT");
787 break;
788 case SDL_JOYBUTTONDOWN:
789 if (joystick) {
790 //GCon->Logf(NAME_Debug, "JOY BUTTON %d", ev.jbutton.button);
791 if (/*ev.jbutton.button >= 0 &&*/ (unsigned)ev.jbutton.button <= 15) {
792 joy_newb |= 1u<<ev.jbutton.button;
795 break;
796 case SDL_JOYBUTTONUP:
797 if (joystick) {
798 if (/*ev.jbutton.button >= 0 &&*/ (unsigned)ev.jbutton.button <= 15) {
799 joy_newb &= ~(1u<<ev.jbutton.button);
802 break;
803 // controllers
804 case SDL_CONTROLLERAXISMOTION:
805 if (controller && ev.caxis.which == jid) {
806 normal_value = calcStickTriggerValue(ev.caxis.axis, ev.caxis.value);
807 #if 0
808 const char *axisName = "<unknown>";
809 switch (ev.caxis.axis) {
810 case SDL_CONTROLLER_AXIS_LEFTX: axisName = "LEFTX"; break;
811 case SDL_CONTROLLER_AXIS_LEFTY: axisName = "LEFTY"; break;
812 case SDL_CONTROLLER_AXIS_RIGHTX: axisName = "RIGHTX"; break;
813 case SDL_CONTROLLER_AXIS_RIGHTY: axisName = "RIGHTY"; break;
814 case SDL_CONTROLLER_AXIS_TRIGGERLEFT: axisName = "TRIGLEFT"; break;
815 case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: axisName = "TRIGRIGHT"; break;
816 default: break;
818 GCon->Logf(NAME_Debug, "CTL AXIS '%s' (%d): motion=%d; nval=%d", axisName, ev.caxis.axis, ev.caxis.value, normal_value);
819 #endif
820 switch (ev.caxis.axis) {
821 case SDL_CONTROLLER_AXIS_LEFTX: joy_x[0] = normal_value; break;
822 case SDL_CONTROLLER_AXIS_LEFTY: joy_y[0] = normal_value; break;
823 case SDL_CONTROLLER_AXIS_RIGHTX: joy_x[1] = normal_value; break;
824 case SDL_CONTROLLER_AXIS_RIGHTY: joy_y[1] = normal_value; break;
825 //HACK: consider both triggers as buttons for now
826 case SDL_CONTROLLER_AXIS_TRIGGERLEFT: CtlTriggerButton(0, (normal_value != 0)); break;
827 case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: CtlTriggerButton(1, (normal_value != 0)); break;
830 break;
831 case SDL_CONTROLLERBUTTONDOWN:
832 case SDL_CONTROLLERBUTTONUP:
833 if (controller && ev.cbutton.which == jid) {
834 #if 0
835 const char *buttName = "<unknown>";
836 switch (ev.cbutton.button) {
837 case SDL_CONTROLLER_BUTTON_A: buttName = "K_BUTTON_A"; break;
838 case SDL_CONTROLLER_BUTTON_B: buttName = "K_BUTTON_B"; break;
839 case SDL_CONTROLLER_BUTTON_X: buttName = "K_BUTTON_X"; break;
840 case SDL_CONTROLLER_BUTTON_Y: buttName = "K_BUTTON_Y"; break;
841 case SDL_CONTROLLER_BUTTON_BACK: buttName = "K_BUTTON_BACK"; break;
842 case SDL_CONTROLLER_BUTTON_GUIDE: buttName = "K_BUTTON_GUIDE"; break;
843 case SDL_CONTROLLER_BUTTON_START: buttName = "K_BUTTON_START"; break;
844 case SDL_CONTROLLER_BUTTON_LEFTSTICK: buttName = "K_BUTTON_LEFTSTICK"; break;
845 case SDL_CONTROLLER_BUTTON_RIGHTSTICK: buttName = "K_BUTTON_RIGHTSTICK"; break;
846 case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: buttName = "K_BUTTON_LEFTSHOULDER"; break;
847 case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: buttName = "K_BUTTON_RIGHTSHOULDER"; break;
848 case SDL_CONTROLLER_BUTTON_DPAD_UP: buttName = "K_BUTTON_DPAD_UP"; break;
849 case SDL_CONTROLLER_BUTTON_DPAD_DOWN: buttName = "K_BUTTON_DPAD_DOWN"; break;
850 case SDL_CONTROLLER_BUTTON_DPAD_LEFT: buttName = "K_BUTTON_DPAD_LEFT"; break;
851 case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: buttName = "K_BUTTON_DPAD_RIGHT"; break;
852 default: break;
854 GCon->Logf(NAME_Debug, "CTL %d BUTTON %s: '%s' (%d)", ev.cbutton.which, (ev.cbutton.state == SDL_PRESSED ? "DOWN" : "UP"), buttName, ev.cbutton.button);
855 #endif
856 switch (ev.cbutton.button) {
857 case SDL_CONTROLLER_BUTTON_A: GInput->PostKeyEvent(K_BUTTON_A, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
858 case SDL_CONTROLLER_BUTTON_B: GInput->PostKeyEvent(K_BUTTON_B, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
859 case SDL_CONTROLLER_BUTTON_X: GInput->PostKeyEvent(K_BUTTON_X, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
860 case SDL_CONTROLLER_BUTTON_Y: GInput->PostKeyEvent(K_BUTTON_Y, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
861 case SDL_CONTROLLER_BUTTON_BACK: GInput->PostKeyEvent(K_BUTTON_BACK, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
862 case SDL_CONTROLLER_BUTTON_GUIDE: GInput->PostKeyEvent(K_BUTTON_GUIDE, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
863 case SDL_CONTROLLER_BUTTON_START: GInput->PostKeyEvent(K_BUTTON_START, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
864 case SDL_CONTROLLER_BUTTON_LEFTSTICK: GInput->PostKeyEvent(K_BUTTON_LEFTSTICK, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
865 case SDL_CONTROLLER_BUTTON_RIGHTSTICK: GInput->PostKeyEvent(K_BUTTON_RIGHTSTICK, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
866 case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: GInput->PostKeyEvent(K_BUTTON_LEFTSHOULDER, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
867 case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: GInput->PostKeyEvent(K_BUTTON_RIGHTSHOULDER, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
868 case SDL_CONTROLLER_BUTTON_DPAD_UP: GInput->PostKeyEvent(K_BUTTON_DPAD_UP, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
869 case SDL_CONTROLLER_BUTTON_DPAD_DOWN: GInput->PostKeyEvent(K_BUTTON_DPAD_DOWN, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
870 case SDL_CONTROLLER_BUTTON_DPAD_LEFT: GInput->PostKeyEvent(K_BUTTON_DPAD_LEFT, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
871 case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: GInput->PostKeyEvent(K_BUTTON_DPAD_RIGHT, (ev.cbutton.state == SDL_PRESSED ? 1 : 0), curmodflags); break;
872 default: break;
875 break;
876 case SDL_CONTROLLERDEVICEADDED:
877 //GCon->Logf(NAME_Debug, "CTL %d added", ev.cdevice.which);
878 if (SDL_NumJoysticks() == 1 || !(joystick && !controller)) {
879 GCon->Logf(NAME_Debug, "controller attached");
880 OpenJoystick(0);
882 break;
883 case SDL_CONTROLLERDEVICEREMOVED:
884 //GCon->Logf(NAME_Debug, "CTL %d removed", ev.cdevice.which);
885 if (joystick_controller && ev.cdevice.which == jid) {
886 ShutdownJoystick(false);
887 GCon->Logf(NAME_Debug, "controller detached");
889 break;
890 /*k8: i don't know what to do here
891 case SDL_CONTROLLERDEVICEREMAPPED:
892 GCon->Logf(NAME_Debug, "CTL %d remapped", ev.cdevice.which);
893 if (joystick_controller && ev.cdevice.which == jid) {
895 break;
897 #ifdef ANDROID
898 case SDL_FINGERMOTION:
899 Touch_Event(TouchMotion, ev.tfinger.fingerId, ev.tfinger.x, ev.tfinger.y, ev.tfinger.dx, ev.tfinger.dy, ev.tfinger.pressure);
900 break;
901 case SDL_FINGERDOWN:
902 Touch_Event(TouchDown, ev.tfinger.fingerId, ev.tfinger.x, ev.tfinger.y, ev.tfinger.dx, ev.tfinger.dy, ev.tfinger.pressure);
903 break;
904 case SDL_FINGERUP:
905 Touch_Event(TouchUp, ev.tfinger.fingerId, ev.tfinger.x, ev.tfinger.y, ev.tfinger.dx, ev.tfinger.dy, ev.tfinger.pressure);
906 break;
907 #endif
908 // window/other
909 case SDL_WINDOWEVENT:
910 switch (ev.window.event) {
911 case SDL_WINDOWEVENT_FOCUS_GAINED:
912 //GCon->Logf(NAME_Debug, "***FOCUS GAIN; wa=%d; first=%d", (int)winactive, (int)firsttime);
913 DoUnpress();
914 vev.modflags = 0;
915 if (!winactive && mouse) {
916 if (Drawer) {
917 if (!ui_freemouse && (!ui_active || IsUIMouse())) Drawer->WarpMouseToWindowCenter();
918 //SDL_GetMouseState(&mouseLastX, &mouseLastY);
919 if (relativeFailed || !m_relative.asBool()) Drawer->GetMousePosition(&mouseLastX, &mouseLastY);
922 winactive = true;
923 OwnMouse();
924 if (cl) cl->ClearInput();
925 if (in_focusgain_delay.asFloat() > 0) {
926 kbdSuspended = true;
927 timeKbdAllow = Sys_Time()+in_focusgain_delay.asFloat();
928 //GCon->Log(NAME_Debug, "***FOCUS GAIN: KBDSUSPEND!");
930 hyperDown = false;
931 vev.type = ev_winfocus;
932 vev.focused = 1;
933 VObject::PostEvent(vev);
934 break;
935 case SDL_WINDOWEVENT_FOCUS_LOST:
936 //fprintf(stderr, "***FOCUS LOST; first=%d; drawer=%p\n", (int)firsttime, Drawer);
937 DoUnpress();
938 vev.modflags = 0;
939 winactive = false;
940 DisownMouse();
941 if (cl) cl->ClearInput();
942 kbdSuspended = false;
943 hyperDown = false;
944 vev.type = ev_winfocus;
945 vev.focused = 0;
946 VObject::PostEvent(vev);
947 break;
948 //case SDL_WINDOWEVENT_TAKE_FOCUS: Drawer->SDL_SetWindowInputFocus();
949 case SDL_WINDOWEVENT_RESIZED:
950 // this seems to be called on videomode change; but this is not reliable
951 //GCon->Logf("SDL: resized to %dx%d", ev.window.data1, ev.window.data2);
952 break;
953 case SDL_WINDOWEVENT_SIZE_CHANGED:
954 //GCon->Logf("SDL: size changed to %dx%d", ev.window.data1, ev.window.data2);
955 break;
957 break;
958 case SDL_QUIT:
959 bGotCloseRequest = true;
960 GCmdBuf << "Quit\n";
961 break;
962 default:
963 break;
967 // read mouse separately
968 if (mouse && winactive && Drawer) {
969 #if 1
970 // not a typo
971 if (rel_x|rel_y) {
972 mouseCurrX = mouseLastX = clampval(mouseLastX+rel_x, 0, Drawer->getWidth()-1);
973 mouseCurrY = mouseLastY = clampval(mouseLastY+rel_y, 0, Drawer->getHeight()-1);
975 #endif
977 bool currMouseInUI = (ui_active.asBool() || ui_freemouse.asBool());
978 bool currMouseGrabbed = (ui_freemouse.asBool() ? false : IsUIMouse());
979 if (!currMouseInUI) {
980 if ((!relativeFailed && currRelative != m_relative.asBool()) ||
981 currDoGrab != m_grab.asBool())
983 #ifdef SDL_MOUSE_CAPTURE_DEBUG
984 GCon->Logf(NAME_Debug, "SDL: mouse mode changed, recapturing...");
985 #endif
986 DisownMouse();
987 OwnMouse();
990 //SDL_GetMouseState(&mouseCurrX, &mouseCurrY);
991 if (!currRelative) Drawer->GetMousePosition(&mouseCurrX, &mouseCurrY);
992 // check for UI activity changes
993 if (currMouseInUI != uiwasactive) {
994 // UI activity changed
995 uiwasactive = currMouseInUI;
996 uimouselast = currMouseGrabbed;
997 if (!uiwasactive) {
998 // ui deactivated
999 OwnMouse();
1000 HideRealMouse();
1001 } else {
1002 // ui activted
1003 DisownMouse();
1004 if (!uimouselast) {
1005 if (curHidden && ui_want_mouse_at_zero) SDL_WarpMouseGlobal(0, 0);
1006 ShowRealMouse();
1010 // check for "allow mouse in UI" changes
1011 if (currMouseGrabbed != uimouselast) {
1012 // "allow mouse in UI" changed
1013 if (uiwasactive) {
1014 if (gl_current_screen_fsmode == 0) {
1015 if (currMouseGrabbed) HideRealMouse(); else ShowRealMouse();
1016 } else {
1017 HideRealMouse();
1019 if (GRoot) GRoot->SetMouse(currMouseGrabbed);
1021 uimouselast = currMouseGrabbed;
1023 // hide real mouse in fullscreen mode, show in windowed mode (if necessary)
1024 if (gl_current_screen_fsmode != 0 && !curHidden) HideRealMouse();
1025 if (gl_current_screen_fsmode == 0 && curHidden && currMouseInUI && !currMouseGrabbed) ShowRealMouse();
1026 // generate events
1027 if (!currMouseInUI || currMouseGrabbed) {
1028 if (currRelative) {
1029 // not a typo
1030 if (rel_x|rel_y) {
1031 #if 0
1032 if (true/*wasMouseButtons*/) {
1033 GCon->Log(NAME_Debug, "=== BEFORE EVENTS ===");
1034 for (int eidx = 0; eidx < VObject::CountQueuedEvents(); ++eidx) {
1035 if (!VObject::PeekEvent(eidx, &vev)) break;
1036 GCon->Logf(NAME_Debug, " %2d: type=%d; data1=%d; data2=%d; data3=%d; data4=%d", eidx, vev.type, vev.data1, vev.data2, vev.data3, vev.data4);
1039 #endif
1040 vev.clear();
1041 vev.modflags = curmodflags;
1042 vev.type = ev_mouse;
1043 vev.dx = rel_x;
1044 vev.dy = rel_y;
1045 if (true/*wasMouseButtons*/) VObject::InsertEvent(vev); else VObject::PostEvent(vev);
1046 #if 0
1047 if (true/*wasMouseButtons*/) {
1048 GCon->Log(NAME_Debug, "--- AFTER EVENTS ---");
1049 for (int eidx = 0; eidx < VObject::CountQueuedEvents(); ++eidx) {
1050 if (!VObject::PeekEvent(eidx, &vev)) break;
1051 GCon->Logf(NAME_Debug, " %2d: type=%d; data1=%d; data2=%d; data3=%d; data4=%d", eidx, vev.type, vev.data1, vev.data2, vev.data3, vev.data4);
1053 GCon->Log(NAME_Debug, "--------------------");
1055 #endif
1057 vev.clear();
1058 vev.modflags = curmodflags;
1059 vev.type = ev_uimouse;
1060 vev.x = mouseCurrX;
1061 vev.y = mouseCurrY;
1062 VObject::PostEvent(vev);
1064 } else {
1065 // non-relative
1066 Drawer->WarpMouseToWindowCenter();
1067 int dx = mouseCurrX-mouseLastX;
1068 int dy = mouseLastY-mouseCurrY;
1069 if (dx || dy) {
1070 if (!firsttime) {
1071 vev.clear();
1072 vev.modflags = curmodflags;
1073 vev.type = ev_mouse;
1074 vev.dx = dx;
1075 vev.dy = dy;
1076 if (true/*wasMouseButtons*/) VObject::InsertEvent(vev); else VObject::PostEvent(vev);
1078 vev.clear();
1079 vev.modflags = curmodflags;
1080 vev.type = ev_uimouse;
1081 vev.x = mouseCurrX;
1082 vev.y = mouseCurrY;
1083 VObject::PostEvent(vev);
1085 firsttime = false;
1086 Drawer->GetMousePosition(&mouseLastX, &mouseLastY);
1089 } else {
1090 mouseLastX = mouseCurrX;
1091 mouseLastY = mouseCurrY;
1095 PostJoystick();
1097 #ifdef ANDROID
1098 Touch_Update();
1099 #endif
1104 //**************************************************************************
1105 //**
1106 //** JOYSTICK
1107 //**
1108 //**************************************************************************
1110 //==========================================================================
1112 // VSdlInputDevice::ShutdownJoystick
1114 //==========================================================================
1115 void VSdlInputDevice::ShutdownJoystick (bool closesubsys) {
1116 if (haptic) { SDL_HapticClose(haptic); haptic = nullptr; }
1117 if (joystick) { SDL_JoystickClose(joystick); joystick = nullptr; }
1118 if (controller) { SDL_GameControllerClose(controller); controller = nullptr; }
1119 if (closesubsys) {
1120 if (has_haptic) { SDL_QuitSubSystem(SDL_INIT_HAPTIC); has_haptic = false; }
1121 if (joystick_started) { SDL_QuitSubSystem(VSDL_JINIT); joystick_started = false; }
1123 joy_num_buttons = 0;
1124 joystick_controller = false;
1125 joynum = -1;
1126 jid = 0;
1127 memset(&joy_x[0], 0, 2*sizeof(joy_x[0]));
1128 memset(&joy_y[0], 0, 2*sizeof(joy_y[0]));
1129 memset(&joy_oldx[0], 0, 2*sizeof(joy_oldx[0]));
1130 memset(&joy_oldy[0], 0, 2*sizeof(joy_oldy[0]));
1131 if (closesubsys) {
1132 SDL_JoystickEventState(SDL_IGNORE);
1133 SDL_GameControllerEventState(SDL_IGNORE);
1138 //==========================================================================
1140 // VSdlInputDevice::LoadControllerMappings
1142 // deletes stream; it is ok to pass `nullptr`
1144 //==========================================================================
1145 void VSdlInputDevice::LoadControllerMappings (VStream *st) {
1146 if (!st) return;
1148 VStr stname = st->GetName();
1149 int size = st->TotalSize();
1150 if (size <= 0 || size > 1024*1024*32) {
1151 if (size) GCon->Logf(NAME_Error, "cannot load controller mappings from '%s' (bad file size)", *stname);
1152 VStream::Destroy(st);
1153 return;
1156 char *buf = new char[(unsigned)(size+1)];
1157 st->Serialise(buf, size);
1158 const bool wasErr = st->IsError();
1159 VStream::Destroy(st);
1160 if (wasErr) {
1161 GCon->Logf(NAME_Error, "cannot read controller mappings from '%s'", *stname);
1162 delete[] buf;
1163 return;
1166 SDL_RWops *rwo = SDL_RWFromConstMem(buf, size);
1167 if (rwo) {
1168 const int count = SDL_GameControllerAddMappingsFromRW(rwo, 1); // free rwo
1169 if (count < 0) {
1170 GCon->Logf(NAME_Error, "cannot read controller mappings from '%s'", *stname);
1171 } else if (count > 0) {
1172 GCon->Logf(NAME_Init, "read %d controller mapping%s from '%s'", count, (count != 1 ? "s" : ""), *stname);
1174 } else {
1175 GCon->Logf(NAME_Error, "cannot read controller mappings from '%s' (rwo)", *stname);
1178 delete[] buf;
1182 //==========================================================================
1184 // VSdlInputDevice::StartupJoystick
1186 // Initialises joystick
1188 //==========================================================================
1189 void VSdlInputDevice::OpenJoystick (int jnum) {
1190 if (!joystick_started) return;
1192 ShutdownJoystick(false);
1194 int joycount = SDL_NumJoysticks();
1195 if (joycount < 0) {
1196 GCon->Log(NAME_Init, "SDL: joystick/controller initialisation failed (cannot get number of joysticks)");
1197 ShutdownJoystick(true);
1198 return;
1201 if (jnum >= joycount) jnum = joycount-1;
1202 if (jnum < 0) jnum = 0;
1203 joynum = jnum;
1205 if (joycount == 0) return;
1207 if (SDL_IsGameController(joynum)) {
1208 if (cli_NoCotl) {
1209 GCon->Log(NAME_Init, "SDL: skipping controller initialisation due to user request");
1210 ShutdownJoystick(false);
1211 return;
1213 joystick_controller = true;
1214 joy_num_buttons = 0;
1215 controller = SDL_GameControllerOpen(joynum);
1216 if (!controller) {
1217 GCon->Logf(NAME_Init, "SDL: cannot initialise controller #%d", joynum);
1218 ShutdownJoystick(false);
1219 return;
1221 GCon->Logf(NAME_Init, "SDL: joystick is a controller (%s)", SDL_GameControllerNameForIndex(joynum));
1222 SDL_Joystick *cj = SDL_GameControllerGetJoystick(controller);
1223 if (!cj) {
1224 GCon->Log(NAME_Init, "SDL: controller initialisation failed (cannot get joystick info)");
1225 ShutdownJoystick(true);
1226 return;
1228 jid = SDL_JoystickInstanceID(cj);
1229 if (has_haptic) {
1230 haptic = SDL_HapticOpenFromJoystick(cj);
1231 if (haptic) {
1232 GCon->Logf(NAME_Init, "SDL: found haptic support for controller");
1233 } else {
1234 GCon->Logf(NAME_Init, "SDL: cannot open haptic interface");
1237 } else {
1238 if (cli_NoJoy) {
1239 GCon->Log(NAME_Init, "SDL: skipping joystick initialisation due to user request");
1240 ShutdownJoystick(false);
1241 return;
1243 joystick = SDL_JoystickOpen(joynum);
1244 if (!joystick) {
1245 GCon->Logf(NAME_Init, "SDL: cannot initialise joystick #%d", joynum);
1246 ShutdownJoystick(false);
1247 return;
1249 jid = SDL_JoystickInstanceID(joystick);
1250 joy_num_buttons = SDL_JoystickNumButtons(joystick);
1251 GCon->Logf(NAME_Init, "SDL: found joystick with %d buttons", joy_num_buttons);
1254 SDL_JoystickEventState(SDL_ENABLE);
1255 SDL_GameControllerEventState(SDL_ENABLE);
1259 //==========================================================================
1261 // VSdlInputDevice::StartupJoystick
1263 // Initialises joystick
1265 //==========================================================================
1266 void VSdlInputDevice::StartupJoystick () {
1267 ShutdownJoystick(true);
1269 if (cli_NoJoy && cli_NoCotl) {
1270 //GCon->Log(NAME_Init, "SDL: skipping joystick/controller initialisation due to user request");
1271 return;
1274 // load user controller mappings
1275 VStream *rcstrm = FL_OpenFileReadInCfgDir("gamecontrollerdb.txt");
1276 if (rcstrm) {
1277 GCon->Log(NAME_Init, "SDL: loading user-defined controller map");
1278 LoadControllerMappings(rcstrm);
1279 } else {
1280 // no user mappings, load built-in mappings
1281 rcstrm = FL_OpenFileReadBaseOnly("gamecontrollerdb.txt");
1282 GCon->Log(NAME_Init, "SDL: loading built-in controller map");
1283 LoadControllerMappings(rcstrm);
1286 //FIXME: change this!
1287 int jnum = 0;
1288 for (int jparm = GArgs.CheckParmFrom("-joy", -1, true); jparm; jparm = GArgs.CheckParmFrom("-joy", jparm, true)) {
1289 const char *jarg = GArgs[jparm];
1290 //GCon->Logf("***%d:<%s>", jparm, jarg);
1291 vassert(jarg); // just in case
1292 jarg += 4;
1293 if (!jarg[0]) continue;
1294 int n = -1;
1295 if (!VStr::convertInt(jarg, &n)) continue;
1296 if (n < 0) continue;
1297 jnum = n;
1299 GCon->Logf(NAME_Init, "SDL: will try to use joystick #%d", jnum);
1301 if (SDL_InitSubSystem(VSDL_JINIT) < 0) {
1302 GCon->Log(NAME_Init, "SDL: joystick/controller initialisation failed");
1303 cli_NoJoy = 1;
1304 cli_NoCotl = 1;
1305 return;
1307 joystick_started = true;
1309 if (cli_NoCotl) {
1310 has_haptic = false;
1311 } else if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0) {
1312 has_haptic = false;
1313 GCon->Log(NAME_Init, "SDL: cannot init haptic subsystem, force feedback disabled");
1314 } else {
1315 has_haptic = true;
1316 GCon->Log(NAME_Init, "SDL: force feedback enabled");
1319 int joycount = SDL_NumJoysticks();
1320 if (joycount < 0) {
1321 GCon->Log(NAME_Init, "SDL: joystick/controller initialisation failed (cannot get number of joysticks)");
1322 ShutdownJoystick(true);
1323 cli_NoJoy = 1;
1324 cli_NoCotl = 1;
1325 return;
1328 SDL_JoystickEventState(SDL_ENABLE);
1329 SDL_GameControllerEventState(SDL_ENABLE);
1331 if (joycount == 0) {
1332 GCon->Log(NAME_Init, "SDL: no joysticks found");
1333 return;
1336 GCon->Logf(NAME_Init, "SDL: %d joystick%s found", joycount, (joycount == 1 ? "" : "s"));
1338 if (joycount == 1 || (jnum >= 0 && jnum < joycount)) OpenJoystick(jnum);
1342 //==========================================================================
1344 // VSdlInputDevice::PostJoystick
1346 //==========================================================================
1347 void VSdlInputDevice::PostJoystick () {
1348 if (!joystick_started || !(joystick || controller)) return;
1350 for (unsigned jn = 0; jn < 2; ++jn) {
1351 if (joy_oldx[jn] != joy_x[jn] || joy_oldy[jn] != joy_y[jn]) {
1352 //GCon->Logf(NAME_Debug, "joystick: %d (old:%d), %d (old:%d)", joy_x, joy_oldx, joy_y, joy_oldy);
1353 event_t event;
1354 event.clear();
1355 event.type = ev_joystick;
1356 event.dx = joy_x[jn];
1357 event.dy = joy_y[jn];
1358 event.joyidx = (int)jn;
1359 event.modflags = curmodflags;
1360 VObject::PostEvent(event);
1362 joy_oldx[jn] = joy_x[jn];
1363 joy_oldy[jn] = joy_y[jn];
1367 if (joystick) {
1368 const int nb = min2(joy_num_buttons, 15);
1369 for (int i = 0; i < nb; ++i) {
1370 if ((joy_newb&(1u<<i)) != (joy_oldb&(1u<<i))) {
1371 const bool pressed = !!(joy_newb&(1u<<i));
1372 GInput->PostKeyEvent(K_JOY1+i, pressed, curmodflags);
1374 joy_oldb = joy_newb;
1380 //==========================================================================
1382 // VSdlInputDevice::CheckForEscape
1384 //==========================================================================
1385 bool VSdlInputDevice::CheckForEscape () {
1386 bool res = bGotCloseRequest;
1387 SDL_Event ev;
1389 VInputPublic::UnpressAll();
1391 //SDL_PumpEvents();
1392 while (!bGotCloseRequest && SDL_PollEvent(&ev)) {
1393 switch (ev.type) {
1394 //case SDL_KEYDOWN:
1395 case SDL_KEYUP:
1396 switch (ev.key.keysym.sym) {
1397 case SDLK_ESCAPE: res = true; break;
1398 default: break;
1400 break;
1401 case SDL_QUIT:
1402 bGotCloseRequest = true;
1403 res = true;
1404 GCmdBuf << "Quit\n";
1405 break;
1406 default:
1407 break;
1411 return res;
1415 //**************************************************************************
1416 //**
1417 //** INPUT
1418 //**
1419 //**************************************************************************
1421 //==========================================================================
1423 // VInputDevice::CreateDevice
1425 //==========================================================================
1426 VInputDevice *VInputDevice::CreateDevice () {
1427 return new VSdlInputDevice();