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