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;
465 if (sc->lines_size <= sc->nlines)
467 sc->lines_size = (sc->lines_size * 1.2) + sc->nlines;
468 sc->lines = (line **)
469 realloc (sc->lines, sc->lines_size * sizeof(*sc->lines));
472 fprintf (stderr, "%s: out of memory (%d lines)\n",
473 progname, sc->lines_size);
478 sc->lines[sc->nlines-1] = ln;
483 /* frees the object and removes it from the list.
486 free_line (fliptext_configuration *sc, line *line)
489 for (i = 0; i < sc->nlines; i++)
490 if (sc->lines[i] == line)
492 if (i == sc->nlines) abort();
493 for (; i < sc->nlines-1; i++)
494 sc->lines[i] = sc->lines[i+1];
504 draw_line (ModeInfo *mi, line *line)
506 int wire = MI_IS_WIREFRAME(mi);
507 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
509 if (! line->text || !*line->text ||
510 line->state == NEW || line->state == HESITATE || line->state == DEAD)
514 glTranslatef (line->current.x, line->current.y, line->current.z);
516 glRotatef (line->cth, 0, 1, 0);
519 glTranslatef (-line->width, 0, 0);
520 else if (alignment == 0)
521 glTranslatef (-line->width/2, 0, 0);
523 glScalef (sc->font_scale, sc->font_scale, sc->font_scale);
525 glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]);
528 print_texture_string (sc->texfont, line->text);
532 char *s = line->text;
535 glDisable (GL_TEXTURE_2D);
536 glColor3f (0.4, 0.4, 0.4);
540 w = texture_string_width (sc->texfont, c, &h);
541 glBegin (GL_LINE_LOOP);
542 glVertex3f (0, 0, 0);
543 glVertex3f (w, 0, 0);
544 glVertex3f (w, h, 0);
545 glVertex3f (0, h, 0);
547 glTranslatef (w, 0, 0);
552 glDisable (GL_TEXTURE_2D);
553 glColor3f (0.4, 0.4, 0.4);
554 glBegin (GL_LINE_LOOP);
555 glVertex3f (0, 0, 0);
556 glVertex3f (line->width/sc->font_scale, 0, 0);
557 glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0);
558 glVertex3f (0, line->height/sc->font_scale, 0);
560 if (!wire) glEnable (GL_TEXTURE_2D);
565 mi->polygon_count += strlen (line->text);
569 tick_line (fliptext_configuration *sc, line *line)
571 int stagger = 60; /* frames of delay between line spin-outs */
572 int slide = 600; /* frames in a slide in/out */
573 int linger = 0; /* frames to pause with no motion */
576 if (line->state >= DEAD) abort();
577 if (++line->step >= line->steps)
582 if (linger == 0 && line->state == LINGER)
587 case HESITATE: /* entering state HESITATE */
588 switch (sc->anim_type)
591 line->steps = (line->cluster_pos * stagger);
594 line->steps = stagger * (line->cluster_size - line->cluster_pos);
597 line->steps = stagger * line->cluster_pos;
605 memset (line->color, 0, sizeof (line->color));
606 switch (sc->anim_type)
608 case SCROLL_BOTTOM: /* entering state BOTTOM IN */
611 line->from.y = (sc->bottom_margin -
613 (line->cluster_pos + 1)));
614 line->to.y += (line->height *
615 ((line->cluster_size/2.0) - line->cluster_pos));
619 case SCROLL_TOP: /* entering state TOP IN */
622 line->from.y = (sc->top_margin +
624 (line->cluster_size - line->cluster_pos)));
625 line->to.y += (line->height *
626 ((line->cluster_size/2.0) - line->cluster_pos));
630 case SPIN: /* entering state SPIN IN */
633 line->to.y += (line->height *
634 ((line->cluster_size/2.0) - line->cluster_pos));
635 line->from.y += (line->height *
636 ((line->cluster_size/2.0) - line->cluster_pos));
649 memcpy (sc->color, line->color, sizeof(line->color));
650 switch (sc->anim_type)
652 case SCROLL_BOTTOM: /* entering state BOTTOM OUT */
653 line->from = line->to;
655 line->to.y = (sc->top_margin +
657 (line->cluster_size - line->cluster_pos)));
661 case SCROLL_TOP: /* entering state TOP OUT */
662 line->from = line->to;
664 line->to.y = (sc->bottom_margin -
666 (line->cluster_pos + 1)));
670 case SPIN: /* entering state SPIN OUT */
671 line->from = line->to;
673 line->to.y += (line->height *
674 ((line->cluster_size/2.0) - line->cluster_pos));
676 line->fth = line->tth;
687 memcpy (sc->color, line->color, sizeof(line->color));
688 line->from = line->to;
689 line->steps = linger;
696 line->steps /= speed;
703 i = (double) line->step / line->steps;
704 if (line->state == IN) i = 1-i;
706 if (line->state == IN) i = 1-i;
708 line->color[0] = sc->color[0] * (line->state == IN ? i : 1-i);
709 line->color[1] = sc->color[1] * (line->state == IN ? i : 1-i);
710 line->color[2] = sc->color[2] * (line->state == IN ? i : 1-i);
711 line->color[3] = sc->color[3] * (line->state == IN ? i : 1-i);
713 line->current.x = line->from.x + (i * (line->to.x - line->from.x));
714 line->current.y = line->from.y + (i * (line->to.y - line->from.y));
715 line->current.z = line->from.z + (i * (line->to.z - line->from.z));
716 line->cth = line->fth + (i * (line->tth - line->fth));
729 /* Start a new cluster of lines going.
730 Pick their anim type, and in, mid, and out positions.
733 reset_lines (ModeInfo *mi)
735 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
738 GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh;
740 sc->rotation.x = 5 - BELLRAND(10);
741 sc->rotation.y = 5 - BELLRAND(10);
742 sc->rotation.z = 5 - BELLRAND(10);
744 switch (random() % 7)
746 case 0: sc->anim_type = SCROLL_TOP; break;
747 case 1: sc->anim_type = SCROLL_BOTTOM; break;
748 default: sc->anim_type = SPIN; break;
751 minx = sc->left_margin * 0.9;
752 maxx = sc->right_margin * 0.9;
754 miny = sc->bottom_margin * 0.9;
755 maxy = sc->top_margin * 0.9;
757 minz = sc->left_margin * 5;
758 maxz = sc->right_margin * 2;
760 maxw = sc->font_wrap_pixels * sc->font_scale;
761 maxh = max_lines * sc->line_height * sc->font_scale;
763 if (maxw > maxx - minx)
765 if (maxh > maxy - miny)
768 if (alignment_random_p)
769 alignment = (random() % 3) - 1;
771 if (alignment == -1) maxx -= maxw;
772 else if (alignment == 1) minx += maxw;
773 else minx += maxw/2, maxx -= maxw/2;
778 sc->mid.x = minx + frand (maxx - minx);
779 sc->mid.y = miny + frand (maxy - miny);
781 sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
782 sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin;
784 sc->in.y = miny + frand(maxy - miny);
785 sc->out.y = miny + frand(maxy - miny);
787 sc->in.z = minz + frand(maxz - minz);
788 sc->out.z = minz + frand(maxz - minz);
792 if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4;
793 if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4;
795 for (i = 0; i < max_lines; i++)
797 line *line = make_line (sc, (i == 0));
798 if (!line) break; /* no text available */
799 if (i >= min_lines &&
800 (!line->text || !*line->text)) /* blank after min */
804 for (i = 0; i < sc->nlines; i++)
806 line *line = sc->lines[i];
809 line->from.y = sc->bottom_margin;
814 line->from.y = prev->from.y - prev->height;
815 line->to.y = prev->to.y - prev->height;
817 line->cluster_pos = i;
818 line->cluster_size = sc->nlines;
825 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
828 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
830 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
833 a[0] = c.red / 65536.0;
834 a[1] = c.green / 65536.0;
835 a[2] = c.blue / 65536.0;
840 /* Window management, etc
843 reshape_fliptext (ModeInfo *mi, int width, int height)
845 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
846 GLfloat h = (GLfloat) height / (GLfloat) width;
848 glViewport (0, 0, (GLint) width, (GLint) height);
850 glMatrixMode(GL_PROJECTION);
852 gluPerspective (60.0, 1/h, 0.01, 100.0);
854 glMatrixMode(GL_MODELVIEW);
856 gluLookAt( 0.0, 0.0, 2.6,
860 glClear(GL_COLOR_BUFFER_BIT);
862 sc->right_margin = sc->top_margin / h;
863 sc->left_margin = -sc->right_margin;
868 init_fliptext (ModeInfo *mi)
870 int wire = MI_IS_WIREFRAME(mi);
872 fliptext_configuration *sc;
875 scs = (fliptext_configuration *)
876 calloc (MI_NUM_SCREENS(mi), sizeof (fliptext_configuration));
878 fprintf(stderr, "%s: out of memory\n", progname);
882 sc = &scs[MI_SCREEN(mi)];
883 sc->lines = (line **) calloc (max_lines+1, sizeof(char *));
886 sc = &scs[MI_SCREEN(mi)];
888 if ((sc->glx_context = init_GL(mi)) != NULL) {
889 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
892 program = get_string_resource ("program", "Program");
896 sc->texfont = load_texture_font (MI_DISPLAY(mi), "font");
897 check_gl_error ("loading font");
898 cw = texture_string_width (sc->texfont, "n", &lh);
900 sc->line_height = lh;
905 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
907 glEnable (GL_ALPHA_TEST);
908 glEnable (GL_TEXTURE_2D);
910 # ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT
911 /* "Anistropic filtering helps for quadrilateral-angled textures.
912 A sharper image is accomplished by interpolating and filtering
913 multiple samples from one or more mipmaps to better approximate
914 very distorted textures. This is the next level of filtering
915 after trilinear filtering." */
916 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
921 /* The default font is (by fiat) "18 points".
922 Interpret the user's font size request relative to that.
924 sc->font_scale = 3 * (font_size / 18.0);
926 if (target_columns <= 2) target_columns = 2;
928 /* Figure out what the wrap column should be, in font-coordinate pixels.
929 Compute it from the given -columns value, but don't let it be wider
933 GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */
934 sc->font_wrap_pixels = target_columns * sc->char_width;
935 if (sc->font_wrap_pixels > maxw ||
936 sc->font_wrap_pixels <= 0)
937 sc->font_wrap_pixels = maxw;
940 sc->buf_size = target_columns * max_lines;
941 sc->buf = (char *) calloc (1, sc->buf_size);
943 sc->subproc_relaunch_delay = 2 * 1000; /* 2 seconds */
945 alignment_random_p = False;
946 if (!alignment_str || !*alignment_str ||
947 !strcasecmp(alignment_str, "left"))
949 else if (!strcasecmp(alignment_str, "center") ||
950 !strcasecmp(alignment_str, "middle"))
952 else if (!strcasecmp(alignment_str, "right"))
954 else if (!strcasecmp(alignment_str, "random"))
955 alignment = -1, alignment_random_p = True;
960 "%s: alignment must be left/center/right/random, not \"%s\"\n",
961 progname, alignment_str);
965 launch_text_generator (sc);
967 if (max_lines < 1) max_lines = 1;
968 min_lines = max_lines * 0.66;
969 if (min_lines > max_lines - 3) min_lines = max_lines - 4;
970 if (min_lines < 1) min_lines = 1;
972 parse_color (mi, "foreground",
973 get_string_resource("foreground", "Foreground"),
976 sc->top_margin = (sc->char_width * 100);
977 sc->bottom_margin = -sc->top_margin;
978 reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */
983 draw_fliptext (ModeInfo *mi)
985 fliptext_configuration *sc = &scs[MI_SCREEN(mi)];
986 Display *dpy = MI_DISPLAY(mi);
987 Window window = MI_WINDOW(mi);
990 if (!sc->glx_context)
993 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
994 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
996 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
998 mi->polygon_count = 0;
1002 GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin);
1006 glRotatef (sc->rotation.x, 1, 0, 0);
1007 glRotatef (sc->rotation.y, 0, 1, 0);
1008 glRotatef (sc->rotation.z, 0, 0, 1);
1011 glDisable (GL_TEXTURE_2D);
1013 glBegin (GL_LINE_LOOP);
1014 glVertex3f (sc->left_margin, sc->top_margin, 0);
1015 glVertex3f (sc->right_margin, sc->top_margin, 0);
1016 glVertex3f (sc->right_margin, sc->bottom_margin, 0);
1017 glVertex3f (sc->left_margin, sc->bottom_margin, 0);
1020 glVertex3f (sc->in.x, sc->top_margin, sc->in.z);
1021 glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z);
1022 glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z);
1023 glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z);
1024 glVertex3f (sc->out.x, sc->top_margin, sc->out.z);
1025 glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z);
1027 glEnable (GL_TEXTURE_2D);
1030 for (i = 0; i < sc->nlines; i++)
1032 line *line = sc->lines[i];
1033 draw_line (mi, line);
1034 tick_line (sc, line);
1037 for (i = sc->nlines-1; i >= 0; i--)
1039 line *line = sc->lines[i];
1040 if (line->state == DEAD)
1041 free_line (sc, line);
1044 if (sc->nlines == 0)
1049 if (mi->fps_p) do_fps (mi);
1051 glXSwapBuffers(dpy, window);