395bb605e6c8a96db3a5753b06b7cb62da12e2a0
[xonotic/darkplaces.git] / vid_glx.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
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
20 #include <signal.h>
21
22 #include <dlfcn.h>
23
24 #include <X11/Xlib.h>
25 #include <X11/Xutil.h>
26 #include <X11/Xatom.h>
27 #include <X11/XKBlib.h> // TODO possibly ifdef this out on non-supporting systems... Solaris (as always)?
28 #include <GL/glx.h>
29
30 #include "quakedef.h"
31
32 #include <X11/keysym.h>
33 #include <X11/cursorfont.h>
34 #include <X11/xpm.h>
35
36 #include <X11/extensions/XShm.h>
37 #if !defined(__APPLE__) && !defined(__MACH__) && !defined(SUNOS)
38 #include <X11/extensions/xf86dga.h>
39 #endif
40 #include <X11/extensions/xf86vmode.h>
41
42 #include "nexuiz.xpm"
43 #include "darkplaces.xpm"
44
45 // Tell startup code that we have a client
46 int cl_available = true;
47
48 // note: if we used the XRandR extension we could support refresh rates
49 qboolean vid_supportrefreshrate = false;
50
51 //GLX prototypes
52 XVisualInfo *(GLAPIENTRY *qglXChooseVisual)(Display *dpy, int screen, int *attribList);
53 GLXContext (GLAPIENTRY *qglXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct);
54 void (GLAPIENTRY *qglXDestroyContext)(Display *dpy, GLXContext ctx);
55 Bool (GLAPIENTRY *qglXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx);
56 void (GLAPIENTRY *qglXSwapBuffers)(Display *dpy, GLXDrawable drawable);
57 const char *(GLAPIENTRY *qglXQueryExtensionsString)(Display *dpy, int screen);
58
59 //GLX_ARB_get_proc_address
60 void *(GLAPIENTRY *qglXGetProcAddressARB)(const GLubyte *procName);
61
62 static dllfunction_t getprocaddressfuncs[] =
63 {
64         {"glXGetProcAddressARB", (void **) &qglXGetProcAddressARB},
65         {NULL, NULL}
66 };
67
68 //GLX_SGI_swap_control
69 GLint (GLAPIENTRY *qglXSwapIntervalSGI)(GLint interval);
70
71 static dllfunction_t swapcontrolfuncs[] =
72 {
73         {"glXSwapIntervalSGI", (void **) &qglXSwapIntervalSGI},
74         {NULL, NULL}
75 };
76
77 static Display *vidx11_display = NULL;
78 static int vidx11_screen;
79 static Window win, root;
80 static GLXContext ctx = NULL;
81
82 Atom wm_delete_window_atom;
83 Atom net_wm_state_atom;
84 Atom net_wm_state_hidden_atom;
85 Atom net_wm_state_fullscreen_atom;
86
87 #define KEY_MASK (KeyPressMask | KeyReleaseMask)
88 #define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \
89                     PointerMotionMask | ButtonMotionMask)
90 #define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | \
91                 StructureNotifyMask | FocusChangeMask | EnterWindowMask | \
92                 LeaveWindowMask)
93
94
95 static qboolean mouse_avail = true;
96 static qboolean vid_usingmousegrab = false;
97 static qboolean vid_usingmouse = false;
98 static qboolean vid_usinghidecursor = false;
99 static qboolean vid_usingvsync = false;
100 static qboolean vid_usevsync = false;
101 static qboolean vid_x11_hardwaregammasupported = false;
102 static qboolean vid_x11_dgasupported = false;
103 static int vid_x11_gammarampsize = 0;
104
105 #if !defined(__APPLE__) && !defined(SUNOS)
106 cvar_t vid_dgamouse = {CVAR_SAVE, "vid_dgamouse", "0", "make use of DGA mouse input"};
107 static qboolean vid_usingdgamouse = false;
108 #endif
109 cvar_t vid_netwmfullscreen = {CVAR_SAVE, "vid_netwmfullscreen", "0", "make use _NET_WM_STATE_FULLSCREEN; turn this off if fullscreen does not work for you"};
110
111 qboolean vidmode_ext = false;
112
113 static int win_x, win_y;
114
115 static XF86VidModeModeInfo init_vidmode, game_vidmode;
116 static qboolean vid_isfullscreen = false;
117 static qboolean vid_isvidmodefullscreen = false;
118 static qboolean vid_isnetwmfullscreen = false;
119 static qboolean vid_isoverrideredirect = false;
120
121 static Visual *vidx11_visual;
122 static Colormap vidx11_colormap;
123
124 /*-----------------------------------------------------------------------*/
125
126 static int XLateKey(XKeyEvent *ev, char *ascii)
127 {
128         int key = 0;
129         char buf[64];
130         KeySym keysym, shifted;
131
132         keysym = XLookupKeysym (ev, 0);
133         XLookupString(ev, buf, sizeof buf, &shifted, 0);
134         *ascii = buf[0];
135
136         switch(keysym)
137         {
138                 case XK_KP_Page_Up:      key = K_KP_PGUP; break;
139                 case XK_Page_Up:         key = K_PGUP; break;
140
141                 case XK_KP_Page_Down: key = K_KP_PGDN; break;
142                 case XK_Page_Down:       key = K_PGDN; break;
143
144                 case XK_KP_Home: key = K_KP_HOME; break;
145                 case XK_Home:    key = K_HOME; break;
146
147                 case XK_KP_End:  key = K_KP_END; break;
148                 case XK_End:     key = K_END; break;
149
150                 case XK_KP_Left: key = K_KP_LEFTARROW; break;
151                 case XK_Left:    key = K_LEFTARROW; break;
152
153                 case XK_KP_Right: key = K_KP_RIGHTARROW; break;
154                 case XK_Right:  key = K_RIGHTARROW;             break;
155
156                 case XK_KP_Down: key = K_KP_DOWNARROW; break;
157                 case XK_Down:    key = K_DOWNARROW; break;
158
159                 case XK_KP_Up:   key = K_KP_UPARROW; break;
160                 case XK_Up:              key = K_UPARROW;        break;
161
162                 case XK_Escape: key = K_ESCAPE;         break;
163
164                 case XK_KP_Enter: key = K_KP_ENTER;     break;
165                 case XK_Return: key = K_ENTER;           break;
166
167                 case XK_Tab:            key = K_TAB;                     break;
168
169                 case XK_F1:              key = K_F1;                            break;
170
171                 case XK_F2:              key = K_F2;                            break;
172
173                 case XK_F3:              key = K_F3;                            break;
174
175                 case XK_F4:              key = K_F4;                            break;
176
177                 case XK_F5:              key = K_F5;                            break;
178
179                 case XK_F6:              key = K_F6;                            break;
180
181                 case XK_F7:              key = K_F7;                            break;
182
183                 case XK_F8:              key = K_F8;                            break;
184
185                 case XK_F9:              key = K_F9;                            break;
186
187                 case XK_F10:            key = K_F10;                     break;
188
189                 case XK_F11:            key = K_F11;                     break;
190
191                 case XK_F12:            key = K_F12;                     break;
192
193                 case XK_BackSpace: key = K_BACKSPACE; break;
194
195                 case XK_KP_Delete: key = K_KP_DEL; break;
196                 case XK_Delete: key = K_DEL; break;
197
198                 case XK_Pause:  key = K_PAUSE;           break;
199
200                 case XK_Shift_L:
201                 case XK_Shift_R:        key = K_SHIFT;          break;
202
203                 case XK_Execute:
204                 case XK_Control_L:
205                 case XK_Control_R:      key = K_CTRL;            break;
206
207                 case XK_Alt_L:
208                 case XK_Meta_L:
209                 case XK_ISO_Level3_Shift:
210                 case XK_Alt_R:
211                 case XK_Meta_R: key = K_ALT;                    break;
212
213                 case XK_KP_Begin: key = K_KP_5; break;
214
215                 case XK_Insert:key = K_INS; break;
216                 case XK_KP_Insert: key = K_KP_INS; break;
217
218                 case XK_KP_Multiply: key = K_KP_MULTIPLY; break;
219                 case XK_KP_Add:  key = K_KP_PLUS; break;
220                 case XK_KP_Subtract: key = K_KP_MINUS; break;
221                 case XK_KP_Divide: key = K_KP_SLASH; break;
222
223                 case XK_section:        key = '~'; break;
224
225                 default:
226                         if (keysym < 32)
227                                 break;
228
229                         if (keysym >= 'A' && keysym <= 'Z')
230                                 key = keysym - 'A' + 'a';
231                         else
232                                 key = keysym;
233
234                         break;
235         }
236
237         return key;
238 }
239
240 static Cursor CreateNullCursor(Display *display, Window root)
241 {
242         Pixmap cursormask;
243         XGCValues xgc;
244         GC gc;
245         XColor dummycolour;
246         Cursor cursor;
247
248         cursormask = XCreatePixmap(display, root, 1, 1, 1);
249         xgc.function = GXclear;
250         gc =  XCreateGC(display, cursormask, GCFunction, &xgc);
251         XFillRectangle(display, cursormask, gc, 0, 0, 1, 1);
252         dummycolour.pixel = 0;
253         dummycolour.red = 0;
254         dummycolour.flags = 04;
255         cursor = XCreatePixmapCursor(display, cursormask, cursormask, &dummycolour,&dummycolour, 0,0);
256         XFreePixmap(display,cursormask);
257         XFreeGC(display,gc);
258         return cursor;
259 }
260
261 void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor)
262 {
263         static int originalmouseparms_num;
264         static int originalmouseparms_denom;
265         static int originalmouseparms_threshold;
266         static qboolean restore_spi;
267
268 #if !defined(__APPLE__) && !defined(SUNOS)
269         qboolean usedgamouse;
270 #endif
271
272         if (!vidx11_display || !win)
273                 return;
274
275         if (relative)
276                 fullscreengrab = true;
277
278         if (!mouse_avail)
279                 fullscreengrab = relative = hidecursor = false;
280
281 #if !defined(__APPLE__) && !defined(SUNOS)
282         usedgamouse = relative && vid_dgamouse.integer;
283         if (!vid_x11_dgasupported)
284                 usedgamouse = false;
285         if (fullscreengrab && vid_usingmouse && (vid_usingdgamouse != usedgamouse))
286                 VID_SetMouse(false, false, false); // ungrab first!
287 #endif
288
289         if (vid_usingmousegrab != fullscreengrab)
290         {
291                 vid_usingmousegrab = fullscreengrab;
292                 cl_ignoremousemoves = 2;
293                 if (fullscreengrab)
294                 {
295                         XGrabPointer(vidx11_display, win,  True, 0, GrabModeAsync, GrabModeAsync, win, None, CurrentTime);
296                         if (vid_grabkeyboard.integer || vid_isoverrideredirect)
297                                 XGrabKeyboard(vidx11_display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
298                 }
299                 else
300                 {
301                         XUngrabPointer(vidx11_display, CurrentTime);
302                         XUngrabKeyboard(vidx11_display, CurrentTime);
303                 }
304         }
305
306         if (relative)
307         {
308                 if (!vid_usingmouse)
309                 {
310                         XWindowAttributes attribs_1;
311                         XSetWindowAttributes attribs_2;
312
313                         XGetWindowAttributes(vidx11_display, win, &attribs_1);
314                         attribs_2.event_mask = attribs_1.your_event_mask | KEY_MASK | MOUSE_MASK;
315                         XChangeWindowAttributes(vidx11_display, win, CWEventMask, &attribs_2);
316
317 #if !defined(__APPLE__) && !defined(SUNOS)
318                         vid_usingdgamouse = usedgamouse;
319                         if (usedgamouse)
320                         {
321                                 XF86DGADirectVideo(vidx11_display, DefaultScreen(vidx11_display), XF86DGADirectMouse);
322                                 XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0);
323                         }
324                         else
325 #endif
326                                 XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2);
327
328 // COMMANDLINEOPTION: X11 Input: -noforcemparms disables setting of mouse parameters (not used with DGA, windows only)
329 #if !defined(__APPLE__) && !defined(SUNOS)
330                         if (!COM_CheckParm ("-noforcemparms") && !usedgamouse)
331 #else
332                         if (!COM_CheckParm ("-noforcemparms"))
333 #endif
334                         {
335                                 XGetPointerControl(vidx11_display, &originalmouseparms_num, &originalmouseparms_denom, &originalmouseparms_threshold);
336                                 XChangePointerControl (vidx11_display, true, false, 1, 1, -1); // TODO maybe change threshold here, or remove this comment
337                                 restore_spi = true;
338                         }
339                         else
340                                 restore_spi = false;
341
342                         cl_ignoremousemoves = 2;
343                         vid_usingmouse = true;
344                 }
345         }
346         else
347         {
348                 if (vid_usingmouse)
349                 {
350 #if !defined(__APPLE__) && !defined(SUNOS)
351                         if (vid_usingdgamouse)
352                                 XF86DGADirectVideo(vidx11_display, DefaultScreen(vidx11_display), 0);
353                         vid_usingdgamouse = false;
354 #endif
355                         cl_ignoremousemoves = 2;
356
357                         if (restore_spi)
358                                 XChangePointerControl (vidx11_display, true, true, originalmouseparms_num, originalmouseparms_denom, originalmouseparms_threshold);
359                         restore_spi = false;
360
361                         vid_usingmouse = false;
362                 }
363         }
364
365         if (vid_usinghidecursor != hidecursor)
366         {
367                 vid_usinghidecursor = hidecursor;
368                 if (hidecursor)
369                         XDefineCursor(vidx11_display, win, CreateNullCursor(vidx11_display, win));
370                 else
371                         XUndefineCursor(vidx11_display, win);
372         }
373 }
374
375 static keynum_t buttonremap[18] =
376 {
377         K_MOUSE1,
378         K_MOUSE3,
379         K_MOUSE2,
380         K_MWHEELUP,
381         K_MWHEELDOWN,
382         K_MOUSE4,
383         K_MOUSE5,
384         K_MOUSE6,
385         K_MOUSE7,
386         K_MOUSE8,
387         K_MOUSE9,
388         K_MOUSE10,
389         K_MOUSE11,
390         K_MOUSE12,
391         K_MOUSE13,
392         K_MOUSE14,
393         K_MOUSE15,
394         K_MOUSE16,
395 };
396
397 static void HandleEvents(void)
398 {
399         XEvent event;
400         int key;
401         char ascii;
402         qboolean dowarp = false;
403
404         if (!vidx11_display)
405                 return;
406
407         while (XPending(vidx11_display))
408         {
409                 XNextEvent(vidx11_display, &event);
410
411                 switch (event.type)
412                 {
413                 case KeyPress:
414                         // key pressed
415                         key = XLateKey (&event.xkey, &ascii);
416                         Key_Event(key, ascii, true);
417                         break;
418
419                 case KeyRelease:
420                         // key released
421                         key = XLateKey (&event.xkey, &ascii);
422                         Key_Event(key, ascii, false);
423                         break;
424
425                 case MotionNotify:
426                         // mouse moved
427                         if (vid_usingmouse)
428                         {
429 #if !defined(__APPLE__) && !defined(SUNOS)
430                                 if (vid_usingdgamouse)
431                                 {
432                                         in_mouse_x += event.xmotion.x_root;
433                                         in_mouse_y += event.xmotion.y_root;
434                                 }
435                                 else
436 #endif
437                                 {
438                                         if (!event.xmotion.send_event)
439                                         {
440                                                 in_mouse_x += event.xmotion.x - in_windowmouse_x;
441                                                 in_mouse_y += event.xmotion.y - in_windowmouse_y;
442                                                 //if (abs(vid.width/2 - event.xmotion.x) + abs(vid.height/2 - event.xmotion.y))
443                                                 if (vid_stick_mouse.integer || abs(vid.width/2 - event.xmotion.x) > vid.width / 4 || abs(vid.height/2 - event.xmotion.y) > vid.height / 4)
444                                                         dowarp = true;
445                                         }
446                                 }
447                         }
448                         in_windowmouse_x = event.xmotion.x;
449                         in_windowmouse_y = event.xmotion.y;
450                         break;
451
452                 case ButtonPress:
453                         // mouse button pressed
454                         if (event.xbutton.button <= 18)
455                                 Key_Event(buttonremap[event.xbutton.button - 1], 0, true);
456                         else
457                                 Con_Printf("HandleEvents: ButtonPress gave value %d, 1-18 expected\n", event.xbutton.button);
458                         break;
459
460                 case ButtonRelease:
461                         // mouse button released
462                         if (event.xbutton.button <= 18)
463                                 Key_Event(buttonremap[event.xbutton.button - 1], 0, false);
464                         else
465                                 Con_Printf("HandleEvents: ButtonRelease gave value %d, 1-18 expected\n", event.xbutton.button);
466                         break;
467
468                 case CreateNotify:
469                         // window created
470                         win_x = event.xcreatewindow.x;
471                         win_y = event.xcreatewindow.y;
472                         break;
473
474                 case ConfigureNotify:
475                         // window changed size/location
476                         win_x = event.xconfigure.x;
477                         win_y = event.xconfigure.y;
478                         if(vid_resizable.integer < 2)
479                         {
480                                 vid.width = event.xconfigure.width;
481                                 vid.height = event.xconfigure.height;
482                         }
483                         break;
484                 case DestroyNotify:
485                         // window has been destroyed
486                         Sys_Quit(0);
487                         break;
488                 case ClientMessage:
489                         // window manager messages
490                         if ((event.xclient.format == 32) && ((unsigned int)event.xclient.data.l[0] == wm_delete_window_atom))
491                                 Sys_Quit(0);
492                         break;
493                 case MapNotify:
494                         if (vid_isoverrideredirect)
495                                 break;
496                         // window restored
497                         vid_hidden = false;
498                         VID_RestoreSystemGamma();
499
500                         if(vid_isvidmodefullscreen)
501                         {
502                                 // set our video mode
503                                 XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &game_vidmode);
504
505                                 // Move the viewport to top left
506                                 XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0);
507                         }
508
509                         if(vid_isnetwmfullscreen)
510                         {
511                                 // make sure it's fullscreen
512                                 XEvent event;
513                                 event.type = ClientMessage;
514                                 event.xclient.serial = 0;
515                                 event.xclient.send_event = True;
516                                 event.xclient.message_type = net_wm_state_atom;
517                                 event.xclient.window = win;
518                                 event.xclient.format = 32;
519                                 event.xclient.data.l[0] = 1;
520                                 event.xclient.data.l[1] = net_wm_state_fullscreen_atom;
521                                 event.xclient.data.l[2] = 0;
522                                 event.xclient.data.l[3] = 1;
523                                 event.xclient.data.l[4] = 0;
524                                 XSendEvent(vidx11_display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
525                         }
526
527                         dowarp = true;
528
529                         break;
530                 case UnmapNotify:
531                         if (vid_isoverrideredirect)
532                                 break;
533                         // window iconified/rolledup/whatever
534                         vid_hidden = true;
535                         VID_RestoreSystemGamma();
536
537                         if(vid_isvidmodefullscreen)
538                                 XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &init_vidmode);
539
540                         break;
541                 case FocusIn:
542                         if (vid_isoverrideredirect)
543                                 break;
544                         // window is now the input focus
545                         vid_activewindow = true;
546                         break;
547                 case FocusOut:
548                         if (vid_isoverrideredirect)
549                                 break;
550
551                         if(vid_isnetwmfullscreen && event.xfocus.mode == NotifyNormal)
552                         {
553                                 // iconify netwm fullscreen window when it loses focus
554                                 // when the user selects it in the taskbar, the window manager will map it again and send MapNotify
555                                 XEvent event;
556                                 event.type = ClientMessage;
557                                 event.xclient.serial = 0;
558                                 event.xclient.send_event = True;
559                                 event.xclient.message_type = net_wm_state_atom;
560                                 event.xclient.window = win;
561                                 event.xclient.format = 32;
562                                 event.xclient.data.l[0] = 1;
563                                 event.xclient.data.l[1] = net_wm_state_hidden_atom;
564                                 event.xclient.data.l[2] = 0;
565                                 event.xclient.data.l[3] = 1;
566                                 event.xclient.data.l[4] = 0;
567                                 XSendEvent(vidx11_display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event);
568                         }
569
570                         // window is no longer the input focus
571                         vid_activewindow = false;
572                         VID_RestoreSystemGamma();
573
574                         break;
575                 case EnterNotify:
576                         // mouse entered window
577                         break;
578                 case LeaveNotify:
579                         // mouse left window
580                         break;
581                 }
582         }
583
584         if (dowarp)
585         {
586                 /* move the mouse to the window center again */
587                 // we'll catch the warp motion by its send_event flag, updating the
588                 // stored mouse position without adding any delta motion
589                 XEvent event;
590                 event.type = MotionNotify;
591                 event.xmotion.display = vidx11_display;
592                 event.xmotion.window = win;
593                 event.xmotion.x = vid.width / 2;
594                 event.xmotion.y = vid.height / 2;
595                 XSendEvent(vidx11_display, win, False, PointerMotionMask, &event);
596                 XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2);
597         }
598 }
599
600 static void *prjobj = NULL;
601
602 static void GL_CloseLibrary(void)
603 {
604         if (prjobj)
605                 dlclose(prjobj);
606         prjobj = NULL;
607         gl_driver[0] = 0;
608         qglXGetProcAddressARB = NULL;
609         gl_extensions = "";
610         gl_platform = "";
611         gl_platformextensions = "";
612 }
613
614 static int GL_OpenLibrary(const char *name)
615 {
616         Con_Printf("Loading OpenGL driver %s\n", name);
617         GL_CloseLibrary();
618         if (!(prjobj = dlopen(name, RTLD_LAZY | RTLD_GLOBAL)))
619         {
620                 Con_Printf("Unable to open symbol list for %s\n", name);
621                 return false;
622         }
623         strlcpy(gl_driver, name, sizeof(gl_driver));
624         return true;
625 }
626
627 void *GL_GetProcAddress(const char *name)
628 {
629         void *p = NULL;
630         if (qglXGetProcAddressARB != NULL)
631                 p = (void *) qglXGetProcAddressARB((GLubyte *)name);
632         if (p == NULL)
633                 p = (void *) dlsym(prjobj, name);
634         return p;
635 }
636
637 void VID_Shutdown(void)
638 {
639         if (!ctx || !vidx11_display)
640                 return;
641
642         VID_SetMouse(false, false, false);
643         VID_RestoreSystemGamma();
644
645         // FIXME: glXDestroyContext here?
646         if (vid_isvidmodefullscreen)
647                 XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &init_vidmode);
648         if (win)
649                 XDestroyWindow(vidx11_display, win);
650         XCloseDisplay(vidx11_display);
651
652         vid_hidden = true;
653         vid_isfullscreen = false;
654         vid_isnetwmfullscreen = false;
655         vid_isvidmodefullscreen = false;
656         vid_isoverrideredirect = false;
657         vidx11_display = NULL;
658         win = 0;
659         ctx = NULL;
660
661         GL_CloseLibrary();
662         Key_ClearStates ();
663 }
664
665 void signal_handler(int sig)
666 {
667         Con_Printf("Received signal %d, exiting...\n", sig);
668         VID_RestoreSystemGamma();
669         Sys_Quit(1);
670 }
671
672 void InitSig(void)
673 {
674         signal(SIGHUP, signal_handler);
675         signal(SIGINT, signal_handler);
676         signal(SIGQUIT, signal_handler);
677         signal(SIGILL, signal_handler);
678         signal(SIGTRAP, signal_handler);
679         signal(SIGIOT, signal_handler);
680         signal(SIGBUS, signal_handler);
681         signal(SIGFPE, signal_handler);
682         signal(SIGSEGV, signal_handler);
683         signal(SIGTERM, signal_handler);
684 }
685
686 void VID_Finish (void)
687 {
688         vid_usevsync = vid_vsync.integer && !cls.timedemo && gl_videosyncavailable;
689         if (vid_usingvsync != vid_usevsync && gl_videosyncavailable)
690         {
691                 vid_usingvsync = vid_usevsync;
692                 if (qglXSwapIntervalSGI (vid_usevsync))
693                         Con_Print("glXSwapIntervalSGI didn't accept the vid_vsync change, it will take effect on next vid_restart (GLX_SGI_swap_control does not allow turning off vsync)\n");
694         }
695
696         if (r_render.integer)
697         {
698                 CHECKGLERROR
699                 if (r_speeds.integer || gl_finish.integer)
700                 {
701                         qglFinish();CHECKGLERROR
702                 }
703                 qglXSwapBuffers(vidx11_display, win);CHECKGLERROR
704         }
705
706         if (vid_x11_hardwaregammasupported)
707                 VID_UpdateGamma(false, vid_x11_gammarampsize);
708 }
709
710 int VID_SetGamma(unsigned short *ramps, int rampsize)
711 {
712         return XF86VidModeSetGammaRamp(vidx11_display, vidx11_screen, rampsize, ramps, ramps + rampsize, ramps + rampsize*2);
713 }
714
715 int VID_GetGamma(unsigned short *ramps, int rampsize)
716 {
717         return XF86VidModeGetGammaRamp(vidx11_display, vidx11_screen, rampsize, ramps, ramps + rampsize, ramps + rampsize*2);
718 }
719
720 void VID_Init(void)
721 {
722 #if !defined(__APPLE__) && !defined(SUNOS)
723         Cvar_RegisterVariable (&vid_dgamouse);
724 #endif
725         Cvar_RegisterVariable (&vid_netwmfullscreen);
726         InitSig(); // trap evil signals
727 // COMMANDLINEOPTION: Input: -nomouse disables mouse support (see also vid_mouse cvar)
728         if (COM_CheckParm ("-nomouse"))
729                 mouse_avail = false;
730 }
731
732 void VID_BuildGLXAttrib(int *attrib, qboolean stencil, qboolean stereobuffer, int samples)
733 {
734         *attrib++ = GLX_RGBA;
735         *attrib++ = GLX_RED_SIZE;*attrib++ = stencil ? 8 : 5;
736         *attrib++ = GLX_GREEN_SIZE;*attrib++ = stencil ? 8 : 5;
737         *attrib++ = GLX_BLUE_SIZE;*attrib++ = stencil ? 8 : 5;
738         *attrib++ = GLX_DOUBLEBUFFER;
739         *attrib++ = GLX_DEPTH_SIZE;*attrib++ = stencil ? 24 : 16;
740         // if stencil is enabled, ask for alpha too
741         if (stencil)
742         {
743                 *attrib++ = GLX_STENCIL_SIZE;*attrib++ = 8;
744                 *attrib++ = GLX_ALPHA_SIZE;*attrib++ = 8;
745         }
746         if (stereobuffer)
747                 *attrib++ = GLX_STEREO;
748         if (samples > 1)
749         {
750                 *attrib++ = GLX_SAMPLE_BUFFERS_ARB;
751                 *attrib++ = 1;
752                 *attrib++ = GLX_SAMPLES_ARB;
753                 *attrib++ = samples;
754         }
755         *attrib++ = None;
756 }
757
758 int VID_InitMode(int fullscreen, int width, int height, int bpp, int refreshrate, int stereobuffer, int samples)
759 {
760         int i;
761         int attrib[32];
762         XSetWindowAttributes attr;
763         XClassHint *clshints;
764         XWMHints *wmhints;
765         XSizeHints *szhints;
766         unsigned long mask;
767         XVisualInfo *visinfo;
768         int MajorVersion, MinorVersion;
769         const char *drivername;
770
771         vid_isfullscreen = false;
772         vid_isnetwmfullscreen = false;
773         vid_isvidmodefullscreen = false;
774         vid_isoverrideredirect = false;
775
776 #if defined(__APPLE__) && defined(__MACH__)
777         drivername = "/usr/X11R6/lib/libGL.1.dylib";
778 #else
779         drivername = "libGL.so.1";
780 #endif
781 // COMMANDLINEOPTION: Linux GLX: -gl_driver <drivername> selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it
782 // COMMANDLINEOPTION: BSD GLX: -gl_driver <drivername> selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it
783 // LordHavoc: although this works on MacOSX, it's useless there (as there is only one system libGL)
784         i = COM_CheckParm("-gl_driver");
785         if (i && i < com_argc - 1)
786                 drivername = com_argv[i + 1];
787         if (!GL_OpenLibrary(drivername))
788         {
789                 Con_Printf("Unable to load GL driver \"%s\"\n", drivername);
790                 return false;
791         }
792
793         if (!(vidx11_display = XOpenDisplay(NULL)))
794         {
795                 Con_Print("Couldn't open the X display\n");
796                 return false;
797         }
798
799         // LordHavoc: making the close button on a window do the right thing
800         // seems to involve this mess, sigh...
801         wm_delete_window_atom = XInternAtom(vidx11_display, "WM_DELETE_WINDOW", false);
802         net_wm_state_atom = XInternAtom(vidx11_display, "_NET_WM_STATE", false);
803         net_wm_state_fullscreen_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_FULLSCREEN", false);
804         net_wm_state_hidden_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_HIDDEN", false);
805
806         // make autorepeat send keypress/keypress/.../keyrelease instead of intervening keyrelease
807         XkbSetDetectableAutoRepeat(vidx11_display, true, NULL);
808
809         vidx11_screen = DefaultScreen(vidx11_display);
810         root = RootWindow(vidx11_display, vidx11_screen);
811
812         // Get video mode list
813         MajorVersion = MinorVersion = 0;
814         if (!XF86VidModeQueryVersion(vidx11_display, &MajorVersion, &MinorVersion))
815                 vidmode_ext = false;
816         else
817         {
818                 Con_DPrintf("Using XFree86-VidModeExtension Version %d.%d\n", MajorVersion, MinorVersion);
819                 vidmode_ext = true;
820         }
821
822         if ((qglXChooseVisual = (XVisualInfo *(GLAPIENTRY *)(Display *dpy, int screen, int *attribList))GL_GetProcAddress("glXChooseVisual")) == NULL
823          || (qglXCreateContext = (GLXContext (GLAPIENTRY *)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct))GL_GetProcAddress("glXCreateContext")) == NULL
824          || (qglXDestroyContext = (void (GLAPIENTRY *)(Display *dpy, GLXContext ctx))GL_GetProcAddress("glXDestroyContext")) == NULL
825          || (qglXMakeCurrent = (Bool (GLAPIENTRY *)(Display *dpy, GLXDrawable drawable, GLXContext ctx))GL_GetProcAddress("glXMakeCurrent")) == NULL
826          || (qglXSwapBuffers = (void (GLAPIENTRY *)(Display *dpy, GLXDrawable drawable))GL_GetProcAddress("glXSwapBuffers")) == NULL
827          || (qglXQueryExtensionsString = (const char *(GLAPIENTRY *)(Display *dpy, int screen))GL_GetProcAddress("glXQueryExtensionsString")) == NULL)
828         {
829                 Con_Printf("glX functions not found in %s\n", gl_driver);
830                 return false;
831         }
832
833         VID_BuildGLXAttrib(attrib, bpp == 32, stereobuffer, samples);
834         visinfo = qglXChooseVisual(vidx11_display, vidx11_screen, attrib);
835         if (!visinfo)
836         {
837                 Con_Print("Couldn't get an RGB, Double-buffered, Depth visual\n");
838                 return false;
839         }
840
841         if (fullscreen)
842         {
843                 if(vid_netwmfullscreen.integer)
844                 {
845                         // TODO detect WM support
846                         vid_isnetwmfullscreen = true;
847                         vid_isfullscreen = true;
848                         // width and height will be filled in later
849                 }
850
851                 if(!vid_isfullscreen && vidmode_ext)
852                 {
853                         int best_fit, best_dist, dist, x, y;
854
855                         // Are we going fullscreen?  If so, let's change video mode
856                         XF86VidModeModeLine *current_vidmode;
857                         XF86VidModeModeInfo **vidmodes;
858                         int num_vidmodes;
859
860                         // This nice hack comes from the SDL source code
861                         current_vidmode = (XF86VidModeModeLine*)((char*)&init_vidmode + sizeof(init_vidmode.dotclock));
862                         XF86VidModeGetModeLine(vidx11_display, vidx11_screen, (int*)&init_vidmode.dotclock, current_vidmode);
863
864                         XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes);
865                         best_dist = 0;
866                         best_fit = -1;
867
868                         for (i = 0; i < num_vidmodes; i++)
869                         {
870                                 if (width > vidmodes[i]->hdisplay || height > vidmodes[i]->vdisplay)
871                                         continue;
872
873                                 x = width - vidmodes[i]->hdisplay;
874                                 y = height - vidmodes[i]->vdisplay;
875                                 dist = (x * x) + (y * y);
876                                 if (best_fit == -1 || dist < best_dist)
877                                 {
878                                         best_dist = dist;
879                                         best_fit = i;
880                                 }
881                         }
882
883                         if (best_fit != -1)
884                         {
885                                 // LordHavoc: changed from ActualWidth/ActualHeight =,
886                                 // to width/height =, so the window will take the full area of
887                                 // the mode chosen
888                                 width = vidmodes[best_fit]->hdisplay;
889                                 height = vidmodes[best_fit]->vdisplay;
890
891                                 // change to the mode
892                                 XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, vidmodes[best_fit]);
893                                 memcpy(&game_vidmode, vidmodes[best_fit], sizeof(game_vidmode));
894                                 vid_isvidmodefullscreen = true;
895                                 vid_isfullscreen = true;
896
897                                 // Move the viewport to top left
898                                 XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0);
899                         }
900
901                         free(vidmodes);
902                 }
903
904                 if(!vid_isfullscreen)
905                 {
906                         // sorry, no FS available
907                         // use the full desktop resolution
908                         vid_isfullscreen = true;
909                         // width and height will be filled in later
910                         width = DisplayWidth(vidx11_display, vidx11_screen);
911                         height = DisplayHeight(vidx11_display, vidx11_screen);
912                 }
913         }
914
915         // LordHavoc: save the visual for use in gamma ramp settings later
916         vidx11_visual = visinfo->visual;
917
918         /* window attributes */
919         attr.background_pixel = 0;
920         attr.border_pixel = 0;
921         // LordHavoc: save the colormap for later, too
922         vidx11_colormap = attr.colormap = XCreateColormap(vidx11_display, root, visinfo->visual, AllocNone);
923         attr.event_mask = X_MASK;
924
925         if (fullscreen)
926         {
927                 if(vid_isnetwmfullscreen)
928                 {
929                         mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask;
930                         attr.backing_store = NotUseful;
931                         attr.save_under = False;
932                 }
933                 else
934                 {
935                         mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect;
936                         attr.override_redirect = True;
937                         attr.backing_store = NotUseful;
938                         attr.save_under = False;
939                         vid_isoverrideredirect = true; // so it knows to grab
940                 }
941         }
942         else
943         {
944                 mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;
945         }
946
947         win = XCreateWindow(vidx11_display, root, 0, 0, width, height, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &attr);
948
949         wmhints = XAllocWMHints();
950         if(XpmCreatePixmapFromData(vidx11_display, win,
951                 (gamemode == GAME_NEXUIZ) ? nexuiz_xpm : darkplaces_xpm,
952                 &wmhints->icon_pixmap, &wmhints->icon_mask, NULL) == XpmSuccess)
953                 wmhints->flags |= IconPixmapHint | IconMaskHint;
954
955         clshints = XAllocClassHint();
956         clshints->res_name = strdup(gamename);
957         clshints->res_class = strdup("DarkPlaces");
958
959         szhints = XAllocSizeHints();
960         if(vid_resizable.integer == 0 && !vid_isnetwmfullscreen)
961         {
962                 szhints->min_width = szhints->max_width = width;
963                 szhints->min_height = szhints->max_height = height;
964                 szhints->flags |= PMinSize | PMaxSize;
965         }
966
967         XmbSetWMProperties(vidx11_display, win, gamename, gamename, (char **) com_argv, com_argc, szhints, wmhints, clshints);
968         XFree(clshints);
969         XFree(wmhints);
970         XFree(szhints);
971
972         //XStoreName(vidx11_display, win, gamename);
973         XMapWindow(vidx11_display, win);
974
975         XSetWMProtocols(vidx11_display, win, &wm_delete_window_atom, 1);
976
977         if (vid_isoverrideredirect)
978         {
979                 XMoveWindow(vidx11_display, win, 0, 0);
980                 XRaiseWindow(vidx11_display, win);
981                 XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0);
982                 XFlush(vidx11_display);
983         }
984
985         if(vid_isvidmodefullscreen)
986         {
987                 // Move the viewport to top left
988                 XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0);
989         }
990
991         //XSync(vidx11_display, False);
992
993         ctx = qglXCreateContext(vidx11_display, visinfo, NULL, True);
994         if (!ctx)
995         {
996                 Con_Printf ("glXCreateContext failed\n");
997                 return false;
998         }
999
1000         if (!qglXMakeCurrent(vidx11_display, win, ctx))
1001         {
1002                 Con_Printf ("glXMakeCurrent failed\n");
1003                 return false;
1004         }
1005
1006         XSync(vidx11_display, False);
1007
1008         if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL)
1009         {
1010                 Con_Printf ("glGetString not found in %s\n", gl_driver);
1011                 return false;
1012         }
1013
1014         gl_extensions = (const char *)qglGetString(GL_EXTENSIONS);
1015         gl_platform = "GLX";
1016         gl_platformextensions = qglXQueryExtensionsString(vidx11_display, vidx11_screen);
1017
1018         gl_videosyncavailable = false;
1019
1020 // COMMANDLINEOPTION: Linux GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions)
1021 // COMMANDLINEOPTION: BSD GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions)
1022 // COMMANDLINEOPTION: MacOSX GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions)
1023         GL_CheckExtension("GLX_ARB_get_proc_address", getprocaddressfuncs, "-nogetprocaddress", false);
1024 // COMMANDLINEOPTION: Linux GLX: -novideosync disables GLX_SGI_swap_control
1025 // COMMANDLINEOPTION: BSD GLX: -novideosync disables GLX_SGI_swap_control
1026 // COMMANDLINEOPTION: MacOSX GLX: -novideosync disables GLX_SGI_swap_control
1027         gl_videosyncavailable = GL_CheckExtension("GLX_SGI_swap_control", swapcontrolfuncs, "-novideosync", false);
1028
1029         vid_usingmousegrab = false;
1030         vid_usingmouse = false;
1031         vid_usinghidecursor = false;
1032         vid_usingvsync = false;
1033         vid_hidden = false;
1034         vid_activewindow = true;
1035         vid_x11_hardwaregammasupported = XF86VidModeGetGammaRampSize(vidx11_display, vidx11_screen, &vid_x11_gammarampsize) != 0;
1036 #if !defined(__APPLE__) && !defined(SUNOS)
1037         vid_x11_dgasupported = XF86DGAQueryVersion(vidx11_display, &MajorVersion, &MinorVersion);
1038         if (!vid_x11_dgasupported)
1039                 Con_Print( "Failed to detect XF86DGA Mouse extension\n" );
1040 #endif
1041         GL_Init();
1042         return true;
1043 }
1044
1045 void Sys_SendKeyEvents(void)
1046 {
1047         static qboolean sound_active = true;
1048
1049         // enable/disable sound on focus gain/loss
1050         if (!vid_hidden && (vid_activewindow || !snd_mutewhenidle.integer))
1051         {
1052                 if (!sound_active)
1053                 {
1054                         S_UnblockSound ();
1055                         sound_active = true;
1056                 }
1057         }
1058         else
1059         {
1060                 if (sound_active)
1061                 {
1062                         S_BlockSound ();
1063                         sound_active = false;
1064                 }
1065         }
1066
1067         HandleEvents();
1068 }
1069
1070 void IN_Move (void)
1071 {
1072 }