afbefc8624912485fc0870c3d124ee61ff1b72ae
[xscreensaver] / hacks / glx / starwars.c
1 /*
2  * starwars, Copyright (c) 1998-2001 Jamie Zawinski <jwz@jwz.org> and
3  * Claudio Matauoka <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  */
33
34 #include <X11/Intrinsic.h>
35
36 extern XtAppContext app;
37
38 #define PROGCLASS       "StarWars"
39 #define HACK_INIT       init_sws
40 #define HACK_DRAW       draw_sws
41 #define sws_opts        xlockmore_opts
42
43 #define DEF_PROGRAM    ZIPPY_PROGRAM
44 #define DEF_LINES      "500"
45 #define DEF_STEPS      "35"
46 #define DEF_SPIN       "0.03"
47 #define DEF_FONT_SIZE  "-1"
48 #define DEF_COLUMNS    "-1"
49 #define DEF_WRAP       "True"
50 #define DEF_ALIGN      "Center"
51
52 #define TAB_WIDTH        8
53
54 #define BASE_FONT_SIZE    18 /* magic */
55 #define BASE_FONT_COLUMNS 80 /* magic */
56
57
58 #define DEFAULTS        "*delay:        40000 \n"                    \
59                         "*program:      " DEF_PROGRAM           "\n" \
60                         "*lines:        " DEF_LINES             "\n" \
61                         "*spin:         " DEF_SPIN              "\n" \
62                         "*steps:        " DEF_STEPS             "\n" \
63                         "*starwars.fontSize: " DEF_FONT_SIZE    "\n" \
64                         "*starwars.columns:  " DEF_COLUMNS      "\n" \
65                         "*starwars.lineWrap: " DEF_WRAP         "\n" \
66                         "*starwars.alignment:" DEF_ALIGN        "\n"
67
68 #undef countof
69 #define countof(x) (sizeof((x))/sizeof((*x)))
70
71 #include "xlockmore.h"
72
73 #ifdef USE_GL /* whole file */
74
75 #include <GL/glu.h>
76 #include "glutstroke.h"
77 #include "glut_roman.h"
78 #define GLUT_FONT (&glutStrokeRoman)
79
80
81 typedef struct {
82   GLXContext *glx_context;
83
84   GLuint text_list, star_list;
85
86   FILE *pipe;
87   XtInputId pipe_id;
88   Time subproc_relaunch_delay;
89
90   char buf [1024];
91   int buf_tail;
92   char **lines;
93   int total_lines;
94   int columns;
95
96   double star_theta;
97   double line_height;
98   double font_scale;
99   double intra_line_scroll;
100
101 } sws_configuration;
102
103
104 static sws_configuration *scs = NULL;
105
106 static char *program;
107 static int max_lines;
108 static int scroll_steps;
109 static float star_spin;
110 static float font_size;
111 static int target_columns;
112 static int wrap_p;
113 static char *alignment_str;
114 static int alignment;
115
116 static XrmOptionDescRec opts[] = {
117   {"-program",   ".starwars.program",  XrmoptionSepArg, (caddr_t) 0 },
118   {"-lines",     ".starwars.lines",    XrmoptionSepArg, (caddr_t) 0 },
119   {"-steps",     ".starwars.steps",    XrmoptionSepArg, (caddr_t) 0 },
120   {"-spin",      ".starwars.spin",     XrmoptionSepArg, (caddr_t) 0 },
121   {"-size",      ".starwars.fontSize", XrmoptionSepArg, (caddr_t) 0 },
122   {"-columns",   ".starwars.columns",  XrmoptionSepArg, (caddr_t) 0 },
123   {"-wrap",      ".starwars.lineWrap", XrmoptionNoArg,  (caddr_t) "True" },
124   {"-no-wrap",   ".starwars.lineWrap", XrmoptionNoArg,  (caddr_t) "False" },
125   {"-nowrap",    ".starwars.lineWrap", XrmoptionNoArg,  (caddr_t) "False" },
126   {"-left",      ".starwars.alignment",XrmoptionNoArg,  (caddr_t) "Left" },
127   {"-right",     ".starwars.alignment",XrmoptionNoArg,  (caddr_t) "Right" },
128   {"-center",    ".starwars.alignment",XrmoptionNoArg,  (caddr_t) "Center" },
129 };
130
131 static argtype vars[] = {
132   {(caddr_t *) &program,        "program", "Program", DEF_PROGRAM, t_String},
133   {(caddr_t *) &max_lines,      "lines",   "Integer", DEF_LINES,   t_Int},
134   {(caddr_t *) &scroll_steps,   "steps",   "Integer", DEF_STEPS,   t_Int},
135   {(caddr_t *) &star_spin,      "spin",    "Float",   DEF_SPIN,    t_Float},
136   {(caddr_t *) &font_size,      "fontSize","Float",   DEF_STEPS,   t_Float},
137   {(caddr_t *) &target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
138   {(caddr_t *) &wrap_p,         "lineWrap","Boolean", DEF_COLUMNS, t_Bool},
139   {(caddr_t *) &alignment_str,  "alignment","Alignment",DEF_ALIGN, t_String},
140 };
141
142 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
143
144
145
146 /* Tabs are bad, mmmkay? */
147
148 static char *
149 untabify (const char *string)
150 {
151   char *result = (char *) malloc ((strlen(string) * 8) + 1);
152   char *out = result;
153   int col = 0;
154   while (*string)
155     {
156       if (*string == '\t')
157         {
158           do {
159             col++;
160             *out++ = ' ';
161           } while (col % TAB_WIDTH);
162           string++;
163         }
164       else if (*string == '\r' || *string == '\n')
165         {
166           *out++ = *string++;
167           col = 0;
168         }
169       else
170         {
171           *out++ = *string++;
172           col++;
173         }
174     }
175   *out = 0;
176   return result;
177 }
178
179
180 \f
181 /* Subprocess.
182    (This bit mostly cribbed from phosphor.c)
183  */
184
185 static void drain_input (sws_configuration *sc);
186
187 static void
188 subproc_cb (XtPointer closure, int *source, XtInputId *id)
189 {
190   sws_configuration *sc = (sws_configuration *) closure;
191   drain_input (sc);
192 }
193
194
195 static void
196 launch_text_generator (sws_configuration *sc)
197 {
198   char *oprogram = get_string_resource ("program", "Program");
199   char *program = (char *) malloc (strlen (oprogram) + 10);
200
201   strcpy (program, "( ");
202   strcat (program, oprogram);
203   strcat (program, " ) 2>&1");
204
205   if ((sc->pipe = popen (program, "r")))
206     {
207       sc->pipe_id =
208         XtAppAddInput (app, fileno (sc->pipe),
209                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
210                        subproc_cb, (XtPointer) sc);
211     }
212   else
213     {
214       perror (program);
215     }
216 }
217
218
219 static void
220 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
221 {
222   sws_configuration *sc = (sws_configuration *) closure;
223   launch_text_generator (sc);
224 }
225
226
227 /* When the subprocess has generated some output, this reads as much as it
228    can into sc->buf at sc->buf_tail.
229  */
230 static void
231 drain_input (sws_configuration *sc)
232 {
233   if (sc->buf_tail < sizeof(sc->buf) - 2)
234     {
235       int target = sizeof(sc->buf) - sc->buf_tail - 2;
236       int n = read (fileno (sc->pipe),
237                     (void *) (sc->buf + sc->buf_tail),
238                     target);
239       if (n > 0)
240         {
241           sc->buf_tail += n;
242           sc->buf[sc->buf_tail] = 0;
243         }
244       else
245         {
246           XtRemoveInput (sc->pipe_id);
247           sc->pipe_id = 0;
248           pclose (sc->pipe);
249           sc->pipe = 0;
250
251           /* If the process didn't print a terminating newline, add one. */
252           if (sc->buf_tail > 1 &&
253               sc->buf[sc->buf_tail-1] != '\n')
254             {
255               sc->buf[sc->buf_tail++] = '\n';
256               sc->buf[sc->buf_tail] = 0;
257             }
258
259           /* Then add one more, just for giggles. */
260           sc->buf[sc->buf_tail++] = '\n';
261           sc->buf[sc->buf_tail] = 0;
262
263           /* Set up a timer to re-launch the subproc in a bit. */
264           XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
265                            relaunch_generator_timer,
266                            (XtPointer) sc);
267         }
268     }
269 }
270
271
272 /* Populates the sc->lines list with as many lines as are currently in
273    sc->buf (which was filled by drain_input().
274  */
275 static void
276 get_more_lines (sws_configuration *sc)
277 {
278   int col = 0;
279   char *s = sc->buf;
280   while (sc->total_lines < max_lines)
281     {
282       if (s >= sc->buf + sc->buf_tail)
283         {
284           /* Reached end of buffer before end of line.  Bail. */
285           return;
286         }
287
288       if (*s == '\n' || col > sc->columns)
289         {
290           int L = s - sc->buf;
291
292           if (*s == '\n')
293             *s++ = 0;
294           else
295             {
296               /* We wrapped -- try to back up to the previous word boundary. */
297               char *s2 = s;
298               int n = 0;
299               while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
300                 s2--, n++;
301               if (s2 > sc->buf)
302                 {
303                   s = s2;
304                   *s++ = 0;
305                   L = s - sc->buf;
306                 }
307             }
308
309           sc->lines[sc->total_lines] = (char *) malloc (L+1);
310           memcpy (sc->lines[sc->total_lines], sc->buf, L);
311           sc->lines[sc->total_lines][L] = 0;
312
313           {
314             char *t = sc->lines[sc->total_lines];
315             char *ut = untabify (t);
316             sc->lines[sc->total_lines] = ut;
317             free (t);
318           }
319
320           sc->total_lines++;
321
322           if (sc->buf_tail > (s - sc->buf))
323             {
324               int i = sc->buf_tail - (s - sc->buf);
325               memcpy (sc->buf, s, i);
326               sc->buf_tail = i;
327               sc->buf[sc->buf_tail] = 0;
328             }
329           else
330             {
331               sc->buf_tail = 0;
332             }
333
334           sc->buf[sc->buf_tail] = 0;
335           s = sc->buf;
336           col = 0;
337         }
338       else
339         {
340           col++;
341           if (*s == '\t')
342             col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
343           s++;
344         }
345     }
346 }
347
348
349 static void
350 draw_string (int x, int y, const char *s)
351 {
352   if (!s || !*s) return;
353   glPushMatrix ();
354   glTranslatef (x, y, 0);
355   while (*s)
356     glutStrokeCharacter (GLUT_FONT, *s++);
357   glPopMatrix ();
358 }
359
360
361 #if 0
362 static void
363 grid (double width, double height, double spacing, double z)
364 {
365   double x, y;
366   for (y = 0; y <= height/2; y += spacing)
367     {
368       glBegin(GL_LINE_LOOP);
369       glVertex3f(-width/2,  y, z);
370       glVertex3f( width/2,  y, z);
371       glEnd();
372       glBegin(GL_LINE_LOOP);
373       glVertex3f(-width/2, -y, z);
374       glVertex3f( width/2, -y, z);
375       glEnd();
376     }
377   for (x = 0; x <= width/2; x += spacing)
378     {
379       glBegin(GL_LINE_LOOP);
380       glVertex3f( x, -height/2, z);
381       glVertex3f( x,  height/2, z);
382       glEnd();
383       glBegin(GL_LINE_LOOP);
384       glVertex3f(-x, -height/2, z);
385       glVertex3f(-x,  height/2, z);
386       glEnd();
387     }
388
389   glBegin(GL_LINE_LOOP);
390   glVertex3f(-width, 0, z);
391   glVertex3f( width, 0, z);
392   glEnd();
393   glBegin(GL_LINE_LOOP);
394   glVertex3f(0, -height, z);
395   glVertex3f(0,  height, z);
396   glEnd();
397 }
398
399 static void
400 box (double width, double height, double depth)
401 {
402   glBegin(GL_LINE_LOOP);
403   glVertex3f(-width/2,  -height/2, -depth/2);
404   glVertex3f(-width/2,   height/2, -depth/2);
405   glVertex3f( width/2,   height/2, -depth/2);
406   glVertex3f( width/2,  -height/2, -depth/2);
407   glEnd();
408   glBegin(GL_LINE_LOOP);
409   glVertex3f(-width/2,  -height/2,  depth/2);
410   glVertex3f(-width/2,   height/2,  depth/2);
411   glVertex3f( width/2,   height/2,  depth/2);
412   glVertex3f( width/2,  -height/2,  depth/2);
413   glEnd();
414   glBegin(GL_LINE_LOOP);
415   glVertex3f(-width/2,  -height/2, -depth/2);
416   glVertex3f(-width/2,  -height/2,  depth/2);
417   glVertex3f(-width/2,   height/2,  depth/2);
418   glVertex3f(-width/2,   height/2, -depth/2);
419   glEnd();
420   glBegin(GL_LINE_LOOP);
421   glVertex3f( width/2,  -height/2, -depth/2);
422   glVertex3f( width/2,  -height/2,  depth/2);
423   glVertex3f( width/2,   height/2,  depth/2);
424   glVertex3f( width/2,   height/2, -depth/2);
425   glEnd();
426
427   glEnd();
428   glBegin(GL_LINE_LOOP);
429   glVertex3f(-width/2,   height/2,  depth/2);
430   glVertex3f(-width/2,  -height/2, -depth/2);
431   glEnd();
432   glBegin(GL_LINE_LOOP);
433   glVertex3f( width/2,   height/2,  depth/2);
434   glVertex3f( width/2,  -height/2, -depth/2);
435   glEnd();
436   glBegin(GL_LINE_LOOP);
437   glVertex3f(-width/2,  -height/2,  depth/2);
438   glVertex3f(-width/2,   height/2, -depth/2);
439   glEnd();
440   glBegin(GL_LINE_LOOP);
441   glVertex3f( width/2,  -height/2,  depth/2);
442   glVertex3f( width/2,   height/2, -depth/2);
443   glEnd();
444 }
445 #endif /* 0 */
446
447
448 /* Window management, etc
449  */
450 static void
451 reshape (sws_configuration *sc, int width, int height)
452 {
453   static Bool stars_done = False;
454
455   glViewport (0, 0, (GLint) width, (GLint) height);
456   if (!stars_done)
457     {
458       int i;
459       int nstars = width * height / 320;
460       glDeleteLists (sc->star_list, 1);
461       sc->star_list = glGenLists (1);
462       glNewList (sc->star_list, GL_COMPILE);
463       glBegin (GL_POINTS);
464       for (i = 0; i < nstars; i++)
465         {
466           GLfloat c = 0.6 + 0.3 * random() / RAND_MAX;
467           glColor3f (c, c, c);
468           glVertex3f (2 * width  * (0.5 - 1.0 * random() / RAND_MAX),
469                       2 * height * (0.5 - 1.0 * random() / RAND_MAX),
470                       0.0);
471         }
472       glEnd ();
473       glEndList ();
474       stars_done = True;
475     }
476 }
477
478
479 static void
480 gl_init (ModeInfo *mi)
481 {
482   sws_configuration *sc = &scs[MI_SCREEN(mi)];
483
484   program = get_string_resource ("program", "Program");
485
486   glMatrixMode (GL_MODELVIEW);
487
488   glDisable (GL_LIGHTING);
489   glDisable (GL_DEPTH_TEST);
490
491   sc->text_list = glGenLists (1);
492   glNewList (sc->text_list, GL_COMPILE);
493   glEndList ();
494
495   sc->star_list = glGenLists (1);
496   glNewList (sc->star_list, GL_COMPILE);
497   glEndList ();
498 }
499
500
501 void 
502 init_sws (ModeInfo *mi)
503 {
504   double font_height;
505
506   sws_configuration *sc;
507
508   if (!scs) {
509     scs = (sws_configuration *)
510       calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
511     if (!scs) {
512       fprintf(stderr, "%s: out of memory\n", progname);
513       exit(1);
514     }
515
516     sc = &scs[MI_SCREEN(mi)];
517     sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
518   }
519
520   sc = &scs[MI_SCREEN(mi)];
521
522   if ((sc->glx_context = init_GL(mi)) != NULL) {
523     gl_init(mi);
524     reshape(sc, MI_WIDTH(mi), MI_HEIGHT(mi));
525   }
526
527
528
529   font_height = GLUT_FONT->top - GLUT_FONT->bottom;
530   sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z');   /* 'n' seems
531                                                                 too wide */
532   if (target_columns > 0)
533     {
534       sc->columns = target_columns;
535     }
536   else
537     {
538       if (font_size <= 0)
539         font_size = BASE_FONT_SIZE;
540       sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
541     }
542
543   sc->font_scale /= sc->columns;
544   sc->line_height = font_height * sc->font_scale;
545
546
547   if (!wrap_p) sc->columns = 1000;  /* wrap anyway, if it's absurdly long. */
548
549   sc->subproc_relaunch_delay = 2 * 1000;
550   sc->total_lines = max_lines-1;
551   launch_text_generator (sc);
552
553   if (random() & 1)
554     star_spin = -star_spin;
555
556   if (!alignment_str || !*alignment_str ||
557       !strcasecmp(alignment_str, "left"))
558     alignment = -1;
559   else if (!strcasecmp(alignment_str, "center") ||
560            !strcasecmp(alignment_str, "middle"))
561     alignment = 0;
562   else if (!strcasecmp(alignment_str, "right"))
563     alignment = 1;
564   else
565     {
566       fprintf (stderr,
567                "%s: alignment must be left, center, or right, not \"%s\"\n",
568                progname, alignment_str);
569       exit (1);
570     }
571 }
572
573
574 void
575 draw_sws (ModeInfo *mi)
576 {
577   sws_configuration *sc = &scs[MI_SCREEN(mi)];
578   Display *dpy = MI_DISPLAY(mi);
579   Window window = MI_WINDOW(mi);
580   int i;
581
582   if (!sc->glx_context)
583     return;
584
585   if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
586     XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
587
588   glDrawBuffer (GL_BACK);
589   glXMakeCurrent (dpy, window, *(sc->glx_context));
590
591   glClear (GL_COLOR_BUFFER_BIT);
592
593   glPushMatrix ();
594
595   glMatrixMode (GL_PROJECTION);
596   glLoadIdentity ();
597   glOrtho (-0.5 * MI_WIDTH(mi),  0.5 * MI_WIDTH(mi),
598            -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
599            -100.0, 100.0);
600   glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
601   glCallList (sc->star_list);
602
603   glLoadIdentity ();
604   gluPerspective (80.0, 4.0/3.0, 10, 500000);
605   glMatrixMode (GL_MODELVIEW);
606   gluLookAt (0.0, 0.0, 4600.0,
607              0.0, 0.0, 0.0,
608              0.0, 1.0, 0.0);
609
610   glRotatef (-60.0, 1.0, 0.0, 0.0);
611
612   /* The above gives us an arena where the bottom edge of the screen is
613      represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
614
615   /* Now let's move the origin to the front of the screen. */
616   glTranslatef (0.0, -3140, 0.0);
617
618   /* And then let's scale so that the bottom of the screen is 1.0 wide. */
619   glScalef (4200, 4200, 4200);
620
621   /* Scroll to current position */
622   glTranslatef (0.0, sc->intra_line_scroll, 0.0);
623
624   glColor3f (1.0, 1.0, 0.4);
625   glCallList (sc->text_list);
626
627   sc->intra_line_scroll += sc->line_height / scroll_steps;
628
629   if (sc->intra_line_scroll >= sc->line_height)
630     {
631       static time_t reshape_time = 0;
632       time_t now = time((time_t) 0);
633       if (reshape_time != now)  /* only poll for reshape once a second */
634         {
635           reshape_time = now;
636           XGetWindowAttributes (dpy, window, &mi->xgwa);
637           reshape(sc, MI_WIDTH(mi), MI_HEIGHT(mi));
638         }
639
640       sc->intra_line_scroll = 0;
641
642       /* Drop the oldest line off the end. */
643       if (sc->lines[0])
644         free (sc->lines[0]);
645
646       /* Scroll the contents of the lines array toward 0. */
647       if (sc->total_lines > 0)
648         {
649           for (i = 1; i < sc->total_lines; i++)
650             sc->lines[i-1] = sc->lines[i];
651           sc->lines[--sc->total_lines] = 0;
652         }
653
654       /* Bring in new lines at the end. */
655       get_more_lines (sc);
656
657       if (sc->total_lines < max_lines)
658         /* Oops, we ran out of text... well, insert some blank lines
659            here so that new text still pulls in from the bottom of
660            the screen, isntead of just appearing. */
661         sc->total_lines = max_lines;
662
663       glDeleteLists (sc->text_list, 1);
664       sc->text_list = glGenLists (1);
665       glNewList (sc->text_list, GL_COMPILE);
666       glPushMatrix ();
667       glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
668       for (i = 0; i < sc->total_lines; i++)
669         {
670           int offscreen_lines = 3;
671
672           double x = -0.5;
673           double y =  ((sc->total_lines - (i + offscreen_lines) - 1)
674                        * sc->line_height);
675           double xoff = 0;
676           char *line = sc->lines[i];
677 #if 0
678           char n[20];
679           sprintf(n, "%d:", i);
680           draw_string (x / sc->font_scale, y / sc->font_scale, n);
681 #endif
682           if (!line || !*line)
683             continue;
684
685           if (alignment >= 0)
686             xoff = 1.0 - (glutStrokeLength(GLUT_FONT, line) * sc->font_scale);
687           if (alignment == 0)
688             xoff /= 2;
689
690           draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
691         }
692       glPopMatrix ();
693       glEndList ();
694     }
695
696   glPopMatrix ();
697
698   glFinish();
699   glXSwapBuffers(dpy, window);
700
701   sc->star_theta += star_spin;
702 }
703
704 #endif /* USE_GL */