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