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