glut_gamemode.c revision c041511d
1
2/* Copyright (c) Mark J. Kilgard, 1998. */
3
4/* This program is freely distributable without licensing fees
5   and is provided without guarantee or warrantee expressed or
6   implied. This program is -not- in the public domain. */
7
8#ifdef __VMS
9#include <GL/vms_x_fix.h>
10#endif
11
12#include <assert.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16
17#include "glutint.h"
18
19#ifndef _WIN32
20#include <X11/Xlib.h>
21#include <X11/Xatom.h>
22
23/* SGI optimization introduced in IRIX 6.3 to avoid X server
24   round trips for interning common X atoms. */
25#if defined(_SGI_EXTRA_PREDEFINES) && !defined(NO_FAST_ATOMS)
26#include <X11/SGIFastAtom.h>
27#else
28#define XSGIFastInternAtom(dpy,string,fast_name,how) XInternAtom(dpy,string,how)
29#endif
30#endif  /* not _WIN32 */
31
32int __glutDisplaySettingsChanged = 0;
33static DisplayMode *dmodes, *currentDm = NULL;
34static int ndmodes = -1;
35GLUTwindow *__glutGameModeWindow = NULL;
36
37#ifdef TEST
38static char *compstr[] =
39{
40  "none", "=", "!=", "<=", ">=", ">", "<", "~"
41};
42static char *capstr[] =
43{
44  "width", "height", "bpp", "hertz", "num"
45};
46#endif
47
48void
49__glutCloseDownGameMode(void)
50{
51  if (__glutDisplaySettingsChanged) {
52#ifdef _WIN32
53    /* Assumes that display settings have been changed, that
54       is __glutDisplaySettingsChanged is true. */
55    ChangeDisplaySettings(NULL, 0);
56#endif
57    __glutDisplaySettingsChanged = 0;
58  }
59  __glutGameModeWindow = NULL;
60}
61
62void GLUTAPIENTRY
63glutLeaveGameMode(void)
64{
65  if (__glutGameModeWindow == NULL) {
66    __glutWarning("not in game mode so cannot leave game mode");
67    return;
68  }
69  __glutDestroyWindow(__glutGameModeWindow,
70    __glutGameModeWindow);
71  XFlush(__glutDisplay);
72  __glutGameModeWindow = NULL;
73}
74
75#ifdef _WIN32
76
77/* Same values as from MSDN's SetDisp.c example. */
78#define MIN_WIDTH 400
79#define MIN_FREQUENCY 60
80
81static void
82initGameModeSupport(void)
83{
84  DEVMODE dm;
85  DWORD mode;
86  int i;
87
88  if (ndmodes >= 0) {
89    /* ndmodes is initially -1 to indicate no
90       dmodes allocated yet. */
91    return;
92  }
93
94  /* Determine how many display modes there are. */
95  ndmodes = 0;
96  mode = 0;
97  while (EnumDisplaySettings(NULL, mode, &dm)) {
98    if (dm.dmPelsWidth >= MIN_WIDTH &&
99      (dm.dmDisplayFrequency == 0 ||
100      dm.dmDisplayFrequency >= MIN_FREQUENCY)) {
101      ndmodes++;
102    }
103    mode++;
104  }
105
106  /* Allocate memory for a list of all the display modes. */
107  dmodes = (DisplayMode*)
108    malloc(ndmodes * sizeof(DisplayMode));
109
110  /* Now that we know how many display modes to expect,
111     enumerate them again and save the information in
112     the list we allocated above. */
113  i = 0;
114  mode = 0;
115  while (EnumDisplaySettings(NULL, mode, &dm)) {
116    /* Try to reject any display settings that seem unplausible. */
117    if (dm.dmPelsWidth >= MIN_WIDTH &&
118      (dm.dmDisplayFrequency == 0 ||
119      dm.dmDisplayFrequency >= MIN_FREQUENCY)) {
120      dmodes[i].devmode = dm;
121      dmodes[i].valid = 1;  /* XXX Not used for now. */
122      dmodes[i].cap[DM_WIDTH] = dm.dmPelsWidth;
123      dmodes[i].cap[DM_HEIGHT] = dm.dmPelsHeight;
124      dmodes[i].cap[DM_PIXEL_DEPTH] = dm.dmBitsPerPel;
125      if (dm.dmDisplayFrequency == 0) {
126 	/* Guess a reasonable guess. */
127	/* Lame Windows 95 version of EnumDisplaySettings. */
128        dmodes[i].cap[DM_HERTZ] = 60;
129      } else {
130	dmodes[i].cap[DM_HERTZ] = dm.dmDisplayFrequency;
131      }
132      i++;
133    }
134    mode++;
135  }
136
137  assert(i == ndmodes);
138}
139
140#else
141
142/* X Windows version of initGameModeSupport. */
143static void
144initGameModeSupport(void)
145{
146  if (ndmodes >= 0) {
147    /* ndmodes is initially -1 to indicate no
148       dmodes allocated yet. */
149    return;
150  }
151
152  /* Determine how many display modes there are. */
153  ndmodes = 0;
154}
155
156#endif
157
158/* This routine is based on similiar code in glut_dstr.c */
159static DisplayMode *
160findMatch(DisplayMode * dmodes, int ndmodes,
161  Criterion * criteria, int ncriteria)
162{
163  DisplayMode *found;
164  int *bestScore, *thisScore;
165  int i, j, numok, result = 0, worse, better;
166
167  found = NULL;
168  numok = 1;            /* "num" capability is indexed from 1,
169                           not 0. */
170
171  /* XXX alloca canidate. */
172  bestScore = (int *) malloc(ncriteria * sizeof(int));
173  if (!bestScore) {
174    __glutFatalError("out of memory.");
175  }
176  for (j = 0; j < ncriteria; j++) {
177    /* Very negative number. */
178    bestScore[j] = -32768;
179  }
180
181  /* XXX alloca canidate. */
182  thisScore = (int *) malloc(ncriteria * sizeof(int));
183  if (!thisScore) {
184    __glutFatalError("out of memory.");
185  }
186
187  for (i = 0; i < ndmodes; i++) {
188    if (dmodes[i].valid) {
189      worse = 0;
190      better = 0;
191
192      for (j = 0; j < ncriteria; j++) {
193        int cap, cvalue, dvalue;
194
195        cap = criteria[j].capability;
196        cvalue = criteria[j].value;
197        if (cap == NUM) {
198          dvalue = numok;
199        } else {
200          dvalue = dmodes[i].cap[cap];
201        }
202#ifdef TEST
203        if (verbose)
204          printf("  %s %s %d to %d\n",
205            capstr[cap], compstr[criteria[j].comparison], cvalue, dvalue);
206#endif
207        switch (criteria[j].comparison) {
208        case EQ:
209          result = cvalue == dvalue;
210          thisScore[j] = 1;
211          break;
212        case NEQ:
213          result = cvalue != dvalue;
214          thisScore[j] = 1;
215          break;
216        case LT:
217          result = dvalue < cvalue;
218          thisScore[j] = dvalue - cvalue;
219          break;
220        case GT:
221          result = dvalue > cvalue;
222          thisScore[j] = dvalue - cvalue;
223          break;
224        case LTE:
225          result = dvalue <= cvalue;
226          thisScore[j] = dvalue - cvalue;
227          break;
228        case GTE:
229          result = (dvalue >= cvalue);
230          thisScore[j] = dvalue - cvalue;
231          break;
232        case MIN:
233          result = dvalue >= cvalue;
234          thisScore[j] = cvalue - dvalue;
235          break;
236        }
237
238#ifdef TEST
239        if (verbose)
240          printf("                result=%d   score=%d   bestScore=%d\n", result, thisScore[j], bestScore[j]);
241#endif
242
243        if (result) {
244          if (better || thisScore[j] > bestScore[j]) {
245            better = 1;
246          } else if (thisScore[j] == bestScore[j]) {
247            /* Keep looking. */
248          } else {
249            goto nextDM;
250          }
251        } else {
252          if (cap == NUM) {
253            worse = 1;
254          } else {
255            goto nextDM;
256          }
257        }
258
259      }
260
261      if (better && !worse) {
262        found = &dmodes[i];
263        for (j = 0; j < ncriteria; j++) {
264          bestScore[j] = thisScore[j];
265        }
266      }
267      numok++;
268
269    nextDM:;
270
271    }
272  }
273  free(bestScore);
274  free(thisScore);
275  return found;
276}
277
278/**
279 * Parses strings in the form of:
280 *  800x600
281 *  800x600:16
282 *  800x600@60
283 *  800x600:16@60
284 *  @60
285 *  :16
286 *  :16@60
287 * NOTE that @ before : is not parsed.
288 */
289static int
290specialCaseParse(char *word, Criterion * criterion, int mask)
291{
292  char *xstr, *response;
293  int got;
294  int width, height, bpp, hertz;
295
296  switch(word[0]) {
297  case '0':
298  case '1':
299  case '2':
300  case '3':
301  case '4':
302  case '5':
303  case '6':
304  case '7':
305  case '8':
306  case '9':
307    /* The WWWxHHH case. */
308    if (mask & (1 << DM_WIDTH)) {
309      return -1;
310    }
311    xstr = strpbrk(&word[1], "x");
312    if (xstr) {
313      width = (int) strtol(word, &response, 0);
314      if (response == word || response[0] != 'x') {
315        /* Not a valid number OR needs to be followed by 'x'. */
316	return -1;
317      }
318      height = (int) strtol(&xstr[1], &response, 0);
319      if (response == &xstr[1]) {
320        /* Not a valid number. */
321	return -1;
322      }
323      criterion[0].capability = DM_WIDTH;
324      criterion[0].comparison = EQ;
325      criterion[0].value = width;
326      criterion[1].capability = DM_HEIGHT;
327      criterion[1].comparison = EQ;
328      criterion[1].value = height;
329      got = specialCaseParse(response,
330        &criterion[2], 1 << DM_WIDTH);
331      if (got >= 0) {
332        return got + 2;
333      } else {
334        return -1;
335      }
336    }
337    return -1;
338  case ':':
339    /* The :BPP case. */
340    if (mask & (1 << DM_PIXEL_DEPTH)) {
341      return -1;
342    }
343    bpp = (int) strtol(&word[1], &response, 0);
344    if (response == &word[1]) {
345      /* Not a valid number. */
346      return -1;
347    }
348    criterion[0].capability = DM_PIXEL_DEPTH;
349    criterion[0].comparison = EQ;
350    criterion[0].value = bpp;
351    got = specialCaseParse(response,
352      &criterion[1], 1 << DM_WIDTH | 1 << DM_PIXEL_DEPTH);
353    if (got >= 0) {
354      return got + 1;
355    } else {
356      return -1;
357    }
358  case '@':
359    /* The @HZ case. */
360    if (mask & (1 << DM_HERTZ)) {
361      return -1;
362    }
363    hertz = (int) strtol(&word[1], &response, 0);
364    if (response == &word[1]) {
365      /* Not a valid number. */
366      return -1;
367    }
368    criterion[0].capability = DM_HERTZ;
369    criterion[0].comparison = EQ;
370    criterion[0].value = hertz;
371    got = specialCaseParse(response,
372      &criterion[1], ~DM_HERTZ);
373    if (got >= 0) {
374      return got + 1;
375    } else {
376      return -1;
377    }
378  case '\0':
379    return 0;
380  }
381  return -1;
382}
383
384/* This routine is based on similiar code in glut_dstr.c */
385static int
386parseCriteria(char *word, Criterion * criterion)
387{
388  char *cstr, *vstr, *response;
389  int comparator, value = 0;
390
391  cstr = strpbrk(word, "=><!~");
392  if (cstr) {
393    switch (cstr[0]) {
394    case '=':
395      comparator = EQ;
396      vstr = &cstr[1];
397      break;
398    case '~':
399      comparator = MIN;
400      vstr = &cstr[1];
401      break;
402    case '>':
403      if (cstr[1] == '=') {
404        comparator = GTE;
405        vstr = &cstr[2];
406      } else {
407        comparator = GT;
408        vstr = &cstr[1];
409      }
410      break;
411    case '<':
412      if (cstr[1] == '=') {
413        comparator = LTE;
414        vstr = &cstr[2];
415      } else {
416        comparator = LT;
417        vstr = &cstr[1];
418      }
419      break;
420    case '!':
421      if (cstr[1] == '=') {
422        comparator = NEQ;
423        vstr = &cstr[2];
424      } else {
425        return -1;
426      }
427      break;
428    default:
429      return -1;
430    }
431    value = (int) strtol(vstr, &response, 0);
432    if (response == vstr) {
433      /* Not a valid number. */
434      return -1;
435    }
436    *cstr = '\0';
437  } else {
438    comparator = NONE;
439  }
440  switch (word[0]) {
441  case 'b':
442    if (!strcmp(word, "bpp")) {
443      criterion[0].capability = DM_PIXEL_DEPTH;
444      if (comparator == NONE) {
445        return -1;
446      } else {
447        criterion[0].comparison = comparator;
448        criterion[0].value = value;
449        return 1;
450      }
451    }
452    return -1;
453  case 'h':
454    if (!strcmp(word, "height")) {
455      criterion[0].capability = DM_HEIGHT;
456      if (comparator == NONE) {
457        return -1;
458      } else {
459        criterion[0].comparison = comparator;
460        criterion[0].value = value;
461        return 1;
462      }
463    }
464    if (!strcmp(word, "hertz")) {
465      criterion[0].capability = DM_HERTZ;
466      if (comparator == NONE) {
467        return -1;
468      } else {
469        criterion[0].comparison = comparator;
470        criterion[0].value = value;
471        return 1;
472      }
473    }
474    return -1;
475  case 'n':
476    if (!strcmp(word, "num")) {
477      criterion[0].capability = DM_NUM;
478      if (comparator == NONE) {
479        return -1;
480      } else {
481        criterion[0].comparison = comparator;
482        criterion[0].value = value;
483        return 1;
484      }
485    }
486    return -1;
487  case 'w':
488    if (!strcmp(word, "width")) {
489      criterion[0].capability = DM_WIDTH;
490      if (comparator == NONE) {
491        return -1;
492      } else {
493        criterion[0].comparison = comparator;
494        criterion[0].value = value;
495        return 1;
496      }
497    }
498    return -1;
499  }
500  if (comparator == NONE) {
501    return specialCaseParse(word, criterion, 0);
502  }
503  return -1;
504}
505
506/* This routine is based on similiar code in glut_dstr.c */
507static Criterion *
508parseDisplayString(const char *display, int *ncriteria)
509{
510  Criterion *criteria = NULL;
511  int n, parsed;
512  char *copy, *word;
513
514  copy = __glutStrdup(display);
515  /* Attempt to estimate how many criteria entries should be
516     needed. */
517  n = 0;
518  word = strtok(copy, " \t");
519  while (word) {
520    n++;
521    word = strtok(NULL, " \t");
522  }
523  /* Allocate number of words of criteria.  A word
524     could contain as many as four criteria in the
525     worst case.  Example: 800x600:16@60 */
526  criteria = (Criterion *) malloc(4 * n * sizeof(Criterion));
527  if (!criteria) {
528    __glutFatalError("out of memory.");
529  }
530
531  /* Re-copy the copy of the display string. */
532  strcpy(copy, display);
533
534  n = 0;
535  word = strtok(copy, " \t");
536  while (word) {
537    parsed = parseCriteria(word, &criteria[n]);
538    if (parsed >= 0) {
539      n += parsed;
540    } else {
541      __glutWarning("Unrecognized game mode string word: %s (ignoring)\n", word);
542    }
543    word = strtok(NULL, " \t");
544  }
545
546  free(copy);
547  *ncriteria = n;
548  return criteria;
549}
550
551void GLUTAPIENTRY
552glutGameModeString(const char *string)
553{
554  Criterion *criteria;
555  int ncriteria;
556
557  initGameModeSupport();
558  criteria = parseDisplayString(string, &ncriteria);
559  currentDm = findMatch(dmodes, ndmodes, criteria, ncriteria);
560  free(criteria);
561}
562
563int GLUTAPIENTRY
564glutEnterGameMode(void)
565{
566  GLUTwindow *window;
567  int width, height;
568  Window win;
569
570  if (__glutMappedMenu) {
571    __glutFatalUsage("entering game mode not allowed while menus in use");
572  }
573  if (__glutGameModeWindow) {
574    /* Already in game mode, so blow away game mode
575       window so apps can change resolutions. */
576    window = __glutGameModeWindow;
577    /* Setting the game mode window to NULL tricks
578       the window destroy code into not undoing the
579       screen display change since we plan on immediately
580       doing another mode change. */
581    __glutGameModeWindow = NULL;
582    __glutDestroyWindow(window, window);
583  }
584
585  /* Assume default screen size until we find out if we
586     can actually change the display settings. */
587  width = __glutScreenWidth;
588  height = __glutScreenHeight;
589
590  if (currentDm) {
591#ifdef _WIN32
592    LONG status;
593    static int registered = 0;
594
595    status = ChangeDisplaySettings(&currentDm->devmode,
596      CDS_FULLSCREEN);
597    if (status == DISP_CHANGE_SUCCESSFUL) {
598      __glutDisplaySettingsChanged = 1;
599      width = currentDm->cap[DM_WIDTH];
600      height = currentDm->cap[DM_HEIGHT];
601      if (!registered) {
602        atexit(__glutCloseDownGameMode);
603        registered = 1;
604      }
605    } else {
606      /* Switch back to default resolution. */
607      ChangeDisplaySettings(NULL, 0);
608    }
609#endif
610  }
611
612  window = __glutCreateWindow(NULL, 0, 0,
613    width, height, /* game mode */ 1);
614  win = window->win;
615
616#if !defined(_WIN32)
617  if (__glutMotifHints == None) {
618    __glutMotifHints = XSGIFastInternAtom(__glutDisplay, "_MOTIF_WM_HINTS",
619      SGI_XA__MOTIF_WM_HINTS, 0);
620    if (__glutMotifHints == None) {
621      __glutWarning("Could not intern X atom for _MOTIF_WM_HINTS.");
622    }
623  }
624
625  /* Game mode window is a toplevel window. */
626  XSetWMProtocols(__glutDisplay, win, &__glutWMDeleteWindow, 1);
627#endif
628
629  /* Schedule the fullscreen property to be added and to
630     make sure the window is configured right.  Win32
631     doesn't need this. */
632  window->desiredX = 0;
633  window->desiredY = 0;
634  window->desiredWidth = width;
635  window->desiredHeight = height;
636  window->desiredConfMask |= CWX | CWY | CWWidth | CWHeight;
637#ifdef _WIN32
638  /* Win32 does not want to use GLUT_FULL_SCREEN_WORK
639     for game mode because we need to be maximizing
640     the window in game mode, not just sizing it to
641     take up the full screen.  The Win32-ness of game
642     mode happens when you pass 1 in the gameMode parameter
643     to __glutCreateWindow above.  A gameMode of creates
644     a WS_POPUP window, not a standard WS_OVERLAPPEDWINDOW
645     window.  WS_POPUP ensures the taskbar is hidden. */
646  __glutPutOnWorkList(window,
647    GLUT_CONFIGURE_WORK);
648#else
649  __glutPutOnWorkList(window,
650    GLUT_CONFIGURE_WORK | GLUT_FULL_SCREEN_WORK);
651#endif
652
653  __glutGameModeWindow = window;
654  return window->num + 1;
655}
656
657int GLUTAPIENTRY
658glutGameModeGet(GLenum mode)
659{
660  switch (mode) {
661  case GLUT_GAME_MODE_ACTIVE:
662    return __glutGameModeWindow != NULL;
663  case GLUT_GAME_MODE_POSSIBLE:
664    return currentDm != NULL;
665  case GLUT_GAME_MODE_WIDTH:
666    return currentDm ? currentDm->cap[DM_WIDTH] : -1;
667  case GLUT_GAME_MODE_HEIGHT:
668    return currentDm ? currentDm->cap[DM_HEIGHT] : -1;
669  case GLUT_GAME_MODE_PIXEL_DEPTH:
670    return currentDm ? currentDm->cap[DM_PIXEL_DEPTH] : -1;
671  case GLUT_GAME_MODE_REFRESH_RATE:
672    return currentDm ? currentDm->cap[DM_HERTZ] : -1;
673  case GLUT_GAME_MODE_DISPLAY_CHANGED:
674    return __glutDisplaySettingsChanged;
675  default:
676    return -1;
677  }
678}
679