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