b72c74fc1483847b3e00be95e982f647c82164c5
[freeglut] / src / fg_init.c
1 /*
2  * fg_init.c
3  *
4  * Various freeglut initialization functions.
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 2 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 #define FREEGLUT_BUILDING_LIB
29 #include <GL/freeglut.h>
30 #include "fg_internal.h"
31
32 /*
33  * TODO BEFORE THE STABLE RELEASE:
34  *
35  *  fgDeinitialize()        -- Win32's OK, X11 needs the OS-specific
36  *                             deinitialization done
37  *  glutInitDisplayString() -- display mode string parsing
38  *
39  * Wouldn't it be cool to use gettext() for error messages? I just love
40  * bash saying  "nie znaleziono pliku" instead of "file not found" :)
41  * Is gettext easily portable?
42  */
43
44 /* -- GLOBAL VARIABLES ----------------------------------------------------- */
45
46 /*
47  * A structure pointed by fgDisplay holds all information
48  * regarding the display, screen, root window etc.
49  */
50 SFG_Display fgDisplay;
51
52 /*
53  * The settings for the current freeglut session
54  */
55 SFG_State fgState = { { -1, -1, GL_FALSE },  /* Position */
56                       { 300, 300, GL_TRUE }, /* Size */
57                       GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH,  /* DisplayMode */
58                       GL_FALSE,              /* Initialised */
59                       GLUT_TRY_DIRECT_CONTEXT,  /* DirectContext */
60                       GL_FALSE,              /* ForceIconic */
61                       GL_FALSE,              /* UseCurrentContext */
62                       GL_FALSE,              /* GLDebugSwitch */
63                       GL_FALSE,              /* XSyncSwitch */
64                       GLUT_KEY_REPEAT_ON,    /* KeyRepeat */
65                       INVALID_MODIFIERS,     /* Modifiers */
66                       0,                     /* FPSInterval */
67                       0,                     /* SwapCount */
68                       0,                     /* SwapTime */
69                       0,                     /* Time */
70                       { NULL, NULL },         /* Timers */
71                       { NULL, NULL },         /* FreeTimers */
72                       NULL,                   /* IdleCallback */
73                       0,                      /* ActiveMenus */
74                       NULL,                   /* MenuStateCallback */
75                       NULL,                   /* MenuStatusCallback */
76                       FREEGLUT_MENU_FONT,
77                       { -1, -1, GL_TRUE },    /* GameModeSize */
78                       -1,                     /* GameModeDepth */
79                       -1,                     /* GameModeRefresh */
80                       GLUT_ACTION_EXIT,       /* ActionOnWindowClose */
81                       GLUT_EXEC_STATE_INIT,   /* ExecState */
82                       NULL,                   /* ProgramName */
83                       GL_FALSE,               /* JoysticksInitialised */
84                       0,                      /* NumActiveJoysticks */
85                       GL_FALSE,               /* InputDevsInitialised */
86                       0,                      /* MouseWheelTicks */
87                       1,                      /* AuxiliaryBufferNumber */
88                       4,                      /* SampleNumber */
89                       GL_FALSE,               /* SkipStaleMotion */
90                       GL_FALSE,               /* StrokeFontDrawJoinDots */
91                       GL_FALSE,               /* AllowNegativeWindowPosition */
92                       1,                      /* OpenGL context MajorVersion */
93                       0,                      /* OpenGL context MinorVersion */
94                       0,                      /* OpenGL ContextFlags */
95                       0,                      /* OpenGL ContextProfile */
96                       0,                      /* HasOpenGL20 */
97                       NULL,                   /* ErrorFunc */
98                       NULL                    /* WarningFunc */
99 };
100
101
102 /* -- PRIVATE FUNCTIONS ---------------------------------------------------- */
103
104 extern void fgPlatformInitialize( const char* displayName );
105 extern void fgPlatformDeinitialiseInputDevices ( void );
106 extern void fgPlatformCloseDisplay ( void );
107 extern void fgPlatformDestroyContext ( SFG_PlatformDisplay pDisplay, SFG_WindowContextType MContext );
108
109 void fghParseCommandLineArguments ( int* pargc, char** argv, char **pDisplayName, char **pGeometry )
110 {
111 #ifndef _WIN32_WCE
112     int i, j, argc = *pargc;
113
114     {
115         /* check if GLUT_FPS env var is set */
116         const char *fps = getenv( "GLUT_FPS" );
117
118         if( fps )
119         {
120             int interval;
121             sscanf( fps, "%d", &interval );
122
123             if( interval <= 0 )
124                 fgState.FPSInterval = 5000;  /* 5000 millisecond default */
125             else
126                 fgState.FPSInterval = interval;
127         }
128     }
129
130     *pDisplayName = getenv( "DISPLAY" );
131
132     for( i = 1; i < argc; i++ )
133     {
134         if( strcmp( argv[ i ], "-display" ) == 0 )
135         {
136             if( ++i >= argc )
137                 fgError( "-display parameter must be followed by display name" );
138
139             *pDisplayName = argv[ i ];
140
141             argv[ i - 1 ] = NULL;
142             argv[ i     ] = NULL;
143             ( *pargc ) -= 2;
144         }
145         else if( strcmp( argv[ i ], "-geometry" ) == 0 )
146         {
147             if( ++i >= argc )
148                 fgError( "-geometry parameter must be followed by window "
149                          "geometry settings" );
150
151             *pGeometry = argv[ i ];
152
153             argv[ i - 1 ] = NULL;
154             argv[ i     ] = NULL;
155             ( *pargc ) -= 2;
156         }
157         else if( strcmp( argv[ i ], "-direct" ) == 0)
158         {
159             if( fgState.DirectContext == GLUT_FORCE_INDIRECT_CONTEXT )
160                 fgError( "parameters ambiguity, -direct and -indirect "
161                     "cannot be both specified" );
162
163             fgState.DirectContext = GLUT_FORCE_DIRECT_CONTEXT;
164             argv[ i ] = NULL;
165             ( *pargc )--;
166         }
167         else if( strcmp( argv[ i ], "-indirect" ) == 0 )
168         {
169             if( fgState.DirectContext == GLUT_FORCE_DIRECT_CONTEXT )
170                 fgError( "parameters ambiguity, -direct and -indirect "
171                     "cannot be both specified" );
172
173             fgState.DirectContext = GLUT_FORCE_INDIRECT_CONTEXT;
174             argv[ i ] = NULL;
175             (*pargc)--;
176         }
177         else if( strcmp( argv[ i ], "-iconic" ) == 0 )
178         {
179             fgState.ForceIconic = GL_TRUE;
180             argv[ i ] = NULL;
181             ( *pargc )--;
182         }
183         else if( strcmp( argv[ i ], "-gldebug" ) == 0 )
184         {
185             fgState.GLDebugSwitch = GL_TRUE;
186             argv[ i ] = NULL;
187             ( *pargc )--;
188         }
189         else if( strcmp( argv[ i ], "-sync" ) == 0 )
190         {
191             fgState.XSyncSwitch = GL_TRUE;
192             argv[ i ] = NULL;
193             ( *pargc )--;
194         }
195     }
196
197     /* Compact {argv}. */
198     for( i = j = 1; i < *pargc; i++, j++ )
199     {
200         /* Guaranteed to end because there are "*pargc" arguments left */
201         while ( argv[ j ] == NULL )
202             j++;
203         if ( i != j )
204             argv[ i ] = argv[ j ];
205     }
206
207 #endif /* _WIN32_WCE */
208
209 }
210
211
212 void fghCloseInputDevices ( void )
213 {
214     if ( fgState.JoysticksInitialised )
215         fgJoystickClose( );
216
217     if ( fgState.InputDevsInitialised )
218         fgInputDeviceClose( );
219 }
220
221
222 /*
223  * Perform the freeglut deinitialization...
224  */
225 void fgDeinitialize( void )
226 {
227     SFG_Timer *timer;
228
229     if( !fgState.Initialised )
230     {
231         return;
232     }
233
234     /* If we're in game mode, we want to leave game mode */
235     if( fgStructure.GameModeWindow ) {
236         glutLeaveGameMode();
237     }
238
239     /* If there was a menu created, destroy the rendering context */
240     if( fgStructure.MenuContext )
241     {
242         fgPlatformDestroyContext (fgDisplay.pDisplay, fgStructure.MenuContext->MContext );
243         free( fgStructure.MenuContext );
244         fgStructure.MenuContext = NULL;
245     }
246
247     fgDestroyStructure( );
248
249     while( ( timer = fgState.Timers.First) )
250     {
251         fgListRemove( &fgState.Timers, &timer->Node );
252         free( timer );
253     }
254
255     while( ( timer = fgState.FreeTimers.First) )
256     {
257         fgListRemove( &fgState.FreeTimers, &timer->Node );
258         free( timer );
259     }
260
261     fgPlatformDeinitialiseInputDevices ();
262
263     fgState.MouseWheelTicks = 0;
264
265     fgState.MajorVersion = 1;
266     fgState.MinorVersion = 0;
267     fgState.ContextFlags = 0;
268     fgState.ContextProfile = 0;
269
270     fgState.Initialised = GL_FALSE;
271
272     fgState.Position.X = -1;
273     fgState.Position.Y = -1;
274     fgState.Position.Use = GL_FALSE;
275
276     fgState.Size.X = 300;
277     fgState.Size.Y = 300;
278     fgState.Size.Use = GL_TRUE;
279
280     fgState.DisplayMode = GLUT_RGBA | GLUT_SINGLE | GLUT_DEPTH;
281
282     fgState.DirectContext  = GLUT_TRY_DIRECT_CONTEXT;
283     fgState.ForceIconic         = GL_FALSE;
284     fgState.UseCurrentContext   = GL_FALSE;
285     fgState.GLDebugSwitch       = GL_FALSE;
286     fgState.XSyncSwitch         = GL_FALSE;
287     fgState.ActionOnWindowClose = GLUT_ACTION_EXIT;
288     fgState.ExecState           = GLUT_EXEC_STATE_INIT;
289
290     fgState.KeyRepeat       = GLUT_KEY_REPEAT_ON;
291     fgState.Modifiers       = INVALID_MODIFIERS;
292
293     fgState.GameModeSize.X  = -1;
294     fgState.GameModeSize.Y  = -1;
295     fgState.GameModeDepth   = -1;
296     fgState.GameModeRefresh = -1;
297
298     fgListInit( &fgState.Timers );
299     fgListInit( &fgState.FreeTimers );
300
301     fgState.IdleCallback = NULL;
302     fgState.MenuStateCallback = ( FGCBMenuState )NULL;
303     fgState.MenuStatusCallback = ( FGCBMenuStatus )NULL;
304
305     fgState.SwapCount   = 0;
306     fgState.SwapTime    = 0;
307     fgState.FPSInterval = 0;
308
309     if( fgState.ProgramName )
310     {
311         free( fgState.ProgramName );
312         fgState.ProgramName = NULL;
313     }
314
315     fgPlatformCloseDisplay ();
316
317     fgState.Initialised = GL_FALSE;
318 }
319
320
321 /* -- INTERFACE FUNCTIONS -------------------------------------------------- */
322 #if defined(NEED_XPARSEGEOMETRY_IMPL)
323 #   include "util/xparsegeometry_repl.h"
324 #endif
325
326 /*
327  * Perform initialization. This usually happens on the program startup
328  * and restarting after glutMainLoop termination...
329  */
330 void FGAPIENTRY glutInit( int* pargc, char** argv )
331 {
332     char* displayName = NULL;
333     char* geometry = NULL;
334     if( fgState.Initialised )
335         fgError( "illegal glutInit() reinitialization attempt" );
336
337     if (pargc && *pargc && argv && *argv && **argv)
338     {
339         fgState.ProgramName = strdup (*argv);
340
341         if( !fgState.ProgramName )
342             fgError ("Could not allocate space for the program's name.");
343     }
344
345     fgCreateStructure( );
346
347     fghParseCommandLineArguments ( pargc, argv, &displayName, &geometry );
348
349     /*
350      * Have the display created now. If there wasn't a "-display"
351      * in the program arguments, we will use the DISPLAY environment
352      * variable for opening the X display (see code above):
353      */
354     fgPlatformInitialize( displayName );
355
356     /*
357      * Geometry parsing deferred until here because we may need the screen
358      * size.
359      */
360
361     if ( geometry )
362     {
363         unsigned int parsedWidth, parsedHeight;
364         int mask = XParseGeometry( geometry,
365                                    &fgState.Position.X, &fgState.Position.Y,
366                                    &parsedWidth, &parsedHeight );
367         /* TODO: Check for overflow? */
368         fgState.Size.X = parsedWidth;
369         fgState.Size.Y = parsedHeight;
370
371         if( (mask & (WidthValue|HeightValue)) == (WidthValue|HeightValue) )
372             fgState.Size.Use = GL_TRUE;
373
374         if( ( mask & XNegative ) && !fgState.AllowNegativeWindowPosition )
375             fgState.Position.X += fgDisplay.ScreenWidth - fgState.Size.X;
376
377         if( ( mask & YNegative ) && !fgState.AllowNegativeWindowPosition )
378             fgState.Position.Y += fgDisplay.ScreenHeight - fgState.Size.Y;
379
380         if( (mask & (XValue|YValue)) == (XValue|YValue) )
381             fgState.Position.Use = GL_TRUE;
382     }
383 }
384
385 /*
386  * Undoes all the "glutInit" stuff
387  */
388 void FGAPIENTRY glutExit ( void )
389 {
390   fgDeinitialize ();
391 }
392
393 /*
394  * Sets the default initial window position for new windows
395  */
396 void FGAPIENTRY glutInitWindowPosition( int x, int y )
397 {
398     fgState.Position.X = x;
399     fgState.Position.Y = y;
400
401     if( ( ( x >= 0 ) && ( y >= 0 ) ) || fgState.AllowNegativeWindowPosition )
402         fgState.Position.Use = GL_TRUE;
403     else
404         fgState.Position.Use = GL_FALSE;
405 }
406
407 /*
408  * Sets the default initial window size for new windows
409  */
410 void FGAPIENTRY glutInitWindowSize( int width, int height )
411 {
412     fgState.Size.X = width;
413     fgState.Size.Y = height;
414
415     if( ( width > 0 ) && ( height > 0 ) )
416         fgState.Size.Use = GL_TRUE;
417     else
418         fgState.Size.Use = GL_FALSE;
419 }
420
421 /*
422  * Sets the default display mode for all new windows
423  */
424 void FGAPIENTRY glutInitDisplayMode( unsigned int displayMode )
425 {
426     /* We will make use of this value when creating a new OpenGL context... */
427     fgState.DisplayMode = displayMode;
428 }
429
430
431 /* -- INIT DISPLAY STRING PARSING ------------------------------------------ */
432
433 static char* Tokens[] =
434 {
435     "alpha", "acca", "acc", "blue", "buffer", "conformant", "depth", "double",
436     "green", "index", "num", "red", "rgba", "rgb", "luminance", "stencil",
437     "single", "stereo", "samples", "slow", "win32pdf", "win32pfd", "xvisual",
438     "xstaticgray", "xgrayscale", "xstaticcolor", "xpseudocolor",
439     "xtruecolor", "xdirectcolor",
440     "xstaticgrey", "xgreyscale", "xstaticcolour", "xpseudocolour",
441     "xtruecolour", "xdirectcolour", "borderless", "aux"
442 };
443 #define NUM_TOKENS             (sizeof(Tokens) / sizeof(*Tokens))
444
445 void FGAPIENTRY glutInitDisplayString( const char* displayMode )
446 {
447     int glut_state_flag = 0 ;
448     /*
449      * Unpack a lot of options from a character string.  The options are
450      * delimited by blanks or tabs.
451      */
452     char *token ;
453     size_t len = strlen ( displayMode );
454     char *buffer = (char *)malloc ( (len+1) * sizeof(char) );
455     memcpy ( buffer, displayMode, len );
456     buffer[len] = '\0';
457
458     token = strtok ( buffer, " \t" );
459
460     while ( token )
461     {
462         /* Process this token */
463         int i ;
464
465         /* Temporary fix:  Ignore any length specifications and at least
466          * process the basic token
467          * TODO:  Fix this permanently
468          */
469         size_t cleanlength = strcspn ( token, "=<>~!" );
470
471         for ( i = 0; i < NUM_TOKENS; i++ )
472         {
473             if ( strncmp ( token, Tokens[i], cleanlength ) == 0 ) break ;
474         }
475
476         switch ( i )
477         {
478         case 0 :  /* "alpha":  Alpha color buffer precision in bits */
479             glut_state_flag |= GLUT_ALPHA ;  /* Somebody fix this for me! */
480             break ;
481
482         case 1 :  /* "acca":  Red, green, blue, and alpha accumulation buffer
483                      precision in bits */
484             break ;
485
486         case 2 :  /* "acc":  Red, green, and blue accumulation buffer precision
487                      in bits with zero bits alpha */
488             glut_state_flag |= GLUT_ACCUM ;  /* Somebody fix this for me! */
489             break ;
490
491         case 3 :  /* "blue":  Blue color buffer precision in bits */
492             break ;
493
494         case 4 :  /* "buffer":  Number of bits in the color index color buffer
495                    */
496             break ;
497
498         case 5 :  /* "conformant":  Boolean indicating if the frame buffer
499                      configuration is conformant or not */
500             break ;
501
502         case 6 : /* "depth":  Number of bits of precision in the depth buffer */
503             glut_state_flag |= GLUT_DEPTH ;  /* Somebody fix this for me! */
504             break ;
505
506         case 7 :  /* "double":  Boolean indicating if the color buffer is
507                      double buffered */
508             glut_state_flag |= GLUT_DOUBLE ;
509             break ;
510
511         case 8 :  /* "green":  Green color buffer precision in bits */
512             break ;
513
514         case 9 :  /* "index":  Boolean if the color model is color index or not
515                    */
516             glut_state_flag |= GLUT_INDEX ;
517             break ;
518
519         case 10 :  /* "num":  A special capability  name indicating where the
520                       value represents the Nth frame buffer configuration
521                       matching the description string */
522             break ;
523
524         case 11 :  /* "red":  Red color buffer precision in bits */
525             break ;
526
527         case 12 :  /* "rgba":  Number of bits of red, green, blue, and alpha in
528                       the RGBA color buffer */
529             glut_state_flag |= GLUT_RGBA ;  /* Somebody fix this for me! */
530             break ;
531
532         case 13 :  /* "rgb":  Number of bits of red, green, and blue in the
533                       RGBA color buffer with zero bits alpha */
534             glut_state_flag |= GLUT_RGB ;  /* Somebody fix this for me! */
535             break ;
536
537         case 14 :  /* "luminance":  Number of bits of red in the RGBA and zero
538                       bits of green, blue (alpha not specified) of color buffer
539                       precision */
540             glut_state_flag |= GLUT_LUMINANCE ; /* Somebody fix this for me! */
541             break ;
542
543         case 15 :  /* "stencil":  Number of bits in the stencil buffer */
544             glut_state_flag |= GLUT_STENCIL;  /* Somebody fix this for me! */
545             break ;
546
547         case 16 :  /* "single":  Boolean indicate the color buffer is single
548                       buffered */
549             glut_state_flag |= GLUT_SINGLE ;
550             break ;
551
552         case 17 :  /* "stereo":  Boolean indicating the color buffer supports
553                       OpenGL-style stereo */
554             glut_state_flag |= GLUT_STEREO ;
555             break ;
556
557         case 18 :  /* "samples":  Indicates the number of multisamples to use
558                       based on GLX's SGIS_multisample extension (for
559                       antialiasing) */
560             glut_state_flag |= GLUT_MULTISAMPLE ; /*Somebody fix this for me!*/
561             break ;
562
563         case 19 :  /* "slow":  Boolean indicating if the frame buffer
564                       configuration is slow or not */
565             break ;
566
567         case 20 :  /* "win32pdf": (incorrect spelling but was there before */
568         case 21 :  /* "win32pfd":  matches the Win32 Pixel Format Descriptor by
569                       number */
570 #if TARGET_HOST_MS_WINDOWS
571 #endif
572             break ;
573
574         case 22 :  /* "xvisual":  matches the X visual ID by number */
575 #if TARGET_HOST_POSIX_X11
576 #endif
577             break ;
578
579         case 23 :  /* "xstaticgray": */
580         case 29 :  /* "xstaticgrey":  boolean indicating if the frame buffer
581                       configuration's X visual is of type StaticGray */
582 #if TARGET_HOST_POSIX_X11
583 #endif
584             break ;
585
586         case 24 :  /* "xgrayscale": */
587         case 30 :  /* "xgreyscale":  boolean indicating if the frame buffer
588                       configuration's X visual is of type GrayScale */
589 #if TARGET_HOST_POSIX_X11
590 #endif
591             break ;
592
593         case 25 :  /* "xstaticcolor": */
594         case 31 :  /* "xstaticcolour":  boolean indicating if the frame buffer
595                       configuration's X visual is of type StaticColor */
596 #if TARGET_HOST_POSIX_X11
597 #endif
598             break ;
599
600         case 26 :  /* "xpseudocolor": */
601         case 32 :  /* "xpseudocolour":  boolean indicating if the frame buffer
602                       configuration's X visual is of type PseudoColor */
603 #if TARGET_HOST_POSIX_X11
604 #endif
605             break ;
606
607         case 27 :  /* "xtruecolor": */
608         case 33 :  /* "xtruecolour":  boolean indicating if the frame buffer
609                       configuration's X visual is of type TrueColor */
610 #if TARGET_HOST_POSIX_X11
611 #endif
612             break ;
613
614         case 28 :  /* "xdirectcolor": */
615         case 34 :  /* "xdirectcolour":  boolean indicating if the frame buffer
616                       configuration's X visual is of type DirectColor */
617 #if TARGET_HOST_POSIX_X11
618 #endif
619             break ;
620
621         case 35 :  /* "borderless":  windows should not have borders */
622             glut_state_flag |= GLUT_BORDERLESS;
623             break ;
624
625         case 36 :  /* "aux":  some number of aux buffers */
626             glut_state_flag |= GLUT_AUX;
627             break ;
628
629         case 37 :  /* Unrecognized */
630             fgWarning ( "WARNING - Display string token not recognized:  %s",
631                         token );
632             break ;
633         }
634
635         token = strtok ( NULL, " \t" );
636     }
637
638     free ( buffer );
639
640     /* We will make use of this value when creating a new OpenGL context... */
641     fgState.DisplayMode = glut_state_flag;
642 }
643
644 /* -- SETTING OPENGL 3.0 CONTEXT CREATION PARAMETERS ---------------------- */
645
646 void FGAPIENTRY glutInitContextVersion( int majorVersion, int minorVersion )
647 {
648     /* We will make use of these value when creating a new OpenGL context... */
649     fgState.MajorVersion = majorVersion;
650     fgState.MinorVersion = minorVersion;
651 }
652
653
654 void FGAPIENTRY glutInitContextFlags( int flags )
655 {
656     /* We will make use of this value when creating a new OpenGL context... */
657     fgState.ContextFlags = flags;
658 }
659
660 void FGAPIENTRY glutInitContextProfile( int profile )
661 {
662     /* We will make use of this value when creating a new OpenGL context... */
663     fgState.ContextProfile = profile;
664 }
665
666 /* -------------- User Defined Error/Warning Handler Support -------------- */
667
668 /*
669  * Sets the user error handler (note the use of va_list for the args to the fmt)
670  */
671 void FGAPIENTRY glutInitErrorFunc( FGError callback )
672 {
673     /* This allows user programs to handle freeglut errors */
674     fgState.ErrorFunc = callback;
675 }
676
677 /*
678  * Sets the user warning handler (note the use of va_list for the args to the fmt)
679  */
680 void FGAPIENTRY glutInitWarningFunc( FGWarning callback )
681 {
682     /* This allows user programs to handle freeglut warnings */
683     fgState.WarningFunc = callback;
684 }
685
686 /*** END OF FILE ***/