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