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