2 * fliptext, Copyright (c) 2005-2011 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
15 #endif /* HAVE_CONFIG_H */
17 /* Utopia 800 needs 64 512x512 textures (4096x4096 bitmap).
18 Utopia 720 needs 16 512x512 textures (2048x2048 bitmap).
19 Utopia 480 needs 16 512x512 textures (2048x2048 bitmap).
20 Utopia 400 needs 4 512x512 textures (1024x1024 bitmap).
21 Utopia 180 needs 1 512x512 texture.
22 Times 240 needs 1 512x512 texture.
24 #define DEF_FONT "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-iso8859-1"
25 #define DEF_COLOR "#00CCFF"
27 #define DEFAULTS "*delay: 10000 \n" \
28 "*showFPS: False \n" \
29 "*wireframe: False \n" \
31 "*font: " DEF_FONT "\n" \
32 ".foreground: " DEF_COLOR "\n" \
34 # define refresh_fliptext 0
35 # define fliptext_handle_event 0
37 #define countof(x) (sizeof((x))/sizeof((*x)))
40 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
42 #include "xlockmore.h"
44 #include "textclient.h"
46 #ifdef USE_GL /* whole file */
48 /* Should be in <GL/glext.h> */
49 # ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
50 # define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
52 # ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
53 # define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
57 #define DEF_PROGRAM "xscreensaver-text --cols 0" /* don't wrap */
59 #define DEF_FONT_SIZE "20"
60 #define DEF_COLUMNS "80"
61 #define DEF_ALIGNMENT "random"
62 #define DEF_SPEED "1.0"
65 #define FONT_WEIGHT 14
68 typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state;
69 typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type;
71 typedef struct { GLfloat x, y, z; } XYZ;
75 GLfloat width, height; /* size */
76 XYZ from, to, current; /* start, end, and current position */
77 GLfloat fth, tth, cth; /* rotation around Z */
79 int cluster_size; /* how many lines in this cluster */
80 int cluster_pos; /* position of this line in the cluster */
82 line_state state; /* current motion model */
83 int step, steps; /* progress along this path */
91 GLXContext *glx_context;
93 texture_font_data *texfont;
100 int char_width; /* in font units */
101 int line_height; /* in font units */
102 double font_scale; /* convert font units to display units */
104 int font_wrap_pixels; /* in font units (for wrapping text) */
106 int top_margin, bottom_margin;
107 int left_margin, right_margin;
113 line_anim_type anim_type;
118 } fliptext_configuration;
121 static fliptext_configuration *scs = NULL;
123 static char *program;
124 static int max_lines, min_lines;
125 static float font_size;
126 static int target_columns;
127 static char *alignment_str;
128 static int alignment, alignment_random_p;
129 static GLfloat speed;
131 static XrmOptionDescRec opts[] = {
132 {"-program", ".program", XrmoptionSepArg, 0 },
133 {"-lines", ".lines", XrmoptionSepArg, 0 },
134 {"-size", ".fontSize", XrmoptionSepArg, 0 },
135 {"-columns", ".columns", XrmoptionSepArg, 0 },
136 {"-speed", ".speed", XrmoptionSepArg, 0 },
137 /*{"-font", ".font", XrmoptionSepArg, 0 },*/
138 {"-alignment", ".alignment", XrmoptionSepArg, 0 },
139 {"-left", ".alignment", XrmoptionNoArg, "Left" },
140 {"-right", ".alignment", XrmoptionNoArg, "Right" },
141 {"-center", ".alignment", XrmoptionNoArg, "Center" },
144 static argtype vars[] = {
145 {&program, "program", "Program", DEF_PROGRAM, t_String},
146 {&max_lines, "lines", "Integer", DEF_LINES, t_Int},
147 {&font_size, "fontSize", "Float", DEF_FONT_SIZE, t_Float},
148 {&target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
149 {&alignment_str, "alignment", "Alignment", DEF_ALIGNMENT, t_String},
150 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
153 ENTRYPOINT ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL};
157 /* Tabs are bad, mmmkay? */
160 untabify (const char *string)
162 const char *ostring = string;
163 char *result = (char *) malloc ((strlen(string) * 8) + 1);
173 } while (col % TAB_WIDTH);
176 else if (*string == '\r' || *string == '\n')
181 else if (*string == '\010') /* backspace */
183 if (string > ostring)
198 strip (char *s, Bool leading, Bool trailing)
202 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
207 while (*s2 == ' ' || *s2 == '\t')
219 char_width (fliptext_configuration *sc, char c)
224 return texture_string_width (sc->texfont, s, 0);
228 /* Returns a single line of text from the output buffer of the subprocess,
229 taking into account wrapping, centering, etc. Returns 0 if no complete
230 line is currently available.
233 get_one_line (fliptext_configuration *sc)
236 int wrap_pix = sc->font_wrap_pixels;
240 int target = sc->buf_size - sc->buf_tail - 2;
242 /* Fill as much as we can into sc->buf, but stop at newline.
246 char c = textclient_getc (sc->tc);
249 sc->buf[sc->buf_tail++] = c;
250 sc->buf[sc->buf_tail] = 0;
252 if (c == '\r' || c == '\n')
260 if (s >= sc->buf + sc->buf_tail)
261 /* Reached end of buffer before end of line. Bail. */
264 cw = char_width (sc, *s);
266 if (*s == '\r' || *s == '\n' ||
267 col_pix + cw >= wrap_pix)
271 if (*s == '\r' || *s == '\n')
273 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
280 /* We wrapped -- try to back up to the previous word boundary. */
283 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
294 result = (char *) malloc (L+1);
295 memcpy (result, sc->buf, L);
300 char *ut = untabify (t);
301 strip (ut, (alignment == 0), 1); /* if centering, strip
302 leading whitespace too */
307 if (sc->buf_tail > (s - sc->buf))
309 int i = sc->buf_tail - (s - sc->buf);
310 memmove (sc->buf, s, i);
312 sc->buf[sc->buf_tail] = 0;
319 sc->buf[sc->buf_tail] = 0;
330 int tab_pix = TAB_WIDTH * sc->char_width;
331 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
332 col_pix = tab_pix * ((col / tab_pix) + 1);
343 blank_p (const char *s)
346 if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
351 /* Reads some text from the subprocess, and creates and returns a `line'
352 object. Adds that object to the lines list. Returns 0 if no text
355 If skip_blanks_p, then keep trying for new lines of text until we
356 get one that is not empty.
359 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
365 s = get_one_line (sc);
366 if (s && skip_blanks_p && blank_p (s))
374 ln = (line *) calloc (1, sizeof(*ln));
377 ln->width = sc->font_scale * texture_string_width (sc->texfont, s, 0);
378 ln->height = sc->font_scale * sc->line_height;
380 memcpy (ln->color, sc->color, sizeof(ln->color));
383 if (sc->lines_size <= sc->nlines)
385 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
386 sc->lines = (line **)
387 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
390 fprintf (stderr, "%s: out of memory (%d lines)\n",
391 progname, sc->lines_size);
396 sc->lines[sc->nlines-1] = ln;
401 /* frees the object and removes it from the list.
404 free_line (fliptext_configuration *sc, line *line)
407 for (i = 0; i < sc->nlines; i++)
408 if (sc->lines[i] == line)
410 if (i == sc->nlines) abort();
411 for (; i < sc->nlines-1; i++)
412 sc->lines[i] = sc->lines[i+1];
422 draw_line (ModeInfo *mi, line *line)
424 int wire = MI_IS_WIREFRAME(mi);
425 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
427 if (! line->text || !*line->text ||
428 line->state == NEW || line->state == HESITATE || line->state == DEAD)
432 glTranslatef (line->current.x, line->current.y, line->current.z);
434 glRotatef (line->cth, 0, 1, 0);
437 glTranslatef (-line->width, 0, 0);
438 else if (alignment == 0)
439 glTranslatef (-line->width/2, 0, 0);
441 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
443 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
446 print_texture_string (sc->texfont, line->text);
450 char *s = line->text;
453 glDisable (GL_TEXTURE_2D);
454 glColor3f (0.4, 0.4, 0.4);
458 w = texture_string_width (sc->texfont, c, &h);
459 glBegin (GL_LINE_LOOP);
460 glVertex3f (0, 0, 0);
461 glVertex3f (w, 0, 0);
462 glVertex3f (w, h, 0);
463 glVertex3f (0, h, 0);
465 glTranslatef (w, 0, 0);
470 glDisable (GL_TEXTURE_2D);
471 glColor3f (0.4, 0.4, 0.4);
472 glBegin (GL_LINE_LOOP);
473 glVertex3f (0, 0, 0);
474 glVertex3f (line->width/sc->font_scale, 0, 0);
475 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
476 glVertex3f (0, line->height/sc->font_scale, 0);
478 if (!wire) glEnable (GL_TEXTURE_2D);
483 mi->polygon_count += strlen (line->text);
487 tick_line (fliptext_configuration *sc, line *line)
489 int stagger = 30; /* frames of delay between line spin-outs */
490 int slide = 600; /* frames in a slide in/out */
491 int linger = 0; /* frames to pause with no motion */
494 if (line->state >= DEAD) abort();
495 if (++line->step >= line->steps)
500 if (linger == 0 && line->state == LINGER)
503 if (sc->anim_type != SPIN)
508 case HESITATE: /* entering state HESITATE */
509 switch (sc->anim_type)
512 line->steps = (line->cluster_pos * stagger);
515 line->steps = stagger * (line->cluster_size - line->cluster_pos);
518 line->steps = stagger * line->cluster_pos;
527 switch (sc->anim_type)
529 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
532 line->from.y = (sc->bottom_margin -
534 (line->cluster_pos + 1)));
535 line->to.y += (line->height *
536 ((line->cluster_size/2.0) - line->cluster_pos));
540 case SCROLL_TOP: /* entering state TOP IN */
543 line->from.y = (sc->top_margin +
545 (line->cluster_size - line->cluster_pos)));
546 line->to.y += (line->height *
547 ((line->cluster_size/2.0) - line->cluster_pos));
551 case SPIN: /* entering state SPIN IN */
554 line->to.y += (line->height *
555 ((line->cluster_size/2.0) - line->cluster_pos));
556 line->from.y += (line->height *
557 ((line->cluster_size/2.0) - line->cluster_pos));
570 switch (sc->anim_type)
572 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
573 line->from = line->to;
575 line->to.y = (sc->top_margin +
577 (line->cluster_size - line->cluster_pos)));
581 case SCROLL_TOP: /* entering state TOP OUT */
582 line->from = line->to;
584 line->to.y = (sc->bottom_margin -
586 (line->cluster_pos + 1)));
590 case SPIN: /* entering state SPIN OUT */
591 line->from = line->to;
593 line->to.y += (line->height *
594 ((line->cluster_size/2.0) - line->cluster_pos));
596 line->fth = line->tth;
607 line->from = line->to;
608 line->steps = linger;
615 line->steps /= speed;
622 i = (double) line->step / line->steps;
624 /* Move along the path exponentially, slow side towards the middle. */
625 if (line->state == OUT)
628 ii = 1 - ((1-i) * (1-i));
630 line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
631 line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
632 line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
633 line->cth = line->fth + (ii * (line->tth - line->fth));
635 if (line->state == OUT) ii = 1-ii;
636 line->color[3] = sc->color[3] * ii;
649 /* Start a new cluster of lines going.
650 Pick their anim type, and in, mid, and out positions.
653 reset_lines (ModeInfo *mi)
655 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
658 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
660 sc->rotation.x = 5 - BELLRAND(10);
661 sc->rotation.y = 5 - BELLRAND(10);
662 sc->rotation.z = 5 - BELLRAND(10);
664 switch (random() % 8)
666 case 0: sc->anim_type = SCROLL_TOP; break;
667 case 1: sc->anim_type = SCROLL_BOTTOM; break;
668 default: sc->anim_type = SPIN; break;
671 minx = sc->left_margin * 0.9;
672 maxx = sc->right_margin * 0.9;
674 miny = sc->bottom_margin * 0.9;
675 maxy = sc->top_margin * 0.9;
677 minz = sc->left_margin * 5;
678 maxz = sc->right_margin * 2;
680 maxw = sc->font_wrap_pixels * sc->font_scale;
681 maxh = max_lines * sc->line_height * sc->font_scale;
683 if (maxw > maxx - minx)
685 if (maxh > maxy - miny)
688 if (alignment_random_p)
689 alignment = (random() % 3) - 1;
691 if (alignment == -1) maxx -= maxw;
692 else if (alignment == 1) minx += maxw;
693 else minx += maxw/2, maxx -= maxw/2;
698 sc->mid.x = minx + frand (maxx - minx);
699 if (sc->anim_type == SPIN)
700 sc->mid.y = miny + BELLRAND (maxy - miny);
702 sc->mid.y = miny + frand (maxy - miny);
704 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
705 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
707 sc->in.y = miny + frand(maxy - miny);
708 sc->out.y = miny + frand(maxy - miny);
710 sc->in.z = minz + frand(maxz - minz);
711 sc->out.z = minz + frand(maxz - minz);
715 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
716 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
718 for (i = 0; i < max_lines; i++)
720 line *line = make_line (sc, (i == 0));
721 if (!line) break; /* no text available */
722 if (i >= min_lines &&
723 (!line->text || !*line->text)) /* blank after min */
727 for (i = 0; i < sc->nlines; i++)
729 line *line = sc->lines[i];
732 line->from.y = sc->bottom_margin;
737 line->from.y = prev->from.y - prev->height;
738 line->to.y = prev->to.y - prev->height;
740 line->cluster_pos = i;
741 line->cluster_size = sc->nlines;
748 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
751 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
753 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
756 a[0] = c.red / 65536.0;
757 a[1] = c.green / 65536.0;
758 a[2] = c.blue / 65536.0;
763 /* Window management, etc
766 reshape_fliptext (ModeInfo *mi, int width, int height)
768 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
769 GLfloat h = (GLfloat) height / (GLfloat) width;
771 glViewport (0, 0, (GLint) width, (GLint) height);
773 glMatrixMode(GL_PROJECTION);
775 gluPerspective (60.0, 1/h, 0.01, 100.0);
777 glMatrixMode(GL_MODELVIEW);
779 gluLookAt( 0.0, 0.0, 2.6,
783 glClear(GL_COLOR_BUFFER_BIT);
785 sc->right_margin = sc->top_margin / h;
786 sc->left_margin = -sc->right_margin;
791 init_fliptext (ModeInfo *mi)
793 int wire = MI_IS_WIREFRAME(mi);
795 fliptext_configuration *sc;
798 scs = (fliptext_configuration *)
799 calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
801 fprintf(stderr, "%s: out of memory\n", progname);
805 sc = &scs[MI_SCREEN(mi)];
806 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
809 sc = &scs[MI_SCREEN(mi)];
810 sc->dpy = MI_DISPLAY(mi);
812 if ((sc->glx_context = init_GL(mi)) != NULL) {
813 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
814 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
817 program = get_string_resource (mi->dpy, "program", "Program");
821 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
822 check_gl_error ("loading font");
823 cw = texture_string_width (sc->texfont, "n", &lh);
825 sc->line_height = lh;
830 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
832 glEnable (GL_ALPHA_TEST);
833 glEnable (GL_TEXTURE_2D);
835 /* "Anistropic filtering helps for quadrilateral-angled textures.
836 A sharper image is accomplished by interpolating and filtering
837 multiple samples from one or more mipmaps to better approximate
838 very distorted textures. This is the next level of filtering
839 after trilinear filtering." */
840 if (strstr ((char *) glGetString(GL_EXTENSIONS),
841 "GL_EXT_texture_filter_anisotropic"))
843 GLfloat anisotropic = 0.0;
844 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
845 if (anisotropic >= 1.0)
846 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
851 /* The default font is (by fiat) "18 points".
852 Interpret the user's font size request relative to that.
854 sc->font_scale = 3 * (font_size / 18.0);
856 if (target_columns <= 2) target_columns = 2;
858 /* Figure out what the wrap column should be, in font-coordinate pixels.
859 Compute it from the given -columns value, but don't let it be wider
863 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
864 sc->font_wrap_pixels = target_columns * sc->char_width;
865 if (sc->font_wrap_pixels > maxw ||
866 sc->font_wrap_pixels <= 0)
867 sc->font_wrap_pixels = maxw;
870 sc->buf_size = target_columns * max_lines;
871 sc->buf = (char *) calloc (1, sc->buf_size);
873 alignment_random_p = False;
874 if (!alignment_str || !*alignment_str ||
875 !strcasecmp(alignment_str, "left"))
877 else if (!strcasecmp(alignment_str, "center") ||
878 !strcasecmp(alignment_str, "middle"))
880 else if (!strcasecmp(alignment_str, "right"))
882 else if (!strcasecmp(alignment_str, "random"))
883 alignment = -1, alignment_random_p = True;
888 "%s: alignment must be left/center/right/random, not \"%s\"\n",
889 progname, alignment_str);
893 sc->tc = textclient_open (sc->dpy);
895 if (max_lines < 1) max_lines = 1;
896 min_lines = max_lines * 0.66;
897 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
898 if (min_lines < 1) min_lines = 1;
900 parse_color (mi, "foreground",
901 get_string_resource(mi->dpy, "foreground", "Foreground"),
904 sc->top_margin = (sc->char_width * 100);
905 sc->bottom_margin = -sc->top_margin;
906 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
911 draw_fliptext (ModeInfo *mi)
913 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
914 /* XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
915 Display *dpy = MI_DISPLAY(mi);
916 Window window = MI_WINDOW(mi);
919 if (!sc->glx_context)
922 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
925 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
926 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
929 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
931 mi->polygon_count = 0;
934 glRotatef(current_device_rotation(), 0, 0, 1);
936 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
940 glRotatef (sc->rotation.x, 1, 0, 0);
941 glRotatef (sc->rotation.y, 0, 1, 0);
942 glRotatef (sc->rotation.z, 0, 0, 1);
945 glDisable (GL_TEXTURE_2D);
947 glBegin (GL_LINE_LOOP);
948 glVertex3f (sc->left_margin, sc->top_margin, 0);
949 glVertex3f (sc->right_margin, sc->top_margin, 0);
950 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
951 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
954 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
955 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
956 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
957 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
958 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
959 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
961 glEnable (GL_TEXTURE_2D);
964 for (i = 0; i < sc->nlines; i++)
966 line *line = sc->lines[i];
967 draw_line (mi, line);
968 tick_line (sc, line);
971 for (i = sc->nlines-1; i >= 0; i--)
973 line *line = sc->lines[i];
974 if (line->state == DEAD)
975 free_line (sc, line);
983 if (mi->fps_p) do_fps (mi);
985 glXSwapBuffers(dpy, window);
989 release_fliptext (ModeInfo *mi)
993 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
994 fliptext_configuration *sc = &scs[screen];
996 textclient_close (sc->tc);
998 /* #### there's more to free here */
1006 XSCREENSAVER_MODULE ("FlipText", fliptext)