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))
291 #endif /* __linux__ */
293 #ifdef __APPLE__ /* MacOS X + XDarwin */
295 static int done_once = 0;
299 static char *cmd = "cat /usr/X11R6/README";
300 if (!stat (cmd+4, &st))
304 #endif /* __APPLE__ */
307 program = (char *) malloc (strlen (oprogram) + 10);
308 strcpy (program, "( ");
309 strcat (program, oprogram);
310 strcat (program, " ) 2>&1");
312 if ((sc->pipe = popen (program, "r")))
315 XtAppAddInput (app, fileno (sc->pipe),
316 (XtPointer) (XtInputReadMask | XtInputExceptMask),
317 subproc_cb, (XtPointer) sc);
327 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
329 sws_configuration *sc = (sws_configuration *) closure;
330 launch_text_generator (sc);
334 /* When the subprocess has generated some output, this reads as much as it
335 can into sc->buf at sc->buf_tail.
338 drain_input (sws_configuration *sc)
340 if (sc->buf_tail < sizeof(sc->buf) - 2)
342 int target = sizeof(sc->buf) - sc->buf_tail - 2;
343 int n = read (fileno (sc->pipe),
344 (void *) (sc->buf + sc->buf_tail),
349 sc->buf[sc->buf_tail] = 0;
353 XtRemoveInput (sc->pipe_id);
358 /* If the process didn't print a terminating newline, add one. */
359 if (sc->buf_tail > 1 &&
360 sc->buf[sc->buf_tail-1] != '\n')
362 sc->buf[sc->buf_tail++] = '\n';
363 sc->buf[sc->buf_tail] = 0;
366 /* Then add one more, just for giggles. */
367 sc->buf[sc->buf_tail++] = '\n';
368 sc->buf[sc->buf_tail] = 0;
370 /* Set up a timer to re-launch the subproc in a bit. */
371 XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
372 relaunch_generator_timer,
379 /* Populates the sc->lines list with as many lines as are currently in
380 sc->buf (which was filled by drain_input().
383 get_more_lines (sws_configuration *sc)
387 while (sc->total_lines < max_lines)
389 if (s >= sc->buf + sc->buf_tail)
391 /* Reached end of buffer before end of line. Bail. */
395 if (*s == '\r' || *s == '\n' || col > sc->columns)
399 if (*s == '\r' || *s == '\n')
401 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
408 /* We wrapped -- try to back up to the previous word boundary. */
411 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
421 sc->lines[sc->total_lines] = (char *) malloc (L+1);
422 memcpy (sc->lines[sc->total_lines], sc->buf, L);
423 sc->lines[sc->total_lines][L] = 0;
426 char *t = sc->lines[sc->total_lines];
427 char *ut = untabify (t);
428 strip (ut, (alignment == 0), 1); /* if centering, strip
429 leading whitespace too */
430 sc->lines[sc->total_lines] = ut;
436 if (sc->buf_tail > (s - sc->buf))
438 int i = sc->buf_tail - (s - sc->buf);
439 memcpy (sc->buf, s, i);
441 sc->buf[sc->buf_tail] = 0;
448 sc->buf[sc->buf_tail] = 0;
456 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
464 draw_string (int x, int y, const char *s)
466 if (!s || !*s) return;
468 glTranslatef (x, y, 0);
471 glutStrokeCharacter (GLUT_FONT, *s++);
478 grid (double width, double height, double spacing, double z)
481 for (y = 0; y <= height/2; y += spacing)
484 glVertex3f(-width/2, y, z);
485 glVertex3f( width/2, y, z);
486 glVertex3f(-width/2, -y, z);
487 glVertex3f( width/2, -y, z);
490 for (x = 0; x <= width/2; x += spacing)
493 glVertex3f( x, -height/2, z);
494 glVertex3f( x, height/2, z);
495 glVertex3f(-x, -height/2, z);
496 glVertex3f(-x, height/2, z);
501 glVertex3f(-width, 0, z);
502 glVertex3f( width, 0, z);
503 glVertex3f(0, -height, z);
504 glVertex3f(0, height, z);
509 box (double width, double height, double depth)
511 glBegin(GL_LINE_LOOP);
512 glVertex3f(-width/2, -height/2, -depth/2);
513 glVertex3f(-width/2, height/2, -depth/2);
514 glVertex3f( width/2, height/2, -depth/2);
515 glVertex3f( width/2, -height/2, -depth/2);
517 glBegin(GL_LINE_LOOP);
518 glVertex3f(-width/2, -height/2, depth/2);
519 glVertex3f(-width/2, height/2, depth/2);
520 glVertex3f( width/2, height/2, depth/2);
521 glVertex3f( width/2, -height/2, depth/2);
523 glBegin(GL_LINE_LOOP);
524 glVertex3f(-width/2, -height/2, -depth/2);
525 glVertex3f(-width/2, -height/2, depth/2);
526 glVertex3f(-width/2, height/2, depth/2);
527 glVertex3f(-width/2, height/2, -depth/2);
529 glBegin(GL_LINE_LOOP);
530 glVertex3f( width/2, -height/2, -depth/2);
531 glVertex3f( width/2, -height/2, depth/2);
532 glVertex3f( width/2, height/2, depth/2);
533 glVertex3f( width/2, height/2, -depth/2);
538 glVertex3f(-width/2, height/2, depth/2);
539 glVertex3f(-width/2, -height/2, -depth/2);
541 glVertex3f( width/2, height/2, depth/2);
542 glVertex3f( width/2, -height/2, -depth/2);
544 glVertex3f(-width/2, -height/2, depth/2);
545 glVertex3f(-width/2, height/2, -depth/2);
547 glVertex3f( width/2, -height/2, depth/2);
548 glVertex3f( width/2, height/2, -depth/2);
554 /* Construct stars (number of stars is dependent on size of screen) */
556 init_stars (ModeInfo *mi, int width, int height)
558 sws_configuration *sc = &scs[MI_SCREEN(mi)];
560 int nstars = width * height / 320;
563 int steps = max_size / inc;
565 glDeleteLists (sc->star_list, 1);
566 sc->star_list = glGenLists (1);
567 glNewList (sc->star_list, GL_COMPILE);
569 glEnable(GL_POINT_SMOOTH);
571 for (j = 1; j <= steps; j++)
573 glPointSize(inc * j);
575 for (i = 0; i < nstars / steps; i++)
577 glColor3f (0.6 + frand(0.3),
580 glVertex2f (2 * width * (0.5 - frand(1.0)),
581 2 * height * (0.5 - frand(1.0)));
589 /* Window management, etc
592 reshape_sws (ModeInfo *mi, int width, int height)
594 sws_configuration *sc = &scs[MI_SCREEN(mi)];
596 /* Set up matrices for perspective text display
599 GLfloat desired_aspect = (GLfloat) 3/4;
600 int w = mi->xgwa.width;
601 int h = mi->xgwa.height;
604 h = w * desired_aspect;
607 glMatrixMode (GL_PROJECTION);
608 glViewport (0, 0, w, h);
610 glMatrixMode (GL_MODELVIEW);
612 gluPerspective (80.0, 1/desired_aspect, 10, 500000);
613 gluLookAt (0.0, 0.0, 4600.0,
616 glRotatef (-60.0, 1.0, 0.0, 0.0);
618 /* The above gives us an arena where the bottom edge of the screen is
619 represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
621 /* Now let's move the origin to the front of the screen. */
622 glTranslatef (0.0, -3140, 0.0);
624 /* And then let's scale so that the bottom of the screen is 1.0 wide. */
625 glScalef (4200, 4200, 4200);
629 /* Compute the height in pixels of the line at the bottom of the screen. */
631 GLdouble mm[17], pm[17];
633 GLfloat x = 0.5, y1 = 0, z = 0;
634 GLfloat y2 = sc->line_height;
635 GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
637 glGetDoublev (GL_MODELVIEW_MATRIX, mm);
638 glGetDoublev (GL_PROJECTION_MATRIX, pm);
639 glGetIntegerv (GL_VIEWPORT, vp);
640 gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
641 gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
642 sc->line_pixel_height = (wy2 - wy1);
646 /* Compute the best looking line thickness for the bottom line.
649 sc->line_thickness = 1.0;
651 sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
653 if (sc->line_thickness < 1.2)
654 sc->line_thickness = 1.0;
659 gl_init (ModeInfo *mi)
661 sws_configuration *sc = &scs[MI_SCREEN(mi)];
663 program = get_string_resource ("program", "Program");
665 glDisable (GL_LIGHTING);
666 glDisable (GL_DEPTH_TEST);
670 glEnable (GL_LINE_SMOOTH);
671 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
672 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
676 sc->text_list = glGenLists (1);
677 glNewList (sc->text_list, GL_COMPILE);
680 sc->star_list = glGenLists (1);
681 glNewList (sc->star_list, GL_COMPILE);
684 sc->line_thickness = 1.0;
689 init_sws (ModeInfo *mi)
693 sws_configuration *sc;
696 scs = (sws_configuration *)
697 calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
699 fprintf(stderr, "%s: out of memory\n", progname);
703 sc = &scs[MI_SCREEN(mi)];
704 sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
707 sc = &scs[MI_SCREEN(mi)];
709 if ((sc->glx_context = init_GL(mi)) != NULL) {
711 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
712 init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
716 font_height = GLUT_FONT->top - GLUT_FONT->bottom;
717 sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems
719 if (target_columns > 0)
721 sc->columns = target_columns;
726 font_size = BASE_FONT_SIZE;
727 sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
730 sc->font_scale /= sc->columns;
731 sc->line_height = font_height * sc->font_scale;
734 if (!wrap_p) sc->columns = 1000; /* wrap anyway, if it's absurdly long. */
736 sc->subproc_relaunch_delay = 2 * 1000;
737 sc->total_lines = max_lines-1;
740 star_spin = -star_spin;
742 if (!alignment_str || !*alignment_str ||
743 !strcasecmp(alignment_str, "left"))
745 else if (!strcasecmp(alignment_str, "center") ||
746 !strcasecmp(alignment_str, "middle"))
748 else if (!strcasecmp(alignment_str, "right"))
753 "%s: alignment must be left, center, or right, not \"%s\"\n",
754 progname, alignment_str);
758 launch_text_generator (sc);
760 /* one more reshape, after line_height has been computed */
761 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
766 draw_stars (ModeInfo *mi)
768 sws_configuration *sc = &scs[MI_SCREEN(mi)];
770 glMatrixMode (GL_PROJECTION);
775 glMatrixMode (GL_MODELVIEW);
779 glOrtho (-0.5 * MI_WIDTH(mi), 0.5 * MI_WIDTH(mi),
780 -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
782 glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
783 glCallList (sc->star_list);
787 glMatrixMode (GL_PROJECTION);
792 draw_sws (ModeInfo *mi)
794 sws_configuration *sc = &scs[MI_SCREEN(mi)];
795 Display *dpy = MI_DISPLAY(mi);
796 Window window = MI_WINDOW(mi);
799 if (!sc->glx_context)
802 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
803 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
805 glDrawBuffer (GL_BACK);
806 glXMakeCurrent (dpy, window, *(sc->glx_context));
808 glClear (GL_COLOR_BUFFER_BIT);
812 glMatrixMode (GL_MODELVIEW);
816 glColor3f (0.4, 0.4, 0.4);
818 glTranslatef(0, 1, 0);
820 glTranslatef(0, -1, 0);
822 grid (1, 1, sc->line_height, 0);
825 /* Scroll to current position */
826 glTranslatef (0.0, sc->intra_line_scroll, 0.0);
828 glColor3f (1.0, 1.0, 0.4);
829 glCallList (sc->text_list);
831 sc->intra_line_scroll += sc->line_height / scroll_steps;
833 if (sc->intra_line_scroll >= sc->line_height)
835 sc->intra_line_scroll = 0;
837 /* Drop the oldest line off the end. */
841 /* Scroll the contents of the lines array toward 0. */
842 if (sc->total_lines > 0)
844 for (i = 1; i < sc->total_lines; i++)
845 sc->lines[i-1] = sc->lines[i];
846 sc->lines[--sc->total_lines] = 0;
849 /* Bring in new lines at the end. */
852 if (sc->total_lines < max_lines)
853 /* Oops, we ran out of text... well, insert some blank lines
854 here so that new text still pulls in from the bottom of
855 the screen, isntead of just appearing. */
856 sc->total_lines = max_lines;
858 glDeleteLists (sc->text_list, 1);
859 sc->text_list = glGenLists (1);
860 glNewList (sc->text_list, GL_COMPILE);
862 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
863 for (i = 0; i < sc->total_lines; i++)
865 int offscreen_lines = 3;
868 double y = ((sc->total_lines - (i + offscreen_lines) - 1)
871 char *line = sc->lines[i];
874 sprintf(n, "%d:", i);
875 draw_string (x / sc->font_scale, y / sc->font_scale, n);
880 if (sc->line_thickness != 1)
882 int max_thick_lines = MAX_THICK_LINES;
883 GLfloat thinnest_line = 1.0;
884 GLfloat thickest_line = sc->line_thickness;
885 GLfloat range = thickest_line - thinnest_line;
888 int j = sc->total_lines - i - 1;
890 if (j > max_thick_lines)
891 thickness = thinnest_line;
893 thickness = (thinnest_line +
894 (range * ((max_thick_lines - j) /
895 (GLfloat) max_thick_lines)));
897 glLineWidth (thickness);
901 xoff = 1.0 - (glutStrokeLength(GLUT_FONT, line) * sc->font_scale);
907 double factor = 1.0 * i / sc->total_lines;
908 glColor3f (factor, factor, 0.5 * factor);
911 draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
919 if (mi->fps_p) do_fps (mi);
921 glXSwapBuffers(dpy, window);
923 sc->star_theta += star_spin;