1f27a7ebf26e1e67d635dd573b616974a3ede196
[xscreensaver] / hacks / glx / starwars.c
1 /*
2  * starwars, Copyright (c) 1998-2001 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. Hacked by Claudio Matsuoka. Includes portions of
15  *           mjk's GLUT library, Copyright (c) 1994, 1995, 1996 by Mark J.
16  *           Kilgard. Roman simplex stroke font Copyright (c) 1989, 1990,
17  *           1991 by Sun Microsystems, Inc. and the X Consortium.
18  *
19  *      Notes:
20  *         - I tried texturized fonts but the roman simplex stroke font
21  *           was the most readable for the 80-column text from fortune.
22  *         - The proportional font is bad for text from ps(1) or w(1).
23  *         - Apparently the RIVA TNT cards for PCs don't like the stars to
24  *           be drawn in orthogonal perspective, causing unnecessary system
25  *           load.
26  *
27  *      History:
28  *           20000221 claudio   First version
29  *           20010124 jwz       Rewrote large sections to add the ability to
30  *                              run a subprocess, customization of the font
31  *                              size and other parameters, etc.
32  *           20010224 jepler@mail.inetnebr.com  made the lines be anti-aliased,
33  *                              made the text fade to black at the end.
34  */
35
36 #include <X11/Intrinsic.h>
37
38 extern XtAppContext app;
39
40 #define PROGCLASS       "StarWars"
41 #define HACK_INIT       init_sws
42 #define HACK_DRAW       draw_sws
43 #define HACK_RESHAPE    reshape_sws
44 #define sws_opts        xlockmore_opts
45
46 #define DEF_PROGRAM    "(default)"
47 #define DEF_LINES      "125"
48 #define DEF_STEPS      "35"
49 #define DEF_SPIN       "0.03"
50 #define DEF_FONT_SIZE  "-1"
51 #define DEF_COLUMNS    "-1"
52 #define DEF_WRAP       "True"
53 #define DEF_ALIGN      "Center"
54 #define DEF_SMOOTH     "True"
55 #define DEF_THICK      "True"
56 #define DEF_FADE       "True"
57
58 #define TAB_WIDTH        8
59
60 #define BASE_FONT_SIZE    18 /* magic */
61 #define BASE_FONT_COLUMNS 80 /* magic */
62
63 #define MAX_THICK_LINES   25
64 #define FONT_WEIGHT       14
65 #define KEEP_ASPECT
66 #undef DEBUG
67
68 #define DEFAULTS        "*delay:        40000 \n"                    \
69                         "*showFPS:      False \n"                    \
70                         "*fpsTop:       True \n"                     \
71                         "*program:      " DEF_PROGRAM           "\n" \
72                         "*lines:        " DEF_LINES             "\n" \
73                         "*spin:         " DEF_SPIN              "\n" \
74                         "*steps:        " DEF_STEPS             "\n" \
75                         "*smooth:       " DEF_SMOOTH            "\n" \
76                         "*thick:        " DEF_THICK             "\n" \
77                         "*fade:         " DEF_FADE              "\n" \
78                         "*starwars.fontSize: " DEF_FONT_SIZE    "\n" \
79                         "*starwars.columns:  " DEF_COLUMNS      "\n" \
80                         "*starwars.lineWrap: " DEF_WRAP         "\n" \
81                         "*starwars.alignment:" DEF_ALIGN        "\n"
82
83 #undef countof
84 #define countof(x) (sizeof((x))/sizeof((*x)))
85
86 #include "xlockmore.h"
87
88 #ifdef USE_GL /* whole file */
89
90 #include <ctype.h>
91 #include <GL/glu.h>
92 #include <sys/stat.h>
93 #include "glutstroke.h"
94 #include "glut_roman.h"
95 #define GLUT_FONT (&glutStrokeRoman)
96
97 #ifdef HAVE_UNAME
98 # include <sys/utsname.h>
99 #endif /* HAVE_UNAME */
100
101
102 typedef struct {
103   GLXContext *glx_context;
104
105   GLuint text_list, star_list;
106
107   FILE *pipe;
108   XtInputId pipe_id;
109   Time subproc_relaunch_delay;
110
111   char buf [1024];
112   int buf_tail;
113   char **lines;
114   int total_lines;
115   int columns;
116
117   double star_theta;
118   double line_height;
119   double font_scale;
120   double intra_line_scroll;
121
122   int line_pixel_height;
123   GLfloat line_thickness;
124
125 } sws_configuration;
126
127
128 static sws_configuration *scs = NULL;
129
130 static char *program;
131 static int max_lines;
132 static int scroll_steps;
133 static float star_spin;
134 static float font_size;
135 static int target_columns;
136 static int wrap_p;
137 static int smooth_p;
138 static int thick_p;
139 static int fade_p;
140 static char *alignment_str;
141 static int alignment;
142
143 static XrmOptionDescRec opts[] = {
144   {"-program",   ".starwars.program",  XrmoptionSepArg, (caddr_t) 0 },
145   {"-lines",     ".starwars.lines",    XrmoptionSepArg, (caddr_t) 0 },
146   {"-steps",     ".starwars.steps",    XrmoptionSepArg, (caddr_t) 0 },
147   {"-spin",      ".starwars.spin",     XrmoptionSepArg, (caddr_t) 0 },
148   {"-size",      ".starwars.fontSize", XrmoptionSepArg, (caddr_t) 0 },
149   {"-columns",   ".starwars.columns",  XrmoptionSepArg, (caddr_t) 0 },
150   {"-smooth",    ".starwars.smooth",   XrmoptionNoArg,  (caddr_t) "True" },
151   {"-no-smooth", ".starwars.smooth",   XrmoptionNoArg,  (caddr_t) "False" },
152   {"-thick",     ".starwars.thick",    XrmoptionNoArg,  (caddr_t) "True" },
153   {"-no-thick",  ".starwars.thick",    XrmoptionNoArg,  (caddr_t) "False" },
154   {"-fade",      ".starwars.fade",     XrmoptionNoArg,  (caddr_t) "True" },
155   {"-no-fade",   ".starwars.fade",     XrmoptionNoArg,  (caddr_t) "False" },
156   {"-wrap",      ".starwars.lineWrap", XrmoptionNoArg,  (caddr_t) "True" },
157   {"-no-wrap",   ".starwars.lineWrap", XrmoptionNoArg,  (caddr_t) "False" },
158   {"-nowrap",    ".starwars.lineWrap", XrmoptionNoArg,  (caddr_t) "False" },
159   {"-left",      ".starwars.alignment",XrmoptionNoArg,  (caddr_t) "Left" },
160   {"-right",     ".starwars.alignment",XrmoptionNoArg,  (caddr_t) "Right" },
161   {"-center",    ".starwars.alignment",XrmoptionNoArg,  (caddr_t) "Center" },
162 };
163
164 static argtype vars[] = {
165   {(caddr_t *) &program,        "program", "Program", DEF_PROGRAM, t_String},
166   {(caddr_t *) &max_lines,      "lines",   "Integer", DEF_LINES,   t_Int},
167   {(caddr_t *) &scroll_steps,   "steps",   "Integer", DEF_STEPS,   t_Int},
168   {(caddr_t *) &star_spin,      "spin",    "Float",   DEF_SPIN,    t_Float},
169   {(caddr_t *) &font_size,      "fontSize","Float",   DEF_STEPS,   t_Float},
170   {(caddr_t *) &target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
171   {(caddr_t *) &wrap_p,         "lineWrap","Boolean", DEF_COLUMNS, t_Bool},
172   {(caddr_t *) &alignment_str,  "alignment","Alignment",DEF_ALIGN, t_String},
173   {(caddr_t *) &smooth_p,       "smooth",  "Boolean", DEF_SMOOTH,  t_Bool},
174   {(caddr_t *) &thick_p,        "thick",   "Boolean", DEF_THICK,   t_Bool},
175   {(caddr_t *) &fade_p,         "fade",    "Boolean", DEF_FADE,    t_Bool},
176 };
177
178 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
179
180
181
182 /* Tabs are bad, mmmkay? */
183
184 static char *
185 untabify (const char *string)
186 {
187   const char *ostring = string;
188   char *result = (char *) malloc ((strlen(string) * 8) + 1);
189   char *out = result;
190   int col = 0;
191   while (*string)
192     {
193       if (*string == '\t')
194         {
195           do {
196             col++;
197             *out++ = ' ';
198           } while (col % TAB_WIDTH);
199           string++;
200         }
201       else if (*string == '\r' || *string == '\n')
202         {
203           *out++ = *string++;
204           col = 0;
205         }
206       else if (*string == '\010')    /* backspace */
207         {
208           if (string > ostring)
209             out--, string++;
210         }
211       else
212         {
213           *out++ = *string++;
214           col++;
215         }
216     }
217   *out = 0;
218
219   return result;
220 }
221
222 static void
223 strip (char *s, Bool leading, Bool trailing)
224 {
225   int L = strlen(s);
226   if (trailing)
227     while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
228       s[L--] = 0;
229   if (leading)
230     {
231       char *s2 = s;
232       while (*s2 == ' ' || *s2 == '\t')
233         s2++;
234       if (s == s2)
235         return;
236       while (*s2)
237         *s++ = *s2++;
238       *s = 0;
239     }
240 }
241
242
243 \f
244 /* Subprocess.
245    (This bit mostly cribbed from phosphor.c)
246  */
247
248 static void drain_input (sws_configuration *sc);
249
250 static void
251 subproc_cb (XtPointer closure, int *source, XtInputId *id)
252 {
253   sws_configuration *sc = (sws_configuration *) closure;
254   drain_input (sc);
255 }
256
257
258 static void
259 launch_text_generator (sws_configuration *sc)
260 {
261   char *oprogram = get_string_resource ("program", "Program");
262   char *program;
263
264   if (!strcasecmp(oprogram, "(default)"))
265     {
266       oprogram = FORTUNE_PROGRAM;
267
268 #ifdef __linux__
269       {
270         static int done_once = 0;
271         if (!done_once)
272           {
273             struct utsname uts;
274             struct stat st;
275             done_once = 1;
276             if (uname (&uts) == 0)
277               {
278                 static char cmd[200];
279                 char *s;
280                 /* strip version at the first non-digit-dash-dot, to
281                    lose any "SMP" crap at the end. */
282                 for (s = uts.release; *s; s++)
283                   if (!isdigit(*s) && *s != '.' && *s != '-')
284                     *s = 0;
285                 sprintf (cmd, "cat /usr/src/linux-%s/README", uts.release);
286                 if (!stat (cmd+4, &st))
287                   oprogram = cmd;
288               }
289           }
290       }
291 #endif /* __linux__ */
292
293 #ifdef __APPLE__   /* MacOS X + XDarwin */
294       {
295         static int done_once = 0;
296         if (!done_once)
297           {
298             struct stat st;
299             static char *cmd = "cat /usr/X11R6/README";
300             if (!stat (cmd+4, &st))
301               oprogram = cmd;
302           }
303       }
304 #endif /* __APPLE__ */
305     }
306
307   program = (char *) malloc (strlen (oprogram) + 10);
308   strcpy (program, "( ");
309   strcat (program, oprogram);
310   strcat (program, " ) 2>&1");
311
312   if ((sc->pipe = popen (program, "r")))
313     {
314       sc->pipe_id =
315         XtAppAddInput (app, fileno (sc->pipe),
316                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
317                        subproc_cb, (XtPointer) sc);
318     }
319   else
320     {
321       perror (program);
322     }
323 }
324
325
326 static void
327 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
328 {
329   sws_configuration *sc = (sws_configuration *) closure;
330   launch_text_generator (sc);
331 }
332
333
334 /* When the subprocess has generated some output, this reads as much as it
335    can into sc->buf at sc->buf_tail.
336  */
337 static void
338 drain_input (sws_configuration *sc)
339 {
340   if (sc->buf_tail < sizeof(sc->buf) - 2)
341     {
342       int target = sizeof(sc->buf) - sc->buf_tail - 2;
343       int n = read (fileno (sc->pipe),
344                     (void *) (sc->buf + sc->buf_tail),
345                     target);
346       if (n > 0)
347         {
348           sc->buf_tail += n;
349           sc->buf[sc->buf_tail] = 0;
350         }
351       else
352         {
353           XtRemoveInput (sc->pipe_id);
354           sc->pipe_id = 0;
355           pclose (sc->pipe);
356           sc->pipe = 0;
357
358           /* If the process didn't print a terminating newline, add one. */
359           if (sc->buf_tail > 1 &&
360               sc->buf[sc->buf_tail-1] != '\n')
361             {
362               sc->buf[sc->buf_tail++] = '\n';
363               sc->buf[sc->buf_tail] = 0;
364             }
365
366           /* Then add one more, just for giggles. */
367           sc->buf[sc->buf_tail++] = '\n';
368           sc->buf[sc->buf_tail] = 0;
369
370           /* Set up a timer to re-launch the subproc in a bit. */
371           XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
372                            relaunch_generator_timer,
373                            (XtPointer) sc);
374         }
375     }
376 }
377
378
379 /* Populates the sc->lines list with as many lines as are currently in
380    sc->buf (which was filled by drain_input().
381  */
382 static void
383 get_more_lines (sws_configuration *sc)
384 {
385   int col = 0;
386   char *s = sc->buf;
387   while (sc->total_lines < max_lines)
388     {
389       if (s >= sc->buf + sc->buf_tail)
390         {
391           /* Reached end of buffer before end of line.  Bail. */
392           return;
393         }
394
395       if (*s == '\r' || *s == '\n' || col > sc->columns)
396         {
397           int L = s - sc->buf;
398
399           if (*s == '\r' || *s == '\n')
400             {
401               if (*s == '\r' && s[1] == '\n')  /* swallow CRLF too */
402                 *s++ = 0;
403
404               *s++ = 0;
405             }
406           else
407             {
408               /* We wrapped -- try to back up to the previous word boundary. */
409               char *s2 = s;
410               int n = 0;
411               while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
412                 s2--, n++;
413               if (s2 > sc->buf)
414                 {
415                   s = s2;
416                   *s++ = 0;
417                   L = s - sc->buf;
418                 }
419             }
420
421           sc->lines[sc->total_lines] = (char *) malloc (L+1);
422           memcpy (sc->lines[sc->total_lines], sc->buf, L);
423           sc->lines[sc->total_lines][L] = 0;
424
425           {
426             char *t = sc->lines[sc->total_lines];
427             char *ut = untabify (t);
428             strip (ut, (alignment == 0), 1); /* if centering, strip
429                                                 leading whitespace too */
430             sc->lines[sc->total_lines] = ut;
431             free (t);
432           }
433
434           sc->total_lines++;
435
436           if (sc->buf_tail > (s - sc->buf))
437             {
438               int i = sc->buf_tail - (s - sc->buf);
439               memcpy (sc->buf, s, i);
440               sc->buf_tail = i;
441               sc->buf[sc->buf_tail] = 0;
442             }
443           else
444             {
445               sc->buf_tail = 0;
446             }
447
448           sc->buf[sc->buf_tail] = 0;
449           s = sc->buf;
450           col = 0;
451         }
452       else
453         {
454           col++;
455           if (*s == '\t')
456             col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
457           s++;
458         }
459     }
460 }
461
462
463 static void
464 draw_string (int x, int y, const char *s)
465 {
466   if (!s || !*s) return;
467   glPushMatrix ();
468   glTranslatef (x, y, 0);
469
470   while (*s)
471     glutStrokeCharacter (GLUT_FONT, *s++);
472   glPopMatrix ();
473 }
474
475
476 #ifdef DEBUG
477 static void
478 grid (double width, double height, double spacing, double z)
479 {
480   double x, y;
481   for (y = 0; y <= height/2; y += spacing)
482     {
483       glBegin(GL_LINES);
484       glVertex3f(-width/2,  y, z);
485       glVertex3f( width/2,  y, z);
486       glVertex3f(-width/2, -y, z);
487       glVertex3f( width/2, -y, z);
488       glEnd();
489     }
490   for (x = 0; x <= width/2; x += spacing)
491     {
492       glBegin(GL_LINES);
493       glVertex3f( x, -height/2, z);
494       glVertex3f( x,  height/2, z);
495       glVertex3f(-x, -height/2, z);
496       glVertex3f(-x,  height/2, z);
497       glEnd();
498     }
499
500   glBegin(GL_LINES);
501   glVertex3f(-width, 0, z);
502   glVertex3f( width, 0, z);
503   glVertex3f(0, -height, z);
504   glVertex3f(0,  height, z);
505   glEnd();
506 }
507
508 static void
509 box (double width, double height, double depth)
510 {
511   glBegin(GL_LINE_LOOP);
512   glVertex3f(-width/2,  -height/2, -depth/2);
513   glVertex3f(-width/2,   height/2, -depth/2);
514   glVertex3f( width/2,   height/2, -depth/2);
515   glVertex3f( width/2,  -height/2, -depth/2);
516   glEnd();
517   glBegin(GL_LINE_LOOP);
518   glVertex3f(-width/2,  -height/2,  depth/2);
519   glVertex3f(-width/2,   height/2,  depth/2);
520   glVertex3f( width/2,   height/2,  depth/2);
521   glVertex3f( width/2,  -height/2,  depth/2);
522   glEnd();
523   glBegin(GL_LINE_LOOP);
524   glVertex3f(-width/2,  -height/2, -depth/2);
525   glVertex3f(-width/2,  -height/2,  depth/2);
526   glVertex3f(-width/2,   height/2,  depth/2);
527   glVertex3f(-width/2,   height/2, -depth/2);
528   glEnd();
529   glBegin(GL_LINE_LOOP);
530   glVertex3f( width/2,  -height/2, -depth/2);
531   glVertex3f( width/2,  -height/2,  depth/2);
532   glVertex3f( width/2,   height/2,  depth/2);
533   glVertex3f( width/2,   height/2, -depth/2);
534   glEnd();
535
536   glEnd();
537   glBegin(GL_LINES);
538   glVertex3f(-width/2,   height/2,  depth/2);
539   glVertex3f(-width/2,  -height/2, -depth/2);
540
541   glVertex3f( width/2,   height/2,  depth/2);
542   glVertex3f( width/2,  -height/2, -depth/2);
543
544   glVertex3f(-width/2,  -height/2,  depth/2);
545   glVertex3f(-width/2,   height/2, -depth/2);
546
547   glVertex3f( width/2,  -height/2,  depth/2);
548   glVertex3f( width/2,   height/2, -depth/2);
549   glEnd();
550 }
551 #endif /* DEBUG */
552
553
554 /* Construct stars (number of stars is dependent on size of screen) */
555 static void
556 init_stars (ModeInfo *mi, int width, int height)
557 {
558   sws_configuration *sc = &scs[MI_SCREEN(mi)];
559   int i, j;
560   int nstars = width * height / 320;
561   int max_size = 3;
562   GLfloat inc = 0.5;
563   int steps = max_size / inc;
564
565   glDeleteLists (sc->star_list, 1);
566   sc->star_list = glGenLists (1);
567   glNewList (sc->star_list, GL_COMPILE);
568
569   glEnable(GL_POINT_SMOOTH);
570
571   for (j = 1; j <= steps; j++)
572     {
573       glPointSize(inc * j);
574       glBegin (GL_POINTS);
575       for (i = 0; i < nstars / steps; i++)
576         {
577           glColor3f (0.6 + frand(0.3),
578                      0.6 + frand(0.3),
579                      0.6 + frand(0.3));
580           glVertex2f (2 * width  * (0.5 - frand(1.0)),
581                       2 * height * (0.5 - frand(1.0)));
582         }
583       glEnd ();
584     }
585   glEndList ();
586 }
587
588
589 /* Window management, etc
590  */
591 void
592 reshape_sws (ModeInfo *mi, int width, int height)
593 {
594   sws_configuration *sc = &scs[MI_SCREEN(mi)];
595
596   /* Set up matrices for perspective text display
597    */
598   {
599     GLfloat desired_aspect = (GLfloat) 3/4;
600     int w = mi->xgwa.width;
601     int h = mi->xgwa.height;
602
603 #ifdef KEEP_ASPECT
604     h = w * desired_aspect;
605 #endif
606
607     glMatrixMode (GL_PROJECTION);
608     glViewport (0, 0, w, h);
609
610     glMatrixMode (GL_MODELVIEW);
611     glLoadIdentity ();
612     gluPerspective (80.0, 1/desired_aspect, 10, 500000);
613     gluLookAt (0.0, 0.0, 4600.0,
614                0.0, 0.0, 0.0,
615                0.0, 1.0, 0.0);
616     glRotatef (-60.0, 1.0, 0.0, 0.0);
617
618     /* The above gives us an arena where the bottom edge of the screen is
619        represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
620
621     /* Now let's move the origin to the front of the screen. */
622     glTranslatef (0.0, -3140, 0.0);
623
624     /* And then let's scale so that the bottom of the screen is 1.0 wide. */
625     glScalef (4200, 4200, 4200);
626   }
627
628
629   /* Compute the height in pixels of the line at the bottom of the screen. */
630   {
631     GLdouble mm[17], pm[17];
632     GLint vp[5];
633     GLfloat x = 0.5, y1 = 0, z = 0;
634     GLfloat y2 = sc->line_height;
635     GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
636
637     glGetDoublev (GL_MODELVIEW_MATRIX, mm);
638     glGetDoublev (GL_PROJECTION_MATRIX, pm);
639     glGetIntegerv (GL_VIEWPORT, vp);
640     gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
641     gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
642     sc->line_pixel_height = (wy2 - wy1);
643     glLineWidth (1);
644   }
645
646   /* Compute the best looking line thickness for the bottom line.
647    */
648   if (!thick_p)
649     sc->line_thickness = 1.0;
650   else
651     sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
652
653   if (sc->line_thickness < 1.2)
654     sc->line_thickness = 1.0;
655 }
656
657
658 static void
659 gl_init (ModeInfo *mi)
660 {
661   sws_configuration *sc = &scs[MI_SCREEN(mi)];
662
663   program = get_string_resource ("program", "Program");
664
665   glDisable (GL_LIGHTING);
666   glDisable (GL_DEPTH_TEST);
667
668   if (smooth_p) 
669     {
670       glEnable (GL_LINE_SMOOTH);
671       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
672       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
673       glEnable (GL_BLEND);
674     }
675
676   sc->text_list = glGenLists (1);
677   glNewList (sc->text_list, GL_COMPILE);
678   glEndList ();
679
680   sc->star_list = glGenLists (1);
681   glNewList (sc->star_list, GL_COMPILE);
682   glEndList ();
683
684   sc->line_thickness = 1.0;
685 }
686
687
688 void 
689 init_sws (ModeInfo *mi)
690 {
691   double font_height;
692
693   sws_configuration *sc;
694
695   if (!scs) {
696     scs = (sws_configuration *)
697       calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
698     if (!scs) {
699       fprintf(stderr, "%s: out of memory\n", progname);
700       exit(1);
701     }
702
703     sc = &scs[MI_SCREEN(mi)];
704     sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
705   }
706
707   sc = &scs[MI_SCREEN(mi)];
708
709   if ((sc->glx_context = init_GL(mi)) != NULL) {
710     gl_init(mi);
711     reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
712     init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
713   }
714
715
716   font_height = GLUT_FONT->top - GLUT_FONT->bottom;
717   sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z');   /* 'n' seems
718                                                                 too wide */
719   if (target_columns > 0)
720     {
721       sc->columns = target_columns;
722     }
723   else
724     {
725       if (font_size <= 0)
726         font_size = BASE_FONT_SIZE;
727       sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
728     }
729
730   sc->font_scale /= sc->columns;
731   sc->line_height = font_height * sc->font_scale;
732
733
734   if (!wrap_p) sc->columns = 1000;  /* wrap anyway, if it's absurdly long. */
735
736   sc->subproc_relaunch_delay = 2 * 1000;
737   sc->total_lines = max_lines-1;
738
739   if (random() & 1)
740     star_spin = -star_spin;
741
742   if (!alignment_str || !*alignment_str ||
743       !strcasecmp(alignment_str, "left"))
744     alignment = -1;
745   else if (!strcasecmp(alignment_str, "center") ||
746            !strcasecmp(alignment_str, "middle"))
747     alignment = 0;
748   else if (!strcasecmp(alignment_str, "right"))
749     alignment = 1;
750   else
751     {
752       fprintf (stderr,
753                "%s: alignment must be left, center, or right, not \"%s\"\n",
754                progname, alignment_str);
755       exit (1);
756     }
757
758   launch_text_generator (sc);
759
760   /* one more reshape, after line_height has been computed */
761   reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
762 }
763
764
765 static void
766 draw_stars (ModeInfo *mi)
767 {
768   sws_configuration *sc = &scs[MI_SCREEN(mi)];
769
770   glMatrixMode (GL_PROJECTION);
771   glPushMatrix ();
772   {
773     glLoadIdentity ();
774
775     glMatrixMode (GL_MODELVIEW);
776     glPushMatrix ();
777     {
778       glLoadIdentity ();
779       glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
780                -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
781                -100.0, 100.0);
782       glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
783       glCallList (sc->star_list);
784     }
785     glPopMatrix ();
786   }
787   glMatrixMode (GL_PROJECTION);
788   glPopMatrix ();
789 }
790
791 void
792 draw_sws (ModeInfo *mi)
793 {
794   sws_configuration *sc = &scs[MI_SCREEN(mi)];
795   Display *dpy = MI_DISPLAY(mi);
796   Window window = MI_WINDOW(mi);
797   int i;
798
799   if (!sc->glx_context)
800     return;
801
802   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
803     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
804
805   glDrawBuffer (GL_BACK);
806   glXMakeCurrent (dpy, window, *(sc->glx_context));
807
808   glClear (GL_COLOR_BUFFER_BIT);
809
810   draw_stars (mi);
811
812   glMatrixMode (GL_MODELVIEW);
813   glPushMatrix ();
814
815 #ifdef DEBUG
816   glColor3f (0.4, 0.4, 0.4);
817   glLineWidth (1);
818   glTranslatef(0, 1, 0);
819   box (1, 1, 1);
820   glTranslatef(0, -1, 0);
821   box (1, 1, 1);
822   grid (1, 1, sc->line_height, 0);
823 #endif /* DEBUG */
824
825   /* Scroll to current position */
826   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
827
828   glColor3f (1.0, 1.0, 0.4);
829   glCallList (sc->text_list);
830
831   sc->intra_line_scroll += sc->line_height / scroll_steps;
832
833   if (sc->intra_line_scroll >= sc->line_height)
834     {
835       sc->intra_line_scroll = 0;
836
837       /* Drop the oldest line off the end. */
838       if (sc->lines[0])
839         free (sc->lines[0]);
840
841       /* Scroll the contents of the lines array toward 0. */
842       if (sc->total_lines > 0)
843         {
844           for (i = 1; i < sc->total_lines; i++)
845             sc->lines[i-1] = sc->lines[i];
846           sc->lines[--sc->total_lines] = 0;
847         }
848
849       /* Bring in new lines at the end. */
850       get_more_lines (sc);
851
852       if (sc->total_lines < max_lines)
853         /* Oops, we ran out of text... well, insert some blank lines
854            here so that new text still pulls in from the bottom of
855            the screen, isntead of just appearing. */
856         sc->total_lines = max_lines;
857
858       glDeleteLists (sc->text_list, 1);
859       sc->text_list = glGenLists (1);
860       glNewList (sc->text_list, GL_COMPILE);
861       glPushMatrix ();
862       glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
863       for (i = 0; i < sc->total_lines; i++)
864         {
865           int offscreen_lines = 3;
866
867           double x = -0.5;
868           double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
869                        * sc->line_height);
870           double xoff = 0;
871           char *line = sc->lines[i];
872 #ifdef DEBUG
873           char n[20];
874           sprintf(n, "%d:", i);
875           draw_string (x / sc->font_scale, y / sc->font_scale, n);
876 #endif /* DEBUG */
877           if (!line || !*line)
878             continue;
879
880           if (sc->line_thickness != 1)
881             {
882               int max_thick_lines = MAX_THICK_LINES;
883               GLfloat thinnest_line = 1.0;
884               GLfloat thickest_line = sc->line_thickness;
885               GLfloat range = thickest_line - thinnest_line;
886               GLfloat thickness;
887
888               int j = sc->total_lines - i - 1;
889
890               if (j > max_thick_lines)
891                 thickness = thinnest_line;
892               else
893                 thickness = (thinnest_line +
894                              (range * ((max_thick_lines - j) /
895                                        (GLfloat) max_thick_lines)));
896
897               glLineWidth (thickness);
898             }
899
900           if (alignment >= 0)
901             xoff = 1.0 - (glutStrokeLength(GLUT_FONT, line) * sc->font_scale);
902           if (alignment == 0)
903             xoff /= 2;
904
905           if (fade_p)
906             {
907               double factor = 1.0 * i / sc->total_lines;
908               glColor3f (factor, factor, 0.5 * factor);
909             }
910
911           draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
912         }
913       glPopMatrix ();
914       glEndList ();
915     }
916
917   glPopMatrix ();
918
919   if (mi->fps_p) do_fps (mi);
920   glFinish();
921   glXSwapBuffers(dpy, window);
922
923   sc->star_theta += star_spin;
924 }
925
926 #endif /* USE_GL */