2 * fliptext, Copyright (c) 2005-2015 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 #define DEF_FONT "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-*-*"
18 #define DEF_COLOR "#00CCFF"
20 #define DEFAULTS "*delay: 10000 \n" \
21 "*showFPS: False \n" \
22 "*wireframe: False \n" \
24 "*texFontCacheSize: 60 \n" \
25 "*font: " DEF_FONT "\n" \
26 ".foreground: " DEF_COLOR "\n" \
27 "*program: xscreensaver-text --cols 0" /* don't wrap */
29 # define refresh_fliptext 0
30 # define release_fliptext 0
31 # define fliptext_handle_event 0
33 #define countof(x) (sizeof((x))/sizeof((*x)))
36 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
38 #include "xlockmore.h"
40 #include "textclient.h"
42 #ifdef USE_GL /* whole file */
44 /* Should be in <GL/glext.h> */
45 # ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
46 # define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
48 # ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
49 # define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
54 #define DEF_FONT_SIZE "20"
55 #define DEF_COLUMNS "80"
56 #define DEF_ALIGNMENT "random"
57 #define DEF_SPEED "1.0"
60 #define FONT_WEIGHT 14
63 typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state;
64 typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type;
66 typedef struct { GLfloat x, y, z; } XYZ;
70 GLfloat width, height; /* size */
71 XYZ from, to, current; /* start, end, and current position */
72 GLfloat fth, tth, cth; /* rotation around Z */
74 int cluster_size; /* how many lines in this cluster */
75 int cluster_pos; /* position of this line in the cluster */
77 line_state state; /* current motion model */
78 int step, steps; /* progress along this path */
86 GLXContext *glx_context;
88 texture_font_data *texfont;
97 int char_width; /* in font units */
98 int line_height; /* in font units */
99 double font_scale; /* convert font units to display units */
101 int font_wrap_pixels; /* in font units (for wrapping text) */
103 int top_margin, bottom_margin;
104 int left_margin, right_margin;
110 line_anim_type anim_type;
115 } fliptext_configuration;
118 static fliptext_configuration *scs = NULL;
120 static int max_lines, min_lines;
121 static float font_size;
122 static int target_columns;
123 static char *alignment_str;
124 static int alignment_random_p;
125 static GLfloat speed;
127 static XrmOptionDescRec opts[] = {
128 {"-lines", ".lines", XrmoptionSepArg, 0 },
129 {"-size", ".fontSize", XrmoptionSepArg, 0 },
130 {"-columns", ".columns", XrmoptionSepArg, 0 },
131 {"-speed", ".speed", XrmoptionSepArg, 0 },
132 /*{"-font", ".font", XrmoptionSepArg, 0 },*/
133 {"-alignment", ".alignment", XrmoptionSepArg, 0 },
134 {"-left", ".alignment", XrmoptionNoArg, "Left" },
135 {"-right", ".alignment", XrmoptionNoArg, "Right" },
136 {"-center", ".alignment", XrmoptionNoArg, "Center" },
139 static argtype vars[] = {
140 {&max_lines, "lines", "Integer", DEF_LINES, t_Int},
141 {&font_size, "fontSize", "Float", DEF_FONT_SIZE, t_Float},
142 {&target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
143 {&alignment_str, "alignment", "Alignment", DEF_ALIGNMENT, t_String},
144 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
147 ENTRYPOINT ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL};
151 /* Tabs are bad, mmmkay? */
154 untabify (const char *string)
156 const char *ostring = string;
157 char *result = (char *) malloc ((strlen(string) * 8) + 1);
167 } while (col % TAB_WIDTH);
170 else if (*string == '\r' || *string == '\n')
175 else if (*string == '\010') /* backspace */
177 if (string > ostring)
192 strip (char *s, Bool leading, Bool trailing)
196 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
201 while (*s2 == ' ' || *s2 == '\t')
213 char_width (fliptext_configuration *sc, char c)
219 texture_string_metrics (sc->texfont, s, &e, 0, 0);
224 /* Returns a single line of text from the output buffer of the subprocess,
225 taking into account wrapping, centering, etc. Returns 0 if no complete
226 line is currently available.
229 get_one_line (fliptext_configuration *sc)
232 int wrap_pix = sc->font_wrap_pixels;
236 int target = sc->buf_size - sc->buf_tail - 2;
238 /* Fill as much as we can into sc->buf, but stop at newline.
242 int c = textclient_getc (sc->tc);
245 sc->buf[sc->buf_tail++] = (char) c;
246 sc->buf[sc->buf_tail] = 0;
248 if (c == '\r' || c == '\n')
256 if (s >= sc->buf + sc->buf_tail)
257 /* Reached end of buffer before end of line. Bail. */
260 cw = char_width (sc, *s);
262 if (*s == '\r' || *s == '\n' ||
263 col_pix + cw >= wrap_pix)
267 if (*s == '\r' || *s == '\n')
269 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
276 /* We wrapped -- try to back up to the previous word boundary. */
279 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
290 result = (char *) malloc (L+1);
291 memcpy (result, sc->buf, L);
296 char *ut = untabify (t);
297 strip (ut, (sc->alignment == 0), 1); /* if centering, strip
298 leading whitespace too */
303 if (sc->buf_tail > (s - sc->buf))
305 int i = sc->buf_tail - (s - sc->buf);
306 memmove (sc->buf, s, i);
308 sc->buf[sc->buf_tail] = 0;
315 sc->buf[sc->buf_tail] = 0;
326 int tab_pix = TAB_WIDTH * sc->char_width;
327 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
328 col_pix = tab_pix * ((col / tab_pix) + 1);
339 blank_p (const char *s)
342 if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
347 /* Reads some text from the subprocess, and creates and returns a `line'
348 object. Adds that object to the lines list. Returns 0 if no text
351 If skip_blanks_p, then keep trying for new lines of text until we
352 get one that is not empty.
355 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
362 s = get_one_line (sc);
363 if (s && skip_blanks_p && blank_p (s))
371 ln = (line *) calloc (1, sizeof(*ln));
374 texture_string_metrics (sc->texfont, s, &e, 0, 0);
375 ln->width = sc->font_scale * e.width;
376 ln->height = sc->font_scale * sc->line_height;
378 memcpy (ln->color, sc->color, sizeof(ln->color));
381 if (sc->lines_size <= sc->nlines)
383 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
384 sc->lines = (line **)
385 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
388 fprintf (stderr, "%s: out of memory (%d lines)\n",
389 progname, sc->lines_size);
394 sc->lines[sc->nlines-1] = ln;
399 /* frees the object and removes it from the list.
402 free_line (fliptext_configuration *sc, line *line)
405 for (i = 0; i < sc->nlines; i++)
406 if (sc->lines[i] == line)
408 if (i == sc->nlines) abort();
409 for (; i < sc->nlines-1; i++)
410 sc->lines[i] = sc->lines[i+1];
420 draw_line (ModeInfo *mi, line *line)
422 int wire = MI_IS_WIREFRAME(mi);
423 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
425 if (! line->text || !*line->text ||
426 line->state == NEW || line->state == HESITATE || line->state == DEAD)
430 glTranslatef (line->current.x, line->current.y, line->current.z);
432 glRotatef (line->cth, 0, 1, 0);
434 if (sc->alignment == 1)
435 glTranslatef (-line->width, 0, 0);
436 else if (sc->alignment == 0)
437 glTranslatef (-line->width/2, 0, 0);
439 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
441 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
444 print_texture_string (sc->texfont, line->text);
448 char *s = line->text;
451 glDisable (GL_TEXTURE_2D);
452 glColor3f (0.4, 0.4, 0.4);
457 texture_string_metrics (sc->texfont, c, &e, 0, 0);
459 h = e.ascent + e.descent;
460 glBegin (GL_LINE_LOOP);
461 glVertex3f (0, 0, 0);
462 glVertex3f (w, 0, 0);
463 glVertex3f (w, h, 0);
464 glVertex3f (0, h, 0);
466 glTranslatef (w, 0, 0);
471 glDisable (GL_TEXTURE_2D);
472 glColor3f (0.4, 0.4, 0.4);
473 glBegin (GL_LINE_LOOP);
474 glVertex3f (0, 0, 0);
475 glVertex3f (line->width/sc->font_scale, 0, 0);
476 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
477 glVertex3f (0, line->height/sc->font_scale, 0);
479 if (!wire) glEnable (GL_TEXTURE_2D);
484 mi->polygon_count += strlen (line->text);
488 tick_line (fliptext_configuration *sc, line *line)
490 int stagger = 30; /* frames of delay between line spin-outs */
491 int slide = 600; /* frames in a slide in/out */
492 int linger = 0; /* frames to pause with no motion */
495 if (line->state >= DEAD) abort();
496 if (++line->step >= line->steps)
501 if (linger == 0 && line->state == LINGER)
504 if (sc->anim_type != SPIN)
509 case HESITATE: /* entering state HESITATE */
510 switch (sc->anim_type)
513 line->steps = (line->cluster_pos * stagger);
516 line->steps = stagger * (line->cluster_size - line->cluster_pos);
519 line->steps = stagger * line->cluster_pos;
528 switch (sc->anim_type)
530 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
533 line->from.y = (sc->bottom_margin -
535 (line->cluster_pos + 1)));
536 line->to.y += (line->height *
537 ((line->cluster_size/2.0) - line->cluster_pos));
541 case SCROLL_TOP: /* entering state TOP IN */
544 line->from.y = (sc->top_margin +
546 (line->cluster_size - line->cluster_pos)));
547 line->to.y += (line->height *
548 ((line->cluster_size/2.0) - line->cluster_pos));
552 case SPIN: /* entering state SPIN IN */
555 line->to.y += (line->height *
556 ((line->cluster_size/2.0) - line->cluster_pos));
557 line->from.y += (line->height *
558 ((line->cluster_size/2.0) - line->cluster_pos));
571 switch (sc->anim_type)
573 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
574 line->from = line->to;
576 line->to.y = (sc->top_margin +
578 (line->cluster_size - line->cluster_pos)));
582 case SCROLL_TOP: /* entering state TOP OUT */
583 line->from = line->to;
585 line->to.y = (sc->bottom_margin -
587 (line->cluster_pos + 1)));
591 case SPIN: /* entering state SPIN OUT */
592 line->from = line->to;
594 line->to.y += (line->height *
595 ((line->cluster_size/2.0) - line->cluster_pos));
597 line->fth = line->tth;
608 line->from = line->to;
609 line->steps = linger;
616 line->steps /= speed;
623 i = (double) line->step / line->steps;
625 /* Move along the path exponentially, slow side towards the middle. */
626 if (line->state == OUT)
629 ii = 1 - ((1-i) * (1-i));
631 line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
632 line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
633 line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
634 line->cth = line->fth + (ii * (line->tth - line->fth));
636 if (line->state == OUT) ii = 1-ii;
637 line->color[3] = sc->color[3] * ii;
650 /* Start a new cluster of lines going.
651 Pick their anim type, and in, mid, and out positions.
654 reset_lines (ModeInfo *mi)
656 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
659 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
661 sc->rotation.x = 5 - BELLRAND(10);
662 sc->rotation.y = 5 - BELLRAND(10);
663 sc->rotation.z = 5 - BELLRAND(10);
665 switch (random() % 8)
667 case 0: sc->anim_type = SCROLL_TOP; break;
668 case 1: sc->anim_type = SCROLL_BOTTOM; break;
669 default: sc->anim_type = SPIN; break;
672 minx = sc->left_margin * 0.9;
673 maxx = sc->right_margin * 0.9;
675 miny = sc->bottom_margin * 0.9;
676 maxy = sc->top_margin * 0.9;
678 minz = sc->left_margin * 5;
679 maxz = sc->right_margin * 2;
681 maxw = sc->font_wrap_pixels * sc->font_scale;
682 maxh = max_lines * sc->line_height * sc->font_scale;
684 if (maxw > maxx - minx)
686 if (maxh > maxy - miny)
689 if (alignment_random_p)
690 sc->alignment = (random() % 3) - 1;
692 if (sc->alignment == -1) maxx -= maxw;
693 else if (sc->alignment == 1) minx += maxw;
694 else minx += maxw/2, maxx -= maxw/2;
699 sc->mid.x = minx + frand (maxx - minx);
700 if (sc->anim_type == SPIN)
701 sc->mid.y = miny + BELLRAND (maxy - miny);
703 sc->mid.y = miny + frand (maxy - miny);
705 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
706 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
708 sc->in.y = miny + frand(maxy - miny);
709 sc->out.y = miny + frand(maxy - miny);
711 sc->in.z = minz + frand(maxz - minz);
712 sc->out.z = minz + frand(maxz - minz);
716 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
717 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
719 for (i = 0; i < max_lines; i++)
721 line *line = make_line (sc, (i == 0));
722 if (!line) break; /* no text available */
723 if (i >= min_lines &&
724 (!line->text || !*line->text)) /* blank after min */
728 for (i = 0; i < sc->nlines; i++)
730 line *line = sc->lines[i];
733 line->from.y = sc->bottom_margin;
738 line->from.y = prev->from.y - prev->height;
739 line->to.y = prev->to.y - prev->height;
741 line->cluster_pos = i;
742 line->cluster_size = sc->nlines;
749 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
752 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
754 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
757 a[0] = c.red / 65536.0;
758 a[1] = c.green / 65536.0;
759 a[2] = c.blue / 65536.0;
764 /* Window management, etc
767 reshape_fliptext (ModeInfo *mi, int width, int height)
769 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
770 GLfloat h = (GLfloat) height / (GLfloat) width;
772 glViewport (0, 0, (GLint) width, (GLint) height);
774 glMatrixMode(GL_PROJECTION);
776 gluPerspective (60.0, 1/h, 0.01, 100.0);
778 glMatrixMode(GL_MODELVIEW);
780 gluLookAt( 0.0, 0.0, 2.6,
784 glClear(GL_COLOR_BUFFER_BIT);
786 sc->right_margin = sc->top_margin / h;
787 sc->left_margin = -sc->right_margin;
791 static void free_fliptext (ModeInfo *mi);
795 init_fliptext (ModeInfo *mi)
797 int wire = MI_IS_WIREFRAME(mi);
799 fliptext_configuration *sc;
801 MI_INIT(mi, scs, free_fliptext);
803 sc = &scs[MI_SCREEN(mi)];
804 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
806 sc->dpy = MI_DISPLAY(mi);
808 if ((sc->glx_context = init_GL(mi)) != NULL) {
809 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
810 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
815 int cw, lh, ascent, descent;
816 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
817 check_gl_error ("loading font");
818 texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
820 lh = ascent + descent;
822 sc->line_height = lh;
827 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
829 glEnable (GL_ALPHA_TEST);
830 glEnable (GL_TEXTURE_2D);
832 /* "Anistropic filtering helps for quadrilateral-angled textures.
833 A sharper image is accomplished by interpolating and filtering
834 multiple samples from one or more mipmaps to better approximate
835 very distorted textures. This is the next level of filtering
836 after trilinear filtering." */
837 if (strstr ((char *) glGetString(GL_EXTENSIONS),
838 "GL_EXT_texture_filter_anisotropic"))
840 GLfloat anisotropic = 0.0;
841 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
842 if (anisotropic >= 1.0)
843 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
848 /* The default font is (by fiat) "18 points".
849 Interpret the user's font size request relative to that.
851 sc->font_scale = 3 * (font_size / 18.0);
853 if (target_columns <= 2) target_columns = 2;
855 /* Figure out what the wrap column should be, in font-coordinate pixels.
856 Compute it from the given -columns value, but don't let it be wider
860 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
861 sc->font_wrap_pixels = target_columns * sc->char_width;
862 if (sc->font_wrap_pixels > maxw ||
863 sc->font_wrap_pixels <= 0)
864 sc->font_wrap_pixels = maxw;
867 sc->buf_size = target_columns * max_lines;
868 sc->buf = (char *) calloc (1, sc->buf_size);
870 alignment_random_p = False;
871 if (!alignment_str || !*alignment_str ||
872 !strcasecmp(alignment_str, "left"))
874 else if (!strcasecmp(alignment_str, "center") ||
875 !strcasecmp(alignment_str, "middle"))
877 else if (!strcasecmp(alignment_str, "right"))
879 else if (!strcasecmp(alignment_str, "random"))
880 sc->alignment = -1, alignment_random_p = True;
885 "%s: alignment must be left/center/right/random, not \"%s\"\n",
886 progname, alignment_str);
890 sc->tc = textclient_open (sc->dpy);
892 if (max_lines < 1) max_lines = 1;
893 min_lines = max_lines * 0.66;
894 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
895 if (min_lines < 1) min_lines = 1;
897 parse_color (mi, "foreground",
898 get_string_resource(mi->dpy, "foreground", "Foreground"),
901 sc->top_margin = (sc->char_width * 100);
902 sc->bottom_margin = -sc->top_margin;
903 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
908 draw_fliptext (ModeInfo *mi)
910 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
911 /* XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
912 Display *dpy = MI_DISPLAY(mi);
913 Window window = MI_WINDOW(mi);
916 if (!sc->glx_context)
919 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
922 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
923 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
926 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
928 mi->polygon_count = 0;
931 glRotatef(current_device_rotation(), 0, 0, 1);
933 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
937 glRotatef (sc->rotation.x, 1, 0, 0);
938 glRotatef (sc->rotation.y, 0, 1, 0);
939 glRotatef (sc->rotation.z, 0, 0, 1);
942 glDisable (GL_TEXTURE_2D);
944 glBegin (GL_LINE_LOOP);
945 glVertex3f (sc->left_margin, sc->top_margin, 0);
946 glVertex3f (sc->right_margin, sc->top_margin, 0);
947 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
948 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
951 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
952 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
953 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
954 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
955 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
956 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
958 glEnable (GL_TEXTURE_2D);
961 for (i = 0; i < sc->nlines; i++)
963 line *line = sc->lines[i];
964 draw_line (mi, line);
965 tick_line (sc, line);
968 for (i = sc->nlines-1; i >= 0; i--)
970 line *line = sc->lines[i];
971 if (line->state == DEAD)
972 free_line (sc, line);
980 if (mi->fps_p) do_fps (mi);
982 glXSwapBuffers(dpy, window);
986 free_fliptext (ModeInfo *mi)
988 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
990 textclient_close (sc->tc);
993 /* #### there's more to free here */
996 XSCREENSAVER_MODULE ("FlipText", fliptext)