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 */
93 #include "glutstroke.h"
94 #include "glut_roman.h"
95 #define GLUT_FONT (&glutStrokeRoman)
98 # include <sys/utsname.h>
99 #endif /* HAVE_UNAME */
103 GLXContext *glx_context;
105 GLuint text_list, star_list;
109 Time subproc_relaunch_delay;
120 double intra_line_scroll;
122 int line_pixel_height;
123 GLfloat line_thickness;
128 static sws_configuration *scs = NULL;
130 static char *program;
131 static int max_lines;
132 static int scroll_steps;
133 static float star_spin;
134 static float font_size;
135 static int target_columns;
140 static char *alignment_str;
141 static int alignment;
143 static XrmOptionDescRec opts[] = {
144 {"-program", ".starwars.program", XrmoptionSepArg, (caddr_t) 0 },
145 {"-lines", ".starwars.lines", XrmoptionSepArg, (caddr_t) 0 },
146 {"-steps", ".starwars.steps", XrmoptionSepArg, (caddr_t) 0 },
147 {"-spin", ".starwars.spin", XrmoptionSepArg, (caddr_t) 0 },
148 {"-size", ".starwars.fontSize", XrmoptionSepArg, (caddr_t) 0 },
149 {"-columns", ".starwars.columns", XrmoptionSepArg, (caddr_t) 0 },
150 {"-smooth", ".starwars.smooth", XrmoptionNoArg, (caddr_t) "True" },
151 {"-no-smooth", ".starwars.smooth", XrmoptionNoArg, (caddr_t) "False" },
152 {"-thick", ".starwars.thick", XrmoptionNoArg, (caddr_t) "True" },
153 {"-no-thick", ".starwars.thick", XrmoptionNoArg, (caddr_t) "False" },
154 {"-fade", ".starwars.fade", XrmoptionNoArg, (caddr_t) "True" },
155 {"-no-fade", ".starwars.fade", XrmoptionNoArg, (caddr_t) "False" },
156 {"-wrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "True" },
157 {"-no-wrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "False" },
158 {"-nowrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "False" },
159 {"-left", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Left" },
160 {"-right", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Right" },
161 {"-center", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Center" },
164 static argtype vars[] = {
165 {(caddr_t *) &program, "program", "Program", DEF_PROGRAM, t_String},
166 {(caddr_t *) &max_lines, "lines", "Integer", DEF_LINES, t_Int},
167 {(caddr_t *) &scroll_steps, "steps", "Integer", DEF_STEPS, t_Int},
168 {(caddr_t *) &star_spin, "spin", "Float", DEF_SPIN, t_Float},
169 {(caddr_t *) &font_size, "fontSize","Float", DEF_STEPS, t_Float},
170 {(caddr_t *) &target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
171 {(caddr_t *) &wrap_p, "lineWrap","Boolean", DEF_COLUMNS, t_Bool},
172 {(caddr_t *) &alignment_str, "alignment","Alignment",DEF_ALIGN, t_String},
173 {(caddr_t *) &smooth_p, "smooth", "Boolean", DEF_SMOOTH, t_Bool},
174 {(caddr_t *) &thick_p, "thick", "Boolean", DEF_THICK, t_Bool},
175 {(caddr_t *) &fade_p, "fade", "Boolean", DEF_FADE, t_Bool},
178 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
182 /* Tabs are bad, mmmkay? */
185 untabify (const char *string)
187 const char *ostring = string;
188 char *result = (char *) malloc ((strlen(string) * 8) + 1);
198 } while (col % TAB_WIDTH);
201 else if (*string == '\r' || *string == '\n')
206 else if (*string == '\010') /* backspace */
208 if (string > ostring)
223 strip (char *s, Bool leading, Bool trailing)
227 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
232 while (*s2 == ' ' || *s2 == '\t')
245 (This bit mostly cribbed from phosphor.c)
248 static void drain_input (sws_configuration *sc);
251 subproc_cb (XtPointer closure, int *source, XtInputId *id)
253 sws_configuration *sc = (sws_configuration *) closure;
259 launch_text_generator (sws_configuration *sc)
261 char *oprogram = get_string_resource ("program", "Program");
264 if (!strcasecmp(oprogram, "(default)"))
266 oprogram = FORTUNE_PROGRAM;
270 static int done_once = 0;
276 if (uname (&uts) == 0)
278 static char cmd[200];
280 /* strip version at the first non-digit-dash-dot, to
281 lose any "SMP" crap at the end. */
282 for (s = uts.release; *s; s++)
283 if (!isdigit(*s) && *s != '.' && *s != '-')
285 sprintf (cmd, "cat /usr/src/linux-%s/README", uts.release);
286 if (!stat (cmd+4, &st))
290 /* kernel source not installed? try X... */
291 strcpy (cmd, "cat /usr/X11R6/lib/X11/doc/README");
292 if (!stat (cmd+4, &st))
298 #endif /* __linux__ */
300 #ifdef __APPLE__ /* MacOS X + XDarwin */
302 static int done_once = 0;
306 static char *cmd = "cat /usr/X11R6/README";
307 if (!stat (cmd+4, &st))
311 #endif /* __APPLE__ */
314 program = (char *) malloc (strlen (oprogram) + 10);
315 strcpy (program, "( ");
316 strcat (program, oprogram);
317 strcat (program, " ) 2>&1");
319 if ((sc->pipe = popen (program, "r")))
322 XtAppAddInput (app, fileno (sc->pipe),
323 (XtPointer) (XtInputReadMask | XtInputExceptMask),
324 subproc_cb, (XtPointer) sc);
334 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
336 sws_configuration *sc = (sws_configuration *) closure;
337 launch_text_generator (sc);
341 /* When the subprocess has generated some output, this reads as much as it
342 can into sc->buf at sc->buf_tail.
345 drain_input (sws_configuration *sc)
347 if (sc->buf_tail < sizeof(sc->buf) - 2)
349 int target = sizeof(sc->buf) - sc->buf_tail - 2;
350 int n = read (fileno (sc->pipe),
351 (void *) (sc->buf + sc->buf_tail),
356 sc->buf[sc->buf_tail] = 0;
360 XtRemoveInput (sc->pipe_id);
365 /* If the process didn't print a terminating newline, add one. */
366 if (sc->buf_tail > 1 &&
367 sc->buf[sc->buf_tail-1] != '\n')
369 sc->buf[sc->buf_tail++] = '\n';
370 sc->buf[sc->buf_tail] = 0;
373 /* Then add one more, just for giggles. */
374 sc->buf[sc->buf_tail++] = '\n';
375 sc->buf[sc->buf_tail] = 0;
377 /* Set up a timer to re-launch the subproc in a bit. */
378 XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
379 relaunch_generator_timer,
386 /* Populates the sc->lines list with as many lines as are currently in
387 sc->buf (which was filled by drain_input().
390 get_more_lines (sws_configuration *sc)
394 while (sc->total_lines < max_lines)
396 if (s >= sc->buf + sc->buf_tail)
398 /* Reached end of buffer before end of line. Bail. */
402 if (*s == '\r' || *s == '\n' || col > sc->columns)
406 if (*s == '\r' || *s == '\n')
408 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
415 /* We wrapped -- try to back up to the previous word boundary. */
418 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
428 sc->lines[sc->total_lines] = (char *) malloc (L+1);
429 memcpy (sc->lines[sc->total_lines], sc->buf, L);
430 sc->lines[sc->total_lines][L] = 0;
433 char *t = sc->lines[sc->total_lines];
434 char *ut = untabify (t);
435 strip (ut, (alignment == 0), 1); /* if centering, strip
436 leading whitespace too */
437 sc->lines[sc->total_lines] = ut;
443 if (sc->buf_tail > (s - sc->buf))
445 int i = sc->buf_tail - (s - sc->buf);
446 memmove (sc->buf, s, i);
448 sc->buf[sc->buf_tail] = 0;
455 sc->buf[sc->buf_tail] = 0;
463 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
471 draw_string (int x, int y, const char *s)
473 if (!s || !*s) return;
475 glTranslatef (x, y, 0);
478 glutStrokeCharacter (GLUT_FONT, *s++);
485 grid (double width, double height, double spacing, double z)
488 for (y = 0; y <= height/2; y += spacing)
491 glVertex3f(-width/2, y, z);
492 glVertex3f( width/2, y, z);
493 glVertex3f(-width/2, -y, z);
494 glVertex3f( width/2, -y, z);
497 for (x = 0; x <= width/2; x += spacing)
500 glVertex3f( x, -height/2, z);
501 glVertex3f( x, height/2, z);
502 glVertex3f(-x, -height/2, z);
503 glVertex3f(-x, height/2, z);
508 glVertex3f(-width, 0, z);
509 glVertex3f( width, 0, z);
510 glVertex3f(0, -height, z);
511 glVertex3f(0, height, z);
516 box (double width, double height, double depth)
518 glBegin(GL_LINE_LOOP);
519 glVertex3f(-width/2, -height/2, -depth/2);
520 glVertex3f(-width/2, height/2, -depth/2);
521 glVertex3f( width/2, height/2, -depth/2);
522 glVertex3f( width/2, -height/2, -depth/2);
524 glBegin(GL_LINE_LOOP);
525 glVertex3f(-width/2, -height/2, depth/2);
526 glVertex3f(-width/2, height/2, depth/2);
527 glVertex3f( width/2, height/2, depth/2);
528 glVertex3f( width/2, -height/2, depth/2);
530 glBegin(GL_LINE_LOOP);
531 glVertex3f(-width/2, -height/2, -depth/2);
532 glVertex3f(-width/2, -height/2, depth/2);
533 glVertex3f(-width/2, height/2, depth/2);
534 glVertex3f(-width/2, height/2, -depth/2);
536 glBegin(GL_LINE_LOOP);
537 glVertex3f( width/2, -height/2, -depth/2);
538 glVertex3f( width/2, -height/2, depth/2);
539 glVertex3f( width/2, height/2, depth/2);
540 glVertex3f( width/2, height/2, -depth/2);
545 glVertex3f(-width/2, height/2, depth/2);
546 glVertex3f(-width/2, -height/2, -depth/2);
548 glVertex3f( width/2, height/2, depth/2);
549 glVertex3f( width/2, -height/2, -depth/2);
551 glVertex3f(-width/2, -height/2, depth/2);
552 glVertex3f(-width/2, height/2, -depth/2);
554 glVertex3f( width/2, -height/2, depth/2);
555 glVertex3f( width/2, height/2, -depth/2);
561 /* Construct stars (number of stars is dependent on size of screen) */
563 init_stars (ModeInfo *mi, int width, int height)
565 sws_configuration *sc = &scs[MI_SCREEN(mi)];
567 int nstars = width * height / 320;
570 int steps = max_size / inc;
572 glDeleteLists (sc->star_list, 1);
573 sc->star_list = glGenLists (1);
574 glNewList (sc->star_list, GL_COMPILE);
576 glEnable(GL_POINT_SMOOTH);
578 for (j = 1; j <= steps; j++)
580 glPointSize(inc * j);
582 for (i = 0; i < nstars / steps; i++)
584 glColor3f (0.6 + frand(0.3),
587 glVertex2f (2 * width * (0.5 - frand(1.0)),
588 2 * height * (0.5 - frand(1.0)));
596 /* Window management, etc
599 reshape_sws (ModeInfo *mi, int width, int height)
601 sws_configuration *sc = &scs[MI_SCREEN(mi)];
603 /* Set up matrices for perspective text display
606 GLfloat desired_aspect = (GLfloat) 3/4;
607 int w = mi->xgwa.width;
608 int h = mi->xgwa.height;
611 h = w * desired_aspect;
614 glMatrixMode (GL_PROJECTION);
615 glViewport (0, 0, w, h);
617 glMatrixMode (GL_MODELVIEW);
619 gluPerspective (80.0, 1/desired_aspect, 10, 500000);
620 gluLookAt (0.0, 0.0, 4600.0,
623 glRotatef (-60.0, 1.0, 0.0, 0.0);
625 /* The above gives us an arena where the bottom edge of the screen is
626 represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
628 /* Now let's move the origin to the front of the screen. */
629 glTranslatef (0.0, -3140, 0.0);
631 /* And then let's scale so that the bottom of the screen is 1.0 wide. */
632 glScalef (4200, 4200, 4200);
636 /* Compute the height in pixels of the line at the bottom of the screen. */
638 GLdouble mm[17], pm[17];
640 GLfloat x = 0.5, y1 = 0, z = 0;
641 GLfloat y2 = sc->line_height;
642 GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
644 glGetDoublev (GL_MODELVIEW_MATRIX, mm);
645 glGetDoublev (GL_PROJECTION_MATRIX, pm);
646 glGetIntegerv (GL_VIEWPORT, vp);
647 gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
648 gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
649 sc->line_pixel_height = (wy2 - wy1);
653 /* Compute the best looking line thickness for the bottom line.
656 sc->line_thickness = 1.0;
658 sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
660 if (sc->line_thickness < 1.2)
661 sc->line_thickness = 1.0;
666 gl_init (ModeInfo *mi)
668 sws_configuration *sc = &scs[MI_SCREEN(mi)];
670 program = get_string_resource ("program", "Program");
672 glDisable (GL_LIGHTING);
673 glDisable (GL_DEPTH_TEST);
677 glEnable (GL_LINE_SMOOTH);
678 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
679 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
683 sc->text_list = glGenLists (1);
684 glNewList (sc->text_list, GL_COMPILE);
687 sc->star_list = glGenLists (1);
688 glNewList (sc->star_list, GL_COMPILE);
691 sc->line_thickness = 1.0;
696 init_sws (ModeInfo *mi)
700 sws_configuration *sc;
703 scs = (sws_configuration *)
704 calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
706 fprintf(stderr, "%s: out of memory\n", progname);
710 sc = &scs[MI_SCREEN(mi)];
711 sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
714 sc = &scs[MI_SCREEN(mi)];
716 if ((sc->glx_context = init_GL(mi)) != NULL) {
718 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
719 init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
723 font_height = GLUT_FONT->top - GLUT_FONT->bottom;
724 sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems
726 if (target_columns > 0)
728 sc->columns = target_columns;
733 font_size = BASE_FONT_SIZE;
734 sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
737 sc->font_scale /= sc->columns;
738 sc->line_height = font_height * sc->font_scale;
741 if (!wrap_p) sc->columns = 1000; /* wrap anyway, if it's absurdly long. */
743 sc->subproc_relaunch_delay = 2 * 1000;
744 sc->total_lines = max_lines-1;
747 star_spin = -star_spin;
749 if (!alignment_str || !*alignment_str ||
750 !strcasecmp(alignment_str, "left"))
752 else if (!strcasecmp(alignment_str, "center") ||
753 !strcasecmp(alignment_str, "middle"))
755 else if (!strcasecmp(alignment_str, "right"))
760 "%s: alignment must be left, center, or right, not \"%s\"\n",
761 progname, alignment_str);
765 launch_text_generator (sc);
767 /* one more reshape, after line_height has been computed */
768 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
773 draw_stars (ModeInfo *mi)
775 sws_configuration *sc = &scs[MI_SCREEN(mi)];
777 glMatrixMode (GL_PROJECTION);
782 glMatrixMode (GL_MODELVIEW);
786 glOrtho (-0.5 * MI_WIDTH(mi), 0.5 * MI_WIDTH(mi),
787 -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
789 glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
790 glCallList (sc->star_list);
794 glMatrixMode (GL_PROJECTION);
799 draw_sws (ModeInfo *mi)
801 sws_configuration *sc = &scs[MI_SCREEN(mi)];
802 Display *dpy = MI_DISPLAY(mi);
803 Window window = MI_WINDOW(mi);
806 if (!sc->glx_context)
809 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
810 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
812 glDrawBuffer (GL_BACK);
813 glXMakeCurrent (dpy, window, *(sc->glx_context));
815 glClear (GL_COLOR_BUFFER_BIT);
819 glMatrixMode (GL_MODELVIEW);
823 glColor3f (0.4, 0.4, 0.4);
825 glTranslatef(0, 1, 0);
827 glTranslatef(0, -1, 0);
829 grid (1, 1, sc->line_height, 0);
832 /* Scroll to current position */
833 glTranslatef (0.0, sc->intra_line_scroll, 0.0);
835 glColor3f (1.0, 1.0, 0.4);
836 glCallList (sc->text_list);
838 sc->intra_line_scroll += sc->line_height / scroll_steps;
840 if (sc->intra_line_scroll >= sc->line_height)
842 sc->intra_line_scroll = 0;
844 /* Drop the oldest line off the end. */
848 /* Scroll the contents of the lines array toward 0. */
849 if (sc->total_lines > 0)
851 for (i = 1; i < sc->total_lines; i++)
852 sc->lines[i-1] = sc->lines[i];
853 sc->lines[--sc->total_lines] = 0;
856 /* Bring in new lines at the end. */
859 if (sc->total_lines < max_lines)
860 /* Oops, we ran out of text... well, insert some blank lines
861 here so that new text still pulls in from the bottom of
862 the screen, isntead of just appearing. */
863 sc->total_lines = max_lines;
865 glDeleteLists (sc->text_list, 1);
866 sc->text_list = glGenLists (1);
867 glNewList (sc->text_list, GL_COMPILE);
869 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
870 for (i = 0; i < sc->total_lines; i++)
872 int offscreen_lines = 3;
875 double y = ((sc->total_lines - (i + offscreen_lines) - 1)
878 char *line = sc->lines[i];
881 sprintf(n, "%d:", i);
882 draw_string (x / sc->font_scale, y / sc->font_scale, n);
887 if (sc->line_thickness != 1)
889 int max_thick_lines = MAX_THICK_LINES;
890 GLfloat thinnest_line = 1.0;
891 GLfloat thickest_line = sc->line_thickness;
892 GLfloat range = thickest_line - thinnest_line;
895 int j = sc->total_lines - i - 1;
897 if (j > max_thick_lines)
898 thickness = thinnest_line;
900 thickness = (thinnest_line +
901 (range * ((max_thick_lines - j) /
902 (GLfloat) max_thick_lines)));
904 glLineWidth (thickness);
908 xoff = 1.0 - (glutStrokeLength(GLUT_FONT,
909 (unsigned char *) line)
916 double factor = 1.0 * i / sc->total_lines;
917 glColor3f (factor, factor, 0.5 * factor);
920 draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
928 if (mi->fps_p) do_fps (mi);
930 glXSwapBuffers(dpy, window);
932 sc->star_theta += star_spin;