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