gamemode-x11: fixed the conditions checking for XR&R >= 1.1, as it where they would...
[freeglut] / src / x11 / fg_gamemode_x11.c
1 /*
2  * freeglut_gamemode_x11.c
3  *
4  * The game mode handling code.
5  *
6  * Copyright (c) 1999-2000 Pawel W. Olszta. All Rights Reserved.
7  * Written by Pawel W. Olszta, <olszta@sourceforge.net>
8  * Copied for Platform code by Evan Felix <karcaw at gmail.com>
9  * Creation date: Thur Feb 2 2012
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
24  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
25  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27  */
28
29 #include <GL/freeglut.h>
30 #include "../fg_internal.h"
31
32 static int xrandr_resize(int xsz, int ysz, int rate, int just_checking)
33 {
34 #ifdef HAVE_X11_EXTENSIONS_XRANDR_H
35     int event_base, error_base, ver_major, ver_minor, use_rate;
36     XRRScreenConfiguration *xrr_config = 0;
37     Status result = -1;
38
39     /* must check at runtime for the availability of the extension */
40     if(!XRRQueryExtension(fgDisplay.pDisplay.Display, &event_base, &error_base)) {
41         return -1;
42     }
43
44     XRRQueryVersion(fgDisplay.pDisplay.Display, &ver_major, &ver_minor);
45
46     /* we only heed the rate if we CAN actually use it (Xrandr >= 1.1) and
47      * the user actually cares about it (rate > 0)
48      */
49     use_rate = ( rate > 0 ) && ( ( ver_major > 1 ) ||
50                                          ( ( ver_major == 1 ) && ( ver_minor >= 1 ) ) );
51
52     /* this loop is only so that the whole thing will be repeated if someone
53      * else changes video mode between our query of the current information and
54      * the attempt to change it.
55      */
56     do {
57         XRRScreenSize *ssizes;
58         short *rates;
59         Rotation rot;
60         int i, ssizes_count, rates_count, curr, res_idx = -1;
61         Time timestamp, cfg_timestamp;
62
63         if(xrr_config) {
64             XRRFreeScreenConfigInfo(xrr_config);
65         }
66
67         if(!(xrr_config = XRRGetScreenInfo(fgDisplay.pDisplay.Display, fgDisplay.pDisplay.RootWindow))) {
68             fgWarning("XRRGetScreenInfo failed");
69             break;
70         }
71         ssizes = XRRConfigSizes(xrr_config, &ssizes_count);
72         curr = XRRConfigCurrentConfiguration(xrr_config, &rot);
73         timestamp = XRRConfigTimes(xrr_config, &cfg_timestamp);
74
75         /* if either of xsz or ysz are unspecified, use the current values */
76         if(xsz <= 0)
77             xsz = fgState.GameModeSize.X = ssizes[curr].width;
78         if(ysz <= 0)
79             ysz = fgState.GameModeSize.Y = ssizes[curr].height;
80
81
82         if(xsz == ssizes[curr].width && ysz == ssizes[curr].height) {
83             /* no need to switch, we're already in the requested resolution */
84             res_idx = curr;
85         } else {
86             for(i=0; i<ssizes_count; i++) {
87                 if(ssizes[i].width == xsz && ssizes[i].height == ysz) {
88                     res_idx = i;
89                     break;  /* found it */
90                 }
91             }
92         }
93         if(res_idx == -1)
94             break;  /* no matching resolution */
95
96 #if ( RANDR_MAJOR > 1 ) || ( ( RANDR_MAJOR == 1 ) && ( RANDR_MINOR >= 1 ) )
97         if(use_rate) {
98             rate = fgState.GameModeRefresh;
99
100             /* for the selected resolution, let's find out if there is
101              * a matching refresh rate available.
102              */
103             rates = XRRConfigRates(xrr_config, res_idx, &rates_count);
104
105             for(i=0; i<rates_count; i++) {
106                 if(rates[i] == rate) {
107                     break;
108                 }
109             }
110             if(i == rates_count) {
111                 break; /* no matching rate */
112             }
113         }
114 #endif
115
116         if(just_checking) {
117             result = 0;
118             break;
119         }
120
121 #if ( RANDR_MAJOR > 1 ) || ( ( RANDR_MAJOR == 1 ) && ( RANDR_MINOR >= 1 ) )
122         if(use_rate)
123             result = XRRSetScreenConfigAndRate(fgDisplay.pDisplay.Display, xrr_config,
124                     fgDisplay.pDisplay.RootWindow, res_idx, rot, rate, timestamp);
125         else
126 #endif
127             result = XRRSetScreenConfig(fgDisplay.pDisplay.Display, xrr_config,
128                     fgDisplay.pDisplay.RootWindow, res_idx, rot, timestamp);
129
130     } while(result == RRSetConfigInvalidTime);
131
132     if(xrr_config) {
133         XRRFreeScreenConfigInfo(xrr_config);
134     }
135
136     if(result == 0) {
137         return 0;
138     }
139
140 #endif  /* HAVE_X11_EXTENSIONS_XRANDR_H */
141     return -1;
142 }
143
144 /*
145  * Remembers the current visual settings, so that
146  * we can change them and restore later...
147  */
148 void fgPlatformRememberState( void )
149 {
150     int event_base, error_base;
151
152     /*
153      * Remember the current pointer location before going fullscreen
154      * for restoring it later:
155      */
156     Window junk_window;
157     unsigned int junk_mask;
158
159     XQueryPointer(fgDisplay.pDisplay.Display, fgDisplay.pDisplay.RootWindow,
160             &junk_window, &junk_window,
161             &fgDisplay.pDisplay.DisplayPointerX, &fgDisplay.pDisplay.DisplayPointerY,
162             &fgDisplay.pDisplay.DisplayPointerX, &fgDisplay.pDisplay.DisplayPointerY, &junk_mask);
163
164 #   ifdef HAVE_X11_EXTENSIONS_XRANDR_H
165     if(XRRQueryExtension(fgDisplay.pDisplay.Display, &event_base, &error_base)) {
166         XRRScreenConfiguration *xrr_config;
167         XRRScreenSize *ssizes;
168         Rotation rot;
169         int ssize_count, curr;
170
171         if((xrr_config = XRRGetScreenInfo(fgDisplay.pDisplay.Display, fgDisplay.pDisplay.RootWindow))) {
172             ssizes = XRRConfigSizes(xrr_config, &ssize_count);
173             curr = XRRConfigCurrentConfiguration(xrr_config, &rot);
174
175             fgDisplay.pDisplay.prev_xsz = ssizes[curr].width;
176             fgDisplay.pDisplay.prev_ysz = ssizes[curr].height;
177             fgDisplay.pDisplay.prev_refresh = -1;
178
179 #       if ( RANDR_MAJOR > 1 ) || ( ( RANDR_MAJOR == 1 ) && ( RANDR_MINOR >= 1 ) )
180             if(fgState.GameModeRefresh != -1) {
181                 fgDisplay.pDisplay.prev_refresh = XRRConfigCurrentRate(xrr_config);
182             }
183 #       endif
184
185             fgDisplay.pDisplay.prev_size_valid = 1;
186
187             XRRFreeScreenConfigInfo(xrr_config);
188         }
189     }
190 #   endif
191
192     /*
193      * This highly depends on the XFree86 extensions,
194      * not approved as X Consortium standards
195      */
196 #   ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
197     if(!XF86VidModeQueryExtension(fgDisplay.pDisplay.Display, &event_base, &error_base)) {
198         return;
199     }
200
201     /*
202      * Remember the current ViewPort location of the screen to be able to
203      * restore the ViewPort on LeaveGameMode():
204      */
205     if( !XF86VidModeGetViewPort(
206              fgDisplay.pDisplay.Display,
207              fgDisplay.pDisplay.Screen,
208              &fgDisplay.pDisplay.DisplayViewPortX,
209              &fgDisplay.pDisplay.DisplayViewPortY ) )
210         fgWarning( "XF86VidModeGetViewPort failed" );
211
212
213     /* Query the current display settings: */
214     fgDisplay.pDisplay.DisplayModeValid =
215       XF86VidModeGetModeLine(
216         fgDisplay.pDisplay.Display,
217         fgDisplay.pDisplay.Screen,
218         &fgDisplay.pDisplay.DisplayModeClock,
219         &fgDisplay.pDisplay.DisplayMode
220     );
221
222     if( !fgDisplay.pDisplay.DisplayModeValid )
223         fgWarning( "XF86VidModeGetModeLine failed" );
224 #   endif
225
226 }
227
228 /*
229  * Restores the previously remembered visual settings
230  */
231 void fgPlatformRestoreState( void )
232 {
233     /* Restore the remembered pointer position: */
234     XWarpPointer(
235         fgDisplay.pDisplay.Display, None, fgDisplay.pDisplay.RootWindow, 0, 0, 0, 0,
236         fgDisplay.pDisplay.DisplayPointerX, fgDisplay.pDisplay.DisplayPointerY
237     );
238
239
240 #   ifdef HAVE_X11_EXTENSIONS_XRANDR_H
241     if(fgDisplay.pDisplay.prev_size_valid) {
242         if(xrandr_resize(fgDisplay.pDisplay.prev_xsz, fgDisplay.pDisplay.prev_ysz, fgDisplay.pDisplay.prev_refresh, 0) != -1) {
243             fgDisplay.pDisplay.prev_size_valid = 0;
244 #       ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
245             fgDisplay.pDisplay.DisplayModeValid = 0;
246 #       endif
247             return;
248         }
249     }
250 #   endif
251
252
253
254 #   ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
255     /*
256      * This highly depends on the XFree86 extensions,
257      * not approved as X Consortium standards
258      */
259
260     if( fgDisplay.pDisplay.DisplayModeValid )
261     {
262         XF86VidModeModeInfo** displayModes;
263         int i, displayModesCount;
264
265         if( !XF86VidModeGetAllModeLines(
266                  fgDisplay.pDisplay.Display,
267                  fgDisplay.pDisplay.Screen,
268                  &displayModesCount,
269                  &displayModes ) )
270         {
271             fgWarning( "XF86VidModeGetAllModeLines failed" );
272             return;
273         }
274
275
276         /*
277          * Check every of the modes looking for one that matches our demands.
278          * If we find one, switch to it and restore the remembered viewport.
279          */
280         for( i = 0; i < displayModesCount; i++ )
281         {
282             if(displayModes[ i ]->hdisplay == fgDisplay.pDisplay.DisplayMode.hdisplay &&
283                displayModes[ i ]->vdisplay == fgDisplay.pDisplay.DisplayMode.vdisplay &&
284                displayModes[ i ]->dotclock == fgDisplay.pDisplay.DisplayModeClock )
285             {
286                 if( !XF86VidModeSwitchToMode(
287                          fgDisplay.pDisplay.Display,
288                          fgDisplay.pDisplay.Screen,
289                          displayModes[ i ] ) )
290                 {
291                     fgWarning( "XF86VidModeSwitchToMode failed" );
292                     break;
293                 }
294
295                 if( !XF86VidModeSetViewPort(
296                          fgDisplay.pDisplay.Display,
297                          fgDisplay.pDisplay.Screen,
298                          fgDisplay.pDisplay.DisplayViewPortX,
299                          fgDisplay.pDisplay.DisplayViewPortY ) )
300                     fgWarning( "XF86VidModeSetViewPort failed" );
301
302
303                 /*
304                  * For the case this would be the last X11 call the application
305                  * calls exit() we've to flush the X11 output queue to have the
306                  * commands sent to the X server before the application exits.
307                  */
308                 XFlush( fgDisplay.pDisplay.Display );
309
310                 fgDisplay.pDisplay.DisplayModeValid = 0;
311 #       ifdef HAVE_X11_EXTENSIONS_XRANDR_H
312                 fgDisplay.pDisplay.prev_size_valid = 0;
313 #       endif
314
315                 break;
316             }
317         }
318         XFree( displayModes );
319     }
320
321 #   endif
322
323 }
324
325 #ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
326
327 /*
328  * Checks a single display mode settings against user's preferences.
329  */
330 static GLboolean fghCheckDisplayMode( int width, int height, int depth, int refresh )
331 {
332     /* The desired values should be stored in fgState structure... */
333     return ( width == fgState.GameModeSize.X ) &&
334            ( height == fgState.GameModeSize.Y ) &&
335            ( depth == fgState.GameModeDepth ) &&
336            ( refresh == fgState.GameModeRefresh );
337 }
338
339 /*
340  * Checks all display modes settings against user's preferences.
341  * Returns the mode number found or -1 if none could be found.
342  */
343 static int fghCheckDisplayModes( GLboolean exactMatch, int displayModesCount, XF86VidModeModeInfo** displayModes )
344 {
345     int i;
346     for( i = 0; i < displayModesCount; i++ )
347     {
348         /* Compute the displays refresh rate, dotclock comes in kHz. */
349         int refresh = ( displayModes[ i ]->dotclock * 1000 ) /
350                       ( displayModes[ i ]->htotal * displayModes[ i ]->vtotal );
351
352         if( fghCheckDisplayMode( displayModes[ i ]->hdisplay,
353                                  displayModes[ i ]->vdisplay,
354                                  fgState.GameModeDepth,
355                                  ( exactMatch ? refresh : fgState.GameModeRefresh ) ) ) {
356             if (!exactMatch)
357             {
358                 /* Update the chosen refresh rate, otherwise a
359                  * glutGameModeGet(GLUT_GAME_MODE_REFRESH_RATE) would not
360                  * return the right values
361                  */
362                 fgState.GameModeRefresh = refresh;
363             }
364
365             return i;
366         }
367     }
368     return -1;
369 }
370
371 #endif
372
373 /*
374  * Changes the current display mode to match user's settings
375  */
376 GLboolean fgPlatformChangeDisplayMode( GLboolean haveToTest )
377 {
378     GLboolean success = GL_FALSE;
379     /* first try to use XRandR, then fallback to XF86VidMode */
380 #   ifdef HAVE_X11_EXTENSIONS_XRANDR_H
381     if(xrandr_resize(fgState.GameModeSize.X, fgState.GameModeSize.Y,
382                 fgState.GameModeRefresh, haveToTest) != -1) {
383         return GL_TRUE;
384     }
385 #   endif
386
387
388     /*
389      * This highly depends on the XFree86 extensions,
390      * not approved as X Consortium standards
391      */
392 #   ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
393
394     /*
395      * This is also used by applications which check modes by calling
396      * glutGameModeGet(GLUT_GAME_MODE_POSSIBLE), so allow the check:
397      */
398     if( haveToTest || fgDisplay.pDisplay.DisplayModeValid )
399     {
400         XF86VidModeModeInfo** displayModes;
401         int i, displayModesCount;
402
403         /* If we don't have a valid modeline in the display structure, which
404          * can happen if this is called from glutGameModeGet instead of
405          * glutEnterGameMode, then we need to query the current mode, to make
406          * unspecified settings to default to their current values.
407          */
408         if(!fgDisplay.pDisplay.DisplayModeValid) {
409             if(!XF86VidModeGetModeLine(fgDisplay.pDisplay.Display, fgDisplay.pDisplay.Screen,
410                     &fgDisplay.pDisplay.DisplayModeClock, &fgDisplay.pDisplay.DisplayMode)) {
411                 return success;
412             }
413         }
414
415         if (fgState.GameModeSize.X == -1)
416         {
417             fgState.GameModeSize.X = fgDisplay.pDisplay.DisplayMode.hdisplay;
418         }
419         if (fgState.GameModeSize.Y == -1)
420         {
421             fgState.GameModeSize.Y = fgDisplay.pDisplay.DisplayMode.vdisplay;
422         }
423         if (fgState.GameModeDepth == -1)
424         {
425             /* can't get color depth from this, nor can we change it, do nothing
426              * TODO: get with XGetVisualInfo()? but then how to set?
427              */
428         }
429         if (fgState.GameModeRefresh == -1)
430         {
431             /* Compute the displays refresh rate, dotclock comes in kHz. */
432             int refresh = ( fgDisplay.pDisplay.DisplayModeClock * 1000 ) /
433                 ( fgDisplay.pDisplay.DisplayMode.htotal * fgDisplay.pDisplay.DisplayMode.vtotal );
434
435             fgState.GameModeRefresh = refresh;
436         }
437
438         /* query all possible display modes */
439         if( !XF86VidModeGetAllModeLines(
440                  fgDisplay.pDisplay.Display,
441                  fgDisplay.pDisplay.Screen,
442                  &displayModesCount,
443                  &displayModes ) )
444         {
445             fgWarning( "XF86VidModeGetAllModeLines failed" );
446             return success;
447         }
448
449
450         /*
451          * Check every of the modes looking for one that matches our demands,
452          * ignoring the refresh rate if no exact match could be found.
453          */
454         i = fghCheckDisplayModes( GL_TRUE, displayModesCount, displayModes );
455         if( i < 0 ) {
456             i = fghCheckDisplayModes( GL_FALSE, displayModesCount, displayModes );
457         }
458         success = ( i < 0 ) ? GL_FALSE : GL_TRUE;
459
460         if( !haveToTest && success ) {
461             if( !XF86VidModeSwitchToMode(
462                      fgDisplay.pDisplay.Display,
463                      fgDisplay.pDisplay.Screen,
464                      displayModes[ i ] ) )
465                 fgWarning( "XF86VidModeSwitchToMode failed" );
466         }
467
468         XFree( displayModes );
469     }
470
471 #   endif
472
473     return success;
474 }
475
476
477 void fgPlatformEnterGameMode( void ) 
478 {
479
480     /*
481      * Sync needed to avoid a real race, the Xserver must have really created
482      * the window before we can grab the pointer into it:
483      */
484     XSync( fgDisplay.pDisplay.Display, False );
485     /*
486      * Grab the pointer to confine it into the window after the calls to
487      * XWrapPointer() which ensure that the pointer really enters the window.
488      *
489      * We also need to wait here until XGrabPointer() returns GrabSuccess,
490      * otherwise the new window is not viewable yet and if the next function
491      * (XSetInputFocus) is called with a not yet viewable window, it will exit
492      * the application which we have to aviod, so wait until it's viewable:
493      */
494     while( GrabSuccess != XGrabPointer(
495                fgDisplay.pDisplay.Display, fgStructure.GameModeWindow->Window.Handle,
496                TRUE,
497                ButtonPressMask | ButtonReleaseMask | ButtonMotionMask
498                | PointerMotionMask,
499                GrabModeAsync, GrabModeAsync,
500                fgStructure.GameModeWindow->Window.Handle, None, CurrentTime) )
501         usleep( 100 );
502     /*
503      * Change input focus to the new window. This will exit the application
504      * if the new window is not viewable yet, see the XGrabPointer loop above.
505      */
506     XSetInputFocus(
507         fgDisplay.pDisplay.Display,
508         fgStructure.GameModeWindow->Window.Handle,
509         RevertToNone,
510         CurrentTime
511     );
512
513     /* Move the Pointer to the middle of the fullscreen window */
514     XWarpPointer(
515         fgDisplay.pDisplay.Display,
516         None,
517         fgDisplay.pDisplay.RootWindow,
518         0, 0, 0, 0,
519         fgState.GameModeSize.X/2, fgState.GameModeSize.Y/2
520     );
521
522 #   ifdef HAVE_X11_EXTENSIONS_XF86VMODE_H
523
524     if( fgDisplay.pDisplay.DisplayModeValid )
525     {
526         int x, y;
527         Window child;
528
529         /* Change to viewport to the window topleft edge: */
530         if( !XF86VidModeSetViewPort( fgDisplay.pDisplay.Display, fgDisplay.pDisplay.Screen, 0, 0 ) )
531             fgWarning( "XF86VidModeSetViewPort failed" );
532
533         /*
534          * Final window repositioning: It could be avoided using an undecorated
535          * window using override_redirect, but this * would possily require
536          * more changes and investigation.
537          */
538
539         /* Get the current postion of the drawable area on screen */
540         XTranslateCoordinates(
541             fgDisplay.pDisplay.Display,
542             fgStructure.CurrentWindow->Window.Handle,
543             fgDisplay.pDisplay.RootWindow,
544             0, 0, &x, &y,
545             &child
546         );
547
548         /* Move the decorataions out of the topleft corner of the display */
549         XMoveWindow( fgDisplay.pDisplay.Display, fgStructure.CurrentWindow->Window.Handle,
550                      -x, -y);
551     }
552
553 #endif
554
555     /* Grab the keyboard, too */
556     XGrabKeyboard(
557         fgDisplay.pDisplay.Display,
558         fgStructure.GameModeWindow->Window.Handle,
559         FALSE,
560         GrabModeAsync, GrabModeAsync,
561         CurrentTime
562     );
563
564 }
565
566 void fgPlatformLeaveGameMode( void ) 
567 {
568     XUngrabPointer( fgDisplay.pDisplay.Display, CurrentTime );
569     XUngrabKeyboard( fgDisplay.pDisplay.Display, CurrentTime );
570 }
571