http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / glx / starwars.c
1 /*
2  * starwars, Copyright (c) 1998-2005 Jamie Zawinski <jwz@jwz.org> and
3  * Claudio Matsuoka <claudio@helllabs.org>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  *
13  * Star Wars -- Phosphor meets a well-known scroller from a galaxy far,
14  *           far away.
15  *
16  * Feb 2000 Claudio Matsuoka    First version.
17  * Jan 2001 Jamie Zawinski      Rewrote large sections to add the ability to
18  *                              run a subprocess, customization of the font
19  *                              size and other parameters, etc.
20  * Feb 2001 jepler@inetnebr.com Added anti-aliased lines, and fade-to-black.
21  * Feb 2005 Jamie Zawinski      Added texture fonts.
22  *
23  *
24  * For the fanboys:
25  *
26  *     starwars -program 'cat starwars.txt' -columns 25 -no-wrap -texture
27  */
28
29 #include <X11/Intrinsic.h>
30
31 extern XtAppContext app;
32
33 #define PROGCLASS       "StarWars"
34 #define HACK_INIT       init_sws
35 #define HACK_DRAW       draw_sws
36 #define HACK_RESHAPE    reshape_sws
37 #define sws_opts        xlockmore_opts
38
39 #define DEF_PROGRAM    "(default)"
40 #define DEF_LINES      "125"
41 #define DEF_STEPS      "35"
42 #define DEF_SPIN       "0.03"
43 #define DEF_FONT_SIZE  "-1"
44 #define DEF_COLUMNS    "-1"
45 #define DEF_WRAP       "True"
46 #define DEF_ALIGN      "Center"
47 #define DEF_SMOOTH     "True"
48 #define DEF_THICK      "True"
49 #define DEF_FADE       "True"
50 #define DEF_TEXTURES   "True"
51 #define DEF_DEBUG      "False"
52
53 /* Utopia 480 needs a 2048x2048 texture.
54    Utopia 400 needs a 1024x1024 texture.
55    Utopia 180 needs a 512x512 texture.
56    Times 240 needs a 512x512 texture.
57  */
58 #define DEF_FONT       "-*-utopia-bold-r-normal-*-*-400-*-*-*-*-iso8859-1"
59
60 #define TAB_WIDTH        8
61
62 #define MAX_THICK_LINES   25
63 #define FONT_WEIGHT       14
64 #define KEEP_ASPECT
65
66 #define DEFAULTS "*delay:         40000 \n"                  \
67                  "*showFPS:       False \n"                  \
68                  "*fpsTop:        True \n"                   \
69                  "*program:     " DEF_PROGRAM           "\n" \
70                  "*lines:       " DEF_LINES             "\n" \
71                  "*spin:        " DEF_SPIN              "\n" \
72                  "*steps:       " DEF_STEPS             "\n" \
73                  "*smooth:      " DEF_SMOOTH            "\n" \
74                  "*thick:       " DEF_THICK             "\n" \
75                  "*fade:        " DEF_FADE              "\n" \
76                  "*textures:    " DEF_TEXTURES          "\n" \
77                  "*fontSize:    " DEF_FONT_SIZE         "\n" \
78                  "*columns:     " DEF_COLUMNS           "\n" \
79                  "*lineWrap:    " DEF_WRAP              "\n" \
80                  "*alignment:   " DEF_ALIGN             "\n" \
81                  "*font:        " DEF_FONT              "\n" \
82                  "*debug:       " DEF_DEBUG             "\n" \
83
84 #undef countof
85 #define countof(x) (sizeof((x))/sizeof((*x)))
86
87 #include "xlockmore.h"
88
89 #ifdef USE_GL /* whole file */
90
91 #include <ctype.h>
92 #include <GL/glu.h>
93 #include <sys/stat.h>
94 #include "texfont.h"
95 #include "glutstroke.h"
96 #include "glut_roman.h"
97 #define GLUT_FONT (&glutStrokeRoman)
98
99 #ifdef HAVE_UNAME
100 # include <sys/utsname.h>
101 #endif /* HAVE_UNAME */
102
103
104 typedef struct {
105   GLXContext *glx_context;
106
107   GLuint text_list, star_list;
108   texture_font_data *texfont;
109   int polygon_count;
110
111   FILE *pipe;
112   XtInputId pipe_id;
113   Time subproc_relaunch_delay;
114
115   char buf [1024];
116   int buf_tail;
117   char **lines;
118   int total_lines;
119
120   double star_theta;
121   double char_width;
122   double line_height;
123   double font_scale;
124   double intra_line_scroll;
125
126   int line_pixel_width;   /* in font units (for wrapping text) */
127   int line_pixel_height;  /* in screen units (for computing line thickness) */
128   GLfloat line_thickness;
129
130 } sws_configuration;
131
132
133 static sws_configuration *scs = NULL;
134
135 static char *program;
136 static int max_lines;
137 static int scroll_steps;
138 static float star_spin;
139 static float font_size;
140 static int target_columns;
141 static int wrap_p;
142 static int smooth_p;
143 static int thick_p;
144 static int fade_p;
145 static int textures_p;
146 static int debug_p;
147 static char *alignment_str;
148 static int alignment;
149
150 static XrmOptionDescRec opts[] = {
151   {"-program",     ".program",   XrmoptionSepArg, 0 },
152   {"-lines",       ".lines",     XrmoptionSepArg, 0 },
153   {"-steps",       ".steps",     XrmoptionSepArg, 0 },
154   {"-spin",        ".spin",      XrmoptionSepArg, 0 },
155   {"-size",        ".fontSize",  XrmoptionSepArg, 0 },
156   {"-columns",     ".columns",   XrmoptionSepArg, 0 },
157   {"-font",        ".font",      XrmoptionSepArg, 0 },
158   {"-fade",        ".fade",      XrmoptionNoArg,  "True"   },
159   {"-no-fade",     ".fade",      XrmoptionNoArg,  "False"  },
160   {"-textures",    ".textures",  XrmoptionNoArg,  "True"   },
161   {"-smooth",      ".smooth",    XrmoptionNoArg,  "True"   },
162   {"-no-smooth",   ".smooth",    XrmoptionNoArg,  "False"  },
163   {"-thick",       ".thick",     XrmoptionNoArg,  "True"   },
164   {"-no-thick",    ".thick",     XrmoptionNoArg,  "False"  },
165   {"-no-textures", ".textures",  XrmoptionNoArg,  "False"  },
166   {"-wrap",        ".lineWrap",  XrmoptionNoArg,  "True"   },
167   {"-no-wrap",     ".lineWrap",  XrmoptionNoArg,  "False"  },
168   {"-nowrap",      ".lineWrap",  XrmoptionNoArg,  "False"  },
169   {"-left",        ".alignment", XrmoptionNoArg,  "Left"   },
170   {"-right",       ".alignment", XrmoptionNoArg,  "Right"  },
171   {"-center",      ".alignment", XrmoptionNoArg,  "Center" },
172   {"-debug",       ".debug",     XrmoptionNoArg,  "True"   },
173 };
174
175 static argtype vars[] = {
176   {&program,        "program",   "Program",    DEF_PROGRAM,   t_String},
177   {&max_lines,      "lines",     "Integer",    DEF_LINES,     t_Int},
178   {&scroll_steps,   "steps",     "Integer",    DEF_STEPS,     t_Int},
179   {&star_spin,      "spin",      "Float",      DEF_SPIN,      t_Float},
180   {&font_size,      "fontSize",  "Float",      DEF_FONT_SIZE, t_Float},
181   {&target_columns, "columns",   "Integer",    DEF_COLUMNS,   t_Int},
182   {&wrap_p,         "lineWrap",  "Boolean",    DEF_WRAP,      t_Bool},
183   {&alignment_str,  "alignment", "Alignment",  DEF_ALIGN,     t_String},
184   {&smooth_p,       "smooth",    "Boolean",    DEF_SMOOTH,    t_Bool},
185   {&thick_p,        "thick",     "Boolean",    DEF_THICK,     t_Bool},
186   {&fade_p,         "fade",      "Boolean",    DEF_FADE,      t_Bool},
187   {&textures_p,     "textures",  "Boolean",    DEF_TEXTURES,  t_Bool},
188   {&debug_p,        "debug",     "Boolean",    DEF_DEBUG,     t_Bool},
189 };
190
191 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
192
193
194
195 /* Tabs are bad, mmmkay? */
196
197 static char *
198 untabify (const char *string)
199 {
200   const char *ostring = string;
201   char *result = (char *) malloc ((strlen(string) * 8) + 1);
202   char *out = result;
203   int col = 0;
204   while (*string)
205     {
206       if (*string == '\t')
207         {
208           do {
209             col++;
210             *out++ = ' ';
211           } while (col % TAB_WIDTH);
212           string++;
213         }
214       else if (*string == '\r' || *string == '\n')
215         {
216           *out++ = *string++;
217           col = 0;
218         }
219       else if (*string == '\010')    /* backspace */
220         {
221           if (string > ostring)
222             out--, string++;
223         }
224       else
225         {
226           *out++ = *string++;
227           col++;
228         }
229     }
230   *out = 0;
231
232   return result;
233 }
234
235 static void
236 strip (char *s, Bool leading, Bool trailing)
237 {
238   int L = strlen(s);
239   if (trailing)
240     while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
241       s[L--] = 0;
242   if (leading)
243     {
244       char *s2 = s;
245       while (*s2 == ' ' || *s2 == '\t')
246         s2++;
247       if (s == s2)
248         return;
249       while (*s2)
250         *s++ = *s2++;
251       *s = 0;
252     }
253 }
254
255
256 /* The GLUT font only has ASCII characters in them, so do what we can to
257    convert Latin1 characters to the nearest ASCII equivalent... 
258  */
259 static void
260 latin1_to_ascii (char *s)
261 {
262   unsigned char *us = (unsigned char *) s;
263   const unsigned char ascii[95] = {
264     '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
265     '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
266     '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
267     'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
268     'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
269     'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
270     'u', 'u', 'y', 'p', 'y' };
271   while (*us)
272     {
273       if (*us >= 161)
274         *us = ascii[*us - 161];
275       else if (*us > 127)
276         *us = '?';
277       us++;
278     }
279 }
280
281 \f
282 /* Subprocess.
283    (This bit mostly cribbed from phosphor.c)
284  */
285
286 static void drain_input (sws_configuration *sc);
287
288 static void
289 subproc_cb (XtPointer closure, int *source, XtInputId *id)
290 {
291   sws_configuration *sc = (sws_configuration *) closure;
292   drain_input (sc);
293 }
294
295
296 static void
297 launch_text_generator (sws_configuration *sc)
298 {
299   char *oprogram = get_string_resource ("program", "Program");
300   char *program;
301
302   if (!strcasecmp(oprogram, "(default)"))
303     {
304       oprogram = FORTUNE_PROGRAM;
305
306 #if defined(__linux__) && defined(HAVE_UNAME)
307       {
308         static int done_once = 0;
309         if (!done_once)
310           {
311             struct utsname uts;
312             struct stat st;
313             done_once = 1;
314             if (uname (&uts) == 0)
315               {
316                 static char cmd[200];
317                 char *s;
318                 /* strip version at the first non-digit-dash-dot, to
319                    lose any "SMP" crap at the end. */
320                 for (s = uts.release; *s; s++)
321                   if (!isdigit(*s) && *s != '.' && *s != '-')
322                     *s = 0;
323                 sprintf (cmd, "cat /usr/src/linux-%s/README", uts.release);
324                 if (!stat (cmd+4, &st))
325                   oprogram = cmd;
326                 else
327                   {
328                     /* kernel source not installed?  try X... */
329                     strcpy (cmd, "cat /usr/X11R6/lib/X11/doc/README");
330                     if (!stat (cmd+4, &st))
331                       oprogram = cmd;
332                   }
333               }
334           }
335       }
336 #endif /* __linux__ && HAVE_UNAME */
337
338 #ifdef __APPLE__   /* MacOS X + XDarwin */
339       {
340         static int done_once = 0;
341         if (!done_once)
342           {
343             struct stat st;
344             static char *cmd = "cat /usr/X11R6/README";
345             if (!stat (cmd+4, &st))
346               oprogram = cmd;
347           }
348       }
349 #endif /* __APPLE__ */
350     }
351
352   program = (char *) malloc (strlen (oprogram) + 10);
353   strcpy (program, "( ");
354   strcat (program, oprogram);
355   strcat (program, " ) 2>&1");
356
357   if ((sc->pipe = popen (program, "r")))
358     {
359       sc->pipe_id =
360         XtAppAddInput (app, fileno (sc->pipe),
361                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
362                        subproc_cb, (XtPointer) sc);
363     }
364   else
365     {
366       perror (program);
367     }
368 }
369
370
371 static void
372 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
373 {
374   sws_configuration *sc = (sws_configuration *) closure;
375   launch_text_generator (sc);
376 }
377
378
379 /* When the subprocess has generated some output, this reads as much as it
380    can into sc->buf at sc->buf_tail.
381  */
382 static void
383 drain_input (sws_configuration *sc)
384 {
385   if (sc->buf_tail < sizeof(sc->buf) - 2)
386     {
387       int target = sizeof(sc->buf) - sc->buf_tail - 2;
388       int n = read (fileno (sc->pipe),
389                     (void *) (sc->buf + sc->buf_tail),
390                     target);
391       if (n > 0)
392         {
393           sc->buf_tail += n;
394           sc->buf[sc->buf_tail] = 0;
395         }
396       else
397         {
398           XtRemoveInput (sc->pipe_id);
399           sc->pipe_id = 0;
400           pclose (sc->pipe);
401           sc->pipe = 0;
402
403           /* If the process didn't print a terminating newline, add one. */
404           if (sc->buf_tail > 1 &&
405               sc->buf[sc->buf_tail-1] != '\n')
406             {
407               sc->buf[sc->buf_tail++] = '\n';
408               sc->buf[sc->buf_tail] = 0;
409             }
410
411           /* Then add one more, just for giggles. */
412           sc->buf[sc->buf_tail++] = '\n';
413           sc->buf[sc->buf_tail] = 0;
414
415           /* Set up a timer to re-launch the subproc in a bit. */
416           XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
417                            relaunch_generator_timer,
418                            (XtPointer) sc);
419         }
420     }
421 }
422
423
424 static int
425 string_width (sws_configuration *sc, const char *s)
426 {
427   if (textures_p)
428     return texture_string_width (sc->texfont, s, 0);
429   else
430     return glutStrokeLength (GLUT_FONT, (unsigned char *) s);
431 }
432
433 static int
434 char_width (sws_configuration *sc, char c)
435 {
436   char s[2];
437   s[0] = c;
438   s[1] = 0;
439   return string_width (sc, s);
440 }
441
442
443 /* Populates the sc->lines list with as many lines as are currently in
444    sc->buf (which was filled by drain_input().
445  */
446 static void
447 get_more_lines (sws_configuration *sc)
448 {
449   /* wrap anyway, if it's absurdly long. */
450   int wrap_pix = (wrap_p ? sc->line_pixel_width : 10000);
451   
452   int col = 0;
453   int col_pix = 0;
454
455   char *s = sc->buf;
456   while (sc->total_lines < max_lines)
457     {
458       int cw;
459
460       if (s >= sc->buf + sc->buf_tail)
461         {
462           /* Reached end of buffer before end of line.  Bail. */
463           return;
464         }
465
466       cw = char_width (sc, *s);
467
468       if (*s == '\r' || *s == '\n' ||
469           col_pix + cw >= wrap_pix)
470         {
471           int L = s - sc->buf;
472
473           if (*s == '\r' || *s == '\n')
474             {
475               if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
476                 *s++ = 0;
477
478               *s++ = 0;
479             }
480           else
481             {
482               /* We wrapped -- try to back up to the previous word boundary. */
483               char *s2 = s;
484               int n = 0;
485               while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
486                 s2--, n++;
487               if (s2 > sc->buf)
488                 {
489                   s = s2;
490                   *s++ = 0;
491                   L = s - sc->buf;
492                 }
493             }
494
495           sc->lines[sc->total_lines] = (char *) malloc (L+1);
496           memcpy (sc->lines[sc->total_lines], sc->buf, L);
497           sc->lines[sc->total_lines][L] = 0;
498
499           if (!textures_p)
500             latin1_to_ascii (sc->lines[sc->total_lines]);
501
502           {
503             char *t = sc->lines[sc->total_lines];
504             char *ut = untabify (t);
505             strip (ut, (alignment == 0), 1); /* if centering, strip
506                                                 leading whitespace too */
507             sc->lines[sc->total_lines] = ut;
508             free (t);
509           }
510
511           sc->total_lines++;
512
513           if (sc->buf_tail > (s - sc->buf))
514             {
515               int i = sc->buf_tail - (s - sc->buf);
516               memmove (sc->buf, s, i);
517               sc->buf_tail = i;
518               sc->buf[sc->buf_tail] = 0;
519             }
520           else
521             {
522               sc->buf_tail = 0;
523             }
524
525           sc->buf[sc->buf_tail] = 0;
526           s = sc->buf;
527           col = 0;
528           col_pix = 0;
529         }
530       else
531         {
532           col++;
533           col_pix += cw;
534           if (*s == '\t')
535             {
536               int tab_pix = TAB_WIDTH * sc->char_width;
537               col     = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
538               col_pix = tab_pix   * ((col / tab_pix)   + 1);
539             }
540           s++;
541         }
542     }
543 }
544
545
546 static void
547 draw_string (sws_configuration *sc, GLfloat x, GLfloat y, const char *s)
548 {
549   const char *os = s;
550   if (!s || !*s) return;
551   glPushMatrix ();
552   glTranslatef (x, y, 0);
553
554   if (textures_p)
555     print_texture_string (sc->texfont, s);
556   else
557     while (*s)
558       glutStrokeCharacter (GLUT_FONT, *s++);
559   glPopMatrix ();
560
561   if (debug_p)
562     {
563       GLfloat w;
564       GLfloat h = sc->line_height / sc->font_scale;
565       char c[2];
566       c[1]=0;
567       s = os;
568       if (textures_p) glDisable (GL_TEXTURE_2D);
569       glLineWidth (1);
570       glColor3f (0.4, 0.4, 0.4);
571       glPushMatrix ();
572       glTranslatef (x, y, 0);
573       while (*s)
574         {
575           *c = *s++;
576           w = string_width (sc, c);
577           glBegin (GL_LINE_LOOP);
578           glVertex3f (0, 0, 0);
579           glVertex3f (w, 0, 0);
580           glVertex3f (w, h, 0);
581           glVertex3f (0, h, 0);
582           glEnd();
583           glTranslatef (w, 0, 0);
584         }
585       glPopMatrix ();
586       if (textures_p) glEnable (GL_TEXTURE_2D);
587     }
588 }
589
590
591 static void
592 grid (double width, double height, double xspacing, double yspacing, double z)
593 {
594   double x, y;
595   for (y = 0; y <= height/2; y += yspacing)
596     {
597       glBegin(GL_LINES);
598       glVertex3f(-width/2,  y, z);
599       glVertex3f( width/2,  y, z);
600       glVertex3f(-width/2, -y, z);
601       glVertex3f( width/2, -y, z);
602       glEnd();
603     }
604   for (x = 0; x <= width/2; x += xspacing)
605     {
606       glBegin(GL_LINES);
607       glVertex3f( x, -height/2, z);
608       glVertex3f( x,  height/2, z);
609       glVertex3f(-x, -height/2, z);
610       glVertex3f(-x,  height/2, z);
611       glEnd();
612     }
613
614   glBegin(GL_LINES);
615   glVertex3f(-width, 0, z);
616   glVertex3f( width, 0, z);
617   glVertex3f(0, -height, z);
618   glVertex3f(0,  height, z);
619   glEnd();
620 }
621
622 static void
623 box (double width, double height, double depth)
624 {
625   glBegin(GL_LINE_LOOP);
626   glVertex3f(-width/2,  -height/2, -depth/2);
627   glVertex3f(-width/2,   height/2, -depth/2);
628   glVertex3f( width/2,   height/2, -depth/2);
629   glVertex3f( width/2,  -height/2, -depth/2);
630   glEnd();
631   glBegin(GL_LINE_LOOP);
632   glVertex3f(-width/2,  -height/2,  depth/2);
633   glVertex3f(-width/2,   height/2,  depth/2);
634   glVertex3f( width/2,   height/2,  depth/2);
635   glVertex3f( width/2,  -height/2,  depth/2);
636   glEnd();
637   glBegin(GL_LINE_LOOP);
638   glVertex3f(-width/2,  -height/2, -depth/2);
639   glVertex3f(-width/2,  -height/2,  depth/2);
640   glVertex3f(-width/2,   height/2,  depth/2);
641   glVertex3f(-width/2,   height/2, -depth/2);
642   glEnd();
643   glBegin(GL_LINE_LOOP);
644   glVertex3f( width/2,  -height/2, -depth/2);
645   glVertex3f( width/2,  -height/2,  depth/2);
646   glVertex3f( width/2,   height/2,  depth/2);
647   glVertex3f( width/2,   height/2, -depth/2);
648   glEnd();
649
650   glEnd();
651   glBegin(GL_LINES);
652   glVertex3f(-width/2,   height/2,  depth/2);
653   glVertex3f(-width/2,  -height/2, -depth/2);
654
655   glVertex3f( width/2,   height/2,  depth/2);
656   glVertex3f( width/2,  -height/2, -depth/2);
657
658   glVertex3f(-width/2,  -height/2,  depth/2);
659   glVertex3f(-width/2,   height/2, -depth/2);
660
661   glVertex3f( width/2,  -height/2,  depth/2);
662   glVertex3f( width/2,   height/2, -depth/2);
663   glEnd();
664 }
665
666
667 /* Construct stars (number of stars is dependent on size of screen) */
668 static void
669 init_stars (ModeInfo *mi, int width, int height)
670 {
671   sws_configuration *sc = &scs[MI_SCREEN(mi)];
672   int i, j;
673   int size = (width > height ? width : height);
674   int nstars = size * size / 320;
675   int max_size = 3;
676   GLfloat inc = 0.5;
677   int steps = max_size / inc;
678
679   glDeleteLists (sc->star_list, 1);
680   sc->star_list = glGenLists (1);
681   glNewList (sc->star_list, GL_COMPILE);
682
683   glEnable(GL_POINT_SMOOTH);
684
685   for (j = 1; j <= steps; j++)
686     {
687       glPointSize(inc * j);
688       glBegin (GL_POINTS);
689       for (i = 0; i < nstars / steps; i++)
690         {
691           glColor3f (0.6 + frand(0.3),
692                      0.6 + frand(0.3),
693                      0.6 + frand(0.3));
694           glVertex2f (2 * size * (0.5 - frand(1.0)),
695                       2 * size * (0.5 - frand(1.0)));
696         }
697       glEnd ();
698     }
699   glEndList ();
700 }
701
702
703 /* Window management, etc
704  */
705 void
706 reshape_sws (ModeInfo *mi, int width, int height)
707 {
708   sws_configuration *sc = &scs[MI_SCREEN(mi)];
709
710   /* Set up matrices for perspective text display
711    */
712   {
713     GLfloat desired_aspect = (GLfloat) 3/4;
714     int w = mi->xgwa.width;
715     int h = mi->xgwa.height;
716     int yoff = 0;
717
718 #ifdef KEEP_ASPECT
719     {
720       int h2 = w * desired_aspect;
721       yoff = (h - h2) / 2;      /* Wide window: letterbox at top and bottom. */
722       if (yoff < 0) yoff = 0;   /* Tall window: clip off the top. */
723       h = h2;
724     }
725 #endif
726
727     glMatrixMode (GL_PROJECTION);
728     glViewport (0, yoff, w, h);
729
730     glMatrixMode (GL_MODELVIEW);
731     glLoadIdentity ();
732     gluPerspective (80.0, 1/desired_aspect, 1000, 55000);
733     gluLookAt (0.0, 0.0, 4600.0,
734                0.0, 0.0, 0.0,
735                0.0, 1.0, 0.0);
736     glRotatef (-60.0, 1.0, 0.0, 0.0);
737
738 #if 0
739     glRotatef (60.0, 1.0, 0.0, 0.0);
740     glTranslatef (260, 3200, 0);
741     glScalef (1.85, 1.85, 1);
742 #endif
743
744     /* The above gives us an arena where the bottom edge of the screen is
745        represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
746
747     /* Now let's move the origin to the front of the screen. */
748     glTranslatef (0.0, -3140, 0.0);
749
750     /* And then let's scale so that the bottom of the screen is 1.0 wide. */
751     glScalef (4200, 4200, 4200);
752   }
753
754
755   /* Compute the height in pixels of the line at the bottom of the screen. */
756   {
757     GLdouble mm[17], pm[17];
758     GLint vp[5];
759     GLfloat x = 0.5, y1 = 0, z = 0;
760     GLfloat y2 = sc->line_height;
761     GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
762
763     glGetDoublev (GL_MODELVIEW_MATRIX, mm);
764     glGetDoublev (GL_PROJECTION_MATRIX, pm);
765     glGetIntegerv (GL_VIEWPORT, vp);
766     gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
767     gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
768     sc->line_pixel_height = (wy2 - wy1);
769     glLineWidth (1);
770   }
771
772   /* Compute the best looking line thickness for the bottom line.
773    */
774   if (!thick_p)
775     sc->line_thickness = 1.0;
776   else
777     sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
778
779   if (sc->line_thickness < 1.2)
780     sc->line_thickness = 1.0;
781 }
782
783
784 static void
785 gl_init (ModeInfo *mi)
786 {
787   sws_configuration *sc = &scs[MI_SCREEN(mi)];
788
789   program = get_string_resource ("program", "Program");
790
791   glDisable (GL_LIGHTING);
792   glDisable (GL_DEPTH_TEST);
793
794   if (smooth_p)
795     {
796       glEnable (GL_LINE_SMOOTH);
797       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
798       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
799       glEnable (GL_BLEND);
800     }
801
802   sc->text_list = glGenLists (1);
803   glNewList (sc->text_list, GL_COMPILE);
804   glEndList ();
805
806   sc->star_list = glGenLists (1);
807   glNewList (sc->star_list, GL_COMPILE);
808   glEndList ();
809
810   sc->line_thickness = 1.0;
811 }
812
813
814 void 
815 init_sws (ModeInfo *mi)
816 {
817   double font_height;
818
819   sws_configuration *sc;
820
821   if (!scs) {
822     scs = (sws_configuration *)
823       calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
824     if (!scs) {
825       fprintf(stderr, "%s: out of memory\n", progname);
826       exit(1);
827     }
828
829     sc = &scs[MI_SCREEN(mi)];
830     sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
831   }
832
833   sc = &scs[MI_SCREEN(mi)];
834
835   if ((sc->glx_context = init_GL(mi)) != NULL) {
836     gl_init(mi);
837     reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
838     init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
839   }
840
841   if (textures_p)
842     {
843       int cw, lh;
844       sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
845       cw = texture_string_width (sc->texfont, "n", &lh);
846       sc->char_width = cw;
847       font_height = lh;
848       glEnable(GL_ALPHA_TEST);
849       glEnable (GL_TEXTURE_2D);
850     }
851   else
852     {
853       font_height = GLUT_FONT->top - GLUT_FONT->bottom;
854       sc->char_width = glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems wide */
855     }
856   
857   sc->font_scale = 1.0 / sc->char_width;
858
859
860   /* We consider a font that consumes 80 columns to be "18 points".
861
862      If neither -size nor -columns was specified, default to 60 columns
863      (which is 24 points.)
864
865      If both were specified, -columns has priority.
866    */
867   {
868     int base_col  = 80;
869     int base_size = 18;
870
871     if (target_columns <= 0 && font_size <= 0)
872       target_columns = 60;
873
874     if (target_columns > 0)
875       font_size = base_size * (base_col / (double) target_columns);
876     else if (font_size > 0)
877       target_columns = base_col * (base_size / (double) font_size);
878   }
879
880   sc->line_pixel_width = target_columns * sc->char_width;
881
882   sc->font_scale /= target_columns;
883   sc->line_height = font_height * sc->font_scale;
884
885
886   sc->subproc_relaunch_delay = 2 * 1000;
887   sc->total_lines = max_lines-1;
888
889   if (random() & 1)
890     star_spin = -star_spin;
891
892   if (!alignment_str || !*alignment_str ||
893       !strcasecmp(alignment_str, "left"))
894     alignment = -1;
895   else if (!strcasecmp(alignment_str, "center") ||
896            !strcasecmp(alignment_str, "middle"))
897     alignment = 0;
898   else if (!strcasecmp(alignment_str, "right"))
899     alignment = 1;
900   else
901     {
902       fprintf (stderr,
903                "%s: alignment must be left, center, or right, not \"%s\"\n",
904                progname, alignment_str);
905       exit (1);
906     }
907
908   launch_text_generator (sc);
909
910   /* one more reshape, after line_height has been computed */
911   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
912 }
913
914
915 static void
916 draw_stars (ModeInfo *mi)
917 {
918   sws_configuration *sc = &scs[MI_SCREEN(mi)];
919
920   glMatrixMode (GL_PROJECTION);
921   glPushMatrix ();
922   {
923     glLoadIdentity ();
924
925     glMatrixMode (GL_MODELVIEW);
926     glPushMatrix ();
927     {
928       glLoadIdentity ();
929       glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
930                -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
931                -100.0, 100.0);
932       glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
933       if (textures_p) glDisable (GL_TEXTURE_2D);
934       glCallList (sc->star_list);
935       if (textures_p) glEnable (GL_TEXTURE_2D);
936     }
937     glPopMatrix ();
938   }
939   glMatrixMode (GL_PROJECTION);
940   glPopMatrix ();
941 }
942
943 void
944 draw_sws (ModeInfo *mi)
945 {
946   sws_configuration *sc = &scs[MI_SCREEN(mi)];
947   Display *dpy = MI_DISPLAY(mi);
948   Window window = MI_WINDOW(mi);
949   int i;
950
951   if (!sc->glx_context)
952     return;
953
954   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
955     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
956
957   glDrawBuffer (GL_BACK);
958   glXMakeCurrent (dpy, window, *(sc->glx_context));
959
960   glClear (GL_COLOR_BUFFER_BIT);
961
962   draw_stars (mi);
963
964   glMatrixMode (GL_MODELVIEW);
965   glPushMatrix ();
966
967   if (debug_p)
968     {
969       int i;
970       glPushMatrix ();
971       if (textures_p) glDisable (GL_TEXTURE_2D);
972       glLineWidth (1);
973       glColor3f (0.4, 0.4, 0.4);
974       glTranslatef (0,-1, 0);
975       for (i = 0; i < 16; i++)
976         {
977           box (1, 1, 1);
978           grid (1, 1, sc->char_width * sc->font_scale, sc->line_height, 0);
979           glTranslatef(0, 1, 0);
980         }
981       if (textures_p) glEnable (GL_TEXTURE_2D);
982       glPopMatrix ();
983     }
984
985   /* Scroll to current position */
986   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
987
988   glColor3f (1.0, 1.0, 0.4);
989   glCallList (sc->text_list);
990   mi->polygon_count = sc->polygon_count;
991
992   sc->intra_line_scroll += sc->line_height / scroll_steps;
993
994   if (sc->intra_line_scroll >= sc->line_height)
995     {
996       sc->intra_line_scroll = 0;
997
998       /* Drop the oldest line off the end. */
999       if (sc->lines[0])
1000         free (sc->lines[0]);
1001
1002       /* Scroll the contents of the lines array toward 0. */
1003       if (sc->total_lines > 0)
1004         {
1005           for (i = 1; i < sc->total_lines; i++)
1006             sc->lines[i-1] = sc->lines[i];
1007           sc->lines[--sc->total_lines] = 0;
1008         }
1009
1010       /* Bring in new lines at the end. */
1011       get_more_lines (sc);
1012
1013       if (sc->total_lines < max_lines)
1014         /* Oops, we ran out of text... well, insert some blank lines
1015            here so that new text still pulls in from the bottom of
1016            the screen, isntead of just appearing. */
1017         sc->total_lines = max_lines;
1018
1019       glDeleteLists (sc->text_list, 1);
1020       sc->text_list = glGenLists (1);
1021       glNewList (sc->text_list, GL_COMPILE);
1022       sc->polygon_count = 0;
1023       glPushMatrix ();
1024       glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
1025       for (i = 0; i < sc->total_lines; i++)
1026         {
1027           double fade = (fade_p ? 1.0 * i / sc->total_lines : 1.0);
1028           int offscreen_lines = 3;
1029
1030           double x = -0.5;
1031           double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
1032                        * sc->line_height);
1033           double xoff = 0;
1034           char *line = sc->lines[i];
1035
1036           if (debug_p)
1037             {
1038               double xx = x * 1.4;  /* a little more to the left */
1039               char n[20];
1040               sprintf(n, "%d:", i);
1041               draw_string (sc, xx / sc->font_scale, y / sc->font_scale, n);
1042             }
1043
1044           if (!line || !*line)
1045             continue;
1046
1047           if (sc->line_thickness != 1 && !textures_p)
1048             {
1049               int max_thick_lines = MAX_THICK_LINES;
1050               GLfloat thinnest_line = 1.0;
1051               GLfloat thickest_line = sc->line_thickness;
1052               GLfloat range = thickest_line - thinnest_line;
1053               GLfloat thickness;
1054
1055               int j = sc->total_lines - i - 1;
1056
1057               if (j > max_thick_lines)
1058                 thickness = thinnest_line;
1059               else
1060                 thickness = (thinnest_line +
1061                              (range * ((max_thick_lines - j) /
1062                                        (GLfloat) max_thick_lines)));
1063
1064               glLineWidth (thickness);
1065             }
1066
1067           if (alignment >= 0)
1068             {
1069               int n = string_width (sc, line);
1070               xoff = 1.0 - (n * sc->font_scale);
1071             }
1072
1073           if (alignment == 0)
1074             xoff /= 2;
1075
1076           glColor3f (fade, fade, 0.5 * fade);
1077           draw_string (sc, (x + xoff) / sc->font_scale, y / sc->font_scale,
1078                        line);
1079           if (textures_p)
1080             sc->polygon_count += strlen (line);
1081         }
1082       glPopMatrix ();
1083       glEndList ();
1084     }
1085
1086   glPopMatrix ();
1087
1088   if (mi->fps_p) do_fps (mi);
1089   glFinish();
1090   glXSwapBuffers(dpy, window);
1091
1092   sc->star_theta += star_spin;
1093 }
1094
1095 #endif /* USE_GL */