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