2 * starwars, Copyright (c) 1998-2001 Jamie Zawinski <jwz@jwz.org> and
3 * Claudio Matsuoka <claudio@helllabs.org>
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
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.
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
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.
36 #include <X11/Intrinsic.h>
38 extern XtAppContext app;
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
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"
60 #define BASE_FONT_SIZE 18 /* magic */
61 #define BASE_FONT_COLUMNS 80 /* magic */
63 #define MAX_THICK_LINES 25
64 #define FONT_WEIGHT 14
68 #define DEFAULTS "*delay: 40000 \n" \
69 "*showFPS: False \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"
84 #define countof(x) (sizeof((x))/sizeof((*x)))
86 #include "xlockmore.h"
88 #ifdef USE_GL /* whole file */
92 #include "glutstroke.h"
93 #include "glut_roman.h"
94 #define GLUT_FONT (&glutStrokeRoman)
98 GLXContext *glx_context;
100 GLuint text_list, star_list;
104 Time subproc_relaunch_delay;
115 double intra_line_scroll;
117 int line_pixel_height;
118 GLfloat line_thickness;
123 static sws_configuration *scs = NULL;
125 static char *program;
126 static int max_lines;
127 static int scroll_steps;
128 static float star_spin;
129 static float font_size;
130 static int target_columns;
135 static char *alignment_str;
136 static int alignment;
138 static XrmOptionDescRec opts[] = {
139 {"-program", ".starwars.program", XrmoptionSepArg, (caddr_t) 0 },
140 {"-lines", ".starwars.lines", XrmoptionSepArg, (caddr_t) 0 },
141 {"-steps", ".starwars.steps", XrmoptionSepArg, (caddr_t) 0 },
142 {"-spin", ".starwars.spin", XrmoptionSepArg, (caddr_t) 0 },
143 {"-size", ".starwars.fontSize", XrmoptionSepArg, (caddr_t) 0 },
144 {"-columns", ".starwars.columns", XrmoptionSepArg, (caddr_t) 0 },
145 {"-smooth", ".starwars.smooth", XrmoptionNoArg, (caddr_t) "True" },
146 {"-no-smooth", ".starwars.smooth", XrmoptionNoArg, (caddr_t) "False" },
147 {"-thick", ".starwars.thick", XrmoptionNoArg, (caddr_t) "True" },
148 {"-no-thick", ".starwars.thick", XrmoptionNoArg, (caddr_t) "False" },
149 {"-fade", ".starwars.fade", XrmoptionNoArg, (caddr_t) "True" },
150 {"-no-fade", ".starwars.fade", XrmoptionNoArg, (caddr_t) "False" },
151 {"-wrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "True" },
152 {"-no-wrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "False" },
153 {"-nowrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "False" },
154 {"-left", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Left" },
155 {"-right", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Right" },
156 {"-center", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Center" },
159 static argtype vars[] = {
160 {(caddr_t *) &program, "program", "Program", DEF_PROGRAM, t_String},
161 {(caddr_t *) &max_lines, "lines", "Integer", DEF_LINES, t_Int},
162 {(caddr_t *) &scroll_steps, "steps", "Integer", DEF_STEPS, t_Int},
163 {(caddr_t *) &star_spin, "spin", "Float", DEF_SPIN, t_Float},
164 {(caddr_t *) &font_size, "fontSize","Float", DEF_STEPS, t_Float},
165 {(caddr_t *) &target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
166 {(caddr_t *) &wrap_p, "lineWrap","Boolean", DEF_COLUMNS, t_Bool},
167 {(caddr_t *) &alignment_str, "alignment","Alignment",DEF_ALIGN, t_String},
168 {(caddr_t *) &smooth_p, "smooth", "Boolean", DEF_SMOOTH, t_Bool},
169 {(caddr_t *) &thick_p, "thick", "Boolean", DEF_THICK, t_Bool},
170 {(caddr_t *) &fade_p, "fade", "Boolean", DEF_FADE, t_Bool},
173 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
177 /* Tabs are bad, mmmkay? */
180 untabify (const char *string)
182 const char *ostring = string;
183 char *result = (char *) malloc ((strlen(string) * 8) + 1);
193 } while (col % TAB_WIDTH);
196 else if (*string == '\r' || *string == '\n')
201 else if (*string == '\010') /* backspace */
203 if (string > ostring)
218 strip (char *s, Bool leading, Bool trailing)
222 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
227 while (*s2 == ' ' || *s2 == '\t')
240 (This bit mostly cribbed from phosphor.c)
243 static void drain_input (sws_configuration *sc);
246 subproc_cb (XtPointer closure, int *source, XtInputId *id)
248 sws_configuration *sc = (sws_configuration *) closure;
254 launch_text_generator (sws_configuration *sc)
256 char *oprogram = get_string_resource ("program", "Program");
259 if (!strcasecmp(oprogram, "(default)"))
262 static int done_once = 0;
264 char *cmd = "cat /usr/src/linux/README";
265 if (!(done_once++) && !stat (cmd+4, &st))
269 oprogram = ZIPPY_PROGRAM;
272 program = (char *) malloc (strlen (oprogram) + 10);
273 strcpy (program, "( ");
274 strcat (program, oprogram);
275 strcat (program, " ) 2>&1");
277 if ((sc->pipe = popen (program, "r")))
280 XtAppAddInput (app, fileno (sc->pipe),
281 (XtPointer) (XtInputReadMask | XtInputExceptMask),
282 subproc_cb, (XtPointer) sc);
292 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
294 sws_configuration *sc = (sws_configuration *) closure;
295 launch_text_generator (sc);
299 /* When the subprocess has generated some output, this reads as much as it
300 can into sc->buf at sc->buf_tail.
303 drain_input (sws_configuration *sc)
305 if (sc->buf_tail < sizeof(sc->buf) - 2)
307 int target = sizeof(sc->buf) - sc->buf_tail - 2;
308 int n = read (fileno (sc->pipe),
309 (void *) (sc->buf + sc->buf_tail),
314 sc->buf[sc->buf_tail] = 0;
318 XtRemoveInput (sc->pipe_id);
323 /* If the process didn't print a terminating newline, add one. */
324 if (sc->buf_tail > 1 &&
325 sc->buf[sc->buf_tail-1] != '\n')
327 sc->buf[sc->buf_tail++] = '\n';
328 sc->buf[sc->buf_tail] = 0;
331 /* Then add one more, just for giggles. */
332 sc->buf[sc->buf_tail++] = '\n';
333 sc->buf[sc->buf_tail] = 0;
335 /* Set up a timer to re-launch the subproc in a bit. */
336 XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
337 relaunch_generator_timer,
344 /* Populates the sc->lines list with as many lines as are currently in
345 sc->buf (which was filled by drain_input().
348 get_more_lines (sws_configuration *sc)
352 while (sc->total_lines < max_lines)
354 if (s >= sc->buf + sc->buf_tail)
356 /* Reached end of buffer before end of line. Bail. */
360 if (*s == '\n' || col > sc->columns)
368 /* We wrapped -- try to back up to the previous word boundary. */
371 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
381 sc->lines[sc->total_lines] = (char *) malloc (L+1);
382 memcpy (sc->lines[sc->total_lines], sc->buf, L);
383 sc->lines[sc->total_lines][L] = 0;
386 char *t = sc->lines[sc->total_lines];
387 char *ut = untabify (t);
388 strip (ut, (alignment == 0), 1); /* if centering, strip
389 leading whitespace too */
390 sc->lines[sc->total_lines] = ut;
396 if (sc->buf_tail > (s - sc->buf))
398 int i = sc->buf_tail - (s - sc->buf);
399 memcpy (sc->buf, s, i);
401 sc->buf[sc->buf_tail] = 0;
408 sc->buf[sc->buf_tail] = 0;
416 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
424 draw_string (int x, int y, const char *s)
426 if (!s || !*s) return;
428 glTranslatef (x, y, 0);
431 glutStrokeCharacter (GLUT_FONT, *s++);
438 grid (double width, double height, double spacing, double z)
441 for (y = 0; y <= height/2; y += spacing)
444 glVertex3f(-width/2, y, z);
445 glVertex3f( width/2, y, z);
446 glVertex3f(-width/2, -y, z);
447 glVertex3f( width/2, -y, z);
450 for (x = 0; x <= width/2; x += spacing)
453 glVertex3f( x, -height/2, z);
454 glVertex3f( x, height/2, z);
455 glVertex3f(-x, -height/2, z);
456 glVertex3f(-x, height/2, z);
461 glVertex3f(-width, 0, z);
462 glVertex3f( width, 0, z);
463 glVertex3f(0, -height, z);
464 glVertex3f(0, height, z);
469 box (double width, double height, double depth)
471 glBegin(GL_LINE_LOOP);
472 glVertex3f(-width/2, -height/2, -depth/2);
473 glVertex3f(-width/2, height/2, -depth/2);
474 glVertex3f( width/2, height/2, -depth/2);
475 glVertex3f( width/2, -height/2, -depth/2);
477 glBegin(GL_LINE_LOOP);
478 glVertex3f(-width/2, -height/2, depth/2);
479 glVertex3f(-width/2, height/2, depth/2);
480 glVertex3f( width/2, height/2, depth/2);
481 glVertex3f( width/2, -height/2, depth/2);
483 glBegin(GL_LINE_LOOP);
484 glVertex3f(-width/2, -height/2, -depth/2);
485 glVertex3f(-width/2, -height/2, depth/2);
486 glVertex3f(-width/2, height/2, depth/2);
487 glVertex3f(-width/2, height/2, -depth/2);
489 glBegin(GL_LINE_LOOP);
490 glVertex3f( width/2, -height/2, -depth/2);
491 glVertex3f( width/2, -height/2, depth/2);
492 glVertex3f( width/2, height/2, depth/2);
493 glVertex3f( width/2, height/2, -depth/2);
498 glVertex3f(-width/2, height/2, depth/2);
499 glVertex3f(-width/2, -height/2, -depth/2);
501 glVertex3f( width/2, height/2, depth/2);
502 glVertex3f( width/2, -height/2, -depth/2);
504 glVertex3f(-width/2, -height/2, depth/2);
505 glVertex3f(-width/2, height/2, -depth/2);
507 glVertex3f( width/2, -height/2, depth/2);
508 glVertex3f( width/2, height/2, -depth/2);
514 /* Construct stars (number of stars is dependent on size of screen) */
516 init_stars (ModeInfo *mi, int width, int height)
518 sws_configuration *sc = &scs[MI_SCREEN(mi)];
520 int nstars = width * height / 320;
523 int steps = max_size / inc;
525 glDeleteLists (sc->star_list, 1);
526 sc->star_list = glGenLists (1);
527 glNewList (sc->star_list, GL_COMPILE);
529 glEnable(GL_POINT_SMOOTH);
531 for (j = 1; j <= steps; j++)
533 glPointSize(inc * j);
535 for (i = 0; i < nstars / steps; i++)
537 glColor3f (0.6 + frand(0.3),
540 glVertex2f (2 * width * (0.5 - frand(1.0)),
541 2 * height * (0.5 - frand(1.0)));
549 /* Window management, etc
552 reshape_sws (ModeInfo *mi, int width, int height)
554 sws_configuration *sc = &scs[MI_SCREEN(mi)];
556 /* Set up matrices for perspective text display
559 GLfloat desired_aspect = (GLfloat) 3/4;
560 int w = mi->xgwa.width;
561 int h = mi->xgwa.height;
564 h = w * desired_aspect;
567 glMatrixMode (GL_PROJECTION);
568 glViewport (0, 0, w, h);
570 glMatrixMode (GL_MODELVIEW);
572 gluPerspective (80.0, 1/desired_aspect, 10, 500000);
573 gluLookAt (0.0, 0.0, 4600.0,
576 glRotatef (-60.0, 1.0, 0.0, 0.0);
578 /* The above gives us an arena where the bottom edge of the screen is
579 represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
581 /* Now let's move the origin to the front of the screen. */
582 glTranslatef (0.0, -3140, 0.0);
584 /* And then let's scale so that the bottom of the screen is 1.0 wide. */
585 glScalef (4200, 4200, 4200);
589 /* Compute the height in pixels of the line at the bottom of the screen. */
591 GLdouble mm[17], pm[17];
593 GLfloat x = 0.5, y1 = 0, z = 0;
594 GLfloat y2 = sc->line_height;
595 GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
597 glGetDoublev (GL_MODELVIEW_MATRIX, mm);
598 glGetDoublev (GL_PROJECTION_MATRIX, pm);
599 glGetIntegerv (GL_VIEWPORT, vp);
600 gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
601 gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
602 sc->line_pixel_height = (wy2 - wy1);
606 /* Compute the best looking line thickness for the bottom line.
609 sc->line_thickness = 1.0;
611 sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
613 if (sc->line_thickness < 1.2)
614 sc->line_thickness = 1.0;
619 gl_init (ModeInfo *mi)
621 sws_configuration *sc = &scs[MI_SCREEN(mi)];
623 program = get_string_resource ("program", "Program");
625 glDisable (GL_LIGHTING);
626 glDisable (GL_DEPTH_TEST);
630 glEnable (GL_LINE_SMOOTH);
631 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
632 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
636 sc->text_list = glGenLists (1);
637 glNewList (sc->text_list, GL_COMPILE);
640 sc->star_list = glGenLists (1);
641 glNewList (sc->star_list, GL_COMPILE);
644 sc->line_thickness = 1.0;
649 init_sws (ModeInfo *mi)
653 sws_configuration *sc;
656 scs = (sws_configuration *)
657 calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
659 fprintf(stderr, "%s: out of memory\n", progname);
663 sc = &scs[MI_SCREEN(mi)];
664 sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
667 sc = &scs[MI_SCREEN(mi)];
669 if ((sc->glx_context = init_GL(mi)) != NULL) {
671 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
672 init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
676 font_height = GLUT_FONT->top - GLUT_FONT->bottom;
677 sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems
679 if (target_columns > 0)
681 sc->columns = target_columns;
686 font_size = BASE_FONT_SIZE;
687 sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
690 sc->font_scale /= sc->columns;
691 sc->line_height = font_height * sc->font_scale;
694 if (!wrap_p) sc->columns = 1000; /* wrap anyway, if it's absurdly long. */
696 sc->subproc_relaunch_delay = 2 * 1000;
697 sc->total_lines = max_lines-1;
700 star_spin = -star_spin;
702 if (!alignment_str || !*alignment_str ||
703 !strcasecmp(alignment_str, "left"))
705 else if (!strcasecmp(alignment_str, "center") ||
706 !strcasecmp(alignment_str, "middle"))
708 else if (!strcasecmp(alignment_str, "right"))
713 "%s: alignment must be left, center, or right, not \"%s\"\n",
714 progname, alignment_str);
718 launch_text_generator (sc);
723 draw_stars (ModeInfo *mi)
725 sws_configuration *sc = &scs[MI_SCREEN(mi)];
727 glMatrixMode (GL_PROJECTION);
732 glMatrixMode (GL_MODELVIEW);
736 glOrtho (-0.5 * MI_WIDTH(mi), 0.5 * MI_WIDTH(mi),
737 -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
739 glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
740 glCallList (sc->star_list);
744 glMatrixMode (GL_PROJECTION);
749 draw_sws (ModeInfo *mi)
751 sws_configuration *sc = &scs[MI_SCREEN(mi)];
752 Display *dpy = MI_DISPLAY(mi);
753 Window window = MI_WINDOW(mi);
756 if (!sc->glx_context)
759 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
760 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
762 glDrawBuffer (GL_BACK);
763 glXMakeCurrent (dpy, window, *(sc->glx_context));
765 glClear (GL_COLOR_BUFFER_BIT);
769 glMatrixMode (GL_MODELVIEW);
773 glColor3f (0.4, 0.4, 0.4);
775 glTranslatef(0, 1, 0);
777 glTranslatef(0, -1, 0);
779 grid (1, 1, sc->line_height, 0);
782 /* Scroll to current position */
783 glTranslatef (0.0, sc->intra_line_scroll, 0.0);
785 glColor3f (1.0, 1.0, 0.4);
786 glCallList (sc->text_list);
788 sc->intra_line_scroll += sc->line_height / scroll_steps;
790 if (sc->intra_line_scroll >= sc->line_height)
792 sc->intra_line_scroll = 0;
794 /* Drop the oldest line off the end. */
798 /* Scroll the contents of the lines array toward 0. */
799 if (sc->total_lines > 0)
801 for (i = 1; i < sc->total_lines; i++)
802 sc->lines[i-1] = sc->lines[i];
803 sc->lines[--sc->total_lines] = 0;
806 /* Bring in new lines at the end. */
809 if (sc->total_lines < max_lines)
810 /* Oops, we ran out of text... well, insert some blank lines
811 here so that new text still pulls in from the bottom of
812 the screen, isntead of just appearing. */
813 sc->total_lines = max_lines;
815 glDeleteLists (sc->text_list, 1);
816 sc->text_list = glGenLists (1);
817 glNewList (sc->text_list, GL_COMPILE);
819 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
820 for (i = 0; i < sc->total_lines; i++)
822 int offscreen_lines = 3;
825 double y = ((sc->total_lines - (i + offscreen_lines) - 1)
828 char *line = sc->lines[i];
831 sprintf(n, "%d:", i);
832 draw_string (x / sc->font_scale, y / sc->font_scale, n);
837 if (sc->line_thickness != 1)
839 int max_thick_lines = MAX_THICK_LINES;
840 GLfloat thinnest_line = 1.0;
841 GLfloat thickest_line = sc->line_thickness;
842 GLfloat range = thickest_line - thinnest_line;
845 int j = sc->total_lines - i - 1;
847 if (j > max_thick_lines)
848 thickness = thinnest_line;
850 thickness = (thinnest_line +
851 (range * ((max_thick_lines - j) /
852 (GLfloat) max_thick_lines)));
854 glLineWidth (thickness);
858 xoff = 1.0 - (glutStrokeLength(GLUT_FONT, line) * sc->font_scale);
864 double factor = 1.0 * i / sc->total_lines;
865 glColor3f (factor, factor, 0.5 * factor);
868 draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
876 if (mi->fps_p) do_fps (mi);
878 glXSwapBuffers(dpy, window);
880 sc->star_theta += star_spin;