Line data Source code
1 : /**
2 : * @file input.cpp
3 : * @brief SDL input handling
4 : *
5 : * SDL handles low-level window manager and key inputs for libprimis
6 : * this file determines how the game parses the SDL information it is given
7 : *
8 : * includes handling for when the window manager tells the game to change state
9 : * and handling for how the game should react to key inputs (though obviously this
10 : * is rebindable by the client)
11 : *
12 : */
13 :
14 : #include "../libprimis-headers/cube.h"
15 : #include "../../shared/stream.h"
16 :
17 : #include "console.h"
18 : #include "control.h"
19 : #include "input.h"
20 : #include "ui.h"
21 :
22 : #include "render/rendergl.h"
23 : #include "render/renderwindow.h"
24 :
25 : //sets SDL relative mouse mode to enabled if 1, 0 otherwise
26 : static VARNP(relativemouse, userelativemouse, 0, 1, 1);
27 :
28 : bool grabinput = false, //used in iengine
29 : minimized = false; //used in iengine
30 : static bool shouldgrab = false,
31 : canrelativemouse = true,
32 : relativemouse = false;
33 : static int keyrepeatmask = 0,
34 : textinputmask = 0;
35 : static Uint32 textinputtime = 0;
36 :
37 : static VAR(textinputfilter, 0, 5, 1000); //delay in ms between text input events
38 :
39 2 : void keyrepeat(bool on, int mask)
40 : {
41 2 : if(on)
42 : {
43 2 : keyrepeatmask |= mask;
44 : }
45 : else
46 : {
47 0 : keyrepeatmask &= ~mask;
48 : }
49 2 : }
50 :
51 2 : void textinput(bool on, int mask)
52 : {
53 2 : if(on)
54 : {
55 2 : if(!textinputmask)
56 : {
57 1 : SDL_StartTextInput();
58 1 : textinputtime = SDL_GetTicks();
59 : }
60 2 : textinputmask |= mask;
61 : }
62 : else
63 : {
64 0 : textinputmask &= ~mask;
65 0 : if(!textinputmask)
66 : {
67 0 : SDL_StopTextInput();
68 : }
69 : }
70 2 : }
71 :
72 0 : void inputgrab(bool on)
73 : {
74 0 : if(on)
75 : {
76 0 : SDL_ShowCursor(SDL_FALSE);
77 0 : if(canrelativemouse && userelativemouse)
78 : {
79 0 : if(SDL_SetRelativeMouseMode(SDL_TRUE) >= 0)
80 : {
81 0 : SDL_SetWindowGrab(screen, SDL_TRUE);
82 0 : relativemouse = true;
83 : }
84 : else
85 : {
86 0 : SDL_SetWindowGrab(screen, SDL_FALSE);
87 0 : canrelativemouse = false;
88 0 : relativemouse = false;
89 : }
90 : }
91 : }
92 : else
93 : {
94 0 : SDL_ShowCursor(SDL_TRUE);
95 0 : if(relativemouse)
96 : {
97 0 : SDL_SetRelativeMouseMode(SDL_FALSE);
98 0 : SDL_SetWindowGrab(screen, SDL_FALSE);
99 0 : relativemouse = false;
100 : }
101 : }
102 0 : shouldgrab = false;
103 0 : }
104 :
105 : static std::vector<SDL_Event> events;
106 :
107 0 : static void pushevent(const SDL_Event &e)
108 : {
109 0 : events.push_back(e);
110 0 : }
111 :
112 0 : static bool filterevent(const SDL_Event &event)
113 : {
114 0 : switch(event.type)
115 : {
116 0 : case SDL_MOUSEMOTION:
117 0 : if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
118 : {
119 0 : if(event.motion.x == hudw() / 2 && event.motion.y == hudh() / 2)
120 : {
121 0 : return false; // ignore any motion events generated by SDL_WarpMouse
122 : }
123 : }
124 0 : break;
125 : }
126 0 : return true;
127 : }
128 :
129 0 : static bool pollevent(SDL_Event &event)
130 : {
131 0 : while(SDL_PollEvent(&event))
132 : {
133 0 : if(filterevent(event))
134 : {
135 0 : return true;
136 : }
137 : }
138 0 : return false;
139 : }
140 :
141 : //used in iengine
142 0 : bool interceptkey(int sym)
143 : {
144 : static int lastintercept = SDLK_UNKNOWN;
145 0 : int len = lastintercept == sym ? static_cast<int>(events.size()) : 0;
146 : SDL_Event event;
147 0 : while(pollevent(event))
148 : {
149 0 : switch(event.type)
150 : {
151 0 : case SDL_MOUSEMOTION:
152 : {
153 0 : break;
154 : }
155 0 : default:
156 : {
157 0 : pushevent(event);
158 0 : break;
159 : }
160 : }
161 : }
162 0 : lastintercept = sym;
163 0 : if(sym != SDLK_UNKNOWN)
164 : {
165 0 : for(size_t i = len; i < events.size(); i++)
166 : {
167 0 : if(events[i].type == SDL_KEYDOWN && events[i].key.keysym.sym == sym)
168 : {
169 0 : events.erase(events.begin() + i);
170 0 : return true;
171 : }
172 : }
173 : }
174 0 : return false;
175 : }
176 :
177 0 : void ignoremousemotion()
178 : {
179 : SDL_Event e;
180 0 : SDL_PumpEvents();
181 : //go through each event and do nothing
182 0 : while(SDL_PeepEvents(&e, 1, SDL_GETEVENT, SDL_MOUSEMOTION, SDL_MOUSEMOTION))
183 : {
184 : //(empty body)
185 : }
186 0 : }
187 :
188 : /**
189 : * @brief Resets the cursor to the middle of the screen
190 : *
191 : * Moves cursor to the center of the screen, regardless of where it is. This corresponds
192 : * to the position at half of (hudw(), hudh()).
193 : */
194 0 : static void resetmousemotion()
195 : {
196 0 : if(grabinput && !relativemouse && !(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
197 : {
198 0 : SDL_WarpMouseInWindow(screen, hudw() / 2, hudh() / 2); //move to middle of screen
199 : }
200 0 : }
201 :
202 : /**
203 : * @brief Gets cursor information from SDL2
204 : *
205 : * gets the information from SDL2 about the amount by which the cursor has moved, and
206 : * and changes the passed dx, dy values by that amount
207 : *
208 : * @param dx reference to the mouse x delta
209 : * @param dy reference to the mouse y delta
210 : */
211 0 : static void checkmousemotion(int &dx, int &dy)
212 : {
213 0 : for(size_t i = 0; i < events.size(); i++)
214 : {
215 0 : SDL_Event &event = events[i];
216 0 : if(event.type != SDL_MOUSEMOTION)
217 : {
218 0 : if(i > 0)
219 : {
220 0 : events.erase(events.begin(), events.begin() + i);
221 : }
222 0 : return;
223 : }
224 0 : dx += event.motion.xrel;
225 0 : dy += event.motion.yrel;
226 : }
227 0 : events.clear();
228 : SDL_Event event;
229 0 : while(pollevent(event))
230 : {
231 0 : if(event.type != SDL_MOUSEMOTION)
232 : {
233 0 : events.push_back(event);
234 0 : return;
235 : }
236 0 : dx += event.motion.xrel;
237 0 : dy += event.motion.yrel;
238 : }
239 : }
240 :
241 :
242 : //handle different input types
243 : // map: which keymap to map the pressed key to
244 0 : void checkinput(int map)
245 : {
246 0 : constexpr uint minthreshhold = 5000, // minimum value to register inputs
247 0 : maxthreshhold = 27000, // maximum value to register inputs to triggers
248 0 : inverseindex = 16,
249 0 : mousemovescale = 25000; //how much to divide mouse movement by from joystick input
250 :
251 0 : constexpr int strafethreshhold = 16384; //value when to assign movement in strafe pad, signed to allow unary negation
252 :
253 : //carry over joystick states
254 0 : static vec2 lpad,
255 0 : rpad,
256 0 : triggers; //x = left, y = right
257 : SDL_Event event;
258 0 : bool mousemoved = false;
259 0 : while(events.size() || pollevent(event))
260 : {
261 0 : if(events.size())
262 : {
263 0 : event = events[0];
264 0 : events.erase(events.begin());
265 : }
266 0 : switch(event.type)
267 : {
268 0 : case SDL_QUIT:
269 : {
270 0 : exit(EXIT_SUCCESS);
271 : return;
272 : }
273 0 : case SDL_TEXTINPUT:
274 : {
275 0 : if(textinputmask && static_cast<int>(event.text.timestamp-textinputtime) >= textinputfilter)
276 : {
277 0 : size_t len = std::strlen(event.text.text);
278 0 : if(len > 0)
279 : {
280 0 : processtextinput(reinterpret_cast<const char *>(event.text.text), len);
281 : }
282 : }
283 0 : break;
284 : }
285 0 : case SDL_KEYDOWN:
286 : case SDL_KEYUP:
287 : {
288 0 : if(keyrepeatmask || !event.key.repeat)
289 : {
290 0 : processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED, map);
291 : }
292 0 : break;
293 : }
294 0 : case SDL_WINDOWEVENT:
295 : {
296 0 : switch(event.window.event)
297 : {
298 0 : case SDL_WINDOWEVENT_CLOSE:
299 : {
300 0 : exit(EXIT_SUCCESS);
301 : break;
302 : }
303 0 : case SDL_WINDOWEVENT_FOCUS_GAINED:
304 : {
305 0 : shouldgrab = true;
306 0 : break;
307 : }
308 0 : case SDL_WINDOWEVENT_ENTER:
309 : {
310 0 : inputgrab(grabinput = true);
311 0 : break;
312 : }
313 0 : case SDL_WINDOWEVENT_LEAVE:
314 : case SDL_WINDOWEVENT_FOCUS_LOST:
315 : {
316 0 : inputgrab(grabinput = false);
317 0 : break;
318 : }
319 0 : case SDL_WINDOWEVENT_MINIMIZED:
320 : {
321 0 : minimized = true;
322 0 : break;
323 : }
324 0 : case SDL_WINDOWEVENT_MAXIMIZED:
325 : case SDL_WINDOWEVENT_RESTORED:
326 : {
327 0 : minimized = false;
328 0 : break;
329 : }
330 0 : case SDL_WINDOWEVENT_RESIZED:
331 : {
332 0 : break;
333 : }
334 0 : case SDL_WINDOWEVENT_SIZE_CHANGED:
335 : {
336 0 : SDL_GetWindowSize(screen, &screenw, &screenh);
337 0 : if(!(SDL_GetWindowFlags(screen) & SDL_WINDOW_FULLSCREEN))
338 : {
339 : //need to cast enums to ints for std's clamp implementation
340 0 : scr_w = std::clamp(hudw(), static_cast<int>(SCR_MINW), static_cast<int>(SCR_MAXW));
341 0 : scr_h = std::clamp(hudh(), static_cast<int>(SCR_MINH), static_cast<int>(SCR_MAXH));
342 : }
343 0 : gl_resize();
344 0 : break;
345 : }
346 : }
347 0 : break;
348 : }
349 0 : case SDL_MOUSEMOTION:
350 : {
351 0 : if(grabinput)
352 : {
353 0 : int dx = event.motion.xrel,
354 0 : dy = event.motion.yrel;
355 0 : checkmousemotion(dx, dy);
356 0 : if(!UI::movecursor(dx, dy))
357 : {
358 0 : mousemove(dx, dy);
359 : }
360 0 : mousemoved = true;
361 : }
362 0 : else if(shouldgrab)
363 : {
364 0 : inputgrab(grabinput = true);
365 : }
366 0 : break;
367 : }
368 0 : case SDL_MOUSEBUTTONDOWN:
369 : case SDL_MOUSEBUTTONUP:
370 : {
371 0 : switch(event.button.button)
372 : {
373 0 : case SDL_BUTTON_LEFT:
374 : {
375 0 : processkey(Key_Left, event.button.state==SDL_PRESSED, map);
376 0 : break;
377 : }
378 0 : case SDL_BUTTON_MIDDLE:
379 : {
380 0 : processkey(Key_Middle, event.button.state==SDL_PRESSED, map);
381 0 : break;
382 : }
383 0 : case SDL_BUTTON_RIGHT:
384 : {
385 0 : processkey(Key_Right, event.button.state==SDL_PRESSED, map);
386 0 : break;
387 : }
388 0 : case SDL_BUTTON_X1:
389 : {
390 0 : processkey(Key_X1, event.button.state==SDL_PRESSED, map);
391 0 : break;
392 : }
393 0 : case SDL_BUTTON_X2:
394 : {
395 0 : processkey(Key_X2, event.button.state==SDL_PRESSED, map);
396 0 : break;
397 : }
398 : }
399 0 : break;
400 : }
401 0 : case SDL_MOUSEWHEEL:
402 : {
403 : //up
404 0 : if(event.wheel.y > 0)
405 : {
406 0 : processkey(Key_ScrollUp, true, map);
407 0 : processkey(Key_ScrollUp, false, map);
408 : }
409 : //down
410 0 : else if(event.wheel.y < 0)
411 : {
412 0 : processkey(Key_ScrollDown, true, map);
413 0 : processkey(Key_ScrollDown, false, map);
414 : }
415 0 : break;
416 : }
417 0 : case SDL_CONTROLLERBUTTONDOWN:
418 : case SDL_CONTROLLERBUTTONUP:
419 : {
420 0 : processkey(event.cbutton.button + (1<<29), event.cbutton.state == SDL_PRESSED, map); //1<<30 is used for non-char keybinds, 1<<29 is an unused part of the namespace
421 0 : break;
422 : }
423 0 : case SDL_CONTROLLERAXISMOTION:
424 : {
425 0 : uint axis = event.caxis.axis,
426 0 : value = event.caxis.value;
427 0 : switch(axis)
428 : {
429 0 : case 0: //left x axis
430 : //these processkeys will push back 1<<28 to keep out of the way of other binds
431 0 : if(lpad.x >= strafethreshhold)
432 : {
433 0 : processkey(axis + (1<<28), true, map); //strafe in +x direction
434 : }
435 0 : else if(lpad.x <= -strafethreshhold)
436 : {
437 0 : processkey(axis + (1<<28) + inverseindex, true, map); //-x dir
438 : }
439 : else //no input
440 : {
441 0 : processkey(axis + (1<<28), false, map);
442 0 : processkey(axis + (1<<28) + inverseindex, false, map);
443 : }
444 0 : lpad.x = value;
445 0 : break;
446 0 : case 1: //left y axis
447 : //these processkeys will push back 1<<28 to keep out of the way of other binds
448 0 : if(lpad.y >= strafethreshhold)
449 : {
450 0 : processkey(axis + (1<<28), true, map);
451 : }
452 0 : else if(lpad.y <= -strafethreshhold)
453 : {
454 0 : processkey(axis + (1<<28) + inverseindex, true, map);
455 : }
456 : else
457 : {
458 0 : processkey(axis + (1<<28), false, map);
459 0 : processkey(axis + (1<<28) + inverseindex, false, map);
460 : }
461 0 : lpad.y = value;
462 0 : break;
463 0 : case 2: //right x axis
464 0 : rpad.x = value;
465 0 : break;
466 0 : case 3: //right y axis
467 0 : rpad.y = value;
468 0 : break;
469 0 : case 4: //left trigger
470 : //these processkeys will push back 1<<28 to keep out of the way of other binds
471 0 : if(triggers.x >= minthreshhold && value < minthreshhold)
472 : {
473 0 : processkey(axis + (1<<28), false, map); //left trigger has been unpressed
474 : }
475 0 : else if(triggers.x <= maxthreshhold && value > maxthreshhold)
476 : {
477 0 : processkey(axis + (1<<28), true, map); //left trigger has been pressed
478 : }
479 0 : triggers.x = value;
480 0 : break;
481 0 : case 5: //right trigger
482 0 : if(triggers.y >= minthreshhold && value < minthreshhold)
483 : {
484 0 : processkey(axis + (1<<28), false, map); //right trigger has been unpressed
485 : }
486 0 : else if(triggers.y <= maxthreshhold && value > maxthreshhold)
487 : {
488 0 : processkey(axis + (1<<28), true, map); //right trigger has been pressed
489 : }
490 0 : triggers.y = value;
491 : }
492 0 : break;
493 : }
494 : }
495 : }
496 : { //scoping brakets
497 : static int oldmillis;
498 0 : int delta = lastmillis-oldmillis;
499 0 : if(std::abs(rpad.x) > minthreshhold || std::abs(rpad.y) > minthreshhold)
500 : {
501 0 : mousemove(delta*rpad.x/mousemovescale, delta*rpad.y/mousemovescale);
502 : }
503 0 : oldmillis = lastmillis;
504 : }
505 0 : if(mousemoved)
506 : {
507 0 : resetmousemotion();
508 : }
509 0 : }
|