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