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 ZIPPY_PROGRAM
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 */
91 #include "glutstroke.h"
92 #include "glut_roman.h"
93 #define GLUT_FONT (&glutStrokeRoman)
97 GLXContext *glx_context;
99 GLuint text_list, star_list;
103 Time subproc_relaunch_delay;
114 double intra_line_scroll;
116 int line_pixel_height;
117 GLfloat line_thickness;
122 static sws_configuration *scs = NULL;
124 static char *program;
125 static int max_lines;
126 static int scroll_steps;
127 static float star_spin;
128 static float font_size;
129 static int target_columns;
134 static char *alignment_str;
135 static int alignment;
137 static XrmOptionDescRec opts[] = {
138 {"-program", ".starwars.program", XrmoptionSepArg, (caddr_t) 0 },
139 {"-lines", ".starwars.lines", XrmoptionSepArg, (caddr_t) 0 },
140 {"-steps", ".starwars.steps", XrmoptionSepArg, (caddr_t) 0 },
141 {"-spin", ".starwars.spin", XrmoptionSepArg, (caddr_t) 0 },
142 {"-size", ".starwars.fontSize", XrmoptionSepArg, (caddr_t) 0 },
143 {"-columns", ".starwars.columns", XrmoptionSepArg, (caddr_t) 0 },
144 {"-smooth", ".starwars.smooth", XrmoptionNoArg, (caddr_t) "True" },
145 {"-no-smooth", ".starwars.smooth", XrmoptionNoArg, (caddr_t) "False" },
146 {"-thick", ".starwars.thick", XrmoptionNoArg, (caddr_t) "True" },
147 {"-no-thick", ".starwars.thick", XrmoptionNoArg, (caddr_t) "False" },
148 {"-fade", ".starwars.fade", XrmoptionNoArg, (caddr_t) "True" },
149 {"-no-fade", ".starwars.fade", XrmoptionNoArg, (caddr_t) "False" },
150 {"-wrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "True" },
151 {"-no-wrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "False" },
152 {"-nowrap", ".starwars.lineWrap", XrmoptionNoArg, (caddr_t) "False" },
153 {"-left", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Left" },
154 {"-right", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Right" },
155 {"-center", ".starwars.alignment",XrmoptionNoArg, (caddr_t) "Center" },
158 static argtype vars[] = {
159 {(caddr_t *) &program, "program", "Program", DEF_PROGRAM, t_String},
160 {(caddr_t *) &max_lines, "lines", "Integer", DEF_LINES, t_Int},
161 {(caddr_t *) &scroll_steps, "steps", "Integer", DEF_STEPS, t_Int},
162 {(caddr_t *) &star_spin, "spin", "Float", DEF_SPIN, t_Float},
163 {(caddr_t *) &font_size, "fontSize","Float", DEF_STEPS, t_Float},
164 {(caddr_t *) &target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
165 {(caddr_t *) &wrap_p, "lineWrap","Boolean", DEF_COLUMNS, t_Bool},
166 {(caddr_t *) &alignment_str, "alignment","Alignment",DEF_ALIGN, t_String},
167 {(caddr_t *) &smooth_p, "smooth", "Boolean", DEF_SMOOTH, t_Bool},
168 {(caddr_t *) &thick_p, "thick", "Boolean", DEF_THICK, t_Bool},
169 {(caddr_t *) &fade_p, "fade", "Boolean", DEF_FADE, t_Bool},
172 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
176 /* Tabs are bad, mmmkay? */
179 untabify (const char *string)
181 const char *ostring = string;
182 char *result = (char *) malloc ((strlen(string) * 8) + 1);
192 } while (col % TAB_WIDTH);
195 else if (*string == '\r' || *string == '\n')
200 else if (*string == '\010') /* backspace */
202 if (string > ostring)
217 strip (char *s, Bool leading, Bool trailing)
221 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
226 while (*s2 == ' ' || *s2 == '\t')
239 (This bit mostly cribbed from phosphor.c)
242 static void drain_input (sws_configuration *sc);
245 subproc_cb (XtPointer closure, int *source, XtInputId *id)
247 sws_configuration *sc = (sws_configuration *) closure;
253 launch_text_generator (sws_configuration *sc)
255 char *oprogram = get_string_resource ("program", "Program");
256 char *program = (char *) malloc (strlen (oprogram) + 10);
258 strcpy (program, "( ");
259 strcat (program, oprogram);
260 strcat (program, " ) 2>&1");
262 if ((sc->pipe = popen (program, "r")))
265 XtAppAddInput (app, fileno (sc->pipe),
266 (XtPointer) (XtInputReadMask | XtInputExceptMask),
267 subproc_cb, (XtPointer) sc);
277 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
279 sws_configuration *sc = (sws_configuration *) closure;
280 launch_text_generator (sc);
284 /* When the subprocess has generated some output, this reads as much as it
285 can into sc->buf at sc->buf_tail.
288 drain_input (sws_configuration *sc)
290 if (sc->buf_tail < sizeof(sc->buf) - 2)
292 int target = sizeof(sc->buf) - sc->buf_tail - 2;
293 int n = read (fileno (sc->pipe),
294 (void *) (sc->buf + sc->buf_tail),
299 sc->buf[sc->buf_tail] = 0;
303 XtRemoveInput (sc->pipe_id);
308 /* If the process didn't print a terminating newline, add one. */
309 if (sc->buf_tail > 1 &&
310 sc->buf[sc->buf_tail-1] != '\n')
312 sc->buf[sc->buf_tail++] = '\n';
313 sc->buf[sc->buf_tail] = 0;
316 /* Then add one more, just for giggles. */
317 sc->buf[sc->buf_tail++] = '\n';
318 sc->buf[sc->buf_tail] = 0;
320 /* Set up a timer to re-launch the subproc in a bit. */
321 XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
322 relaunch_generator_timer,
329 /* Populates the sc->lines list with as many lines as are currently in
330 sc->buf (which was filled by drain_input().
333 get_more_lines (sws_configuration *sc)
337 while (sc->total_lines < max_lines)
339 if (s >= sc->buf + sc->buf_tail)
341 /* Reached end of buffer before end of line. Bail. */
345 if (*s == '\n' || col > sc->columns)
353 /* We wrapped -- try to back up to the previous word boundary. */
356 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
366 sc->lines[sc->total_lines] = (char *) malloc (L+1);
367 memcpy (sc->lines[sc->total_lines], sc->buf, L);
368 sc->lines[sc->total_lines][L] = 0;
371 char *t = sc->lines[sc->total_lines];
372 char *ut = untabify (t);
373 strip (ut, (alignment == 0), 1); /* if centering, strip
374 leading whitespace too */
375 sc->lines[sc->total_lines] = ut;
381 if (sc->buf_tail > (s - sc->buf))
383 int i = sc->buf_tail - (s - sc->buf);
384 memcpy (sc->buf, s, i);
386 sc->buf[sc->buf_tail] = 0;
393 sc->buf[sc->buf_tail] = 0;
401 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
409 draw_string (int x, int y, const char *s)
411 if (!s || !*s) return;
413 glTranslatef (x, y, 0);
416 glutStrokeCharacter (GLUT_FONT, *s++);
423 grid (double width, double height, double spacing, double z)
426 for (y = 0; y <= height/2; y += spacing)
429 glVertex3f(-width/2, y, z);
430 glVertex3f( width/2, y, z);
431 glVertex3f(-width/2, -y, z);
432 glVertex3f( width/2, -y, z);
435 for (x = 0; x <= width/2; x += spacing)
438 glVertex3f( x, -height/2, z);
439 glVertex3f( x, height/2, z);
440 glVertex3f(-x, -height/2, z);
441 glVertex3f(-x, height/2, z);
446 glVertex3f(-width, 0, z);
447 glVertex3f( width, 0, z);
448 glVertex3f(0, -height, z);
449 glVertex3f(0, height, z);
454 box (double width, double height, double depth)
456 glBegin(GL_LINE_LOOP);
457 glVertex3f(-width/2, -height/2, -depth/2);
458 glVertex3f(-width/2, height/2, -depth/2);
459 glVertex3f( width/2, height/2, -depth/2);
460 glVertex3f( width/2, -height/2, -depth/2);
462 glBegin(GL_LINE_LOOP);
463 glVertex3f(-width/2, -height/2, depth/2);
464 glVertex3f(-width/2, height/2, depth/2);
465 glVertex3f( width/2, height/2, depth/2);
466 glVertex3f( width/2, -height/2, depth/2);
468 glBegin(GL_LINE_LOOP);
469 glVertex3f(-width/2, -height/2, -depth/2);
470 glVertex3f(-width/2, -height/2, depth/2);
471 glVertex3f(-width/2, height/2, depth/2);
472 glVertex3f(-width/2, height/2, -depth/2);
474 glBegin(GL_LINE_LOOP);
475 glVertex3f( width/2, -height/2, -depth/2);
476 glVertex3f( width/2, -height/2, depth/2);
477 glVertex3f( width/2, height/2, depth/2);
478 glVertex3f( width/2, height/2, -depth/2);
483 glVertex3f(-width/2, height/2, depth/2);
484 glVertex3f(-width/2, -height/2, -depth/2);
486 glVertex3f( width/2, height/2, depth/2);
487 glVertex3f( width/2, -height/2, -depth/2);
489 glVertex3f(-width/2, -height/2, depth/2);
490 glVertex3f(-width/2, height/2, -depth/2);
492 glVertex3f( width/2, -height/2, depth/2);
493 glVertex3f( width/2, height/2, -depth/2);
499 /* Window management, etc
502 reshape_sws (ModeInfo *mi, int width, int height)
504 sws_configuration *sc = &scs[MI_SCREEN(mi)];
506 /* Set up matrices for perspective text display
509 GLfloat desired_aspect = (GLfloat) 3/4;
510 int w = mi->xgwa.width;
511 int h = mi->xgwa.height;
514 h = w * desired_aspect;
517 glMatrixMode (GL_PROJECTION);
518 glViewport (0, 0, w, h);
520 glMatrixMode (GL_MODELVIEW);
522 gluPerspective (80.0, 1/desired_aspect, 10, 500000);
523 gluLookAt (0.0, 0.0, 4600.0,
526 glRotatef (-60.0, 1.0, 0.0, 0.0);
528 /* The above gives us an arena where the bottom edge of the screen is
529 represented by the line (-2100,-3140,0) - ( 2100,-3140,0). */
531 /* Now let's move the origin to the front of the screen. */
532 glTranslatef (0.0, -3140, 0.0);
534 /* And then let's scale so that the bottom of the screen is 1.0 wide. */
535 glScalef (4200, 4200, 4200);
539 /* Construct stars (number of stars is dependent on size of screen) */
542 int nstars = width * height / 320;
543 glDeleteLists (sc->star_list, 1);
544 sc->star_list = glGenLists (1);
545 glNewList (sc->star_list, GL_COMPILE);
547 for (i = 0; i < nstars; i++)
549 GLfloat c = 0.6 + 0.3 * random() / RAND_MAX;
551 glVertex3f (2 * width * (0.5 - 1.0 * random() / RAND_MAX),
552 2 * height * (0.5 - 1.0 * random() / RAND_MAX),
560 /* Compute the height in pixels of the line at the bottom of the screen. */
562 GLdouble mm[17], pm[17];
564 GLfloat x = 0.5, y1 = 0, z = 0;
565 GLfloat y2 = sc->line_height;
566 GLdouble wx=-1, wy1=-1, wy2=-1, wz=-1;
568 glGetDoublev (GL_MODELVIEW_MATRIX, mm);
569 glGetDoublev (GL_PROJECTION_MATRIX, pm);
570 glGetIntegerv (GL_VIEWPORT, vp);
571 gluProject (x, y1, z, mm, pm, vp, &wx, &wy1, &wz);
572 gluProject (x, y2, z, mm, pm, vp, &wx, &wy2, &wz);
573 sc->line_pixel_height = (wy2 - wy1);
577 /* Compute the best looking line thickness for the bottom line.
580 sc->line_thickness = 1.0;
582 sc->line_thickness = (GLfloat) sc->line_pixel_height / FONT_WEIGHT;
584 if (sc->line_thickness < 1.2)
585 sc->line_thickness = 1.0;
590 gl_init (ModeInfo *mi)
592 sws_configuration *sc = &scs[MI_SCREEN(mi)];
594 program = get_string_resource ("program", "Program");
596 glDisable (GL_LIGHTING);
597 glDisable (GL_DEPTH_TEST);
601 glEnable (GL_LINE_SMOOTH);
602 glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
603 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
607 sc->text_list = glGenLists (1);
608 glNewList (sc->text_list, GL_COMPILE);
611 sc->star_list = glGenLists (1);
612 glNewList (sc->star_list, GL_COMPILE);
615 sc->line_thickness = 1.0;
620 init_sws (ModeInfo *mi)
624 sws_configuration *sc;
627 scs = (sws_configuration *)
628 calloc (MI_NUM_SCREENS(mi), sizeof (sws_configuration));
630 fprintf(stderr, "%s: out of memory\n", progname);
634 sc = &scs[MI_SCREEN(mi)];
635 sc->lines = (char **) calloc (max_lines+1, sizeof(char *));
638 sc = &scs[MI_SCREEN(mi)];
640 if ((sc->glx_context = init_GL(mi)) != NULL) {
642 reshape_sws (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
647 font_height = GLUT_FONT->top - GLUT_FONT->bottom;
648 sc->font_scale = 1.0 / glutStrokeWidth (GLUT_FONT, 'z'); /* 'n' seems
650 if (target_columns > 0)
652 sc->columns = target_columns;
657 font_size = BASE_FONT_SIZE;
658 sc->columns = BASE_FONT_COLUMNS * ((double) BASE_FONT_SIZE / font_size);
661 sc->font_scale /= sc->columns;
662 sc->line_height = font_height * sc->font_scale;
665 if (!wrap_p) sc->columns = 1000; /* wrap anyway, if it's absurdly long. */
667 sc->subproc_relaunch_delay = 2 * 1000;
668 sc->total_lines = max_lines-1;
669 launch_text_generator (sc);
672 star_spin = -star_spin;
674 if (!alignment_str || !*alignment_str ||
675 !strcasecmp(alignment_str, "left"))
677 else if (!strcasecmp(alignment_str, "center") ||
678 !strcasecmp(alignment_str, "middle"))
680 else if (!strcasecmp(alignment_str, "right"))
685 "%s: alignment must be left, center, or right, not \"%s\"\n",
686 progname, alignment_str);
693 draw_stars (ModeInfo *mi)
695 sws_configuration *sc = &scs[MI_SCREEN(mi)];
697 glMatrixMode (GL_PROJECTION);
702 glMatrixMode (GL_MODELVIEW);
706 glOrtho (-0.5 * MI_WIDTH(mi), 0.5 * MI_WIDTH(mi),
707 -0.5 * MI_HEIGHT(mi), 0.5 * MI_HEIGHT(mi),
709 glRotatef (sc->star_theta, 0.0, 0.0, 1.0);
710 glCallList (sc->star_list);
714 glMatrixMode (GL_PROJECTION);
719 draw_sws (ModeInfo *mi)
721 sws_configuration *sc = &scs[MI_SCREEN(mi)];
722 Display *dpy = MI_DISPLAY(mi);
723 Window window = MI_WINDOW(mi);
726 if (!sc->glx_context)
729 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
730 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
732 glDrawBuffer (GL_BACK);
733 glXMakeCurrent (dpy, window, *(sc->glx_context));
735 glClear (GL_COLOR_BUFFER_BIT);
739 glMatrixMode (GL_MODELVIEW);
743 glColor3f (0.4, 0.4, 0.4);
745 glTranslatef(0, 1, 0);
747 glTranslatef(0, -1, 0);
749 grid (1, 1, sc->line_height, 0);
752 /* Scroll to current position */
753 glTranslatef (0.0, sc->intra_line_scroll, 0.0);
755 glColor3f (1.0, 1.0, 0.4);
756 glCallList (sc->text_list);
758 sc->intra_line_scroll += sc->line_height / scroll_steps;
760 if (sc->intra_line_scroll >= sc->line_height)
762 sc->intra_line_scroll = 0;
764 /* Drop the oldest line off the end. */
768 /* Scroll the contents of the lines array toward 0. */
769 if (sc->total_lines > 0)
771 for (i = 1; i < sc->total_lines; i++)
772 sc->lines[i-1] = sc->lines[i];
773 sc->lines[--sc->total_lines] = 0;
776 /* Bring in new lines at the end. */
779 if (sc->total_lines < max_lines)
780 /* Oops, we ran out of text... well, insert some blank lines
781 here so that new text still pulls in from the bottom of
782 the screen, isntead of just appearing. */
783 sc->total_lines = max_lines;
785 glDeleteLists (sc->text_list, 1);
786 sc->text_list = glGenLists (1);
787 glNewList (sc->text_list, GL_COMPILE);
789 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
790 for (i = 0; i < sc->total_lines; i++)
792 int offscreen_lines = 3;
795 double y = ((sc->total_lines - (i + offscreen_lines) - 1)
798 char *line = sc->lines[i];
801 sprintf(n, "%d:", i);
802 draw_string (x / sc->font_scale, y / sc->font_scale, n);
807 if (sc->line_thickness != 1)
809 int max_thick_lines = MAX_THICK_LINES;
810 GLfloat thinnest_line = 1.0;
811 GLfloat thickest_line = sc->line_thickness;
812 GLfloat range = thickest_line - thinnest_line;
815 int j = sc->total_lines - i - 1;
817 if (j > max_thick_lines)
818 thickness = thinnest_line;
820 thickness = (thinnest_line +
821 (range * ((max_thick_lines - j) /
822 (GLfloat) max_thick_lines)));
824 glLineWidth (thickness);
828 xoff = 1.0 - (glutStrokeLength(GLUT_FONT, line) * sc->font_scale);
834 double factor = 1.0 * i / sc->total_lines;
835 glColor3f (factor, factor, 0.5 * factor);
838 draw_string ((x + xoff) / sc->font_scale, y / sc->font_scale, line);
846 if (mi->fps_p) do_fps (mi);
848 glXSwapBuffers(dpy, window);
850 sc->star_theta += star_spin;