2 * fliptext, Copyright (c) 2005-2014 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 fliptext_handle_event 0
32 #define countof(x) (sizeof((x))/sizeof((*x)))
35 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
37 #include "xlockmore.h"
39 #include "textclient.h"
41 #ifdef USE_GL /* whole file */
43 /* Should be in <GL/glext.h> */
44 # ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT
45 # define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
47 # ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
48 # define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
53 #define DEF_FONT_SIZE "20"
54 #define DEF_COLUMNS "80"
55 #define DEF_ALIGNMENT "random"
56 #define DEF_SPEED "1.0"
59 #define FONT_WEIGHT 14
62 typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state;
63 typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type;
65 typedef struct { GLfloat x, y, z; } XYZ;
69 GLfloat width, height; /* size */
70 XYZ from, to, current; /* start, end, and current position */
71 GLfloat fth, tth, cth; /* rotation around Z */
73 int cluster_size; /* how many lines in this cluster */
74 int cluster_pos; /* position of this line in the cluster */
76 line_state state; /* current motion model */
77 int step, steps; /* progress along this path */
85 GLXContext *glx_context;
87 texture_font_data *texfont;
94 int char_width; /* in font units */
95 int line_height; /* in font units */
96 double font_scale; /* convert font units to display units */
98 int font_wrap_pixels; /* in font units (for wrapping text) */
100 int top_margin, bottom_margin;
101 int left_margin, right_margin;
107 line_anim_type anim_type;
112 } fliptext_configuration;
115 static fliptext_configuration *scs = NULL;
117 static int max_lines, min_lines;
118 static float font_size;
119 static int target_columns;
120 static char *alignment_str;
121 static int alignment, alignment_random_p;
122 static GLfloat speed;
124 static XrmOptionDescRec opts[] = {
125 {"-lines", ".lines", XrmoptionSepArg, 0 },
126 {"-size", ".fontSize", XrmoptionSepArg, 0 },
127 {"-columns", ".columns", XrmoptionSepArg, 0 },
128 {"-speed", ".speed", XrmoptionSepArg, 0 },
129 /*{"-font", ".font", XrmoptionSepArg, 0 },*/
130 {"-alignment", ".alignment", XrmoptionSepArg, 0 },
131 {"-left", ".alignment", XrmoptionNoArg, "Left" },
132 {"-right", ".alignment", XrmoptionNoArg, "Right" },
133 {"-center", ".alignment", XrmoptionNoArg, "Center" },
136 static argtype vars[] = {
137 {&max_lines, "lines", "Integer", DEF_LINES, t_Int},
138 {&font_size, "fontSize", "Float", DEF_FONT_SIZE, t_Float},
139 {&target_columns, "columns", "Integer", DEF_COLUMNS, t_Int},
140 {&alignment_str, "alignment", "Alignment", DEF_ALIGNMENT, t_String},
141 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
144 ENTRYPOINT ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL};
148 /* Tabs are bad, mmmkay? */
151 untabify (const char *string)
153 const char *ostring = string;
154 char *result = (char *) malloc ((strlen(string) * 8) + 1);
164 } while (col % TAB_WIDTH);
167 else if (*string == '\r' || *string == '\n')
172 else if (*string == '\010') /* backspace */
174 if (string > ostring)
189 strip (char *s, Bool leading, Bool trailing)
193 while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t'))
198 while (*s2 == ' ' || *s2 == '\t')
210 char_width (fliptext_configuration *sc, char c)
215 return texture_string_width (sc->texfont, s, 0);
219 /* Returns a single line of text from the output buffer of the subprocess,
220 taking into account wrapping, centering, etc. Returns 0 if no complete
221 line is currently available.
224 get_one_line (fliptext_configuration *sc)
227 int wrap_pix = sc->font_wrap_pixels;
231 int target = sc->buf_size - sc->buf_tail - 2;
233 /* Fill as much as we can into sc->buf, but stop at newline.
237 int c = textclient_getc (sc->tc);
240 sc->buf[sc->buf_tail++] = (char) c;
241 sc->buf[sc->buf_tail] = 0;
243 if (c == '\r' || c == '\n')
251 if (s >= sc->buf + sc->buf_tail)
252 /* Reached end of buffer before end of line. Bail. */
255 cw = char_width (sc, *s);
257 if (*s == '\r' || *s == '\n' ||
258 col_pix + cw >= wrap_pix)
262 if (*s == '\r' || *s == '\n')
264 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
271 /* We wrapped -- try to back up to the previous word boundary. */
274 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
285 result = (char *) malloc (L+1);
286 memcpy (result, sc->buf, L);
291 char *ut = untabify (t);
292 strip (ut, (alignment == 0), 1); /* if centering, strip
293 leading whitespace too */
298 if (sc->buf_tail > (s - sc->buf))
300 int i = sc->buf_tail - (s - sc->buf);
301 memmove (sc->buf, s, i);
303 sc->buf[sc->buf_tail] = 0;
310 sc->buf[sc->buf_tail] = 0;
321 int tab_pix = TAB_WIDTH * sc->char_width;
322 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
323 col_pix = tab_pix * ((col / tab_pix) + 1);
334 blank_p (const char *s)
337 if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
342 /* Reads some text from the subprocess, and creates and returns a `line'
343 object. Adds that object to the lines list. Returns 0 if no text
346 If skip_blanks_p, then keep trying for new lines of text until we
347 get one that is not empty.
350 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
356 s = get_one_line (sc);
357 if (s && skip_blanks_p && blank_p (s))
365 ln = (line *) calloc (1, sizeof(*ln));
368 ln->width = sc->font_scale * texture_string_width (sc->texfont, s, 0);
369 ln->height = sc->font_scale * sc->line_height;
371 memcpy (ln->color, sc->color, sizeof(ln->color));
374 if (sc->lines_size <= sc->nlines)
376 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
377 sc->lines = (line **)
378 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
381 fprintf (stderr, "%s: out of memory (%d lines)\n",
382 progname, sc->lines_size);
387 sc->lines[sc->nlines-1] = ln;
392 /* frees the object and removes it from the list.
395 free_line (fliptext_configuration *sc, line *line)
398 for (i = 0; i < sc->nlines; i++)
399 if (sc->lines[i] == line)
401 if (i == sc->nlines) abort();
402 for (; i < sc->nlines-1; i++)
403 sc->lines[i] = sc->lines[i+1];
413 draw_line (ModeInfo *mi, line *line)
415 int wire = MI_IS_WIREFRAME(mi);
416 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
418 if (! line->text || !*line->text ||
419 line->state == NEW || line->state == HESITATE || line->state == DEAD)
423 glTranslatef (line->current.x, line->current.y, line->current.z);
425 glRotatef (line->cth, 0, 1, 0);
428 glTranslatef (-line->width, 0, 0);
429 else if (alignment == 0)
430 glTranslatef (-line->width/2, 0, 0);
432 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
434 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
437 print_texture_string (sc->texfont, line->text);
441 char *s = line->text;
444 glDisable (GL_TEXTURE_2D);
445 glColor3f (0.4, 0.4, 0.4);
449 w = texture_string_width (sc->texfont, c, &h);
450 glBegin (GL_LINE_LOOP);
451 glVertex3f (0, 0, 0);
452 glVertex3f (w, 0, 0);
453 glVertex3f (w, h, 0);
454 glVertex3f (0, h, 0);
456 glTranslatef (w, 0, 0);
461 glDisable (GL_TEXTURE_2D);
462 glColor3f (0.4, 0.4, 0.4);
463 glBegin (GL_LINE_LOOP);
464 glVertex3f (0, 0, 0);
465 glVertex3f (line->width/sc->font_scale, 0, 0);
466 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
467 glVertex3f (0, line->height/sc->font_scale, 0);
469 if (!wire) glEnable (GL_TEXTURE_2D);
474 mi->polygon_count += strlen (line->text);
478 tick_line (fliptext_configuration *sc, line *line)
480 int stagger = 30; /* frames of delay between line spin-outs */
481 int slide = 600; /* frames in a slide in/out */
482 int linger = 0; /* frames to pause with no motion */
485 if (line->state >= DEAD) abort();
486 if (++line->step >= line->steps)
491 if (linger == 0 && line->state == LINGER)
494 if (sc->anim_type != SPIN)
499 case HESITATE: /* entering state HESITATE */
500 switch (sc->anim_type)
503 line->steps = (line->cluster_pos * stagger);
506 line->steps = stagger * (line->cluster_size - line->cluster_pos);
509 line->steps = stagger * line->cluster_pos;
518 switch (sc->anim_type)
520 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
523 line->from.y = (sc->bottom_margin -
525 (line->cluster_pos + 1)));
526 line->to.y += (line->height *
527 ((line->cluster_size/2.0) - line->cluster_pos));
531 case SCROLL_TOP: /* entering state TOP IN */
534 line->from.y = (sc->top_margin +
536 (line->cluster_size - line->cluster_pos)));
537 line->to.y += (line->height *
538 ((line->cluster_size/2.0) - line->cluster_pos));
542 case SPIN: /* entering state SPIN IN */
545 line->to.y += (line->height *
546 ((line->cluster_size/2.0) - line->cluster_pos));
547 line->from.y += (line->height *
548 ((line->cluster_size/2.0) - line->cluster_pos));
561 switch (sc->anim_type)
563 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
564 line->from = line->to;
566 line->to.y = (sc->top_margin +
568 (line->cluster_size - line->cluster_pos)));
572 case SCROLL_TOP: /* entering state TOP OUT */
573 line->from = line->to;
575 line->to.y = (sc->bottom_margin -
577 (line->cluster_pos + 1)));
581 case SPIN: /* entering state SPIN OUT */
582 line->from = line->to;
584 line->to.y += (line->height *
585 ((line->cluster_size/2.0) - line->cluster_pos));
587 line->fth = line->tth;
598 line->from = line->to;
599 line->steps = linger;
606 line->steps /= speed;
613 i = (double) line->step / line->steps;
615 /* Move along the path exponentially, slow side towards the middle. */
616 if (line->state == OUT)
619 ii = 1 - ((1-i) * (1-i));
621 line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
622 line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
623 line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
624 line->cth = line->fth + (ii * (line->tth - line->fth));
626 if (line->state == OUT) ii = 1-ii;
627 line->color[3] = sc->color[3] * ii;
640 /* Start a new cluster of lines going.
641 Pick their anim type, and in, mid, and out positions.
644 reset_lines (ModeInfo *mi)
646 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
649 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
651 sc->rotation.x = 5 - BELLRAND(10);
652 sc->rotation.y = 5 - BELLRAND(10);
653 sc->rotation.z = 5 - BELLRAND(10);
655 switch (random() % 8)
657 case 0: sc->anim_type = SCROLL_TOP; break;
658 case 1: sc->anim_type = SCROLL_BOTTOM; break;
659 default: sc->anim_type = SPIN; break;
662 minx = sc->left_margin * 0.9;
663 maxx = sc->right_margin * 0.9;
665 miny = sc->bottom_margin * 0.9;
666 maxy = sc->top_margin * 0.9;
668 minz = sc->left_margin * 5;
669 maxz = sc->right_margin * 2;
671 maxw = sc->font_wrap_pixels * sc->font_scale;
672 maxh = max_lines * sc->line_height * sc->font_scale;
674 if (maxw > maxx - minx)
676 if (maxh > maxy - miny)
679 if (alignment_random_p)
680 alignment = (random() % 3) - 1;
682 if (alignment == -1) maxx -= maxw;
683 else if (alignment == 1) minx += maxw;
684 else minx += maxw/2, maxx -= maxw/2;
689 sc->mid.x = minx + frand (maxx - minx);
690 if (sc->anim_type == SPIN)
691 sc->mid.y = miny + BELLRAND (maxy - miny);
693 sc->mid.y = miny + frand (maxy - miny);
695 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
696 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
698 sc->in.y = miny + frand(maxy - miny);
699 sc->out.y = miny + frand(maxy - miny);
701 sc->in.z = minz + frand(maxz - minz);
702 sc->out.z = minz + frand(maxz - minz);
706 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
707 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
709 for (i = 0; i < max_lines; i++)
711 line *line = make_line (sc, (i == 0));
712 if (!line) break; /* no text available */
713 if (i >= min_lines &&
714 (!line->text || !*line->text)) /* blank after min */
718 for (i = 0; i < sc->nlines; i++)
720 line *line = sc->lines[i];
723 line->from.y = sc->bottom_margin;
728 line->from.y = prev->from.y - prev->height;
729 line->to.y = prev->to.y - prev->height;
731 line->cluster_pos = i;
732 line->cluster_size = sc->nlines;
739 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
742 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
744 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
747 a[0] = c.red / 65536.0;
748 a[1] = c.green / 65536.0;
749 a[2] = c.blue / 65536.0;
754 /* Window management, etc
757 reshape_fliptext (ModeInfo *mi, int width, int height)
759 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
760 GLfloat h = (GLfloat) height / (GLfloat) width;
762 glViewport (0, 0, (GLint) width, (GLint) height);
764 glMatrixMode(GL_PROJECTION);
766 gluPerspective (60.0, 1/h, 0.01, 100.0);
768 glMatrixMode(GL_MODELVIEW);
770 gluLookAt( 0.0, 0.0, 2.6,
774 glClear(GL_COLOR_BUFFER_BIT);
776 sc->right_margin = sc->top_margin / h;
777 sc->left_margin = -sc->right_margin;
782 init_fliptext (ModeInfo *mi)
784 int wire = MI_IS_WIREFRAME(mi);
786 fliptext_configuration *sc;
789 scs = (fliptext_configuration *)
790 calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
792 fprintf(stderr, "%s: out of memory\n", progname);
796 sc = &scs[MI_SCREEN(mi)];
797 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
800 sc = &scs[MI_SCREEN(mi)];
801 sc->dpy = MI_DISPLAY(mi);
803 if ((sc->glx_context = init_GL(mi)) != NULL) {
804 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
805 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
810 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
811 check_gl_error ("loading font");
812 cw = texture_string_width (sc->texfont, "n", &lh);
814 sc->line_height = lh;
819 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
821 glEnable (GL_ALPHA_TEST);
822 glEnable (GL_TEXTURE_2D);
824 /* "Anistropic filtering helps for quadrilateral-angled textures.
825 A sharper image is accomplished by interpolating and filtering
826 multiple samples from one or more mipmaps to better approximate
827 very distorted textures. This is the next level of filtering
828 after trilinear filtering." */
829 if (strstr ((char *) glGetString(GL_EXTENSIONS),
830 "GL_EXT_texture_filter_anisotropic"))
832 GLfloat anisotropic = 0.0;
833 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
834 if (anisotropic >= 1.0)
835 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
840 /* The default font is (by fiat) "18 points".
841 Interpret the user's font size request relative to that.
843 sc->font_scale = 3 * (font_size / 18.0);
845 if (target_columns <= 2) target_columns = 2;
847 /* Figure out what the wrap column should be, in font-coordinate pixels.
848 Compute it from the given -columns value, but don't let it be wider
852 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
853 sc->font_wrap_pixels = target_columns * sc->char_width;
854 if (sc->font_wrap_pixels > maxw ||
855 sc->font_wrap_pixels <= 0)
856 sc->font_wrap_pixels = maxw;
859 sc->buf_size = target_columns * max_lines;
860 sc->buf = (char *) calloc (1, sc->buf_size);
862 alignment_random_p = False;
863 if (!alignment_str || !*alignment_str ||
864 !strcasecmp(alignment_str, "left"))
866 else if (!strcasecmp(alignment_str, "center") ||
867 !strcasecmp(alignment_str, "middle"))
869 else if (!strcasecmp(alignment_str, "right"))
871 else if (!strcasecmp(alignment_str, "random"))
872 alignment = -1, alignment_random_p = True;
877 "%s: alignment must be left/center/right/random, not \"%s\"\n",
878 progname, alignment_str);
882 sc->tc = textclient_open (sc->dpy);
884 if (max_lines < 1) max_lines = 1;
885 min_lines = max_lines * 0.66;
886 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
887 if (min_lines < 1) min_lines = 1;
889 parse_color (mi, "foreground",
890 get_string_resource(mi->dpy, "foreground", "Foreground"),
893 sc->top_margin = (sc->char_width * 100);
894 sc->bottom_margin = -sc->top_margin;
895 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
900 draw_fliptext (ModeInfo *mi)
902 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
903 /* XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
904 Display *dpy = MI_DISPLAY(mi);
905 Window window = MI_WINDOW(mi);
908 if (!sc->glx_context)
911 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
914 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
915 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
918 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
920 mi->polygon_count = 0;
923 glRotatef(current_device_rotation(), 0, 0, 1);
925 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
929 glRotatef (sc->rotation.x, 1, 0, 0);
930 glRotatef (sc->rotation.y, 0, 1, 0);
931 glRotatef (sc->rotation.z, 0, 0, 1);
934 glDisable (GL_TEXTURE_2D);
936 glBegin (GL_LINE_LOOP);
937 glVertex3f (sc->left_margin, sc->top_margin, 0);
938 glVertex3f (sc->right_margin, sc->top_margin, 0);
939 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
940 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
943 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
944 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
945 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
946 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
947 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
948 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
950 glEnable (GL_TEXTURE_2D);
953 for (i = 0; i < sc->nlines; i++)
955 line *line = sc->lines[i];
956 draw_line (mi, line);
957 tick_line (sc, line);
960 for (i = sc->nlines-1; i >= 0; i--)
962 line *line = sc->lines[i];
963 if (line->state == DEAD)
964 free_line (sc, line);
972 if (mi->fps_p) do_fps (mi);
974 glXSwapBuffers(dpy, window);
978 release_fliptext (ModeInfo *mi)
982 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
983 fliptext_configuration *sc = &scs[screen];
985 textclient_close (sc->tc);
987 /* #### there's more to free here */
995 XSCREENSAVER_MODULE ("FlipText", fliptext)