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