]> git.xonotic.org Git - xonotic/darkplaces.git/blob - vid_sdl.c
vid: apply vid_width and vid_height cvar changes immediately
[xonotic/darkplaces.git] / vid_sdl.c
1 /*
2 Copyright (C) 2003  T. Joseph Carter
3
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13 See the GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19 #undef WIN32_LEAN_AND_MEAN  //hush a warning, SDL.h redefines this
20 #include <SDL.h>
21 #include <stdio.h>
22
23 #include "quakedef.h"
24 #include "image.h"
25 #include "utf8lib.h"
26
27 #ifndef __IPHONEOS__
28 #ifdef MACOSX
29 #include <Carbon/Carbon.h>
30 #include <IOKit/hidsystem/IOHIDLib.h>
31 #include <IOKit/hidsystem/IOHIDParameter.h>
32 #include <IOKit/hidsystem/event_status_driver.h>
33 #if (MAC_OS_X_VERSION_MIN_REQUIRED < 120000)
34         #define IOMainPort IOMasterPort
35 #endif
36 static cvar_t apple_mouse_noaccel = {CF_CLIENT | CF_ARCHIVE, "apple_mouse_noaccel", "1", "disables mouse acceleration while DarkPlaces is active"};
37 static qbool vid_usingnoaccel;
38 static double originalMouseSpeed = -1.0;
39 static io_connect_t IN_GetIOHandle(void)
40 {
41         io_connect_t iohandle = MACH_PORT_NULL;
42         kern_return_t status;
43         io_service_t iohidsystem = MACH_PORT_NULL;
44         mach_port_t masterport;
45
46         status = IOMainPort(MACH_PORT_NULL, &masterport);
47         if(status != KERN_SUCCESS)
48                 return 0;
49
50         iohidsystem = IORegistryEntryFromPath(masterport, kIOServicePlane ":/IOResources/IOHIDSystem");
51         if(!iohidsystem)
52                 return 0;
53
54         status = IOServiceOpen(iohidsystem, mach_task_self(), kIOHIDParamConnectType, &iohandle);
55         IOObjectRelease(iohidsystem);
56
57         return iohandle;
58 }
59 #endif
60 #endif
61
62
63 // Tell startup code that we have a client
64 int cl_available = true;
65
66 qbool vid_supportrefreshrate = false;
67
68 static qbool vid_usingmouse = false;
69 static qbool vid_usingmouse_relativeworks = false; // SDL2 workaround for unimplemented RelativeMouse mode
70 static qbool vid_usinghidecursor = false;
71 static qbool vid_hasfocus = false;
72 static qbool vid_wmborder_waiting, vid_wmborderless;
73 static SDL_Joystick *vid_sdljoystick = NULL;
74 static SDL_GameController *vid_sdlgamecontroller = NULL;
75 static cvar_t joy_sdl2_trigger_deadzone = {CF_ARCHIVE | CF_CLIENT, "joy_sdl2_trigger_deadzone", "0.5", "deadzone for triggers to be registered as key presses"};
76 // GAME_STEELSTORM specific
77 static cvar_t *steelstorm_showing_map = NULL; // detect but do not create the cvar
78 static cvar_t *steelstorm_showing_mousecursor = NULL; // detect but do not create the cvar
79
80 static int win_half_width = 50;
81 static int win_half_height = 50;
82
83 static SDL_GLContext context;
84 static SDL_Window *window;
85
86 // Input handling
87
88 #ifndef SDLK_PERCENT
89 #define SDLK_PERCENT '%'
90 #endif
91
92 static int MapKey( unsigned int sdlkey )
93 {
94         switch(sdlkey)
95         {
96         // sdlkey can be Unicode codepoint for non-ascii keys, which are valid
97         default:                      return sdlkey & SDLK_SCANCODE_MASK ? 0 : sdlkey;
98 //      case SDLK_UNKNOWN:            return K_UNKNOWN;
99         case SDLK_RETURN:             return K_ENTER;
100         case SDLK_ESCAPE:             return K_ESCAPE;
101         case SDLK_BACKSPACE:          return K_BACKSPACE;
102         case SDLK_TAB:                return K_TAB;
103         case SDLK_SPACE:              return K_SPACE;
104         case SDLK_EXCLAIM:            return '!';
105         case SDLK_QUOTEDBL:           return '"';
106         case SDLK_HASH:               return '#';
107         case SDLK_PERCENT:            return '%';
108         case SDLK_DOLLAR:             return '$';
109         case SDLK_AMPERSAND:          return '&';
110         case SDLK_QUOTE:              return '\'';
111         case SDLK_LEFTPAREN:          return '(';
112         case SDLK_RIGHTPAREN:         return ')';
113         case SDLK_ASTERISK:           return '*';
114         case SDLK_PLUS:               return '+';
115         case SDLK_COMMA:              return ',';
116         case SDLK_MINUS:              return '-';
117         case SDLK_PERIOD:             return '.';
118         case SDLK_SLASH:              return '/';
119         case SDLK_0:                  return '0';
120         case SDLK_1:                  return '1';
121         case SDLK_2:                  return '2';
122         case SDLK_3:                  return '3';
123         case SDLK_4:                  return '4';
124         case SDLK_5:                  return '5';
125         case SDLK_6:                  return '6';
126         case SDLK_7:                  return '7';
127         case SDLK_8:                  return '8';
128         case SDLK_9:                  return '9';
129         case SDLK_COLON:              return ':';
130         case SDLK_SEMICOLON:          return ';';
131         case SDLK_LESS:               return '<';
132         case SDLK_EQUALS:             return '=';
133         case SDLK_GREATER:            return '>';
134         case SDLK_QUESTION:           return '?';
135         case SDLK_AT:                 return '@';
136         case SDLK_LEFTBRACKET:        return '[';
137         case SDLK_BACKSLASH:          return '\\';
138         case SDLK_RIGHTBRACKET:       return ']';
139         case SDLK_CARET:              return '^';
140         case SDLK_UNDERSCORE:         return '_';
141         case SDLK_BACKQUOTE:          return '`';
142         case SDLK_a:                  return 'a';
143         case SDLK_b:                  return 'b';
144         case SDLK_c:                  return 'c';
145         case SDLK_d:                  return 'd';
146         case SDLK_e:                  return 'e';
147         case SDLK_f:                  return 'f';
148         case SDLK_g:                  return 'g';
149         case SDLK_h:                  return 'h';
150         case SDLK_i:                  return 'i';
151         case SDLK_j:                  return 'j';
152         case SDLK_k:                  return 'k';
153         case SDLK_l:                  return 'l';
154         case SDLK_m:                  return 'm';
155         case SDLK_n:                  return 'n';
156         case SDLK_o:                  return 'o';
157         case SDLK_p:                  return 'p';
158         case SDLK_q:                  return 'q';
159         case SDLK_r:                  return 'r';
160         case SDLK_s:                  return 's';
161         case SDLK_t:                  return 't';
162         case SDLK_u:                  return 'u';
163         case SDLK_v:                  return 'v';
164         case SDLK_w:                  return 'w';
165         case SDLK_x:                  return 'x';
166         case SDLK_y:                  return 'y';
167         case SDLK_z:                  return 'z';
168         case SDLK_CAPSLOCK:           return K_CAPSLOCK;
169         case SDLK_F1:                 return K_F1;
170         case SDLK_F2:                 return K_F2;
171         case SDLK_F3:                 return K_F3;
172         case SDLK_F4:                 return K_F4;
173         case SDLK_F5:                 return K_F5;
174         case SDLK_F6:                 return K_F6;
175         case SDLK_F7:                 return K_F7;
176         case SDLK_F8:                 return K_F8;
177         case SDLK_F9:                 return K_F9;
178         case SDLK_F10:                return K_F10;
179         case SDLK_F11:                return K_F11;
180         case SDLK_F12:                return K_F12;
181         case SDLK_PRINTSCREEN:        return K_PRINTSCREEN;
182         case SDLK_SCROLLLOCK:         return K_SCROLLOCK;
183         case SDLK_PAUSE:              return K_PAUSE;
184         case SDLK_INSERT:             return K_INS;
185         case SDLK_HOME:               return K_HOME;
186         case SDLK_PAGEUP:             return K_PGUP;
187 #ifdef __IPHONEOS__
188         case SDLK_DELETE:             return K_BACKSPACE;
189 #else
190         case SDLK_DELETE:             return K_DEL;
191 #endif
192         case SDLK_END:                return K_END;
193         case SDLK_PAGEDOWN:           return K_PGDN;
194         case SDLK_RIGHT:              return K_RIGHTARROW;
195         case SDLK_LEFT:               return K_LEFTARROW;
196         case SDLK_DOWN:               return K_DOWNARROW;
197         case SDLK_UP:                 return K_UPARROW;
198         case SDLK_NUMLOCKCLEAR:       return K_NUMLOCK;
199         case SDLK_KP_DIVIDE:          return K_KP_DIVIDE;
200         case SDLK_KP_MULTIPLY:        return K_KP_MULTIPLY;
201         case SDLK_KP_MINUS:           return K_KP_MINUS;
202         case SDLK_KP_PLUS:            return K_KP_PLUS;
203         case SDLK_KP_ENTER:           return K_KP_ENTER;
204         case SDLK_KP_1:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_1 : K_END);
205         case SDLK_KP_2:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_2 : K_DOWNARROW);
206         case SDLK_KP_3:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_3 : K_PGDN);
207         case SDLK_KP_4:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_4 : K_LEFTARROW);
208         case SDLK_KP_5:               return K_KP_5;
209         case SDLK_KP_6:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_6 : K_RIGHTARROW);
210         case SDLK_KP_7:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_7 : K_HOME);
211         case SDLK_KP_8:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_8 : K_UPARROW);
212         case SDLK_KP_9:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_9 : K_PGUP);
213         case SDLK_KP_0:               return ((SDL_GetModState() & KMOD_NUM) ? K_KP_0 : K_INS);
214         case SDLK_KP_PERIOD:          return ((SDL_GetModState() & KMOD_NUM) ? K_KP_PERIOD : K_DEL);
215 //      case SDLK_APPLICATION:        return K_APPLICATION;
216 //      case SDLK_POWER:              return K_POWER;
217         case SDLK_KP_EQUALS:          return K_KP_EQUALS;
218 //      case SDLK_F13:                return K_F13;
219 //      case SDLK_F14:                return K_F14;
220 //      case SDLK_F15:                return K_F15;
221 //      case SDLK_F16:                return K_F16;
222 //      case SDLK_F17:                return K_F17;
223 //      case SDLK_F18:                return K_F18;
224 //      case SDLK_F19:                return K_F19;
225 //      case SDLK_F20:                return K_F20;
226 //      case SDLK_F21:                return K_F21;
227 //      case SDLK_F22:                return K_F22;
228 //      case SDLK_F23:                return K_F23;
229 //      case SDLK_F24:                return K_F24;
230 //      case SDLK_EXECUTE:            return K_EXECUTE;
231 //      case SDLK_HELP:               return K_HELP;
232 //      case SDLK_MENU:               return K_MENU;
233 //      case SDLK_SELECT:             return K_SELECT;
234 //      case SDLK_STOP:               return K_STOP;
235 //      case SDLK_AGAIN:              return K_AGAIN;
236 //      case SDLK_UNDO:               return K_UNDO;
237 //      case SDLK_CUT:                return K_CUT;
238 //      case SDLK_COPY:               return K_COPY;
239 //      case SDLK_PASTE:              return K_PASTE;
240 //      case SDLK_FIND:               return K_FIND;
241 //      case SDLK_MUTE:               return K_MUTE;
242 //      case SDLK_VOLUMEUP:           return K_VOLUMEUP;
243 //      case SDLK_VOLUMEDOWN:         return K_VOLUMEDOWN;
244 //      case SDLK_KP_COMMA:           return K_KP_COMMA;
245 //      case SDLK_KP_EQUALSAS400:     return K_KP_EQUALSAS400;
246 //      case SDLK_ALTERASE:           return K_ALTERASE;
247 //      case SDLK_SYSREQ:             return K_SYSREQ;
248 //      case SDLK_CANCEL:             return K_CANCEL;
249 //      case SDLK_CLEAR:              return K_CLEAR;
250 //      case SDLK_PRIOR:              return K_PRIOR;
251 //      case SDLK_RETURN2:            return K_RETURN2;
252 //      case SDLK_SEPARATOR:          return K_SEPARATOR;
253 //      case SDLK_OUT:                return K_OUT;
254 //      case SDLK_OPER:               return K_OPER;
255 //      case SDLK_CLEARAGAIN:         return K_CLEARAGAIN;
256 //      case SDLK_CRSEL:              return K_CRSEL;
257 //      case SDLK_EXSEL:              return K_EXSEL;
258 //      case SDLK_KP_00:              return K_KP_00;
259 //      case SDLK_KP_000:             return K_KP_000;
260 //      case SDLK_THOUSANDSSEPARATOR: return K_THOUSANDSSEPARATOR;
261 //      case SDLK_DECIMALSEPARATOR:   return K_DECIMALSEPARATOR;
262 //      case SDLK_CURRENCYUNIT:       return K_CURRENCYUNIT;
263 //      case SDLK_CURRENCYSUBUNIT:    return K_CURRENCYSUBUNIT;
264 //      case SDLK_KP_LEFTPAREN:       return K_KP_LEFTPAREN;
265 //      case SDLK_KP_RIGHTPAREN:      return K_KP_RIGHTPAREN;
266 //      case SDLK_KP_LEFTBRACE:       return K_KP_LEFTBRACE;
267 //      case SDLK_KP_RIGHTBRACE:      return K_KP_RIGHTBRACE;
268 //      case SDLK_KP_TAB:             return K_KP_TAB;
269 //      case SDLK_KP_BACKSPACE:       return K_KP_BACKSPACE;
270 //      case SDLK_KP_A:               return K_KP_A;
271 //      case SDLK_KP_B:               return K_KP_B;
272 //      case SDLK_KP_C:               return K_KP_C;
273 //      case SDLK_KP_D:               return K_KP_D;
274 //      case SDLK_KP_E:               return K_KP_E;
275 //      case SDLK_KP_F:               return K_KP_F;
276 //      case SDLK_KP_XOR:             return K_KP_XOR;
277 //      case SDLK_KP_POWER:           return K_KP_POWER;
278 //      case SDLK_KP_PERCENT:         return K_KP_PERCENT;
279 //      case SDLK_KP_LESS:            return K_KP_LESS;
280 //      case SDLK_KP_GREATER:         return K_KP_GREATER;
281 //      case SDLK_KP_AMPERSAND:       return K_KP_AMPERSAND;
282 //      case SDLK_KP_DBLAMPERSAND:    return K_KP_DBLAMPERSAND;
283 //      case SDLK_KP_VERTICALBAR:     return K_KP_VERTICALBAR;
284 //      case SDLK_KP_DBLVERTICALBAR:  return K_KP_DBLVERTICALBAR;
285 //      case SDLK_KP_COLON:           return K_KP_COLON;
286 //      case SDLK_KP_HASH:            return K_KP_HASH;
287 //      case SDLK_KP_SPACE:           return K_KP_SPACE;
288 //      case SDLK_KP_AT:              return K_KP_AT;
289 //      case SDLK_KP_EXCLAM:          return K_KP_EXCLAM;
290 //      case SDLK_KP_MEMSTORE:        return K_KP_MEMSTORE;
291 //      case SDLK_KP_MEMRECALL:       return K_KP_MEMRECALL;
292 //      case SDLK_KP_MEMCLEAR:        return K_KP_MEMCLEAR;
293 //      case SDLK_KP_MEMADD:          return K_KP_MEMADD;
294 //      case SDLK_KP_MEMSUBTRACT:     return K_KP_MEMSUBTRACT;
295 //      case SDLK_KP_MEMMULTIPLY:     return K_KP_MEMMULTIPLY;
296 //      case SDLK_KP_MEMDIVIDE:       return K_KP_MEMDIVIDE;
297 //      case SDLK_KP_PLUSMINUS:       return K_KP_PLUSMINUS;
298 //      case SDLK_KP_CLEAR:           return K_KP_CLEAR;
299 //      case SDLK_KP_CLEARENTRY:      return K_KP_CLEARENTRY;
300 //      case SDLK_KP_BINARY:          return K_KP_BINARY;
301 //      case SDLK_KP_OCTAL:           return K_KP_OCTAL;
302 //      case SDLK_KP_DECIMAL:         return K_KP_DECIMAL;
303 //      case SDLK_KP_HEXADECIMAL:     return K_KP_HEXADECIMAL;
304         case SDLK_LCTRL:              return K_CTRL;
305         case SDLK_LSHIFT:             return K_SHIFT;
306         case SDLK_LALT:               return K_ALT;
307 //      case SDLK_LGUI:               return K_LGUI;
308         case SDLK_RCTRL:              return K_CTRL;
309         case SDLK_RSHIFT:             return K_SHIFT;
310         case SDLK_RALT:               return K_ALT;
311 //      case SDLK_RGUI:               return K_RGUI;
312 //      case SDLK_MODE:               return K_MODE;
313 //      case SDLK_AUDIONEXT:          return K_AUDIONEXT;
314 //      case SDLK_AUDIOPREV:          return K_AUDIOPREV;
315 //      case SDLK_AUDIOSTOP:          return K_AUDIOSTOP;
316 //      case SDLK_AUDIOPLAY:          return K_AUDIOPLAY;
317 //      case SDLK_AUDIOMUTE:          return K_AUDIOMUTE;
318 //      case SDLK_MEDIASELECT:        return K_MEDIASELECT;
319 //      case SDLK_WWW:                return K_WWW;
320 //      case SDLK_MAIL:               return K_MAIL;
321 //      case SDLK_CALCULATOR:         return K_CALCULATOR;
322 //      case SDLK_COMPUTER:           return K_COMPUTER;
323 //      case SDLK_AC_SEARCH:          return K_AC_SEARCH; // Android button
324 //      case SDLK_AC_HOME:            return K_AC_HOME; // Android button
325         case SDLK_AC_BACK:            return K_ESCAPE; // Android button
326 //      case SDLK_AC_FORWARD:         return K_AC_FORWARD; // Android button
327 //      case SDLK_AC_STOP:            return K_AC_STOP; // Android button
328 //      case SDLK_AC_REFRESH:         return K_AC_REFRESH; // Android button
329 //      case SDLK_AC_BOOKMARKS:       return K_AC_BOOKMARKS; // Android button
330 //      case SDLK_BRIGHTNESSDOWN:     return K_BRIGHTNESSDOWN;
331 //      case SDLK_BRIGHTNESSUP:       return K_BRIGHTNESSUP;
332 //      case SDLK_DISPLAYSWITCH:      return K_DISPLAYSWITCH;
333 //      case SDLK_KBDILLUMTOGGLE:     return K_KBDILLUMTOGGLE;
334 //      case SDLK_KBDILLUMDOWN:       return K_KBDILLUMDOWN;
335 //      case SDLK_KBDILLUMUP:         return K_KBDILLUMUP;
336 //      case SDLK_EJECT:              return K_EJECT;
337 //      case SDLK_SLEEP:              return K_SLEEP;
338         }
339 }
340
341 qbool VID_HasScreenKeyboardSupport(void)
342 {
343         return SDL_HasScreenKeyboardSupport() != SDL_FALSE;
344 }
345
346 void VID_ShowKeyboard(qbool show)
347 {
348         if (!SDL_HasScreenKeyboardSupport())
349                 return;
350
351         if (show)
352         {
353                 if (!SDL_IsTextInputActive())
354                         SDL_StartTextInput();
355         }
356         else
357         {
358                 if (SDL_IsTextInputActive())
359                         SDL_StopTextInput();
360         }
361 }
362
363 qbool VID_ShowingKeyboard(void)
364 {
365         return SDL_IsTextInputActive() != 0;
366 }
367
368 static void VID_SetMouse(qbool relative, qbool hidecursor)
369 {
370 #ifndef DP_MOBILETOUCH
371 #ifdef MACOSX
372         if(relative)
373                 if(vid_usingmouse && (vid_usingnoaccel != !!apple_mouse_noaccel.integer))
374                         VID_SetMouse(false, false); // ungrab first!
375 #endif
376         if (vid_usingmouse != relative)
377         {
378                 vid_usingmouse = relative;
379                 cl_ignoremousemoves = 2;
380                 vid_usingmouse_relativeworks = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0;
381 //              Con_Printf("VID_SetMouse(%i, %i) relativeworks = %i\n", (int)relative, (int)hidecursor, (int)vid_usingmouse_relativeworks);
382 #ifdef MACOSX
383                 if(relative)
384                 {
385                         // Save the status of mouse acceleration
386                         originalMouseSpeed = -1.0; // in case of error
387                         if(apple_mouse_noaccel.integer)
388                         {
389                                 io_connect_t mouseDev = IN_GetIOHandle();
390                                 if(mouseDev != 0)
391                                 {
392                                         if(IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess)
393                                         {
394                                                 Con_DPrintf("previous mouse acceleration: %f\n", originalMouseSpeed);
395                                                 if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess)
396                                                 {
397                                                         Con_Print("Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n");
398                                                         Cvar_SetValueQuick(&apple_mouse_noaccel, 0);
399                                                 }
400                                         }
401                                         else
402                                         {
403                                                 Con_Print("Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n");
404                                                 Cvar_SetValueQuick(&apple_mouse_noaccel, 0);
405                                         }
406                                         IOServiceClose(mouseDev);
407                                 }
408                                 else
409                                 {
410                                         Con_Print("Could not disable mouse acceleration (failed at IO_GetIOHandle).\n");
411                                         Cvar_SetValueQuick(&apple_mouse_noaccel, 0);
412                                 }
413                         }
414
415                         vid_usingnoaccel = !!apple_mouse_noaccel.integer;
416                 }
417                 else
418                 {
419                         if(originalMouseSpeed != -1.0)
420                         {
421                                 io_connect_t mouseDev = IN_GetIOHandle();
422                                 if(mouseDev != 0)
423                                 {
424                                         Con_DPrintf("restoring mouse acceleration to: %f\n", originalMouseSpeed);
425                                         if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess)
426                                                 Con_Print("Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n");
427                                         IOServiceClose(mouseDev);
428                                 }
429                                 else
430                                         Con_Print("Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n");
431                         }
432                 }
433 #endif
434         }
435         if (vid_usinghidecursor != hidecursor)
436         {
437                 vid_usinghidecursor = hidecursor;
438                 SDL_ShowCursor( hidecursor ? SDL_DISABLE : SDL_ENABLE);
439         }
440 #endif
441 }
442
443 // multitouch[10][] represents the mouse pointer
444 // multitouch[][0]: finger active
445 // multitouch[][1]: Y
446 // multitouch[][2]: Y
447 // X and Y coordinates are 0-1.
448 #define MAXFINGERS 11
449 float multitouch[MAXFINGERS][3];
450
451 // this one stores how many areas this finger has touched
452 int multitouchs[MAXFINGERS];
453
454 // modified heavily by ELUAN
455 static qbool VID_TouchscreenArea(int corner, float px, float py, float pwidth, float pheight, const char *icon, float textheight, const char *text, float *resultmove, qbool *resultbutton, keynum_t key, const char *typedtext, float deadzone, float oversizepixels_x, float oversizepixels_y, qbool iamexclusive)
456 {
457         int finger;
458         float fx, fy, fwidth, fheight;
459         float overfx, overfy, overfwidth, overfheight;
460         float rel[3];
461         float sqsum;
462         qbool button = false;
463         VectorClear(rel);
464         if (pwidth > 0 && pheight > 0)
465         {
466                 if (corner & 1) px += vid_conwidth.value;
467                 if (corner & 2) py += vid_conheight.value;
468                 if (corner & 4) px += vid_conwidth.value * 0.5f;
469                 if (corner & 8) py += vid_conheight.value * 0.5f;
470                 if (corner & 16) {px *= vid_conwidth.value * (1.0f / 640.0f);py *= vid_conheight.value * (1.0f / 480.0f);pwidth *= vid_conwidth.value * (1.0f / 640.0f);pheight *= vid_conheight.value * (1.0f / 480.0f);}
471                 fx = px / vid_conwidth.value;
472                 fy = py / vid_conheight.value;
473                 fwidth = pwidth / vid_conwidth.value;
474                 fheight = pheight / vid_conheight.value;
475
476                 // try to prevent oversizepixels_* from interfering with the iamexclusive cvar by not letting we start controlling from too far of the actual touch area (areas without resultbuttons should NEVER have the oversizepixels_* parameters set to anything other than 0)
477                 if (resultbutton)
478                         if (!(*resultbutton))
479                         {
480                                 oversizepixels_x *= 0.2;
481                                 oversizepixels_y *= 0.2;
482                         }
483
484                 oversizepixels_x /= vid_conwidth.value;
485                 oversizepixels_y /= vid_conheight.value;
486
487                 overfx = fx - oversizepixels_x;
488                 overfy = fy - oversizepixels_y;
489                 overfwidth = fwidth + 2*oversizepixels_x;
490                 overfheight = fheight + 2*oversizepixels_y;
491
492                 for (finger = 0;finger < MAXFINGERS;finger++)
493                 {
494                         if (multitouchs[finger] && iamexclusive) // for this to work correctly, you must call touch areas in order of highest to lowest priority
495                                 continue;
496
497                         if (multitouch[finger][0] && multitouch[finger][1] >= overfx && multitouch[finger][2] >= overfy && multitouch[finger][1] < overfx + overfwidth && multitouch[finger][2] < overfy + overfheight)
498                         {
499                                 multitouchs[finger]++;
500
501                                 rel[0] = bound(-1, (multitouch[finger][1] - (fx + 0.5f * fwidth)) * (2.0f / fwidth), 1);
502                                 rel[1] = bound(-1, (multitouch[finger][2] - (fy + 0.5f * fheight)) * (2.0f / fheight), 1);
503                                 rel[2] = 0;
504
505                                 sqsum = rel[0]*rel[0] + rel[1]*rel[1];
506                                 // 2d deadzone
507                                 if (sqsum < deadzone*deadzone)
508                                 {
509                                         rel[0] = 0;
510                                         rel[1] = 0;
511                                 }
512                                 else if (sqsum > 1)
513                                 {
514                                         // ignore the third component
515                                         Vector2Normalize2(rel, rel);
516                                 }
517                                 button = true;
518                                 break;
519                         }
520                 }
521                 if (scr_numtouchscreenareas < 128)
522                 {
523                         scr_touchscreenareas[scr_numtouchscreenareas].pic = icon;
524                         scr_touchscreenareas[scr_numtouchscreenareas].text = text;
525                         scr_touchscreenareas[scr_numtouchscreenareas].textheight = textheight;
526                         scr_touchscreenareas[scr_numtouchscreenareas].rect[0] = px;
527                         scr_touchscreenareas[scr_numtouchscreenareas].rect[1] = py;
528                         scr_touchscreenareas[scr_numtouchscreenareas].rect[2] = pwidth;
529                         scr_touchscreenareas[scr_numtouchscreenareas].rect[3] = pheight;
530                         scr_touchscreenareas[scr_numtouchscreenareas].active = button;
531                         // the pics may have alpha too.
532                         scr_touchscreenareas[scr_numtouchscreenareas].activealpha = 1.f;
533                         scr_touchscreenareas[scr_numtouchscreenareas].inactivealpha = 0.95f;
534                         scr_numtouchscreenareas++;
535                 }
536         }
537         if (resultmove)
538         {
539                 if (button)
540                         VectorCopy(rel, resultmove);
541                 else
542                         VectorClear(resultmove);
543         }
544         if (resultbutton)
545         {
546                 if (*resultbutton != button)
547                 {
548                         if ((int)key > 0)
549                                 Key_Event(key, 0, button);
550                         if (typedtext && typedtext[0] && !*resultbutton)
551                         {
552                                 // FIXME: implement UTF8 support - nothing actually specifies a UTF8 string here yet, but should support it...
553                                 int i;
554                                 for (i = 0;typedtext[i];i++)
555                                 {
556                                         Key_Event(K_TEXT, typedtext[i], true);
557                                         Key_Event(K_TEXT, typedtext[i], false);
558                                 }
559                         }
560                 }
561                 *resultbutton = button;
562         }
563         return button;
564 }
565
566 // ELUAN:
567 // not reentrant, but we only need one mouse cursor anyway...
568 static void VID_TouchscreenCursor(float px, float py, float pwidth, float pheight, qbool *resultbutton, keynum_t key)
569 {
570         int finger;
571         float fx, fy, fwidth, fheight;
572         qbool button = false;
573         static int cursorfinger = -1;
574         static int cursorfreemovement = false;
575         static int canclick = false;
576         static int clickxy[2];
577         static int relclickxy[2];
578         static double clickrealtime = 0;
579
580         if (steelstorm_showing_mousecursor && steelstorm_showing_mousecursor->integer)
581         if (pwidth > 0 && pheight > 0)
582         {
583                 fx = px / vid_conwidth.value;
584                 fy = py / vid_conheight.value;
585                 fwidth = pwidth / vid_conwidth.value;
586                 fheight = pheight / vid_conheight.value;
587                 for (finger = 0;finger < MAXFINGERS;finger++)
588                 {
589                         if (multitouch[finger][0] && multitouch[finger][1] >= fx && multitouch[finger][2] >= fy && multitouch[finger][1] < fx + fwidth && multitouch[finger][2] < fy + fheight)
590                         {
591                                 if (cursorfinger == -1)
592                                 {
593                                         clickxy[0] =  multitouch[finger][1] * vid_width.value - 0.5f * pwidth;
594                                         clickxy[1] =  multitouch[finger][2] * vid_height.value - 0.5f * pheight;
595                                         relclickxy[0] =  (multitouch[finger][1] - fx) * vid_width.value - 0.5f * pwidth;
596                                         relclickxy[1] =  (multitouch[finger][2] - fy) * vid_height.value - 0.5f * pheight;
597                                 }
598                                 cursorfinger = finger;
599                                 button = true;
600                                 canclick = true;
601                                 cursorfreemovement = false;
602                                 break;
603                         }
604                 }
605                 if (scr_numtouchscreenareas < 128)
606                 {
607                         if (clickrealtime + 1 > host.realtime)
608                         {
609                                 scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_click.tga";
610                         }
611                         else if (button)
612                         {
613                                 scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_touch.tga";
614                         }
615                         else
616                         {
617                                 switch ((int)host.realtime * 10 % 20)
618                                 {
619                                 case 0:
620                                         scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_touch.tga";
621                                         break;
622                                 default:
623                                         scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_idle.tga";
624                                 }
625                         }
626                         scr_touchscreenareas[scr_numtouchscreenareas].text = "";
627                         scr_touchscreenareas[scr_numtouchscreenareas].textheight = 0;
628                         scr_touchscreenareas[scr_numtouchscreenareas].rect[0] = px;
629                         scr_touchscreenareas[scr_numtouchscreenareas].rect[1] = py;
630                         scr_touchscreenareas[scr_numtouchscreenareas].rect[2] = pwidth;
631                         scr_touchscreenareas[scr_numtouchscreenareas].rect[3] = pheight;
632                         scr_touchscreenareas[scr_numtouchscreenareas].active = button;
633                         scr_touchscreenareas[scr_numtouchscreenareas].activealpha = 1.0f;
634                         scr_touchscreenareas[scr_numtouchscreenareas].inactivealpha = 1.0f;
635                         scr_numtouchscreenareas++;
636                 }
637         }
638
639         if (cursorfinger != -1)
640         {
641                 if (multitouch[cursorfinger][0])
642                 {
643                         if (multitouch[cursorfinger][1] * vid_width.value - 0.5f * pwidth < clickxy[0] - 1 ||
644                                 multitouch[cursorfinger][1] * vid_width.value - 0.5f * pwidth > clickxy[0] + 1 ||
645                                 multitouch[cursorfinger][2] * vid_height.value - 0.5f * pheight< clickxy[1] - 1 ||
646                                 multitouch[cursorfinger][2] * vid_height.value - 0.5f * pheight> clickxy[1] + 1) // finger drifted more than the allowed amount
647                         {
648                                 cursorfreemovement = true;
649                         }
650                         if (cursorfreemovement)
651                         {
652                                 // in_windowmouse_x* is in screen resolution coordinates, not console resolution
653                                 in_windowmouse_x = multitouch[cursorfinger][1] * vid_width.value - 0.5f * pwidth - relclickxy[0];
654                                 in_windowmouse_y = multitouch[cursorfinger][2] * vid_height.value - 0.5f * pheight - relclickxy[1];
655                         }
656                 }
657                 else
658                 {
659                         cursorfinger = -1;
660                 }
661         }
662
663         if (resultbutton)
664         {
665                 if (/**resultbutton != button && */(int)key > 0)
666                 {
667                         if (!button && !cursorfreemovement && canclick)
668                         {
669                                 Key_Event(key, 0, true);
670                                 canclick = false;
671                                 clickrealtime = host.realtime;
672                         }
673
674                         // SS:BR can't qc can't cope with presses and releases on the same frame
675                         if (clickrealtime && clickrealtime + 0.1 < host.realtime)
676                         {
677                                 Key_Event(key, 0, false);
678                                 clickrealtime = 0;
679                         }
680                 }
681
682                 *resultbutton = button;
683         }
684 }
685
686 void VID_BuildJoyState(vid_joystate_t *joystate)
687 {
688         VID_Shared_BuildJoyState_Begin(joystate);
689
690         if (vid_sdljoystick)
691         {
692                 SDL_Joystick *joy = vid_sdljoystick;
693                 int j;
694
695                 if (vid_sdlgamecontroller)
696                 {
697                         for (j = 0; j <= SDL_CONTROLLER_AXIS_MAX; ++j)
698                         {
699                                 joystate->axis[j] = SDL_GameControllerGetAxis(vid_sdlgamecontroller, (SDL_GameControllerAxis)j) * (1.0f / 32767.0f);
700                         }
701                         for (j = 0; j < SDL_CONTROLLER_BUTTON_MAX; ++j)
702                                 joystate->button[j] = SDL_GameControllerGetButton(vid_sdlgamecontroller, (SDL_GameControllerButton)j);
703                         // emulate joy buttons for trigger "axes"
704                         joystate->button[SDL_CONTROLLER_BUTTON_MAX] = VID_JoyState_GetAxis(joystate, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 1, joy_sdl2_trigger_deadzone.value) > 0.0f;
705                         joystate->button[SDL_CONTROLLER_BUTTON_MAX+1] = VID_JoyState_GetAxis(joystate, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 1, joy_sdl2_trigger_deadzone.value) > 0.0f;
706                 }
707                 else
708
709                 {
710                         int numaxes;
711                         int numbuttons;
712                         numaxes = SDL_JoystickNumAxes(joy);
713                         for (j = 0;j < numaxes;j++)
714                                 joystate->axis[j] = SDL_JoystickGetAxis(joy, j) * (1.0f / 32767.0f);
715                         numbuttons = SDL_JoystickNumButtons(joy);
716                         for (j = 0;j < numbuttons;j++)
717                                 joystate->button[j] = SDL_JoystickGetButton(joy, j);
718                 }
719         }
720
721         VID_Shared_BuildJoyState_Finish(joystate);
722 }
723
724 // clear every touch screen area, except the one with button[skip]
725 #define Vid_ClearAllTouchscreenAreas(skip) \
726         if (skip != 0) \
727                 VID_TouchscreenCursor(0, 0, 0, 0, &buttons[0], K_MOUSE1); \
728         if (skip != 1) \
729                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, move, &buttons[1], K_MOUSE4, NULL, 0, 0, 0, false); \
730         if (skip != 2) \
731                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, aim,  &buttons[2], K_MOUSE5, NULL, 0, 0, 0, false); \
732         if (skip != 3) \
733                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[3], K_SHIFT, NULL, 0, 0, 0, false); \
734         if (skip != 4) \
735                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, false); \
736         if (skip != 9) \
737                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[9], K_MOUSE3, NULL, 0, 0, 0, false); \
738         if (skip != 10) \
739                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[10], (keynum_t)'m', NULL, 0, 0, 0, false); \
740         if (skip != 11) \
741                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[11], (keynum_t)'b', NULL, 0, 0, 0, false); \
742         if (skip != 12) \
743                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[12], (keynum_t)'q', NULL, 0, 0, 0, false); \
744         if (skip != 13) \
745                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, false); \
746         if (skip != 14) \
747                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, false); \
748         if (skip != 15) \
749                 VID_TouchscreenArea( 0,  0,  0,  0,  0, NULL                         , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, false); \
750
751 /////////////////////
752 // Movement handling
753 ////
754
755 static void IN_Move_TouchScreen_SteelStorm(void)
756 {
757         // ELUAN
758         int i, numfingers;
759         float xscale, yscale;
760         float move[3], aim[3];
761         static qbool oldbuttons[128];
762         static qbool buttons[128];
763         keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest;
764         memcpy(oldbuttons, buttons, sizeof(oldbuttons));
765         memset(multitouchs, 0, sizeof(multitouchs));
766
767         for (i = 0, numfingers = 0; i < MAXFINGERS - 1; i++)
768                 if (multitouch[i][0])
769                         numfingers++;
770
771         /*
772         Enable this to use a mouse as a touch device (it may conflict with the iamexclusive parameter if a finger is also reported as a mouse at the same location
773         if (numfingers == 1)
774         {
775                 multitouch[MAXFINGERS-1][0] = SDL_GetMouseState(&x, &y) ? 11 : 0;
776                 multitouch[MAXFINGERS-1][1] = (float)x / vid.width;
777                 multitouch[MAXFINGERS-1][2] = (float)y / vid.height;
778         }
779         else
780         {
781                 // disable it so it doesn't get stuck, because SDL seems to stop updating it if there are more than 1 finger on screen
782                 multitouch[MAXFINGERS-1][0] = 0;
783         }*/
784
785         // TODO: make touchscreen areas controlled by a config file or the VMs. THIS IS A MESS!
786         // TODO: can't just clear buttons[] when entering a new keydest, some keys would remain pressed
787         // SS:BR menuqc has many peculiarities, including that it can't accept more than one command per frame and pressing and releasing on the same frame
788
789         // Tuned for the SGS3, use it's value as a base. CLEAN THIS.
790         xscale = vid_touchscreen_density.value / 2.0f;
791         yscale = vid_touchscreen_density.value / 2.0f;
792         switch(keydest)
793         {
794         case key_console:
795                 Vid_ClearAllTouchscreenAreas(14);
796                 VID_TouchscreenArea( 0,   0, 160,  64,  64, "gfx/gui/touch_menu_button.tga"         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, false);
797                 break;
798         case key_game:
799                 if (steelstorm_showing_map && steelstorm_showing_map->integer) // FIXME: another hack to be removed when touchscreen areas go to QC
800                 {
801                         VID_TouchscreenArea( 0,   0,   0, vid_conwidth.value, vid_conheight.value, NULL                         , 0.0f, NULL, NULL, &buttons[10], (keynum_t)'m', NULL, 0, 0, 0, false);
802                         Vid_ClearAllTouchscreenAreas(10);
803                 }
804                 else if (steelstorm_showing_mousecursor && steelstorm_showing_mousecursor->integer)
805                 {
806                         // in_windowmouse_x* is in screen resolution coordinates, not console resolution
807                         VID_TouchscreenCursor((float)in_windowmouse_x/vid_width.value*vid_conwidth.value, (float)in_windowmouse_y/vid_height.value*vid_conheight.value, 192*xscale, 192*yscale, &buttons[0], K_MOUSE1);
808                         Vid_ClearAllTouchscreenAreas(0);
809                 }
810                 else
811                 {
812                         VID_TouchscreenCursor(0, 0, 0, 0, &buttons[0], K_MOUSE1);
813
814                         VID_TouchscreenArea( 2,16*xscale,-240*yscale, 224*xscale, 224*yscale, "gfx/gui/touch_l_thumb_dpad.tga", 0.0f, NULL, move, &buttons[1], (keynum_t)0, NULL, 0.15, 112*xscale, 112*yscale, false);
815
816                         VID_TouchscreenArea( 3,-240*xscale,-160*yscale, 224*xscale, 128*yscale, "gfx/gui/touch_r_thumb_turn_n_shoot.tga"    , 0.0f, NULL, NULL,  0, (keynum_t)0, NULL, 0, 56*xscale, 0, false);
817                         VID_TouchscreenArea( 3,-240*xscale,-256*yscale, 224*xscale, 224*yscale, NULL    , 0.0f, NULL, aim,  &buttons[2], (keynum_t)0, NULL, 0.2, 56*xscale, 0, false);
818
819                         VID_TouchscreenArea( 2, (vid_conwidth.value / 2) - 128,-80,  256,  80, NULL, 0.0f, NULL, NULL, &buttons[3], K_SHIFT, NULL, 0, 0, 0, true);
820
821                         VID_TouchscreenArea( 3,-240*xscale,-256*yscale, 224*xscale,  64*yscale, "gfx/gui/touch_secondary_slide.tga", 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 56*xscale, 0, false);
822                         VID_TouchscreenArea( 3,-240*xscale,-256*yscale, 224*xscale,  160*yscale, NULL , 0.0f, NULL, NULL, &buttons[9], K_MOUSE3, NULL, 0.2, 56*xscale, 0, false);
823
824                         VID_TouchscreenArea( 1,-100,   0, 100, 100, NULL                         , 0.0f, NULL, NULL, &buttons[10], (keynum_t)'m', NULL, 0, 0, 0, true);
825                         VID_TouchscreenArea( 1,-100, 120, 100, 100, NULL                         , 0.0f, NULL, NULL, &buttons[11], (keynum_t)'b', NULL, 0, 0, 0, true);
826                         VID_TouchscreenArea( 0,   0,   0,  64,  64, NULL                         , 0.0f, NULL, NULL, &buttons[12], (keynum_t)'q', NULL, 0, 0, 0, true);
827                         if (developer.integer)
828                                 VID_TouchscreenArea( 0,   0,  96,  64,  64, NULL                         , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true);
829                         else
830                                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, false);
831                         VID_TouchscreenArea( 0,   0, 160,  64,  64, "gfx/gui/touch_menu_button.tga"         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true);
832                         switch(cl.activeweapon)
833                         {
834                         case 14:
835                                 VID_TouchscreenArea( 2,  16*xscale,-320*yscale, 224*xscale, 64*yscale, "gfx/gui/touch_booster.tga" , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, true);
836                                 break;
837                         case 12:
838                                 VID_TouchscreenArea( 2,  16*xscale,-320*yscale, 224*xscale, 64*yscale, "gfx/gui/touch_shockwave.tga" , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, true);
839                                 break;
840                         default:
841                                 VID_TouchscreenArea( 0,  0,  0,  0,  0, NULL , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, false);
842                         }
843                 }
844                 break;
845         default:
846                 if (!steelstorm_showing_mousecursor || !steelstorm_showing_mousecursor->integer)
847                 {
848                         Vid_ClearAllTouchscreenAreas(14);
849                         // this way we can skip cutscenes
850                         VID_TouchscreenArea( 0,   0,   0, vid_conwidth.value, vid_conheight.value, NULL                         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, false);
851                 }
852                 else
853                 {
854                         // in_windowmouse_x* is in screen resolution coordinates, not console resolution
855                         VID_TouchscreenCursor((float)in_windowmouse_x/vid_width.value*vid_conwidth.value, (float)in_windowmouse_y/vid_height.value*vid_conheight.value, 192*xscale, 192*yscale, &buttons[0], K_MOUSE1);
856                         Vid_ClearAllTouchscreenAreas(0);
857                 }
858                 break;
859         }
860
861         if (VID_ShowingKeyboard() && (float)in_windowmouse_y > vid_height.value / 2 - 10)
862                 in_windowmouse_y = 128;
863
864         cl.cmd.forwardmove -= move[1] * cl_forwardspeed.value;
865         cl.cmd.sidemove += move[0] * cl_sidespeed.value;
866         cl.viewangles[0] += aim[1] * cl_pitchspeed.value * cl.realframetime;
867         cl.viewangles[1] -= aim[0] * cl_yawspeed.value * cl.realframetime;
868 }
869
870 static void IN_Move_TouchScreen_Quake(void)
871 {
872         int x, y;
873         float move[3], aim[3], click[3];
874         static qbool oldbuttons[128];
875         static qbool buttons[128];
876         keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest;
877         memcpy(oldbuttons, buttons, sizeof(oldbuttons));
878         memset(multitouchs, 0, sizeof(multitouchs));
879
880         // simple quake controls
881         multitouch[MAXFINGERS-1][0] = SDL_GetMouseState(&x, &y);
882         multitouch[MAXFINGERS-1][1] = x * 32768 / vid.width;
883         multitouch[MAXFINGERS-1][2] = y * 32768 / vid.height;
884
885         // top of screen is toggleconsole and K_ESCAPE
886         switch(keydest)
887         {
888         case key_console:
889                 VID_TouchscreenArea( 0,   0,   0,  64,  64, NULL                         , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true);
890                 VID_TouchscreenArea( 0,  64,   0,  64,  64, "gfx/touch_menu.tga"         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true);
891                 if (!VID_ShowingKeyboard())
892                 {
893                         // user entered a command, close the console now
894                         Con_ToggleConsole_f(cmd_local);
895                 }
896                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[15], (keynum_t)0, NULL, 0, 0, 0, true);
897                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, move, &buttons[0], K_MOUSE4, NULL, 0, 0, 0, true);
898                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, aim,  &buttons[1], K_MOUSE5, NULL, 0, 0, 0, true);
899                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, click,&buttons[2], K_MOUSE1, NULL, 0, 0, 0, true);
900                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[3], K_SPACE, NULL, 0, 0, 0, true);
901                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, true);
902                 break;
903         case key_game:
904                 VID_TouchscreenArea( 0,   0,   0,  64,  64, NULL                         , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true);
905                 VID_TouchscreenArea( 0,  64,   0,  64,  64, "gfx/touch_menu.tga"         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true);
906                 VID_TouchscreenArea( 2,   0,-128, 128, 128, "gfx/touch_movebutton.tga"   , 0.0f, NULL, move, &buttons[0], K_MOUSE4, NULL, 0, 0, 0, true);
907                 VID_TouchscreenArea( 3,-128,-128, 128, 128, "gfx/touch_aimbutton.tga"    , 0.0f, NULL, aim,  &buttons[1], K_MOUSE5, NULL, 0, 0, 0, true);
908                 VID_TouchscreenArea( 2,   0,-160,  64,  32, "gfx/touch_jumpbutton.tga"   , 0.0f, NULL, NULL, &buttons[3], K_SPACE, NULL, 0, 0, 0, true);
909                 VID_TouchscreenArea( 3,-128,-160,  64,  32, "gfx/touch_attackbutton.tga" , 0.0f, NULL, NULL, &buttons[2], K_MOUSE1, NULL, 0, 0, 0, true);
910                 VID_TouchscreenArea( 3, -64,-160,  64,  32, "gfx/touch_attack2button.tga", 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, true);
911                 buttons[15] = false;
912                 break;
913         default:
914                 VID_TouchscreenArea( 0,   0,   0,  64,  64, NULL                         , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true);
915                 VID_TouchscreenArea( 0,  64,   0,  64,  64, "gfx/touch_menu.tga"         , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true);
916                 // in menus, an icon in the corner activates keyboard
917                 VID_TouchscreenArea( 2,   0, -32,  32,  32, "gfx/touch_keyboard.tga"     , 0.0f, NULL, NULL, &buttons[15], (keynum_t)0, NULL, 0, 0, 0, true);
918                 if (buttons[15])
919                         VID_ShowKeyboard(true);
920                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, move, &buttons[0], K_MOUSE4, NULL, 0, 0, 0, true);
921                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, aim,  &buttons[1], K_MOUSE5, NULL, 0, 0, 0, true);
922                 VID_TouchscreenArea(16, -320,-480,640, 960, NULL                         , 0.0f, NULL, click,&buttons[2], K_MOUSE1, NULL, 0, 0, 0, true);
923                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[3], K_SPACE, NULL, 0, 0, 0, true);
924                 VID_TouchscreenArea( 0,   0,   0,   0,   0, NULL                         , 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, true);
925                 if (buttons[2])
926                 {
927                         in_windowmouse_x = x;
928                         in_windowmouse_y = y;
929                 }
930                 break;
931         }
932
933         cl.cmd.forwardmove -= move[1] * cl_forwardspeed.value;
934         cl.cmd.sidemove += move[0] * cl_sidespeed.value;
935         cl.viewangles[0] += aim[1] * cl_pitchspeed.value * cl.realframetime;
936         cl.viewangles[1] -= aim[0] * cl_yawspeed.value * cl.realframetime;
937 }
938
939 void IN_Move( void )
940 {
941         static int old_x = 0, old_y = 0;
942         static int stuck = 0;
943         static keydest_t oldkeydest;
944         static qbool oldshowkeyboard;
945         int x, y;
946         vid_joystate_t joystate;
947         keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest;
948
949         scr_numtouchscreenareas = 0;
950
951         // Only apply the new keyboard state if the input changes.
952         if (keydest != oldkeydest || !!vid_touchscreen_showkeyboard.integer != oldshowkeyboard)
953         {
954                 switch(keydest)
955                 {
956                         case key_console: VID_ShowKeyboard(true);break;
957                         case key_message: VID_ShowKeyboard(true);break;
958                         default: VID_ShowKeyboard(!!vid_touchscreen_showkeyboard.integer); break;
959                 }
960         }
961         oldkeydest = keydest;
962         oldshowkeyboard = !!vid_touchscreen_showkeyboard.integer;
963
964         if (vid_touchscreen.integer)
965         {
966                 switch(gamemode)
967                 {
968                 case GAME_STEELSTORM:
969                         IN_Move_TouchScreen_SteelStorm();
970                         break;
971                 default:
972                         IN_Move_TouchScreen_Quake();
973                         break;
974                 }
975         }
976         else
977         {
978                 if (vid_usingmouse)
979                 {
980                         if (vid_stick_mouse.integer || !vid_usingmouse_relativeworks)
981                         {
982                                 // have the mouse stuck in the middle, example use: prevent expose effect of beryl during the game when not using
983                                 // window grabbing. --blub
984         
985                                 // we need 2 frames to initialize the center position
986                                 if(!stuck)
987                                 {
988                                         SDL_WarpMouseInWindow(window, win_half_width, win_half_height);
989                                         SDL_GetMouseState(&x, &y);
990                                         SDL_GetRelativeMouseState(&x, &y);
991                                         ++stuck;
992                                 } else {
993                                         SDL_GetRelativeMouseState(&x, &y);
994                                         in_mouse_x = x + old_x;
995                                         in_mouse_y = y + old_y;
996                                         SDL_GetMouseState(&x, &y);
997                                         old_x = x - win_half_width;
998                                         old_y = y - win_half_height;
999                                         SDL_WarpMouseInWindow(window, win_half_width, win_half_height);
1000                                 }
1001                         } else {
1002                                 SDL_GetRelativeMouseState( &x, &y );
1003                                 in_mouse_x = x;
1004                                 in_mouse_y = y;
1005                         }
1006                 }
1007
1008                 SDL_GetMouseState(&x, &y);
1009                 in_windowmouse_x = x;
1010                 in_windowmouse_y = y;
1011         }
1012
1013         //Con_Printf("Mouse position: in_mouse %f %f in_windowmouse %f %f\n", in_mouse_x, in_mouse_y, in_windowmouse_x, in_windowmouse_y);
1014
1015         VID_BuildJoyState(&joystate);
1016         VID_ApplyJoyState(&joystate);
1017 }
1018
1019 /////////////////////
1020 // Message Handling
1021 ////
1022
1023 static keynum_t buttonremap[] =
1024 {
1025         K_MOUSE1,
1026         K_MOUSE3,
1027         K_MOUSE2,
1028         K_MOUSE4,
1029         K_MOUSE5,
1030         K_MOUSE6,
1031         K_MOUSE7,
1032         K_MOUSE8,
1033         K_MOUSE9,
1034         K_MOUSE10,
1035         K_MOUSE11,
1036         K_MOUSE12,
1037         K_MOUSE13,
1038         K_MOUSE14,
1039         K_MOUSE15,
1040         K_MOUSE16,
1041 };
1042
1043 //#define DEBUGSDLEVENTS
1044 void Sys_SDL_HandleEvents(void)
1045 {
1046         static qbool sound_active = true;
1047         int keycode;
1048         int i;
1049         const char *chp;
1050         qbool isdown;
1051         Uchar unicode;
1052         SDL_Event event;
1053
1054         VID_EnableJoystick(true);
1055
1056         while( SDL_PollEvent( &event ) )
1057                 loop_start:
1058                 switch( event.type ) {
1059                         case SDL_QUIT:
1060 #ifdef DEBUGSDLEVENTS
1061                                 Con_DPrintf("SDL_Event: SDL_QUIT\n");
1062 #endif
1063                                 host.state = host_shutdown;
1064                                 break;
1065                         case SDL_KEYDOWN:
1066                         case SDL_KEYUP:
1067 #ifdef DEBUGSDLEVENTS
1068                                 if (event.type == SDL_KEYDOWN)
1069                                         Con_DPrintf("SDL_Event: SDL_KEYDOWN %i\n", event.key.keysym.sym);
1070                                 else
1071                                         Con_DPrintf("SDL_Event: SDL_KEYUP %i\n", event.key.keysym.sym);
1072 #endif
1073                                 keycode = MapKey(event.key.keysym.sym);
1074                                 isdown = (event.key.state == SDL_PRESSED);
1075                                 unicode = 0;
1076                                 if(isdown)
1077                                 {
1078                                         if(SDL_PollEvent(&event))
1079                                         {
1080                                                 if(event.type == SDL_TEXTINPUT)
1081                                                 {
1082                                                         // combine key code from SDL_KEYDOWN event and character
1083                                                         // from SDL_TEXTINPUT event in a single Key_Event call
1084 #ifdef DEBUGSDLEVENTS
1085                                                         Con_DPrintf("SDL_Event: SDL_TEXTINPUT - text: %s\n", event.text.text);
1086 #endif
1087                                                         unicode = u8_getchar_utf8_enabled(event.text.text + (int)u8_bytelen(event.text.text, 0), NULL);
1088                                                 }
1089                                                 else
1090                                                 {
1091                                                         if (!VID_JoyBlockEmulatedKeys(keycode))
1092                                                                 Key_Event(keycode, 0, isdown);
1093                                                         goto loop_start;
1094                                                 }
1095                                         }
1096                                 }
1097                                 if (!VID_JoyBlockEmulatedKeys(keycode))
1098                                         Key_Event(keycode, unicode, isdown);
1099                                 break;
1100                         case SDL_MOUSEBUTTONDOWN:
1101                         case SDL_MOUSEBUTTONUP:
1102 #ifdef DEBUGSDLEVENTS
1103                                 if (event.type == SDL_MOUSEBUTTONDOWN)
1104                                         Con_DPrintf("SDL_Event: SDL_MOUSEBUTTONDOWN\n");
1105                                 else
1106                                         Con_DPrintf("SDL_Event: SDL_MOUSEBUTTONUP\n");
1107 #endif
1108                                 if (!vid_touchscreen.integer)
1109                                 if (event.button.button > 0 && event.button.button <= ARRAY_SIZE(buttonremap))
1110                                         Key_Event( buttonremap[event.button.button - 1], 0, event.button.state == SDL_PRESSED );
1111                                 break;
1112                         case SDL_MOUSEWHEEL:
1113                                 // TODO support wheel x direction.
1114                                 i = event.wheel.y;
1115                                 while (i > 0) {
1116                                         --i;
1117                                         Key_Event( K_MWHEELUP, 0, true );
1118                                         Key_Event( K_MWHEELUP, 0, false );
1119                                 }
1120                                 while (i < 0) {
1121                                         ++i;
1122                                         Key_Event( K_MWHEELDOWN, 0, true );
1123                                         Key_Event( K_MWHEELDOWN, 0, false );
1124                                 }
1125                                 break;
1126                         case SDL_JOYBUTTONDOWN:
1127                         case SDL_JOYBUTTONUP:
1128                         case SDL_JOYAXISMOTION:
1129                         case SDL_JOYBALLMOTION:
1130                         case SDL_JOYHATMOTION:
1131 #ifdef DEBUGSDLEVENTS
1132                                 Con_DPrintf("SDL_Event: SDL_JOY*\n");
1133 #endif
1134                                 break;
1135                         case SDL_WINDOWEVENT:
1136 #ifdef DEBUGSDLEVENTS
1137                                 Con_DPrintf("SDL_Event: SDL_WINDOWEVENT %i\n", (int)event.window.event);
1138 #endif
1139                                 //if (event.window.windowID == window) // how to compare?
1140                                 {
1141                                         switch(event.window.event)
1142                                         {
1143                                         case SDL_WINDOWEVENT_SHOWN:
1144                                                 vid_hidden = false;
1145                                                 break;
1146                                         case  SDL_WINDOWEVENT_HIDDEN:
1147                                                 vid_hidden = true;
1148                                                 break;
1149                                         case SDL_WINDOWEVENT_EXPOSED:
1150 #ifdef DEBUGSDLEVENTS
1151                                                 Con_DPrintf("SDL_Event: SDL_WINDOWEVENT_EXPOSED\n");
1152 #endif
1153                                                 break;
1154                                         case SDL_WINDOWEVENT_MOVED:
1155                                                 vid.xPos = event.window.data1;
1156                                                 vid.yPos = event.window.data2;
1157                                                 // Update vid.displayindex (current monitor) as it may have changed
1158                                                 // SDL_GetWindowDisplayIndex() doesn't work if the window manager moves the fullscreen window, but this works:
1159                                                 for (i = 0; i < vid_info_displaycount.integer; ++i)
1160                                                 {
1161                                                         SDL_Rect displaybounds;
1162                                                         if (SDL_GetDisplayBounds(i, &displaybounds) < 0)
1163                                                         {
1164                                                                 Con_Printf(CON_ERROR "Error getting bounds of display %i: \"%s\"\n", i, SDL_GetError());
1165                                                                 return;
1166                                                         }
1167                                                         if (vid.xPos >= displaybounds.x && vid.xPos < displaybounds.x + displaybounds.w)
1168                                                         if (vid.yPos >= displaybounds.y && vid.yPos < displaybounds.y + displaybounds.h)
1169                                                         {
1170                                                                 vid.displayindex = i;
1171                                                                 break;
1172                                                         }
1173                                                 }
1174                                                 // when the window manager adds/removes the border it's likely to move the SDL window
1175                                                 // we'll need to correct that to (re)align the xhair with the monitor
1176                                                 if (vid_wmborder_waiting)
1177                                                 {
1178                                                         SDL_GetWindowBordersSize(window, &i, NULL, NULL, NULL);
1179                                                         if (!i != vid_wmborderless) // border state changed
1180                                                         {
1181                                                                 SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(vid.displayindex), SDL_WINDOWPOS_CENTERED_DISPLAY(vid.displayindex));
1182                                                                 SDL_GetWindowPosition(window, &vid.xPos, &vid.yPos);
1183                                                                 vid_wmborder_waiting = false;
1184                                                         }
1185                                                 }
1186                                                 break;
1187                                         case SDL_WINDOWEVENT_RESIZED: // external events only
1188                                                 if(vid_resizable.integer < 2)
1189                                                 {
1190                                                         //vid.width = event.window.data1;
1191                                                         //vid.height = event.window.data2;
1192                                                         // get the real framebuffer size in case the platform's screen coordinates are DPI scaled
1193                                                         SDL_GL_GetDrawableSize(window, &vid.width, &vid.height);
1194                                                 }
1195                                                 break;
1196                                         case SDL_WINDOWEVENT_SIZE_CHANGED: // internal and external events
1197                                                 break;
1198                                         case SDL_WINDOWEVENT_MINIMIZED:
1199                                                 break;
1200                                         case SDL_WINDOWEVENT_MAXIMIZED:
1201                                                 break;
1202                                         case SDL_WINDOWEVENT_RESTORED:
1203                                                 break;
1204                                         case SDL_WINDOWEVENT_ENTER:
1205                                                 break;
1206                                         case SDL_WINDOWEVENT_LEAVE:
1207                                                 break;
1208                                         case SDL_WINDOWEVENT_FOCUS_GAINED:
1209                                                 vid_hasfocus = true;
1210                                                 break;
1211                                         case SDL_WINDOWEVENT_FOCUS_LOST:
1212                                                 vid_hasfocus = false;
1213                                                 break;
1214                                         case SDL_WINDOWEVENT_CLOSE:
1215                                                 host.state = host_shutdown;
1216                                                 break;
1217                                         case SDL_WINDOWEVENT_TAKE_FOCUS:
1218                                                 break;
1219                                         case SDL_WINDOWEVENT_HIT_TEST:
1220                                                 break;
1221                                         case SDL_WINDOWEVENT_ICCPROF_CHANGED:
1222                                                 break;
1223                                         case SDL_WINDOWEVENT_DISPLAY_CHANGED:
1224                                                 // this event can't be relied on in fullscreen, see SDL_WINDOWEVENT_MOVED above
1225                                                 vid.displayindex = event.window.data1;
1226                                                 break;
1227                                         }
1228                                 }
1229                                 break;
1230                         case SDL_DISPLAYEVENT: // Display hotplugging
1231                                 switch (event.display.event)
1232                                 {
1233                                         case SDL_DISPLAYEVENT_CONNECTED:
1234                                                 Con_Printf(CON_WARN "Display %i connected: %s\n", event.display.display, SDL_GetDisplayName(event.display.display));
1235 #ifdef __linux__
1236                                                 Con_Print(CON_WARN "A vid_restart may be necessary!\n");
1237 #endif
1238                                                 Cvar_SetValueQuick(&vid_info_displaycount, SDL_GetNumVideoDisplays());
1239                                                 // Ideally we'd call VID_ApplyDisplaySettings_c() to try to switch to the preferred display here,
1240                                                 // but we may need a vid_restart first, see comments in VID_ApplyDisplaySettings_c().
1241                                                 break;
1242                                         case SDL_DISPLAYEVENT_DISCONNECTED:
1243                                                 Con_Printf(CON_WARN "Display %i disconnected.\n", event.display.display);
1244 #ifdef __linux__
1245                                                 Con_Print(CON_WARN "A vid_restart may be necessary!\n");
1246 #endif
1247                                                 Cvar_SetValueQuick(&vid_info_displaycount, SDL_GetNumVideoDisplays());
1248                                                 break;
1249                                         case SDL_DISPLAYEVENT_ORIENTATION:
1250                                                 break;
1251                                 }
1252                                 break;
1253                         case SDL_TEXTEDITING:
1254 #ifdef DEBUGSDLEVENTS
1255                                 Con_DPrintf("SDL_Event: SDL_TEXTEDITING - composition = %s, cursor = %d, selection lenght = %d\n", event.edit.text, event.edit.start, event.edit.length);
1256 #endif
1257                                 // FIXME!  this is where composition gets supported
1258                                 break;
1259                         case SDL_TEXTINPUT:
1260 #ifdef DEBUGSDLEVENTS
1261                                 Con_DPrintf("SDL_Event: SDL_TEXTINPUT - text: %s\n", event.text.text);
1262 #endif
1263                                 // convert utf8 string to char
1264                                 // NOTE: this code is supposed to run even if utf8enable is 0
1265                                 chp = event.text.text;
1266                                 while (*chp != 0)
1267                                 {
1268                                         // input the chars one by one (there can be multiple chars when e.g. using an "input method")
1269                                         unicode = u8_getchar_utf8_enabled(chp, &chp);
1270                                         Key_Event(K_TEXT, unicode, true);
1271                                         Key_Event(K_TEXT, unicode, false);
1272                                 }
1273                                 break;
1274                         case SDL_MOUSEMOTION:
1275                                 break;
1276                         case SDL_FINGERDOWN:
1277 #ifdef DEBUGSDLEVENTS
1278                                 Con_DPrintf("SDL_FINGERDOWN for finger %i\n", (int)event.tfinger.fingerId);
1279 #endif
1280                                 for (i = 0;i < MAXFINGERS-1;i++)
1281                                 {
1282                                         if (!multitouch[i][0])
1283                                         {
1284                                                 multitouch[i][0] = event.tfinger.fingerId + 1;
1285                                                 multitouch[i][1] = event.tfinger.x;
1286                                                 multitouch[i][2] = event.tfinger.y;
1287                                                 // TODO: use event.tfinger.pressure?
1288                                                 break;
1289                                         }
1290                                 }
1291                                 if (i == MAXFINGERS-1)
1292                                         Con_DPrintf("Too many fingers at once!\n");
1293                                 break;
1294                         case SDL_FINGERUP:
1295 #ifdef DEBUGSDLEVENTS
1296                                 Con_DPrintf("SDL_FINGERUP for finger %i\n", (int)event.tfinger.fingerId);
1297 #endif
1298                                 for (i = 0;i < MAXFINGERS-1;i++)
1299                                 {
1300                                         if (multitouch[i][0] == event.tfinger.fingerId + 1)
1301                                         {
1302                                                 multitouch[i][0] = 0;
1303                                                 break;
1304                                         }
1305                                 }
1306                                 if (i == MAXFINGERS-1)
1307                                         Con_DPrintf("No SDL_FINGERDOWN event matches this SDL_FINGERMOTION event\n");
1308                                 break;
1309                         case SDL_FINGERMOTION:
1310 #ifdef DEBUGSDLEVENTS
1311                                 Con_DPrintf("SDL_FINGERMOTION for finger %i\n", (int)event.tfinger.fingerId);
1312 #endif
1313                                 for (i = 0;i < MAXFINGERS-1;i++)
1314                                 {
1315                                         if (multitouch[i][0] == event.tfinger.fingerId + 1)
1316                                         {
1317                                                 multitouch[i][1] = event.tfinger.x;
1318                                                 multitouch[i][2] = event.tfinger.y;
1319                                                 break;
1320                                         }
1321                                 }
1322                                 if (i == MAXFINGERS-1)
1323                                         Con_DPrintf("No SDL_FINGERDOWN event matches this SDL_FINGERMOTION event\n");
1324                                 break;
1325                         default:
1326 #ifdef DEBUGSDLEVENTS
1327                                 Con_DPrintf("Received unrecognized SDL_Event type 0x%x\n", event.type);
1328 #endif
1329                                 break;
1330                 }
1331
1332         vid_activewindow = !vid_hidden && vid_hasfocus;
1333
1334         // enable/disable sound on focus gain/loss
1335         if (vid_activewindow || !snd_mutewhenidle.integer)
1336         {
1337                 if (!sound_active)
1338                 {
1339                         S_UnblockSound ();
1340                         sound_active = true;
1341                 }
1342         }
1343         else
1344         {
1345                 if (sound_active)
1346                 {
1347                         S_BlockSound ();
1348                         sound_active = false;
1349                 }
1350         }
1351
1352         if (!vid_activewindow || key_consoleactive || scr_loading)
1353                 VID_SetMouse(false, false);
1354         else if (key_dest == key_menu || key_dest == key_menu_grabbed)
1355                 VID_SetMouse(vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer);
1356         else
1357                 VID_SetMouse(vid_mouse.integer && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0 && (!cls.demoplayback || cl_demo_mousegrab.integer) && !vid_touchscreen.integer, !vid_touchscreen.integer);
1358 }
1359
1360 /////////////////
1361 // Video system
1362 ////
1363
1364 void *GL_GetProcAddress(const char *name)
1365 {
1366         void *p = NULL;
1367         p = SDL_GL_GetProcAddress(name);
1368         return p;
1369 }
1370
1371 qbool GL_ExtensionSupported(const char *name)
1372 {
1373         return SDL_GL_ExtensionSupported(name);
1374 }
1375
1376 /// Applies display settings immediately (no vid_restart required).
1377 static void VID_ApplyDisplaySettings_c(cvar_t *var)
1378 {
1379         unsigned int fullscreenwanted, fullscreencurrent;
1380         unsigned int displaywanted = bound(0, vid_display.integer, vid_info_displaycount.integer - 1);
1381
1382         if (!window)
1383                 return;
1384         Con_DPrintf("%s: %s \"%s\"\n", __func__, var->name, var->string);
1385
1386         fullscreencurrent = SDL_GetWindowFlags(window) & (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN);
1387         if (vid_fullscreen.integer)
1388                 fullscreenwanted = vid_desktopfullscreen.integer ? SDL_WINDOW_FULLSCREEN_DESKTOP : SDL_WINDOW_FULLSCREEN;
1389         else
1390                 fullscreenwanted = 0;
1391
1392         // moving to another display, changing the fullscreen mode or switching to windowed
1393         if (vid.displayindex != displaywanted // SDL seems unable to move any fullscreen window to another display
1394         || fullscreencurrent != fullscreenwanted) // even for desktop <-> exclusive: DESKTOP flag includes FULLSCREEN bit
1395         {
1396                 if (SDL_SetWindowFullscreen(window, 0) < 0)
1397                 {
1398                         Con_Printf(CON_ERROR "ERROR: can't deactivate fullscreen on display %i because %s\n", vid.displayindex, SDL_GetError());
1399                         return;
1400                 }
1401                 vid.fullscreen = false;
1402                 Con_DPrintf("Fullscreen deactivated on display %i\n", vid.displayindex);
1403         }
1404
1405         // switching to windowed
1406         if (!fullscreenwanted)
1407         {
1408                 int toppx;
1409                 SDL_SetWindowSize(window, vid.width = vid_width.integer, vid.height = vid_height.integer);
1410                 SDL_SetWindowResizable(window, vid_resizable.integer ? SDL_TRUE : SDL_FALSE);
1411                 SDL_SetWindowBordered(window, (SDL_bool)!vid_borderless.integer);
1412                 SDL_GetWindowBordersSize(window, &toppx, NULL, NULL, NULL);
1413                 vid_wmborderless = !toppx;
1414                 if (vid_borderless.integer != vid_wmborderless) // this is not the state we're looking for
1415                         vid_wmborder_waiting = true;
1416         }
1417
1418         // moving to another display or switching to windowed
1419         if (vid.displayindex != displaywanted || !fullscreenwanted)
1420         {
1421 //              SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED_DISPLAY(displaywanted), SDL_WINDOWPOS_CENTERED_DISPLAY(displaywanted));
1422 //              SDL_GetWindowPosition(window, &vid.xPos, &vid.yPos);
1423
1424                 /* bones_was_here BUG: after SDL_DISPLAYEVENT hotplug events, on Xorg + NVIDIA,
1425                  * SDL_WINDOWPOS_CENTERED_DISPLAY(displaywanted) may place the window somewhere completely invisible.
1426                  * WORKAROUND: manual positioning seems safer: although SDL_GetDisplayBounds() may return outdated values,
1427                  * SDL_SetWindowPosition() always placed the window somewhere fully visible, even if it wasn't correct,
1428                  * when tested with SDL 2.26.5.
1429                  */
1430                 SDL_Rect displaybounds;
1431                 if (SDL_GetDisplayBounds(displaywanted, &displaybounds) < 0)
1432                 {
1433                         Con_Printf(CON_ERROR "Error getting bounds of display %i: \"%s\"\n", displaywanted, SDL_GetError());
1434                         return;
1435                 }
1436                 vid.xPos = displaybounds.x + 0.5 * (displaybounds.w - vid.width);
1437                 vid.yPos = displaybounds.y + 0.5 * (displaybounds.h - vid.height);
1438                 SDL_SetWindowPosition(window, vid.xPos, vid.yPos);
1439
1440                 vid.displayindex = displaywanted;
1441         }
1442
1443         // switching to a fullscreen mode
1444         if (fullscreenwanted)
1445         {
1446                 if (fullscreenwanted == SDL_WINDOW_FULLSCREEN)
1447                 {
1448                         // When starting in desktop/window mode no hardware mode is set so do it now,
1449                         // this also applies vid_width and vid_height changes immediately without bogus modesets.
1450                         SDL_DisplayMode modewanted, modeclosest;
1451                         modewanted.w = vid_width.integer;
1452                         modewanted.h = vid_height.integer;
1453                         modewanted.refresh_rate = vid_userefreshrate.integer ? vid_refreshrate.integer : 0;
1454                         if (!SDL_GetClosestDisplayMode(vid.displayindex, &modewanted, &modeclosest))
1455                         {
1456                                 Con_Printf(CON_ERROR "Error getting closest mode to %ix%i@%ihz for display %i: \"%s\"\n", modewanted.w, modewanted.h, modewanted.refresh_rate, vid.displayindex, SDL_GetError());
1457                                 return;
1458                         }
1459                         if (SDL_SetWindowDisplayMode(window, &modeclosest) < 0)
1460                         {
1461                                 Con_Printf(CON_ERROR "Error setting mode %ix%i@%ihz for display %i: \"%s\"\n", modeclosest.w, modeclosest.h, modeclosest.refresh_rate, vid.displayindex, SDL_GetError());
1462                                 return;
1463                         }
1464                 }
1465
1466                 if (SDL_SetWindowFullscreen(window, fullscreenwanted) < 0)
1467                 {
1468                         Con_Printf(CON_ERROR "ERROR: can't activate fullscreen on display %i because %s\n", vid.displayindex, SDL_GetError());
1469                         return;
1470                 }
1471                 // get the real framebuffer size in case the platform's screen coordinates are DPI scaled
1472                 SDL_GL_GetDrawableSize(window, &vid.width, &vid.height);
1473                 vid.fullscreen = true;
1474                 Con_DPrintf("Fullscreen activated on display %i\n", vid.displayindex);
1475         }
1476 }
1477
1478 static void VID_SetVsync_c(cvar_t *var)
1479 {
1480         signed char vsyncwanted = cls.timedemo ? 0 : bound(-1, vid_vsync.integer, 1);
1481
1482         if (!context)
1483                 return;
1484 /*
1485 Can't check first: on Wayland SDL_GL_GetSwapInterval() may initially return 0 when vsync is on.
1486 On Xorg it returns the correct value.
1487         if (SDL_GL_GetSwapInterval() == vsyncwanted)
1488                 return;
1489 */
1490
1491         if (SDL_GL_SetSwapInterval(vsyncwanted) >= 0)
1492                 Con_DPrintf("Vsync %s\n", vsyncwanted ? "activated" : "deactivated");
1493         else
1494                 Con_Printf(CON_ERROR "ERROR: can't %s vsync because %s\n", vsyncwanted ? "activate" : "deactivate", SDL_GetError());
1495 }
1496
1497 static void VID_SetHints_c(cvar_t *var)
1498 {
1499         SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH,     vid_mouse_clickthrough.integer     ? "1" : "0");
1500         SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, vid_minimize_on_focus_loss.integer ? "1" : "0");
1501 }
1502
1503 void VID_Init (void)
1504 {
1505         SDL_version version;
1506
1507 #ifndef __IPHONEOS__
1508 #ifdef MACOSX
1509         Cvar_RegisterVariable(&apple_mouse_noaccel);
1510 #endif
1511 #endif
1512 #ifdef DP_MOBILETOUCH
1513         Cvar_SetValueQuick(&vid_touchscreen, 1);
1514 #endif
1515         Cvar_RegisterVariable(&joy_sdl2_trigger_deadzone);
1516
1517 #if defined(__linux__)
1518         // exclusive fullscreen is no longer functional (and when it worked was obnoxious and not faster)
1519         Cvar_SetValueQuick(&vid_desktopfullscreen, 1);
1520         vid_desktopfullscreen.flags |= CF_READONLY;
1521 #endif
1522
1523         Cvar_RegisterCallback(&vid_fullscreen,             VID_ApplyDisplaySettings_c);
1524         Cvar_RegisterCallback(&vid_desktopfullscreen,      VID_ApplyDisplaySettings_c);
1525         Cvar_RegisterCallback(&vid_display,                VID_ApplyDisplaySettings_c);
1526         Cvar_RegisterCallback(&vid_width,                  VID_ApplyDisplaySettings_c);
1527         Cvar_RegisterCallback(&vid_height,                 VID_ApplyDisplaySettings_c);
1528         Cvar_RegisterCallback(&vid_resizable,              VID_ApplyDisplaySettings_c);
1529         Cvar_RegisterCallback(&vid_borderless,             VID_ApplyDisplaySettings_c);
1530         Cvar_RegisterCallback(&vid_vsync,                  VID_SetVsync_c);
1531         Cvar_RegisterCallback(&vid_mouse_clickthrough,     VID_SetHints_c);
1532         Cvar_RegisterCallback(&vid_minimize_on_focus_loss, VID_SetHints_c);
1533
1534         // DPI scaling prevents use of the native resolution, causing blurry rendering
1535         // and/or mouse cursor problems and/or incorrect render area, so we need to opt-out.
1536         // Must be set before first SDL_INIT_VIDEO. Documented in SDL_hints.h.
1537 #ifdef WIN32
1538         // make SDL coordinates == hardware pixels
1539         SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "0");
1540         // use best available awareness mode
1541         SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitorv2");
1542 #endif
1543
1544         if (SDL_Init(SDL_INIT_VIDEO) < 0)
1545                 Sys_Error ("Failed to init SDL video subsystem: %s", SDL_GetError());
1546         if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
1547                 Con_Printf(CON_ERROR "Failed to init SDL joystick subsystem: %s\n", SDL_GetError());
1548
1549         SDL_GetVersion(&version);
1550         Con_Printf("Linked against SDL version %d.%d.%d\n"
1551                    "Using SDL library version %d.%d.%d\n",
1552                    SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL,
1553                    version.major, version.minor, version.patch);
1554 }
1555
1556 static int vid_sdljoystickindex = -1;
1557 void VID_EnableJoystick(qbool enable)
1558 {
1559         int index = joy_enable.integer > 0 ? joy_index.integer : -1;
1560         int numsdljoysticks;
1561         qbool success = false;
1562         int sharedcount = 0;
1563         int sdlindex = -1;
1564         sharedcount = VID_Shared_SetJoystick(index);
1565         if (index >= 0 && index < sharedcount)
1566                 success = true;
1567         sdlindex = index - sharedcount;
1568
1569         numsdljoysticks = SDL_NumJoysticks();
1570         if (sdlindex < 0 || sdlindex >= numsdljoysticks)
1571                 sdlindex = -1;
1572
1573         // update cvar containing count of XInput joysticks + SDL joysticks
1574         if (joy_detected.integer != sharedcount + numsdljoysticks)
1575                 Cvar_SetValueQuick(&joy_detected, sharedcount + numsdljoysticks);
1576
1577         if (vid_sdljoystickindex != sdlindex)
1578         {
1579                 vid_sdljoystickindex = sdlindex;
1580                 // close SDL joystick if active
1581                 if (vid_sdljoystick)
1582                 {
1583                         SDL_JoystickClose(vid_sdljoystick);
1584                         vid_sdljoystick = NULL;
1585                 }
1586                 if (vid_sdlgamecontroller)
1587                 {
1588                         SDL_GameControllerClose(vid_sdlgamecontroller);
1589                         vid_sdlgamecontroller = NULL;
1590                 }
1591                 if (sdlindex >= 0)
1592                 {
1593                         vid_sdljoystick = SDL_JoystickOpen(sdlindex);
1594                         if (vid_sdljoystick)
1595                         {
1596                                 const char *joystickname = SDL_JoystickName(vid_sdljoystick);
1597                                 if (SDL_IsGameController(vid_sdljoystickindex))
1598                                 {
1599                                         vid_sdlgamecontroller = SDL_GameControllerOpen(vid_sdljoystickindex);
1600                                         Con_DPrintf("Using SDL GameController mappings for Joystick %i\n", index);
1601                                 }
1602                                 Con_Printf("Joystick %i opened (SDL_Joystick %i is \"%s\" with %i axes, %i buttons, %i balls)\n", index, sdlindex, joystickname, (int)SDL_JoystickNumAxes(vid_sdljoystick), (int)SDL_JoystickNumButtons(vid_sdljoystick), (int)SDL_JoystickNumBalls(vid_sdljoystick));
1603                         }
1604                         else
1605                         {
1606                                 Con_Printf(CON_ERROR "Joystick %i failed (SDL_JoystickOpen(%i) returned: %s)\n", index, sdlindex, SDL_GetError());
1607                                 sdlindex = -1;
1608                         }
1609                 }
1610         }
1611
1612         if (sdlindex >= 0)
1613                 success = true;
1614
1615         if (joy_active.integer != (success ? 1 : 0))
1616                 Cvar_SetValueQuick(&joy_active, success ? 1 : 0);
1617 }
1618
1619 #ifdef WIN32
1620 static void AdjustWindowBounds(viddef_mode_t *mode, RECT *rect)
1621 {
1622         int workWidth;
1623         int workHeight;
1624         int titleBarPixels = 2;
1625         int screenHeight;
1626         RECT workArea;
1627         LONG width = mode->width; // vid_width
1628         LONG height = mode->height; // vid_height
1629
1630         // adjust width and height for the space occupied by window decorators (title bar, borders)
1631         rect->top = 0;
1632         rect->left = 0;
1633         rect->right = width;
1634         rect->bottom = height;
1635         AdjustWindowRectEx(rect, WS_CAPTION|WS_THICKFRAME, false, 0);
1636
1637         SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);
1638         workWidth = workArea.right - workArea.left;
1639         workHeight = workArea.bottom - workArea.top;
1640
1641         // SDL forces the window height to be <= screen height - 27px (on Win8.1 - probably intended for the title bar) 
1642         // If the task bar is docked to the the left screen border and we move the window to negative y,
1643         // there would be some part of the regular desktop visible on the bottom of the screen.
1644         screenHeight = GetSystemMetrics(SM_CYSCREEN);
1645         if (screenHeight == workHeight)
1646                 titleBarPixels = -rect->top;
1647
1648         //Con_Printf("window mode: %dx%d, workArea: %d/%d-%d/%d (%dx%d), title: %d\n", width, height, workArea.left, workArea.top, workArea.right, workArea.bottom, workArea.right - workArea.left, workArea.bottom - workArea.top, titleBarPixels);
1649
1650         // if height and width matches the physical or previously adjusted screen height and width, adjust it to available desktop area
1651         if ((width == GetSystemMetrics(SM_CXSCREEN) || width == workWidth) && (height == screenHeight || height == workHeight - titleBarPixels))
1652         {
1653                 rect->left = workArea.left;
1654                 mode->width = workWidth;
1655                 rect->top = workArea.top + titleBarPixels;
1656                 mode->height = workHeight - titleBarPixels;
1657         }
1658         else 
1659         {
1660                 rect->left = workArea.left + max(0, (workWidth - width) / 2);
1661                 rect->top = workArea.top + max(0, (workHeight - height) / 2);
1662         }
1663 }
1664 #endif
1665
1666 static qbool VID_InitModeGL(viddef_mode_t *mode)
1667 {
1668         int windowflags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;
1669         int i;
1670 #ifndef USE_GLES2
1671         // SDL usually knows best
1672         const char *drivername = NULL;
1673 #endif
1674
1675         // video display selection (multi-monitor)
1676         Cvar_SetValueQuick(&vid_info_displaycount, SDL_GetNumVideoDisplays());
1677         vid.displayindex = bound(0, vid_display.integer, vid_info_displaycount.integer - 1);
1678         vid.xPos = SDL_WINDOWPOS_CENTERED_DISPLAY(vid.displayindex);
1679         vid.yPos = SDL_WINDOWPOS_CENTERED_DISPLAY(vid.displayindex);
1680         vid_wmborder_waiting = vid_wmborderless = false;
1681
1682         win_half_width = mode->width>>1;
1683         win_half_height = mode->height>>1;
1684
1685         if(vid_resizable.integer)
1686                 windowflags |= SDL_WINDOW_RESIZABLE;
1687
1688 #ifndef USE_GLES2
1689 // COMMANDLINEOPTION: SDL GL: -gl_driver <drivername> selects a GL driver library, default is whatever SDL recommends, useful only for 3dfxogl.dll/3dfxvgl.dll or fxmesa or similar, if you don't know what this is for, you don't need it
1690         i = Sys_CheckParm("-gl_driver");
1691         if (i && i < sys.argc - 1)
1692                 drivername = sys.argv[i + 1];
1693         if (SDL_GL_LoadLibrary(drivername) < 0)
1694         {
1695                 Con_Printf(CON_ERROR "Unable to load GL driver \"%s\": %s\n", drivername, SDL_GetError());
1696                 return false;
1697         }
1698 #endif
1699
1700 #ifdef DP_MOBILETOUCH
1701         // mobile platforms are always fullscreen, we'll get the resolution after opening the window
1702         mode->fullscreen = true;
1703         // hide the menu with SDL_WINDOW_BORDERLESS
1704         windowflags |= SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS;
1705 #endif
1706
1707
1708         if (mode->fullscreen)
1709         {
1710                 if (vid_desktopfullscreen.integer)
1711                 {
1712                         vid_mode_t m = VID_GetDesktopMode();
1713                         mode->width = m.width;
1714                         mode->height = m.height;
1715                         windowflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
1716                 }
1717                 else
1718                         windowflags |= SDL_WINDOW_FULLSCREEN;
1719         }
1720         else
1721         {
1722                 if (vid_borderless.integer)
1723                         windowflags |= SDL_WINDOW_BORDERLESS;
1724                 else
1725                         vid_wmborder_waiting = true; // waiting for border to be added
1726 #ifdef WIN32
1727                 if (!vid_ignore_taskbar.integer)
1728                 {
1729                         RECT rect;
1730                         AdjustWindowBounds(mode, &rect);
1731                         vid.xPos = rect.left;
1732                         vid.xPos = rect.top;
1733                         vid_wmborder_waiting = false;
1734                 }
1735 #endif
1736         }
1737
1738         VID_SetHints_c(NULL);
1739
1740         SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1);
1741         SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8);
1742         SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8);
1743         SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8);
1744         SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE, 8);
1745         SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 24);
1746         SDL_GL_SetAttribute (SDL_GL_STENCIL_SIZE, 8);
1747         if (mode->stereobuffer)
1748                 SDL_GL_SetAttribute (SDL_GL_STEREO, 1);
1749         if (mode->samples > 1)
1750         {
1751                 SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, 1);
1752                 SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, mode->samples);
1753         }
1754
1755 #ifdef USE_GLES2
1756         SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
1757         SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2);
1758         SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0);
1759 #else
1760         SDL_GL_SetAttribute (SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
1761         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
1762         SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
1763         /* Requesting a Core profile and 3.2 minimum is mandatory on macOS and older Mesa drivers.
1764          * It works fine on other drivers too except NVIDIA, see HACK below.
1765          */
1766 #endif
1767
1768         SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, (gl_debug.integer > 0 ? SDL_GL_CONTEXT_DEBUG_FLAG : 0));
1769
1770         window = SDL_CreateWindow(gamename, vid.xPos, vid.yPos, mode->width, mode->height, windowflags);
1771         if (window == NULL)
1772         {
1773                 Con_Printf(CON_ERROR "Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError());
1774                 VID_Shutdown();
1775                 return false;
1776         }
1777         // get the real framebuffer size in case the platform's screen coordinates are DPI scaled
1778         SDL_GL_GetDrawableSize(window, &mode->width, &mode->height);
1779         // After using SDL_WINDOWPOS_CENTERED_DISPLAY we don't know the real position
1780         SDL_GetWindowPosition(window, &vid.xPos, &vid.yPos);
1781
1782         context = SDL_GL_CreateContext(window);
1783         if (context == NULL)
1784                 Sys_Error("Failed to initialize OpenGL context: %s\n", SDL_GetError());
1785
1786         GL_InitFunctions();
1787
1788 #if !defined(USE_GLES2) && !defined(MACOSX)
1789         // NVIDIA hates the Core profile and limits the version to the minimum we specified.
1790         // HACK: to detect NVIDIA we first need a context, fortunately replacing it takes a few milliseconds
1791         gl_vendor = (const char *)qglGetString(GL_VENDOR);
1792         if (strncmp(gl_vendor, "NVIDIA", 6) == 0)
1793         {
1794                 Con_DPrint("The Way It's Meant To Be Played: replacing OpenGL Core profile with Compatibility profile...\n");
1795                 SDL_GL_DeleteContext(context);
1796                 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
1797                 context = SDL_GL_CreateContext(window);
1798                 if (context == NULL)
1799                         Sys_Error("Failed to initialize OpenGL context: %s\n", SDL_GetError());
1800         }
1801 #endif
1802
1803         // apply vid_vsync
1804         Cvar_Callback(&vid_vsync);
1805
1806         vid_hidden = false;
1807         vid_activewindow = true;
1808         vid_hasfocus = true;
1809         vid_usingmouse = false;
1810         vid_usinghidecursor = false;
1811
1812         // clear to black (loading plaque will be seen over this)
1813         GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 0);
1814         VID_Finish(); // checks vid_hidden
1815
1816         GL_Setup();
1817
1818         // VorteX: set other info
1819         Cvar_SetQuick(&gl_info_vendor, gl_vendor);
1820         Cvar_SetQuick(&gl_info_renderer, gl_renderer);
1821         Cvar_SetQuick(&gl_info_version, gl_version);
1822         Cvar_SetQuick(&gl_info_driver, drivername ? drivername : "");
1823
1824         for (i = 0; i < vid_info_displaycount.integer; ++i)
1825                 Con_Printf("Display %i: %s\n", i, SDL_GetDisplayName(i));
1826
1827         return true;
1828 }
1829
1830 qbool VID_InitMode(viddef_mode_t *mode)
1831 {
1832         // GAME_STEELSTORM specific
1833         steelstorm_showing_map = Cvar_FindVar(&cvars_all, "steelstorm_showing_map", ~0);
1834         steelstorm_showing_mousecursor = Cvar_FindVar(&cvars_all, "steelstorm_showing_mousecursor", ~0);
1835
1836         if (!SDL_WasInit(SDL_INIT_VIDEO) && SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
1837                 Sys_Error ("Failed to init SDL video subsystem: %s", SDL_GetError());
1838
1839         Cvar_SetValueQuick(&vid_touchscreen_supportshowkeyboard, SDL_HasScreenKeyboardSupport() ? 1 : 0);
1840         return VID_InitModeGL(mode);
1841 }
1842
1843 void VID_Shutdown (void)
1844 {
1845         VID_EnableJoystick(false);
1846         VID_SetMouse(false, false);
1847
1848         SDL_GL_DeleteContext(context);
1849         context = NULL;
1850         SDL_DestroyWindow(window);
1851         window = NULL;
1852
1853         SDL_QuitSubSystem(SDL_INIT_VIDEO);
1854 }
1855
1856 void VID_Finish (void)
1857 {
1858         VID_UpdateGamma();
1859
1860         if (!vid_hidden)
1861         {
1862                 switch(vid.renderpath)
1863                 {
1864                 case RENDERPATH_GL32:
1865                 case RENDERPATH_GLES2:
1866                         CHECKGLERROR
1867                         if (r_speeds.integer == 2 || gl_finish.integer)
1868                                 GL_Finish();
1869                         SDL_GL_SwapWindow(window);
1870                         break;
1871                 }
1872         }
1873 }
1874
1875 vid_mode_t VID_GetDesktopMode(void)
1876 {
1877         SDL_DisplayMode mode;
1878         int bpp;
1879         Uint32 rmask, gmask, bmask, amask;
1880         vid_mode_t desktop_mode;
1881
1882         SDL_GetDesktopDisplayMode(vid.displayindex, &mode);
1883         SDL_PixelFormatEnumToMasks(mode.format, &bpp, &rmask, &gmask, &bmask, &amask);
1884         desktop_mode.width = mode.w;
1885         desktop_mode.height = mode.h;
1886         desktop_mode.bpp = bpp;
1887         desktop_mode.refreshrate = mode.refresh_rate;
1888         desktop_mode.pixelheight_num = 1;
1889         desktop_mode.pixelheight_denom = 1; // SDL does not provide this
1890         return desktop_mode;
1891 }
1892
1893 size_t VID_ListModes(vid_mode_t *modes, size_t maxcount)
1894 {
1895         size_t k = 0;
1896         int modenum;
1897         int nummodes = SDL_GetNumDisplayModes(vid.displayindex);
1898         SDL_DisplayMode mode;
1899         for (modenum = 0;modenum < nummodes;modenum++)
1900         {
1901                 if (k >= maxcount)
1902                         break;
1903                 if (SDL_GetDisplayMode(vid.displayindex, modenum, &mode))
1904                         continue;
1905                 modes[k].width = mode.w;
1906                 modes[k].height = mode.h;
1907                 // FIXME bpp?
1908                 modes[k].refreshrate = mode.refresh_rate;
1909                 modes[k].pixelheight_num = 1;
1910                 modes[k].pixelheight_denom = 1; // SDL does not provide this
1911                 k++;
1912         }
1913         return k;
1914 }