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 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)
216 texture_string_metrics (sc->texfont, s, &e, 0, 0);
221 /* Returns a single line of text from the output buffer of the subprocess,
222 taking into account wrapping, centering, etc. Returns 0 if no complete
223 line is currently available.
226 get_one_line (fliptext_configuration *sc)
229 int wrap_pix = sc->font_wrap_pixels;
233 int target = sc->buf_size - sc->buf_tail - 2;
235 /* Fill as much as we can into sc->buf, but stop at newline.
239 int c = textclient_getc (sc->tc);
242 sc->buf[sc->buf_tail++] = (char) c;
243 sc->buf[sc->buf_tail] = 0;
245 if (c == '\r' || c == '\n')
253 if (s >= sc->buf + sc->buf_tail)
254 /* Reached end of buffer before end of line. Bail. */
257 cw = char_width (sc, *s);
259 if (*s == '\r' || *s == '\n' ||
260 col_pix + cw >= wrap_pix)
264 if (*s == '\r' || *s == '\n')
266 if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */
273 /* We wrapped -- try to back up to the previous word boundary. */
276 while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t')
287 result = (char *) malloc (L+1);
288 memcpy (result, sc->buf, L);
293 char *ut = untabify (t);
294 strip (ut, (alignment == 0), 1); /* if centering, strip
295 leading whitespace too */
300 if (sc->buf_tail > (s - sc->buf))
302 int i = sc->buf_tail - (s - sc->buf);
303 memmove (sc->buf, s, i);
305 sc->buf[sc->buf_tail] = 0;
312 sc->buf[sc->buf_tail] = 0;
323 int tab_pix = TAB_WIDTH * sc->char_width;
324 col = TAB_WIDTH * ((col / TAB_WIDTH) + 1);
325 col_pix = tab_pix * ((col / tab_pix) + 1);
336 blank_p (const char *s)
339 if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
344 /* Reads some text from the subprocess, and creates and returns a `line'
345 object. Adds that object to the lines list. Returns 0 if no text
348 If skip_blanks_p, then keep trying for new lines of text until we
349 get one that is not empty.
352 make_line (fliptext_configuration *sc, Bool skip_blanks_p)
359 s = get_one_line (sc);
360 if (s && skip_blanks_p && blank_p (s))
368 ln = (line *) calloc (1, sizeof(*ln));
371 texture_string_metrics (sc->texfont, s, &e, 0, 0);
372 ln->width = sc->font_scale * e.width;
373 ln->height = sc->font_scale * sc->line_height;
375 memcpy (ln->color, sc->color, sizeof(ln->color));
378 if (sc->lines_size <= sc->nlines)
380 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
381 sc->lines = (line **)
382 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
385 fprintf (stderr, "%s: out of memory (%d lines)\n",
386 progname, sc->lines_size);
391 sc->lines[sc->nlines-1] = ln;
396 /* frees the object and removes it from the list.
399 free_line (fliptext_configuration *sc, line *line)
402 for (i = 0; i < sc->nlines; i++)
403 if (sc->lines[i] == line)
405 if (i == sc->nlines) abort();
406 for (; i < sc->nlines-1; i++)
407 sc->lines[i] = sc->lines[i+1];
417 draw_line (ModeInfo *mi, line *line)
419 int wire = MI_IS_WIREFRAME(mi);
420 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
422 if (! line->text || !*line->text ||
423 line->state == NEW || line->state == HESITATE || line->state == DEAD)
427 glTranslatef (line->current.x, line->current.y, line->current.z);
429 glRotatef (line->cth, 0, 1, 0);
432 glTranslatef (-line->width, 0, 0);
433 else if (alignment == 0)
434 glTranslatef (-line->width/2, 0, 0);
436 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
438 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
441 print_texture_string (sc->texfont, line->text);
445 char *s = line->text;
448 glDisable (GL_TEXTURE_2D);
449 glColor3f (0.4, 0.4, 0.4);
454 texture_string_metrics (sc->texfont, c, &e, 0, 0);
456 h = e.ascent + e.descent;
457 glBegin (GL_LINE_LOOP);
458 glVertex3f (0, 0, 0);
459 glVertex3f (w, 0, 0);
460 glVertex3f (w, h, 0);
461 glVertex3f (0, h, 0);
463 glTranslatef (w, 0, 0);
468 glDisable (GL_TEXTURE_2D);
469 glColor3f (0.4, 0.4, 0.4);
470 glBegin (GL_LINE_LOOP);
471 glVertex3f (0, 0, 0);
472 glVertex3f (line->width/sc->font_scale, 0, 0);
473 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
474 glVertex3f (0, line->height/sc->font_scale, 0);
476 if (!wire) glEnable (GL_TEXTURE_2D);
481 mi->polygon_count += strlen (line->text);
485 tick_line (fliptext_configuration *sc, line *line)
487 int stagger = 30; /* frames of delay between line spin-outs */
488 int slide = 600; /* frames in a slide in/out */
489 int linger = 0; /* frames to pause with no motion */
492 if (line->state >= DEAD) abort();
493 if (++line->step >= line->steps)
498 if (linger == 0 && line->state == LINGER)
501 if (sc->anim_type != SPIN)
506 case HESITATE: /* entering state HESITATE */
507 switch (sc->anim_type)
510 line->steps = (line->cluster_pos * stagger);
513 line->steps = stagger * (line->cluster_size - line->cluster_pos);
516 line->steps = stagger * line->cluster_pos;
525 switch (sc->anim_type)
527 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
530 line->from.y = (sc->bottom_margin -
532 (line->cluster_pos + 1)));
533 line->to.y += (line->height *
534 ((line->cluster_size/2.0) - line->cluster_pos));
538 case SCROLL_TOP: /* entering state TOP IN */
541 line->from.y = (sc->top_margin +
543 (line->cluster_size - line->cluster_pos)));
544 line->to.y += (line->height *
545 ((line->cluster_size/2.0) - line->cluster_pos));
549 case SPIN: /* entering state SPIN IN */
552 line->to.y += (line->height *
553 ((line->cluster_size/2.0) - line->cluster_pos));
554 line->from.y += (line->height *
555 ((line->cluster_size/2.0) - line->cluster_pos));
568 switch (sc->anim_type)
570 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
571 line->from = line->to;
573 line->to.y = (sc->top_margin +
575 (line->cluster_size - line->cluster_pos)));
579 case SCROLL_TOP: /* entering state TOP OUT */
580 line->from = line->to;
582 line->to.y = (sc->bottom_margin -
584 (line->cluster_pos + 1)));
588 case SPIN: /* entering state SPIN OUT */
589 line->from = line->to;
591 line->to.y += (line->height *
592 ((line->cluster_size/2.0) - line->cluster_pos));
594 line->fth = line->tth;
605 line->from = line->to;
606 line->steps = linger;
613 line->steps /= speed;
620 i = (double) line->step / line->steps;
622 /* Move along the path exponentially, slow side towards the middle. */
623 if (line->state == OUT)
626 ii = 1 - ((1-i) * (1-i));
628 line->current.x = line->from.x + (ii * (line->to.x - line->from.x));
629 line->current.y = line->from.y + (ii * (line->to.y - line->from.y));
630 line->current.z = line->from.z + (ii * (line->to.z - line->from.z));
631 line->cth = line->fth + (ii * (line->tth - line->fth));
633 if (line->state == OUT) ii = 1-ii;
634 line->color[3] = sc->color[3] * ii;
647 /* Start a new cluster of lines going.
648 Pick their anim type, and in, mid, and out positions.
651 reset_lines (ModeInfo *mi)
653 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
656 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
658 sc->rotation.x = 5 - BELLRAND(10);
659 sc->rotation.y = 5 - BELLRAND(10);
660 sc->rotation.z = 5 - BELLRAND(10);
662 switch (random() % 8)
664 case 0: sc->anim_type = SCROLL_TOP; break;
665 case 1: sc->anim_type = SCROLL_BOTTOM; break;
666 default: sc->anim_type = SPIN; break;
669 minx = sc->left_margin * 0.9;
670 maxx = sc->right_margin * 0.9;
672 miny = sc->bottom_margin * 0.9;
673 maxy = sc->top_margin * 0.9;
675 minz = sc->left_margin * 5;
676 maxz = sc->right_margin * 2;
678 maxw = sc->font_wrap_pixels * sc->font_scale;
679 maxh = max_lines * sc->line_height * sc->font_scale;
681 if (maxw > maxx - minx)
683 if (maxh > maxy - miny)
686 if (alignment_random_p)
687 alignment = (random() % 3) - 1;
689 if (alignment == -1) maxx -= maxw;
690 else if (alignment == 1) minx += maxw;
691 else minx += maxw/2, maxx -= maxw/2;
696 sc->mid.x = minx + frand (maxx - minx);
697 if (sc->anim_type == SPIN)
698 sc->mid.y = miny + BELLRAND (maxy - miny);
700 sc->mid.y = miny + frand (maxy - miny);
702 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
703 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
705 sc->in.y = miny + frand(maxy - miny);
706 sc->out.y = miny + frand(maxy - miny);
708 sc->in.z = minz + frand(maxz - minz);
709 sc->out.z = minz + frand(maxz - minz);
713 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
714 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
716 for (i = 0; i < max_lines; i++)
718 line *line = make_line (sc, (i == 0));
719 if (!line) break; /* no text available */
720 if (i >= min_lines &&
721 (!line->text || !*line->text)) /* blank after min */
725 for (i = 0; i < sc->nlines; i++)
727 line *line = sc->lines[i];
730 line->from.y = sc->bottom_margin;
735 line->from.y = prev->from.y - prev->height;
736 line->to.y = prev->to.y - prev->height;
738 line->cluster_pos = i;
739 line->cluster_size = sc->nlines;
746 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
749 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
751 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
754 a[0] = c.red / 65536.0;
755 a[1] = c.green / 65536.0;
756 a[2] = c.blue / 65536.0;
761 /* Window management, etc
764 reshape_fliptext (ModeInfo *mi, int width, int height)
766 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
767 GLfloat h = (GLfloat) height / (GLfloat) width;
769 glViewport (0, 0, (GLint) width, (GLint) height);
771 glMatrixMode(GL_PROJECTION);
773 gluPerspective (60.0, 1/h, 0.01, 100.0);
775 glMatrixMode(GL_MODELVIEW);
777 gluLookAt( 0.0, 0.0, 2.6,
781 glClear(GL_COLOR_BUFFER_BIT);
783 sc->right_margin = sc->top_margin / h;
784 sc->left_margin = -sc->right_margin;
789 init_fliptext (ModeInfo *mi)
791 int wire = MI_IS_WIREFRAME(mi);
793 fliptext_configuration *sc;
796 scs = (fliptext_configuration *)
797 calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
799 fprintf(stderr, "%s: out of memory\n", progname);
803 sc = &scs[MI_SCREEN(mi)];
804 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
807 sc = &scs[MI_SCREEN(mi)];
808 sc->dpy = MI_DISPLAY(mi);
810 if ((sc->glx_context = init_GL(mi)) != NULL) {
811 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
812 clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
817 int cw, lh, ascent, descent;
818 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
819 check_gl_error ("loading font");
820 texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent);
822 lh = ascent + descent;
824 sc->line_height = lh;
829 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
831 glEnable (GL_ALPHA_TEST);
832 glEnable (GL_TEXTURE_2D);
834 /* "Anistropic filtering helps for quadrilateral-angled textures.
835 A sharper image is accomplished by interpolating and filtering
836 multiple samples from one or more mipmaps to better approximate
837 very distorted textures. This is the next level of filtering
838 after trilinear filtering." */
839 if (strstr ((char *) glGetString(GL_EXTENSIONS),
840 "GL_EXT_texture_filter_anisotropic"))
842 GLfloat anisotropic = 0.0;
843 glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic);
844 if (anisotropic >= 1.0)
845 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
850 /* The default font is (by fiat) "18 points".
851 Interpret the user's font size request relative to that.
853 sc->font_scale = 3 * (font_size / 18.0);
855 if (target_columns <= 2) target_columns = 2;
857 /* Figure out what the wrap column should be, in font-coordinate pixels.
858 Compute it from the given -columns value, but don't let it be wider
862 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
863 sc->font_wrap_pixels = target_columns * sc->char_width;
864 if (sc->font_wrap_pixels > maxw ||
865 sc->font_wrap_pixels <= 0)
866 sc->font_wrap_pixels = maxw;
869 sc->buf_size = target_columns * max_lines;
870 sc->buf = (char *) calloc (1, sc->buf_size);
872 alignment_random_p = False;
873 if (!alignment_str || !*alignment_str ||
874 !strcasecmp(alignment_str, "left"))
876 else if (!strcasecmp(alignment_str, "center") ||
877 !strcasecmp(alignment_str, "middle"))
879 else if (!strcasecmp(alignment_str, "right"))
881 else if (!strcasecmp(alignment_str, "random"))
882 alignment = -1, alignment_random_p = True;
887 "%s: alignment must be left/center/right/random, not \"%s\"\n",
888 progname, alignment_str);
892 sc->tc = textclient_open (sc->dpy);
894 if (max_lines < 1) max_lines = 1;
895 min_lines = max_lines * 0.66;
896 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
897 if (min_lines < 1) min_lines = 1;
899 parse_color (mi, "foreground",
900 get_string_resource(mi->dpy, "foreground", "Foreground"),
903 sc->top_margin = (sc->char_width * 100);
904 sc->bottom_margin = -sc->top_margin;
905 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
910 draw_fliptext (ModeInfo *mi)
912 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
913 /* XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/
914 Display *dpy = MI_DISPLAY(mi);
915 Window window = MI_WINDOW(mi);
918 if (!sc->glx_context)
921 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(sc->glx_context));
924 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
925 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
928 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
930 mi->polygon_count = 0;
933 glRotatef(current_device_rotation(), 0, 0, 1);
935 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
939 glRotatef (sc->rotation.x, 1, 0, 0);
940 glRotatef (sc->rotation.y, 0, 1, 0);
941 glRotatef (sc->rotation.z, 0, 0, 1);
944 glDisable (GL_TEXTURE_2D);
946 glBegin (GL_LINE_LOOP);
947 glVertex3f (sc->left_margin, sc->top_margin, 0);
948 glVertex3f (sc->right_margin, sc->top_margin, 0);
949 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
950 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
953 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
954 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
955 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
956 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
957 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
958 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
960 glEnable (GL_TEXTURE_2D);
963 for (i = 0; i < sc->nlines; i++)
965 line *line = sc->lines[i];
966 draw_line (mi, line);
967 tick_line (sc, line);
970 for (i = sc->nlines-1; i >= 0; i--)
972 line *line = sc->lines[i];
973 if (line->state == DEAD)
974 free_line (sc, line);
982 if (mi->fps_p) do_fps (mi);
984 glXSwapBuffers(dpy, window);
988 release_fliptext (ModeInfo *mi)
992 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
993 fliptext_configuration *sc = &scs[screen];
995 textclient_close (sc->tc);
997 /* #### there's more to free here */
1005 XSCREENSAVER_MODULE ("FlipText", fliptext)