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__ */
294 program = (char *) malloc (strlen (oprogram) + 10);
295 strcpy (program, "( ");
296 strcat (program, oprogram);
297 strcat (program, " ) 2>&1");
299 if ((sc->pipe = popen (program, "r")))
302 XtAppAddInput (app, fileno (sc->pipe),
303 (XtPointer) (XtInputReadMask | XtInputExceptMask),
304 subproc_cb, (XtPointer) sc);
314 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
316 sws_configuration *sc = (sws_configuration *) closure;
317 launch_text_generator (sc);
321 /* When the subprocess has generated some output, this reads as much as it
322 can into sc->buf at sc->buf_tail.
325 drain_input (sws_configuration *sc)
327 if (sc->buf_tail < sizeof(sc->buf) - 2)
329 int target = sizeof(sc->buf) - sc->buf_tail - 2;
330 int n = read (fileno (sc->pipe),
331 (void *) (sc->buf + sc->buf_tail),
336 sc->buf[sc->buf_tail] = 0;
340 XtRemoveInput (sc->pipe_id);
345 /* If the process didn't print a terminating newline, add one. */
346 if (sc->buf_tail > 1 &&
347 sc->buf[sc->buf_tail-1] != '\n')
349 sc->buf[sc->buf_tail++] = '\n';
350 sc->buf[sc->buf_tail] = 0;
353 /* Then add one more, just for giggles. */
354 sc->buf[sc->buf_tail++] = '\n';
355 sc->buf[sc->buf_tail] = 0;
357 /* Set up a timer to re-launch the subproc in a bit. */
358 XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
359 relaunch_generator_timer,
366 /* Populates the sc->lines list with as many lines as are currently in
367 sc->buf (which was filled by drain_input().
370 get_more_lines (sws_configuration *sc)
374 while (sc->total_lines < max_lines)
376 if (s >= sc->buf + sc->buf_tail)
378 /* Reached end of buffer before end of line. Bail. */
382 if (*s == '\r' || *s == '\n' || col > sc->columns)
386 if (*s == '\r' || *s == '\n')
388 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
395 /* We wrapped -- try to back up to the previous word boundary. */
398 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
408 sc->lines[sc->total_lines] = (char *) malloc (L+1);
409 memcpy (sc->lines[sc->total_lines], sc->buf, L);
410 sc->lines[sc->total_lines][L] = 0;
413 char *t = sc->lines[sc->total_lines];
414 char *ut = untabify (t);
415 strip (ut, (alignment == 0), 1); /* if centering, strip
416 leading whitespace too */
417 sc->lines[sc->total_lines] = ut;
423 if (sc->buf_tail > (s - sc->buf))
425 int i = sc->buf_tail - (s - sc->buf);
426 memcpy (sc->buf, s, i);
428 sc->buf[sc->buf_tail] = 0;
435 sc->buf[sc->buf_tail] = 0;
443 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
451 draw_string (int x, int y, const char *s)
453 if (!s || !*s) return;
455 glTranslatef (x, y, 0);
458 glutStrokeCharacter (GLUT_FONT, *s++);
465 grid (double width, double height, double spacing, double z)
468 for (y = 0; y <= height/2; y += spacing)
471 glVertex3f(-width/2, y, z);
472 glVertex3f( width/2, y, z);
473 glVertex3f(-width/2, -y, z);
474 glVertex3f( width/2, -y, z);
477 for (x = 0; x <= width/2; x += spacing)
480 glVertex3f( x, -height/2, z);
481 glVertex3f( x, height/2, z);
482 glVertex3f(-x, -height/2, z);
483 glVertex3f(-x, height/2, z);
488 glVertex3f(-width, 0, z);
489 glVertex3f( width, 0, z);
490 glVertex3f(0, -height, z);
491 glVertex3f(0, height, z);
496 box (double width, double height, double depth)
498 glBegin(GL_LINE_LOOP);
499 glVertex3f(-width/2, -height/2, -depth/2);
500 glVertex3f(-width/2, height/2, -depth/2);
501 glVertex3f( width/2, height/2, -depth/2);
502 glVertex3f( width/2, -height/2, -depth/2);
504 glBegin(GL_LINE_LOOP);
505 glVertex3f(-width/2, -height/2, depth/2);
506 glVertex3f(-width/2, height/2, depth/2);
507 glVertex3f( width/2, height/2, depth/2);
508 glVertex3f( width/2, -height/2, depth/2);
510 glBegin(GL_LINE_LOOP);
511 glVertex3f(-width/2, -height/2, -depth/2);
512 glVertex3f(-width/2, -height/2, depth/2);
513 glVertex3f(-width/2, height/2, depth/2);
514 glVertex3f(-width/2, height/2, -depth/2);
516 glBegin(GL_LINE_LOOP);
517 glVertex3f( width/2, -height/2, -depth/2);
518 glVertex3f( width/2, -height/2, depth/2);
519 glVertex3f( width/2, height/2, depth/2);
520 glVertex3f( width/2, height/2, -depth/2);
525 glVertex3f(-width/2, height/2, depth/2);
526 glVertex3f(-width/2, -height/2, -depth/2);
528 glVertex3f( width/2, height/2, depth/2);
529 glVertex3f( width/2, -height/2, -depth/2);
531 glVertex3f(-width/2, -height/2, depth/2);
532 glVertex3f(-width/2, height/2, -depth/2);
534 glVertex3f( width/2, -height/2, depth/2);
535 glVertex3f( width/2, height/2, -depth/2);
541 /* Construct stars (number of stars is dependent on size of screen) */
543 init_stars (ModeInfo *mi, int width, int height)
545 sws_configuration *sc = &scs[MI_SCREEN(mi)];
547 int nstars = width * height / 320;
550 int steps = max_size / inc;
552 glDeleteLists (sc->star_list, 1);
553 sc->star_list = glGenLists (1);
554 glNewList (sc->star_list, GL_COMPILE);
556 glEnable(GL_POINT_SMOOTH);
558 for (j = 1; j <= steps; j++)
560 glPointSize(inc * j);
562 for (i = 0; i < nstars / steps; i++)
564 glColor3f (0.6 + frand(0.3),
567 glVertex2f (2 * width * (0.5 - frand(1.0)),
568 2 * height * (0.5 - frand(1.0)));
576 /* Window management, etc
579 reshape_sws (ModeInfo *mi, int width, int height)
581 sws_configuration *sc = &scs[MI_SCREEN(mi)];
583 /* Set up matrices for perspective text display
586 GLfloat desired_aspect = (GLfloat) 3/4;
587 int w = mi->xgwa.width;
588 int h = mi->xgwa.height;
591 h = w * desired_aspect;
594 glMatrixMode (GL_PROJECTION);
595 glViewport (0, 0, w, h);
597 glMatrixMode (GL_MODELVIEW);
599 gluPerspective (80.0, 1/desired_aspect, 10, 500000);
600 gluLookAt (0.0, 0.0, 4600.0,
603 glRotatef (-60.0, 1.0, 0.0, 0.0);
605 /* The above gives us an arena where the bottom edge of the screen is
606 represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
608 /* Now let's move the origin to the front of the screen. */
609 glTranslatef (0.0, -3140, 0.0);
611 /* And then let's scale so that the bottom of the screen is 1.0 wide. */
612 glScalef (4200, 4200, 4200);
616 /* Compute the height in pixels of the line at the bottom of the screen. */
618 GLdouble mm[17], pm[17];
620 GLfloat x = 0.5, y1 = 0, z = 0;
621 GLfloat y2 = sc->line_height;
622 GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
624 glGetDoublev (GL_MODELVIEW_MATRIX, mm);
625 glGetDoublev (GL_PROJECTION_MATRIX, pm);
626 glGetIntegerv (GL_VIEWPORT, vp);
627 gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
628 gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
629 sc->line_pixel_height = (wy2 - wy1);
633 /* Compute the best looking line thickness for the bottom line.
636 sc->line_thickness = 1.0;
638 sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
640 if (sc->line_thickness < 1.2)
641 sc->line_thickness = 1.0;
646 gl_init (ModeInfo *mi)
648 sws_configuration *sc = &scs[MI_SCREEN(mi)];
650 program = get_string_resource ("program", "Program");
652 glDisable (GL_LIGHTING);
653 glDisable (GL_DEPTH_TEST);
657 glEnable (GL_LINE_SMOOTH);
658 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
659 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
663 sc->text_list = glGenLists (1);
664 glNewList (sc->text_list, GL_COMPILE);
667 sc->star_list = glGenLists (1);
668 glNewList (sc->star_list, GL_COMPILE);
671 sc->line_thickness = 1.0;
676 init_sws (ModeInfo *mi)
680 sws_configuration *sc;
683 scs = (sws_configuration *)
684 calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
686 fprintf(stderr, "%s: out of memory\n", progname);
690 sc = &scs[MI_SCREEN(mi)];
691 sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
694 sc = &scs[MI_SCREEN(mi)];
696 if ((sc->glx_context = init_GL(mi)) != NULL) {
698 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
699 init_stars (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
703 font_height = GLUT_FONT->top - GLUT_FONT->bottom;
704 sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems
706 if (target_columns > 0)
708 sc->columns = target_columns;
713 font_size = BASE_FONT_SIZE;
714 sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
717 sc->font_scale /= sc->columns;
718 sc->line_height = font_height * sc->font_scale;
721 if (!wrap_p) sc->columns = 1000; /* wrap anyway, if it's absurdly long. */
723 sc->subproc_relaunch_delay = 2 * 1000;
724 sc->total_lines = max_lines-1;
727 star_spin = -star_spin;
729 if (!alignment_str || !*alignment_str ||
730 !strcasecmp(alignment_str, "left"))
732 else if (!strcasecmp(alignment_str, "center") ||
733 !strcasecmp(alignment_str, "middle"))
735 else if (!strcasecmp(alignment_str, "right"))
740 "%s: alignment must be left, center, or right, not \"%s\"\n",
741 progname, alignment_str);
745 launch_text_generator (sc);
747 /* one more reshape, after line_height has been computed */
748 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
753 draw_stars (ModeInfo *mi)
755 sws_configuration *sc = &scs[MI_SCREEN(mi)];
757 glMatrixMode (GL_PROJECTION);
762 glMatrixMode (GL_MODELVIEW);
766 glOrtho (-0.5 * MI_WIDTH(mi), 0.5 * MI_WIDTH(mi),
767 -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
769 glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
770 glCallList (sc->star_list);
774 glMatrixMode (GL_PROJECTION);
779 draw_sws (ModeInfo *mi)
781 sws_configuration *sc = &scs[MI_SCREEN(mi)];
782 Display *dpy = MI_DISPLAY(mi);
783 Window window = MI_WINDOW(mi);
786 if (!sc->glx_context)
789 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
790 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
792 glDrawBuffer (GL_BACK);
793 glXMakeCurrent (dpy, window, *(sc->glx_context));
795 glClear (GL_COLOR_BUFFER_BIT);
799 glMatrixMode (GL_MODELVIEW);
803 glColor3f (0.4, 0.4, 0.4);
805 glTranslatef(0, 1, 0);
807 glTranslatef(0, -1, 0);
809 grid (1, 1, sc->line_height, 0);
812 /* Scroll to current position */
813 glTranslatef (0.0, sc->intra_line_scroll, 0.0);
815 glColor3f (1.0, 1.0, 0.4);
816 glCallList (sc->text_list);
818 sc->intra_line_scroll += sc->line_height / scroll_steps;
820 if (sc->intra_line_scroll >= sc->line_height)
822 sc->intra_line_scroll = 0;
824 /* Drop the oldest line off the end. */
828 /* Scroll the contents of the lines array toward 0. */
829 if (sc->total_lines > 0)
831 for (i = 1; i < sc->total_lines; i++)
832 sc->lines[i-1] = sc->lines[i];
833 sc->lines[--sc->total_lines] = 0;
836 /* Bring in new lines at the end. */
839 if (sc->total_lines < max_lines)
840 /* Oops, we ran out of text... well, insert some blank lines
841 here so that new text still pulls in from the bottom of
842 the screen, isntead of just appearing. */
843 sc->total_lines = max_lines;
845 glDeleteLists (sc->text_list, 1);
846 sc->text_list = glGenLists (1);
847 glNewList (sc->text_list, GL_COMPILE);
849 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
850 for (i = 0; i < sc->total_lines; i++)
852 int offscreen_lines = 3;
855 double y = ((sc->total_lines - (i + offscreen_lines) - 1)
858 char *line = sc->lines[i];
861 sprintf(n, "%d:", i);
862 draw_string (x / sc->font_scale, y / sc->font_scale, n);
867 if (sc->line_thickness != 1)
869 int max_thick_lines = MAX_THICK_LINES;
870 GLfloat thinnest_line = 1.0;
871 GLfloat thickest_line = sc->line_thickness;
872 GLfloat range = thickest_line - thinnest_line;
875 int j = sc->total_lines - i - 1;
877 if (j > max_thick_lines)
878 thickness = thinnest_line;
880 thickness = (thinnest_line +
881 (range * ((max_thick_lines - j) /
882 (GLfloat) max_thick_lines)));
884 glLineWidth (thickness);
888 xoff = 1.0 - (glutStrokeLength(GLUT_FONT, line) * sc->font_scale);
894 double factor = 1.0 * i / sc->total_lines;
895 glColor3f (factor, factor, 0.5 * factor);
898 draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
906 if (mi->fps_p) do_fps (mi);
908 glXSwapBuffers(dpy, window);
910 sc->star_theta += star_spin;