(234) Fixed part of bug #926883 (Video mode matching code, memory leaks,
[freeglut] / src / freeglut_gamemode.c
1 /*
2  * freeglut_gamemode.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  * Creation date: Thu Dec 16 1999
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
23  * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
24  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
25  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26  */
27
28 #include <GL/freeglut.h>
29 #include "freeglut_internal.h"
30
31 /*
32  * TODO BEFORE THE STABLE RELEASE:
33  *
34  *  glutGameModeString()    -- missing
35  *  glutEnterGameMode()     -- X11 version
36  *  glutLeaveGameMode()     -- is that correct?
37  *  glutGameModeGet()       -- is that correct?
38  */
39
40
41 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
42
43 /*
44  * Remembers the current visual settings, so that
45  * we can change them and restore later...
46  */
47 static void fghRememberState( void )
48 {
49 #if TARGET_HOST_UNIX_X11
50
51     /*
52      * This highly depends on the XFree86 extensions,
53      * not approved as X Consortium standards
54      */
55 #   ifdef X_XF86VidModeGetModeLine
56
57
58     /*
59      * Remember the current ViewPort location of the screen to be able to
60      * restore the ViewPort on LeaveGameMode():
61      */
62     XF86VidModeGetViewPort(
63         fgDisplay.Display,
64         fgDisplay.Screen,
65         &fgDisplay.DisplayViewPortX,
66         &fgDisplay.DisplayViewPortY
67     );
68
69     /*
70      * Remember the current pointer location before going fullscreen
71      * for restoring it later:
72      */
73     {
74         Window junk_window;
75         unsigned int mask;
76
77         XQueryPointer(
78             fgDisplay.Display, fgDisplay.RootWindow,
79             &junk_window, &junk_window,
80             &fgDisplay.DisplayPointerX, &fgDisplay.DisplayPointerY,
81             &fgDisplay.DisplayPointerX, &fgDisplay.DisplayPointerY, &mask
82         );
83     }
84
85     /* Query the current display settings: */
86     fgDisplay.DisplayModeValid =
87       XF86VidModeGetModeLine(
88         fgDisplay.Display,
89         fgDisplay.Screen,
90         &fgDisplay.DisplayModeClock,
91         &fgDisplay.DisplayMode
92     );
93
94     if( !fgDisplay.DisplayModeValid )
95             fgWarning( "Runtime use of XF86VidModeGetModeLine failed." );
96
97 #   else
98     /*
99      * XXX warning fghRememberState: missing XFree86 video mode extensions,
100      * XXX game mode will not change screen resolution when activated
101      */
102 #   endif
103
104 #elif TARGET_HOST_WIN32 || TARGET_HOST_WINCE
105
106 /*    DEVMODE devMode; */
107
108     /* Grab the current desktop settings... */
109
110 /* hack to get around my stupid cross-gcc headers */
111 #define FREEGLUT_ENUM_CURRENT_SETTINGS -1
112
113     EnumDisplaySettings( NULL, FREEGLUT_ENUM_CURRENT_SETTINGS,
114                          &fgDisplay.DisplayMode );
115
116     /* Make sure we will be restoring all settings needed */
117     fgDisplay.DisplayMode.dmFields |=
118         DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_DISPLAYFREQUENCY;
119
120 #endif
121 }
122
123 /*
124  * Restores the previously remembered visual settings
125  */
126 static void fghRestoreState( void )
127 {
128 #if TARGET_HOST_UNIX_X11
129
130 #   ifdef X_XF86VidModeGetAllModeLines
131     /* Restore the remembered pointer position: */
132     XWarpPointer(
133         fgDisplay.Display, None, fgDisplay.RootWindow, 0, 0, 0, 0,
134         fgDisplay.DisplayPointerX, fgDisplay.DisplayPointerY
135     );
136
137     /*
138      * This highly depends on the XFree86 extensions,
139      * not approved as X Consortium standards
140      */
141
142     if( fgDisplay.DisplayModeValid )
143     {
144         XF86VidModeModeInfo** displayModes;
145         int i, displayModesCount;
146
147         XF86VidModeGetAllModeLines(
148             fgDisplay.Display,
149             fgDisplay.Screen,
150             &displayModesCount,
151             &displayModes
152         );
153
154         /*
155          * Check every of the modes looking for one that matches our demands.
156          * If we find one, switch to it and restore the remembered viewport.
157          */
158         for( i = 0; i < displayModesCount; i++ )
159         {
160             if(displayModes[ i ]->hdisplay == fgDisplay.DisplayMode.hdisplay &&
161                displayModes[ i ]->vdisplay == fgDisplay.DisplayMode.vdisplay &&
162                displayModes[ i ]->dotclock == fgDisplay.DisplayModeClock )
163             {
164                 XF86VidModeSwitchToMode(
165                     fgDisplay.Display,
166                     fgDisplay.Screen,
167                     displayModes[ i ]
168                 );
169                 XF86VidModeSetViewPort(
170                      fgDisplay.Display,
171                      fgDisplay.Screen,
172                      fgDisplay.DisplayViewPortX,
173                      fgDisplay.DisplayViewPortY
174                 );
175
176                 /*
177                  * For the case this would be the last X11 call the application
178                  * calls exit() we've to flush the X11 output queue to have the
179                  * commands sent to the X server before the application exits.
180                  */
181                 XFlush( fgDisplay.Display );
182
183                 break;
184             }
185         }
186         XFree( displayModes );
187     }
188
189 #   else
190     /*
191      * XXX warning fghRestoreState: missing XFree86 video mode extensions,
192      * XXX game mode will not change screen resolution when activated
193      */
194 #   endif
195
196 #elif TARGET_HOST_WIN32 || TARGET_HOST_WINCE
197
198     /* Restore the previously rememebered desktop display settings */
199     ChangeDisplaySettings( &fgDisplay.DisplayMode, 0 );
200
201 #endif
202 }
203
204 /*
205  * Checks the display mode settings against user's preferences
206  */
207 static GLboolean fghCheckDisplayMode( int width, int height, int depth, int refresh )
208 {
209     /* The desired values should be stored in fgState structure... */
210     return ( width == fgState.GameModeSize.X ) &&
211            ( height == fgState.GameModeSize.Y ) &&
212            ( depth == fgState.GameModeDepth ) &&
213            (refresh == fgState.GameModeRefresh );
214 }
215
216 /*
217  * Changes the current display mode to match user's settings
218  */
219 static GLboolean fghChangeDisplayMode( GLboolean haveToTest )
220 {
221     GLboolean success = GL_FALSE;
222 #if TARGET_HOST_UNIX_X11
223
224     /*
225      * This highly depends on the XFree86 extensions,
226      * not approved as X Consortium standards
227      */
228 #   ifdef X_XF86VidModeGetAllModeLines
229
230     /*
231      * This is also used by applcations which check modes by calling
232      * glutGameModeGet(GLUT_GAME_MODE_POSSIBLE), so allow the check:
233      */
234     if( haveToTest || fgDisplay.DisplayModeValid )
235     {
236         XF86VidModeModeInfo** displayModes;
237         int i, ignoreRefreshRate, displayModesCount;
238
239         XF86VidModeGetAllModeLines(
240             fgDisplay.Display,
241             fgDisplay.Screen,
242             &displayModesCount,
243             &displayModes
244         );
245
246         /*
247          * Check every of the modes looking for one that matches our demands,
248          * ignoring the refresh rate if no exact match could be found.
249          */
250         for( ignoreRefreshRate = 0;
251              !success && ( ignoreRefreshRate <= 1 );
252              ignoreRefreshRate++)
253         {
254             for( i = 0;
255                  !success && ( i < displayModesCount );
256                  i++ )
257             {
258                 /* Compute the displays refresh rate, dotclock comes in kHz. */
259                 int refresh = ( displayModes[ i ]->dotclock * 1000 ) /
260                               ( displayModes[ i ]->htotal * displayModes[ i ]->vtotal );
261
262                 success = fghCheckDisplayMode( displayModes[ i ]->hdisplay,
263                                                displayModes[ i ]->vdisplay,
264                                                fgState.GameModeDepth,
265                                                ( ignoreRefreshRate ? fgState.GameModeRefresh : refresh ) );
266             }
267         }
268
269         if( !haveToTest && success ) {
270           XF86VidModeSwitchToMode( fgDisplay.Display, fgDisplay.Screen, displayModes[ i ] );
271         }
272
273         XFree( displayModes );
274     }
275
276 #   else
277     /*
278      * XXX warning fghChangeDisplayMode: missing XFree86 video mode extensions,
279      * XXX game mode will not change screen resolution when activated
280      */
281 #   endif
282
283 #elif TARGET_HOST_WIN32 || TARGET_HOST_WINCE
284
285     unsigned int    displayModes = 0, mode = 0xffffffff;
286     /* HDC      desktopDC; */
287     DEVMODE  devMode;
288
289     /*
290      * Enumerate the available display modes
291      * Try to get a complete match
292      */
293     while( EnumDisplaySettings( NULL, displayModes, &devMode ) )
294     {
295         /* Does the enumerated display mode match the user's preferences? */
296         if( fghCheckDisplayMode( devMode.dmPelsWidth,  devMode.dmPelsHeight,
297                                  devMode.dmBitsPerPel,
298                                  devMode.dmDisplayFrequency ) )
299         {
300             mode = displayModes;
301             break;
302         }
303         displayModes++;
304     }
305
306     if( mode == 0xffffffff )
307     {
308         /* then try without Display Frequency */
309         displayModes = 0;
310
311         /* Enumerate the available display modes */
312         while( EnumDisplaySettings( NULL, displayModes, &devMode ) )
313         {
314             /* then try without Display Frequency */
315             if( fghCheckDisplayMode( devMode.dmPelsWidth,
316                                      devMode.dmPelsHeight,
317                                      devMode.dmBitsPerPel,
318                                      fgState.GameModeRefresh ) )
319             {
320                 mode = displayModes;
321                 break;
322             }
323             displayModes++;
324         }
325     }
326
327     /* Did we find a matching display mode? */
328     if( mode != 0xffffffff )
329     {
330         int retVal = DISP_CHANGE_SUCCESSFUL;
331
332         /* Mark the values we want to modify in the display change call */
333         devMode.dmFields |=
334             DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_DISPLAYFREQUENCY;
335
336         retVal = ChangeDisplaySettings( &devMode, haveToTest ? CDS_TEST : 0 );
337
338         /* I don't know if it's really needed, but looks nice: */
339         success = (retVal == DISP_CHANGE_SUCCESSFUL) ||
340             (retVal == DISP_CHANGE_NOTUPDATED);
341
342         if( !haveToTest && success )
343         {
344             fgState.GameModeSize.X  = devMode.dmPelsWidth;
345             fgState.GameModeSize.Y  = devMode.dmPelsHeight;
346             fgState.GameModeDepth   = devMode.dmBitsPerPel;
347             fgState.GameModeRefresh = devMode.dmDisplayFrequency;
348         }
349     }
350
351 #endif
352
353     return success;
354 }
355
356
357 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
358
359 /*
360  * Sets the game mode display string
361  */
362 void FGAPIENTRY glutGameModeString( const char* string )
363 {
364     int width = 640, height = 480, depth = 16, refresh = 72;
365
366     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutGameModeString" );
367
368     /*
369      * This one seems a bit easier than glutInitDisplayString. The bad thing
370      * about it that I was unable to find the game mode string definition, so
371      * that I assumed it is: "[width]x[height]:[depth]@[refresh rate]", which
372      * appears in all GLUT game mode programs I have seen to date.
373      */
374     if( sscanf( string, "%ix%i:%i@%i", &width, &height, &depth, &refresh ) !=
375         4 )
376         if( sscanf( string, "%ix%i:%i", &width, &height, &depth ) != 3 )
377             if( sscanf( string, "%ix%i@%i", &width, &height, &refresh ) != 3 )
378                 if( sscanf( string, "%ix%i", &width, &height ) != 2 )
379                     if( sscanf( string, ":%i@%i", &depth, &refresh ) != 2 )
380                         if( sscanf( string, ":%i", &depth ) != 1 )
381                             if( sscanf( string, "@%i", &refresh ) != 1 )
382                                 fgWarning(
383                                     "unable to parse game mode string `%s'",
384                                     string
385                                 );
386
387     /* Hopefully it worked, and if not, we still have the default values */
388     fgState.GameModeSize.X  = width;
389     fgState.GameModeSize.Y  = height;
390     fgState.GameModeDepth   = depth;
391     fgState.GameModeRefresh = refresh;
392 }
393
394 /*
395  * Enters the game mode
396  */
397 int FGAPIENTRY glutEnterGameMode( void )
398 {
399     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutEnterGameMode" );
400
401     if( fgStructure.GameMode )
402         fgAddToWindowDestroyList( fgStructure.GameMode );
403     else
404         fghRememberState( );
405
406     if( ! fghChangeDisplayMode( GL_FALSE ) )
407     {
408         fgWarning( "failed to change screen settings" );
409         return FALSE;
410     }
411
412     fgStructure.GameMode = fgCreateWindow(
413         NULL, "FREEGLUT", 0, 0,
414         fgState.GameModeSize.X, fgState.GameModeSize.Y, GL_TRUE, GL_FALSE
415     );
416
417     fgStructure.GameMode->State.IsGameMode = GL_TRUE;
418
419 #if TARGET_HOST_UNIX_X11
420
421     /* Move the window up to the topleft corner */
422     XMoveWindow( fgDisplay.Display, fgStructure.Window->Window.Handle, 0, 0 );
423
424     /*
425      * Sync needed to avoid a real race, the Xserver must have really created
426      * the window before we can grab the pointer into it:
427      */
428     XSync( fgDisplay.Display, False );
429
430     /* Move the Pointer to the middle of the fullscreen window */
431     XWarpPointer(
432         fgDisplay.Display,
433         None,
434         fgDisplay.RootWindow,
435         0, 0, 0, 0,
436         fgState.GameModeSize.X/2, fgState.GameModeSize.Y/2
437     );
438
439     /*
440      * Grab the pointer to confine it into the window after the calls to
441      * XWrapPointer() which ensure that the pointer really enters the window.
442      *
443      * We also need to wait here until XGrabPointer() returns GrabSuccess,
444      * otherwise the new window is not viewable yet and if the next function
445      * (XSetInputFocus) is called with a not yet viewable window, it will exit
446      * the application which we have to aviod, so wait until it's viewable:
447      */
448     while( GrabSuccess != XGrabPointer(
449                fgDisplay.Display, fgStructure.GameMode->Window.Handle,
450                TRUE,
451                ButtonPressMask | ButtonReleaseMask | ButtonMotionMask
452                | PointerMotionMask,
453                GrabModeAsync, GrabModeAsync,
454                fgStructure.GameMode->Window.Handle, None, CurrentTime) )
455         usleep( 100 );
456
457     /*
458      * Change input focus to the new window. This will exit the application
459      * if the new window is not viewable yet, see the XGrabPointer loop above.
460      */
461     XSetInputFocus(
462         fgDisplay.Display,
463         fgStructure.GameMode->Window.Handle,
464         RevertToNone,
465         CurrentTime
466     );
467
468 #   ifdef X_XF86VidModeSetViewPort
469
470     if( fgDisplay.DisplayModeValid )
471     {
472         int x, y;
473         Window child;
474
475         /* Change to viewport to the window topleft edge: */
476         XF86VidModeSetViewPort( fgDisplay.Display, fgDisplay.Screen, 0, 0 );
477
478         /*
479          * Final window repositioning: It could be avoided using an undecorated
480          * window using override_redirect, but this * would possily require
481          * more changes and investigation.
482          */
483
484         /* Get the current postion of the drawable area on screen */
485         XTranslateCoordinates(
486             fgDisplay.Display,
487             fgStructure.Window->Window.Handle,
488             fgDisplay.RootWindow,
489             0, 0, &x, &y,
490             &child
491         );
492
493         /* Move the decorataions out of the topleft corner of the display */
494         XMoveWindow( fgDisplay.Display, fgStructure.Window->Window.Handle,
495                      -x, -y);
496     }
497
498 #endif
499
500     /* Grab the keyboard, too */
501     XGrabKeyboard(
502         fgDisplay.Display,
503         fgStructure.GameMode->Window.Handle,
504         FALSE,
505         GrabModeAsync, GrabModeAsync,
506         CurrentTime
507     );
508
509 #endif
510
511     return TRUE;
512 }
513
514 /*
515  * Leaves the game mode
516  */
517 void FGAPIENTRY glutLeaveGameMode( void )
518 {
519     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutLeaveGameMode" );
520
521     freeglut_return_if_fail( fgStructure.GameMode );
522
523     fgStructure.GameMode->State.IsGameMode = GL_FALSE;
524
525     fgAddToWindowDestroyList( fgStructure.GameMode );
526     fgStructure.GameMode = NULL;
527
528 #if TARGET_HOST_UNIX_X11
529
530     XUngrabPointer( fgDisplay.Display, CurrentTime );
531     XUngrabKeyboard( fgDisplay.Display, CurrentTime );
532
533 #endif
534
535     fghRestoreState();
536 }
537
538 /*
539  * Returns information concerning the freeglut game mode
540  */
541 int FGAPIENTRY glutGameModeGet( GLenum eWhat )
542 {
543     FREEGLUT_EXIT_IF_NOT_INITIALISED ( "glutGameModeGet" );
544
545     switch( eWhat )
546     {
547     case GLUT_GAME_MODE_ACTIVE:
548         return !!fgStructure.GameMode;
549
550     case GLUT_GAME_MODE_POSSIBLE:
551         return fghChangeDisplayMode( GL_TRUE );
552
553     case GLUT_GAME_MODE_WIDTH:
554         return fgState.GameModeSize.X;
555
556     case GLUT_GAME_MODE_HEIGHT:
557         return fgState.GameModeSize.Y;
558
559     case GLUT_GAME_MODE_PIXEL_DEPTH:
560         return fgState.GameModeDepth;
561
562     case GLUT_GAME_MODE_REFRESH_RATE:
563         return fgState.GameModeRefresh;
564
565     case GLUT_GAME_MODE_DISPLAY_CHANGED:
566         /*
567          * This is true if the game mode has been activated successfully..
568          */
569         return !!fgStructure.GameMode;
570     }
571
572     fgWarning( "Unknown gamemode get: %d", eWhat );
573     return -1;
574 }
575
576 /*** END OF FILE ***/