2 * fliptext, Copyright (c) 2005 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
13 #include <X11/Intrinsic.h>
15 extern XtAppContext app;
17 #define PROGCLASS "FlipText"
18 #define HACK_INIT init_fliptext
19 #define HACK_DRAW draw_fliptext
20 #define HACK_RESHAPE reshape_fliptext
21 #define fliptext_opts xlockmore_opts
23 #define DEF_PROGRAM "xscreensaver-text --cols 0" /* don't wrap */
25 #define DEF_FONT_SIZE "20"
26 #define DEF_COLUMNS "80"
27 #define DEF_ALIGN "random"
28 #define DEF_COLOR "#00CCFF"
29 #define DEF_SPEED "1.0"
31 /* Utopia 800 needs 64 512x512 textures (4096x4096 bitmap).
32 Utopia 720 needs 16 512x512 textures (2048x2048 bitmap).
33 Utopia 480 needs 16 512x512 textures (2048x2048 bitmap).
34 Utopia 400 needs 4 512x512 textures (1024x1024 bitmap).
35 Utopia 180 needs 1 512x512 texture.
36 Times 240 needs 1 512x512 texture.
38 #define DEF_FONT "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-iso8859-1"
42 #define FONT_WEIGHT 14
45 #define DEFAULTS "*delay: 10000 \n" \
46 "*showFPS: False \n" \
47 "*wireframe: False \n" \
48 "*font: " DEF_FONT "\n" \
49 ".foreground: " DEF_COLOR "\n" \
52 #define countof(x) (sizeof((x))/sizeof((*x)))
55 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
57 #include "xlockmore.h"
59 #ifdef USE_GL /* whole file */
67 # include <sys/utsname.h>
68 #endif /* HAVE_UNAME */
70 typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state;
71 typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type;
73 typedef struct { GLfloat x, y, z; } XYZ;
77 GLfloat width, height; /* size */
78 XYZ from, to, current; /* start, end, and current position */
79 GLfloat fth, tth, cth; /* rotation around Z */
81 int cluster_size; /* how many lines in this cluster */
82 int cluster_pos; /* position of this line in the cluster */
84 line_state state; /* current motion model */
85 int step, steps; /* progress along this path */
92 GLXContext *glx_context;
94 texture_font_data *texfont;
98 Time subproc_relaunch_delay;
104 int char_width; /* in font units */
105 int line_height; /* in font units */
106 double font_scale; /* convert font units to display units */
108 int font_wrap_pixels; /* in font units (for wrapping text) */
110 int top_margin, bottom_margin;
111 int left_margin, right_margin;
117 line_anim_type anim_type;
122 } fliptext_configuration;
125 static fliptext_configuration *scs = NULL;
127 static char *program;
128 static int max_lines, min_lines;
129 static float font_size;
130 static int target_columns;
131 static char *alignment_str;
132 static int alignment, alignment_random_p;
133 static GLfloat speed;
135 static XrmOptionDescRec opts[] = {
136 {"-program", ".program", XrmoptionSepArg, 0 },
137 {"-lines", ".lines", XrmoptionSepArg, 0 },
138 {"-size", ".fontSize", XrmoptionSepArg, 0 },
139 {"-columns", ".columns", XrmoptionSepArg, 0 },
140 {"-speed", ".speed", XrmoptionSepArg, 0 },
141 /*{"-font", ".font", XrmoptionSepArg, 0 },*/
142 {"-left", ".alignment", XrmoptionNoArg, "Left" },
143 {"-right", ".alignment", XrmoptionNoArg, "Right" },
144 {"-center", ".alignment", XrmoptionNoArg, "Center" },
147 static argtype vars[] = {
148 {&program, "program", "Program", DEF_PROGRAM, t_String},
149 {&max_lines, "lines", "Integer", DEF_LINES, t_Int},
150 {&font_size, "fontSize", "Float", DEF_FONT_SIZE, t_Float},
151 {&target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
152 {&alignment_str, "alignment", "Alignment", DEF_ALIGN, t_String},
153 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
156 ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL};
160 /* Tabs are bad, mmmkay? */
163 untabify (const char *string)
165 const char *ostring = string;
166 char *result = (char *) malloc ((strlen(string) * 8) + 1);
176 } while (col % TAB_WIDTH);
179 else if (*string == '\r' || *string == '\n')
184 else if (*string == '\010') /* backspace */
186 if (string > ostring)
201 strip (char *s, Bool leading, Bool trailing)
205 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
210 while (*s2 == ' ' || *s2 == '\t')
223 (This bit mostly cribbed from phosphor.c)
226 static void drain_input (fliptext_configuration *sc);
229 subproc_cb (XtPointer closure, int *source, XtInputId *id)
231 fliptext_configuration *sc = (fliptext_configuration *) closure;
237 launch_text_generator (fliptext_configuration *sc)
239 char *oprogram = get_string_resource ("program", "Program");
240 char *program = (char *) malloc (strlen (oprogram) + 10);
241 strcpy (program, "( ");
242 strcat (program, oprogram);
243 strcat (program, " ) 2>&1");
245 if ((sc->pipe = popen (program, "r")))
248 XtAppAddInput (app, fileno (sc->pipe),
249 (XtPointer) (XtInputReadMask | XtInputExceptMask),
250 subproc_cb, (XtPointer) sc);
260 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
262 fliptext_configuration *sc = (fliptext_configuration *) closure;
263 launch_text_generator (sc);
267 /* When the subprocess has generated some output, this reads as much as it
268 can into sc->buf at sc->buf_tail.
271 drain_input (fliptext_configuration *sc)
273 if (sc->buf_tail < sc->buf_size - 2)
275 int target = sc->buf_size - sc->buf_tail - 2;
277 ? read (fileno (sc->pipe),
278 (void *) (sc->buf + sc->buf_tail),
284 sc->buf[sc->buf_tail] = 0;
290 XtRemoveInput (sc->pipe_id);
296 /* If the process didn't print a terminating newline, add one. */
297 if (sc->buf_tail > 1 &&
298 sc->buf[sc->buf_tail-1] != '\n')
300 sc->buf[sc->buf_tail++] = '\n';
301 sc->buf[sc->buf_tail] = 0;
304 /* Then add one more, just for giggles. */
305 sc->buf[sc->buf_tail++] = '\n';
306 sc->buf[sc->buf_tail] = 0;
308 /* Set up a timer to re-launch the subproc in a bit. */
309 XtAppAddTimeOut (app, sc->subproc_relaunch_delay,
310 relaunch_generator_timer,
318 char_width (fliptext_configuration *sc, char c)
323 return texture_string_width (sc->texfont, s, 0);
327 /* Returns a single line of text from the output buffer of the subprocess,
328 taking into account wrapping, centering, etc. Returns 0 if no complete
329 line is currently available.
332 get_one_line (fliptext_configuration *sc)
335 int wrap_pix = sc->font_wrap_pixels;
344 if (s >= sc->buf + sc->buf_tail)
345 /* Reached end of buffer before end of line. Bail. */
348 cw = char_width (sc, *s);
350 if (*s == '\r' || *s == '\n' ||
351 col_pix + cw >= wrap_pix)
355 if (*s == '\r' || *s == '\n')
357 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
364 /* We wrapped -- try to back up to the previous word boundary. */
367 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
378 result = (char *) malloc (L+1);
379 memcpy (result, sc->buf, L);
384 char *ut = untabify (t);
385 strip (ut, (alignment == 0), 1); /* if centering, strip
386 leading whitespace too */
391 if (sc->buf_tail > (s - sc->buf))
393 int i = sc->buf_tail - (s - sc->buf);
394 memmove (sc->buf, s, i);
396 sc->buf[sc->buf_tail] = 0;
403 sc->buf[sc->buf_tail] = 0;
414 int tab_pix = TAB_WIDTH * sc->char_width;
415 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
416 col_pix = tab_pix * ((col / tab_pix) + 1);
427 blank_p (const char *s)
430 if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
435 /* Reads some text from the subprocess, and creates and returns a `line'
436 object. Adds that object to the lines list. Returns 0 if no text
439 If skip_blanks_p, then keep trying for new lines of text until we
440 get one that is not empty.
443 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
449 s = get_one_line (sc);
450 if (s && skip_blanks_p && blank_p (s))
458 ln = (line *) calloc (1, sizeof(*ln));
461 ln->width = sc->font_scale * texture_string_width (sc->texfont, s, 0);
462 ln->height = sc->font_scale * sc->line_height;
464 memcpy (ln->color, sc->color, sizeof(ln->color));
467 if (sc->lines_size <= sc->nlines)
469 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
470 sc->lines = (line **)
471 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
474 fprintf (stderr, "%s: out of memory (%d lines)\n",
475 progname, sc->lines_size);
480 sc->lines[sc->nlines-1] = ln;
485 /* frees the object and removes it from the list.
488 free_line (fliptext_configuration *sc, line *line)
491 for (i = 0; i < sc->nlines; i++)
492 if (sc->lines[i] == line)
494 if (i == sc->nlines) abort();
495 for (; i < sc->nlines-1; i++)
496 sc->lines[i] = sc->lines[i+1];
506 draw_line (ModeInfo *mi, line *line)
508 int wire = MI_IS_WIREFRAME(mi);
509 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
511 if (! line->text || !*line->text ||
512 line->state == NEW || line->state == HESITATE || line->state == DEAD)
516 glTranslatef (line->current.x, line->current.y, line->current.z);
518 glRotatef (line->cth, 0, 1, 0);
521 glTranslatef (-line->width, 0, 0);
522 else if (alignment == 0)
523 glTranslatef (-line->width/2, 0, 0);
525 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
527 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
530 print_texture_string (sc->texfont, line->text);
534 char *s = line->text;
537 glDisable (GL_TEXTURE_2D);
538 glColor3f (0.4, 0.4, 0.4);
542 w = texture_string_width (sc->texfont, c, &h);
543 glBegin (GL_LINE_LOOP);
544 glVertex3f (0, 0, 0);
545 glVertex3f (w, 0, 0);
546 glVertex3f (w, h, 0);
547 glVertex3f (0, h, 0);
549 glTranslatef (w, 0, 0);
554 glDisable (GL_TEXTURE_2D);
555 glColor3f (0.4, 0.4, 0.4);
556 glBegin (GL_LINE_LOOP);
557 glVertex3f (0, 0, 0);
558 glVertex3f (line->width/sc->font_scale, 0, 0);
559 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
560 glVertex3f (0, line->height/sc->font_scale, 0);
562 if (!wire) glEnable (GL_TEXTURE_2D);
567 mi->polygon_count += strlen (line->text);
571 tick_line (fliptext_configuration *sc, line *line)
573 int stagger = 30; /* frames of delay between line spin-outs */
574 int slide = 600; /* frames in a slide in/out */
575 int linger = 0; /* frames to pause with no motion */
578 if (line->state >= DEAD) abort();
579 if (++line->step >= line->steps)
584 if (linger == 0 && line->state == LINGER)
587 if (sc->anim_type != SPIN)
592 case HESITATE: /* entering state HESITATE */
593 switch (sc->anim_type)
596 line->steps = (line->cluster_pos * stagger);
599 line->steps = stagger * (line->cluster_size - line->cluster_pos);
602 line->steps = stagger * line->cluster_pos;
611 switch (sc->anim_type)
613 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
616 line->from.y = (sc->bottom_margin -
618 (line->cluster_pos + 1)));
619 line->to.y += (line->height *
620 ((line->cluster_size/2.0) - line->cluster_pos));
624 case SCROLL_TOP: /* entering state TOP IN */
627 line->from.y = (sc->top_margin +
629 (line->cluster_size - line->cluster_pos)));
630 line->to.y += (line->height *
631 ((line->cluster_size/2.0) - line->cluster_pos));
635 case SPIN: /* entering state SPIN IN */
638 line->to.y += (line->height *
639 ((line->cluster_size/2.0) - line->cluster_pos));
640 line->from.y += (line->height *
641 ((line->cluster_size/2.0) - line->cluster_pos));
654 switch (sc->anim_type)
656 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
657 line->from = line->to;
659 line->to.y = (sc->top_margin +
661 (line->cluster_size - line->cluster_pos)));
665 case SCROLL_TOP: /* entering state TOP OUT */
666 line->from = line->to;
668 line->to.y = (sc->bottom_margin -
670 (line->cluster_pos + 1)));
674 case SPIN: /* entering state SPIN OUT */
675 line->from = line->to;
677 line->to.y += (line->height *
678 ((line->cluster_size/2.0) - line->cluster_pos));
680 line->fth = line->tth;
691 line->from = line->to;
692 line->steps = linger;
699 line->steps /= speed;
706 i = (double) line->step / line->steps;
708 /* Move along the path exponentially, slow side towards the middle. */
709 if (line->state == OUT)
712 ii = 1 - ((1-i) * (1-i));
714 line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
715 line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
716 line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
717 line->cth = line->fth + (ii * (line->tth - line->fth));
719 if (line->state == OUT) ii = 1-ii;
720 line->color[3] = sc->color[3] * ii;
733 /* Start a new cluster of lines going.
734 Pick their anim type, and in, mid, and out positions.
737 reset_lines (ModeInfo *mi)
739 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
742 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
744 sc->rotation.x = 5 - BELLRAND(10);
745 sc->rotation.y = 5 - BELLRAND(10);
746 sc->rotation.z = 5 - BELLRAND(10);
748 switch (random() % 8)
750 case 0: sc->anim_type = SCROLL_TOP; break;
751 case 1: sc->anim_type = SCROLL_BOTTOM; break;
752 default: sc->anim_type = SPIN; break;
755 minx = sc->left_margin * 0.9;
756 maxx = sc->right_margin * 0.9;
758 miny = sc->bottom_margin * 0.9;
759 maxy = sc->top_margin * 0.9;
761 minz = sc->left_margin * 5;
762 maxz = sc->right_margin * 2;
764 maxw = sc->font_wrap_pixels * sc->font_scale;
765 maxh = max_lines * sc->line_height * sc->font_scale;
767 if (maxw > maxx - minx)
769 if (maxh > maxy - miny)
772 if (alignment_random_p)
773 alignment = (random() % 3) - 1;
775 if (alignment == -1) maxx -= maxw;
776 else if (alignment == 1) minx += maxw;
777 else minx += maxw/2, maxx -= maxw/2;
782 sc->mid.x = minx + frand (maxx - minx);
783 if (sc->anim_type == SPIN)
784 sc->mid.y = miny + BELLRAND (maxy - miny);
786 sc->mid.y = miny + frand (maxy - miny);
788 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
789 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
791 sc->in.y = miny + frand(maxy - miny);
792 sc->out.y = miny + frand(maxy - miny);
794 sc->in.z = minz + frand(maxz - minz);
795 sc->out.z = minz + frand(maxz - minz);
799 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
800 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
802 for (i = 0; i < max_lines; i++)
804 line *line = make_line (sc, (i == 0));
805 if (!line) break; /* no text available */
806 if (i >= min_lines &&
807 (!line->text || !*line->text)) /* blank after min */
811 for (i = 0; i < sc->nlines; i++)
813 line *line = sc->lines[i];
816 line->from.y = sc->bottom_margin;
821 line->from.y = prev->from.y - prev->height;
822 line->to.y = prev->to.y - prev->height;
824 line->cluster_pos = i;
825 line->cluster_size = sc->nlines;
832 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
835 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
837 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
840 a[0] = c.red / 65536.0;
841 a[1] = c.green / 65536.0;
842 a[2] = c.blue / 65536.0;
847 /* Window management, etc
850 reshape_fliptext (ModeInfo *mi, int width, int height)
852 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
853 GLfloat h = (GLfloat) height / (GLfloat) width;
855 glViewport (0, 0, (GLint) width, (GLint) height);
857 glMatrixMode(GL_PROJECTION);
859 gluPerspective (60.0, 1/h, 0.01, 100.0);
861 glMatrixMode(GL_MODELVIEW);
863 gluLookAt( 0.0, 0.0, 2.6,
867 glClear(GL_COLOR_BUFFER_BIT);
869 sc->right_margin = sc->top_margin / h;
870 sc->left_margin = -sc->right_margin;
875 init_fliptext (ModeInfo *mi)
877 int wire = MI_IS_WIREFRAME(mi);
879 fliptext_configuration *sc;
882 scs = (fliptext_configuration *)
883 calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
885 fprintf(stderr, "%s: out of memory\n", progname);
889 sc = &scs[MI_SCREEN(mi)];
890 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
893 sc = &scs[MI_SCREEN(mi)];
895 if ((sc->glx_context = init_GL(mi)) != NULL) {
896 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
899 program = get_string_resource ("program", "Program");
903 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
904 check_gl_error ("loading font");
905 cw = texture_string_width (sc->texfont, "n", &lh);
907 sc->line_height = lh;
912 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
914 glEnable (GL_ALPHA_TEST);
915 glEnable (GL_TEXTURE_2D);
917 # ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT
918 /* "Anistropic filtering helps for quadrilateral-angled textures.
919 A sharper image is accomplished by interpolating and filtering
920 multiple samples from one or more mipmaps to better approximate
921 very distorted textures. This is the next level of filtering
922 after trilinear filtering." */
923 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
928 /* The default font is (by fiat) "18 points".
929 Interpret the user's font size request relative to that.
931 sc->font_scale = 3 * (font_size / 18.0);
933 if (target_columns <= 2) target_columns = 2;
935 /* Figure out what the wrap column should be, in font-coordinate pixels.
936 Compute it from the given -columns value, but don't let it be wider
940 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
941 sc->font_wrap_pixels = target_columns * sc->char_width;
942 if (sc->font_wrap_pixels > maxw ||
943 sc->font_wrap_pixels <= 0)
944 sc->font_wrap_pixels = maxw;
947 sc->buf_size = target_columns * max_lines;
948 sc->buf = (char *) calloc (1, sc->buf_size);
950 sc->subproc_relaunch_delay = 2 * 1000; /* 2 seconds */
952 alignment_random_p = False;
953 if (!alignment_str || !*alignment_str ||
954 !strcasecmp(alignment_str, "left"))
956 else if (!strcasecmp(alignment_str, "center") ||
957 !strcasecmp(alignment_str, "middle"))
959 else if (!strcasecmp(alignment_str, "right"))
961 else if (!strcasecmp(alignment_str, "random"))
962 alignment = -1, alignment_random_p = True;
967 "%s: alignment must be left/center/right/random, not \"%s\"\n",
968 progname, alignment_str);
972 launch_text_generator (sc);
974 if (max_lines < 1) max_lines = 1;
975 min_lines = max_lines * 0.66;
976 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
977 if (min_lines < 1) min_lines = 1;
979 parse_color (mi, "foreground",
980 get_string_resource("foreground", "Foreground"),
983 sc->top_margin = (sc->char_width * 100);
984 sc->bottom_margin = -sc->top_margin;
985 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
990 draw_fliptext (ModeInfo *mi)
992 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
993 Display *dpy = MI_DISPLAY(mi);
994 Window window = MI_WINDOW(mi);
997 if (!sc->glx_context)
1000 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
1001 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
1003 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1005 mi->polygon_count = 0;
1009 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
1013 glRotatef (sc->rotation.x, 1, 0, 0);
1014 glRotatef (sc->rotation.y, 0, 1, 0);
1015 glRotatef (sc->rotation.z, 0, 0, 1);
1018 glDisable (GL_TEXTURE_2D);
1020 glBegin (GL_LINE_LOOP);
1021 glVertex3f (sc->left_margin, sc->top_margin, 0);
1022 glVertex3f (sc->right_margin, sc->top_margin, 0);
1023 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
1024 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
1027 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
1028 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
1029 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
1030 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
1031 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
1032 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
1034 glEnable (GL_TEXTURE_2D);
1037 for (i = 0; i < sc->nlines; i++)
1039 line *line = sc->lines[i];
1040 draw_line (mi, line);
1041 tick_line (sc, line);
1044 for (i = sc->nlines-1; i >= 0; i--)
1046 line *line = sc->lines[i];
1047 if (line->state == DEAD)
1048 free_line (sc, line);
1051 if (sc->nlines == 0)
1056 if (mi->fps_p) do_fps (mi);
1058 glXSwapBuffers(dpy, window);