/* * fliptext, Copyright (c) 2005-2015 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* HAVE_CONFIG_H */ #define DEF_FONT "-*-utopia-bold-r-normal-*-*-720-*-*-*-*-*-*" #define DEF_COLOR "#00CCFF" #define DEFAULTS "*delay: 10000 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ "*usePty: False \n" \ "*texFontCacheSize: 60 \n" \ "*font: " DEF_FONT "\n" \ ".foreground: " DEF_COLOR "\n" \ "*program: xscreensaver-text --cols 0" /* don't wrap */ # define release_fliptext 0 # define fliptext_handle_event xlockmore_no_events #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #undef BELLRAND #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3) #include "xlockmore.h" #include "texfont.h" #include "textclient.h" #ifdef USE_GL /* whole file */ /* Should be in */ # ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT # define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE # endif # ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT # define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF # endif #define DEF_LINES "8" #define DEF_FONT_SIZE "20" #define DEF_COLUMNS "80" #define DEF_ALIGNMENT "random" #define DEF_SPEED "1.0" #define TAB_WIDTH 8 #define FONT_WEIGHT 14 #define KEEP_ASPECT typedef enum { NEW, HESITATE, IN, LINGER, OUT, DEAD } line_state; typedef enum { SCROLL_BOTTOM, SCROLL_TOP, SPIN } line_anim_type; typedef struct { GLfloat x, y, z; } XYZ; typedef struct { char *text; GLfloat width, height; /* size */ XYZ from, to, current; /* start, end, and current position */ GLfloat fth, tth, cth; /* rotation around Z */ int cluster_size; /* how many lines in this cluster */ int cluster_pos; /* position of this line in the cluster */ line_state state; /* current motion model */ int step, steps; /* progress along this path */ GLfloat color[4]; } line; typedef struct { Display *dpy; GLXContext *glx_context; texture_font_data *texfont; text_data *tc; int alignment; char *buf; int buf_size; int buf_tail; int char_width; /* in font units */ int line_height; /* in font units */ double font_scale; /* convert font units to display units */ int font_wrap_pixels; /* in font units (for wrapping text) */ int top_margin, bottom_margin; int left_margin, right_margin; int nlines; int lines_size; line **lines; line_anim_type anim_type; XYZ in, mid, out; XYZ rotation; GLfloat color[4]; } fliptext_configuration; static fliptext_configuration *scs = NULL; static int max_lines, min_lines; static float font_size; static int target_columns; static char *alignment_str; static int alignment_random_p; static GLfloat speed; static XrmOptionDescRec opts[] = { {"-lines", ".lines", XrmoptionSepArg, 0 }, {"-size", ".fontSize", XrmoptionSepArg, 0 }, {"-columns", ".columns", XrmoptionSepArg, 0 }, {"-speed", ".speed", XrmoptionSepArg, 0 }, /*{"-font", ".font", XrmoptionSepArg, 0 },*/ {"-alignment", ".alignment", XrmoptionSepArg, 0 }, {"-left", ".alignment", XrmoptionNoArg, "Left" }, {"-right", ".alignment", XrmoptionNoArg, "Right" }, {"-center", ".alignment", XrmoptionNoArg, "Center" }, }; static argtype vars[] = { {&max_lines, "lines", "Integer", DEF_LINES, t_Int}, {&font_size, "fontSize", "Float", DEF_FONT_SIZE, t_Float}, {&target_columns, "columns", "Integer", DEF_COLUMNS, t_Int}, {&alignment_str, "alignment", "Alignment", DEF_ALIGNMENT, t_String}, {&speed, "speed", "Speed", DEF_SPEED, t_Float}, }; ENTRYPOINT ModeSpecOpt fliptext_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Tabs are bad, mmmkay? */ static char * untabify (const char *string) { const char *ostring = string; char *result = (char *) malloc ((strlen(string) * 8) + 1); char *out = result; int col = 0; while (*string) { if (*string == '\t') { do { col++; *out++ = ' '; } while (col % TAB_WIDTH); string++; } else if (*string == '\r' || *string == '\n') { *out++ = *string++; col = 0; } else if (*string == '\010') /* backspace */ { if (string > ostring) out--, string++; } else { *out++ = *string++; col++; } } *out = 0; return result; } static void strip (char *s, Bool leading, Bool trailing) { int L = strlen(s); if (trailing) while (L > 0 && (s[L-1] == ' ' || s[L-1] == '\t')) s[L--] = 0; if (leading) { char *s2 = s; while (*s2 == ' ' || *s2 == '\t') s2++; if (s == s2) return; while (*s2) *s++ = *s2++; *s = 0; } } static int char_width (fliptext_configuration *sc, char c) { XCharStruct e; char s[2]; s[0] = c; s[1] = 0; texture_string_metrics (sc->texfont, s, &e, 0, 0); return e.width; } /* Returns a single line of text from the output buffer of the subprocess, taking into account wrapping, centering, etc. Returns 0 if no complete line is currently available. */ static char * get_one_line (fliptext_configuration *sc) { char *result = 0; int wrap_pix = sc->font_wrap_pixels; int col = 0; int col_pix = 0; char *s = sc->buf; int target = sc->buf_size - sc->buf_tail - 2; /* Fill as much as we can into sc->buf, but stop at newline. */ while (target > 0) { int c = textclient_getc (sc->tc); if (c <= 0) break; sc->buf[sc->buf_tail++] = (char) c; sc->buf[sc->buf_tail] = 0; target--; if (c == '\r' || c == '\n') break; } while (!result) { int cw; if (s >= sc->buf + sc->buf_tail) /* Reached end of buffer before end of line. Bail. */ return 0; cw = char_width (sc, *s); if (*s == '\r' || *s == '\n' || col_pix + cw >= wrap_pix) { int L = s - sc->buf; if (*s == '\r' || *s == '\n') { if (*s == '\r' && s[1] == '\n') /* swallow CRLF too */ *s++ = 0; *s++ = 0; } else { /* We wrapped -- try to back up to the previous word boundary. */ char *s2 = s; int n = 0; while (s2 > sc->buf && *s2 != ' ' && *s2 != '\t') s2--, n++; if (s2 > sc->buf) { s = s2; *s++ = 0; L = s - sc->buf; } } if (result) abort(); result = (char *) malloc (L+1); memcpy (result, sc->buf, L); result[L] = 0; { char *t = result; char *ut = untabify (t); strip (ut, (sc->alignment == 0), 1); /* if centering, strip leading whitespace too */ result = ut; free (t); } if (sc->buf_tail > (s - sc->buf)) { int i = sc->buf_tail - (s - sc->buf); memmove (sc->buf, s, i); sc->buf_tail = i; sc->buf[sc->buf_tail] = 0; } else { sc->buf_tail = 0; } sc->buf[sc->buf_tail] = 0; s = sc->buf; col = 0; col_pix = 0; } else { col++; col_pix += cw; if (*s == '\t') { int tab_pix = TAB_WIDTH * sc->char_width; col = TAB_WIDTH * ((col / TAB_WIDTH) + 1); col_pix = tab_pix * ((col / tab_pix) + 1); } s++; } } return result; } static Bool blank_p (const char *s) { for (; *s; s++) if (*s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') return False; return True; } /* Reads some text from the subprocess, and creates and returns a `line' object. Adds that object to the lines list. Returns 0 if no text available yet. If skip_blanks_p, then keep trying for new lines of text until we get one that is not empty. */ static line * make_line (fliptext_configuration *sc, Bool skip_blanks_p) { XCharStruct e; line *ln; char *s; AGAIN: s = get_one_line (sc); if (s && skip_blanks_p && blank_p (s)) { free (s); goto AGAIN; } if (!s) return 0; ln = (line *) calloc (1, sizeof(*ln)); ln->text = s; ln->state = NEW; texture_string_metrics (sc->texfont, s, &e, 0, 0); ln->width = sc->font_scale * e.width; ln->height = sc->font_scale * sc->line_height; memcpy (ln->color, sc->color, sizeof(ln->color)); sc->nlines++; if (sc->lines_size <= sc->nlines) { sc->lines_size = (sc->lines_size * 1.2) + sc->nlines; sc->lines = (line **) realloc (sc->lines, sc->lines_size * sizeof(*sc->lines)); if (! sc->lines) { fprintf (stderr, "%s: out of memory (%d lines)\n", progname, sc->lines_size); exit (1); } } sc->lines[sc->nlines-1] = ln; return ln; } /* frees the object and removes it from the list. */ static void free_line (fliptext_configuration *sc, line *line) { int i; for (i = 0; i < sc->nlines; i++) if (sc->lines[i] == line) break; if (i == sc->nlines) abort(); for (; i < sc->nlines-1; i++) sc->lines[i] = sc->lines[i+1]; sc->lines[i] = 0; sc->nlines--; free (line->text); free (line); } static void draw_line (ModeInfo *mi, line *line) { int wire = MI_IS_WIREFRAME(mi); fliptext_configuration *sc = &scs[MI_SCREEN(mi)]; if (! line->text || !*line->text || line->state == NEW || line->state == HESITATE || line->state == DEAD) return; glPushMatrix(); glTranslatef (line->current.x, line->current.y, line->current.z); glRotatef (line->cth, 0, 1, 0); if (sc->alignment == 1) glTranslatef (-line->width, 0, 0); else if (sc->alignment == 0) glTranslatef (-line->width/2, 0, 0); glScalef (sc->font_scale, sc->font_scale, sc->font_scale); glColor4f (line->color[0], line->color[1], line->color[2], line->color[3]); if (!wire) print_texture_string (sc->texfont, line->text); else { int w, h; char *s = line->text; char c[2]; c[1]=0; glDisable (GL_TEXTURE_2D); glColor3f (0.4, 0.4, 0.4); while (*s) { XCharStruct e; *c = *s++; texture_string_metrics (sc->texfont, c, &e, 0, 0); w = e.width; h = e.ascent + e.descent; glBegin (GL_LINE_LOOP); glVertex3f (0, 0, 0); glVertex3f (w, 0, 0); glVertex3f (w, h, 0); glVertex3f (0, h, 0); glEnd(); glTranslatef (w, 0, 0); } } #if 0 glDisable (GL_TEXTURE_2D); glColor3f (0.4, 0.4, 0.4); glBegin (GL_LINE_LOOP); glVertex3f (0, 0, 0); glVertex3f (line->width/sc->font_scale, 0, 0); glVertex3f (line->width/sc->font_scale, line->height/sc->font_scale, 0); glVertex3f (0, line->height/sc->font_scale, 0); glEnd(); if (!wire) glEnable (GL_TEXTURE_2D); #endif glPopMatrix(); mi->polygon_count += strlen (line->text); } static void tick_line (fliptext_configuration *sc, line *line) { int stagger = 30; /* frames of delay between line spin-outs */ int slide = 600; /* frames in a slide in/out */ int linger = 0; /* frames to pause with no motion */ double i, ii; if (line->state >= DEAD) abort(); if (++line->step >= line->steps) { line->state++; line->step = 0; if (linger == 0 && line->state == LINGER) line->state++; if (sc->anim_type != SPIN) stagger *= 2; switch (line->state) { case HESITATE: /* entering state HESITATE */ switch (sc->anim_type) { case SPIN: line->steps = (line->cluster_pos * stagger); break; case SCROLL_TOP: line->steps = stagger * (line->cluster_size - line->cluster_pos); break; case SCROLL_BOTTOM: line->steps = stagger * line->cluster_pos; break; default: abort(); } break; case IN: line->color[3] = 0; switch (sc->anim_type) { case SCROLL_BOTTOM: /* entering state BOTTOM IN */ line->from = sc->in; line->to = sc->mid; line->from.y = (sc->bottom_margin - (line->height * (line->cluster_pos + 1))); line->to.y += (line->height * ((line->cluster_size/2.0) - line->cluster_pos)); line->steps = slide; break; case SCROLL_TOP: /* entering state TOP IN */ line->from = sc->in; line->to = sc->mid; line->from.y = (sc->top_margin + (line->height * (line->cluster_size - line->cluster_pos))); line->to.y += (line->height * ((line->cluster_size/2.0) - line->cluster_pos)); line->steps = slide; break; case SPIN: /* entering state SPIN IN */ line->from = sc->in; line->to = sc->mid; line->to.y += (line->height * ((line->cluster_size/2.0) - line->cluster_pos)); line->from.y += (line->height * ((line->cluster_size/2.0) - line->cluster_pos)); line->fth = 270; line->tth = 0; line->steps = slide; break; default: abort(); } break; case OUT: switch (sc->anim_type) { case SCROLL_BOTTOM: /* entering state BOTTOM OUT */ line->from = line->to; line->to = sc->out; line->to.y = (sc->top_margin + (line->height * (line->cluster_size - line->cluster_pos))); line->steps = slide; break; case SCROLL_TOP: /* entering state TOP OUT */ line->from = line->to; line->to = sc->out; line->to.y = (sc->bottom_margin - (line->height * (line->cluster_pos + 1))); line->steps = slide; break; case SPIN: /* entering state SPIN OUT */ line->from = line->to; line->to = sc->out; line->to.y += (line->height * ((line->cluster_size/2.0) - line->cluster_pos)); line->fth = line->tth; line->tth = -270; line->steps = slide; break; default: abort(); } break; case LINGER: line->from = line->to; line->steps = linger; break; default: break; } line->steps /= speed; } switch (line->state) { case IN: case OUT: i = (double) line->step / line->steps; /* Move along the path exponentially, slow side towards the middle. */ if (line->state == OUT) ii = i * i; else ii = 1 - ((1-i) * (1-i)); line->current.x = line->from.x + (ii * (line->to.x - line->from.x)); line->current.y = line->from.y + (ii * (line->to.y - line->from.y)); line->current.z = line->from.z + (ii * (line->to.z - line->from.z)); line->cth = line->fth + (ii * (line->tth - line->fth)); if (line->state == OUT) ii = 1-ii; line->color[3] = sc->color[3] * ii; break; case HESITATE: case LINGER: case DEAD: break; default: abort(); } } /* Start a new cluster of lines going. Pick their anim type, and in, mid, and out positions. */ static void reset_lines (ModeInfo *mi) { fliptext_configuration *sc = &scs[MI_SCREEN(mi)]; int i; line *prev = 0; GLfloat minx, maxx, miny, maxy, minz, maxz, maxw, maxh; sc->rotation.x = 5 - BELLRAND(10); sc->rotation.y = 5 - BELLRAND(10); sc->rotation.z = 5 - BELLRAND(10); switch (random() % 8) { case 0: sc->anim_type = SCROLL_TOP; break; case 1: sc->anim_type = SCROLL_BOTTOM; break; default: sc->anim_type = SPIN; break; } minx = sc->left_margin * 0.9; maxx = sc->right_margin * 0.9; miny = sc->bottom_margin * 0.9; maxy = sc->top_margin * 0.9; minz = sc->left_margin * 5; maxz = sc->right_margin * 2; maxw = sc->font_wrap_pixels * sc->font_scale; maxh = max_lines * sc->line_height * sc->font_scale; if (maxw > maxx - minx) maxw = maxx - minx; if (maxh > maxy - miny) maxh = maxy - miny; if (alignment_random_p) sc->alignment = (random() % 3) - 1; if (sc->alignment == -1) maxx -= maxw; else if (sc->alignment == 1) minx += maxw; else minx += maxw/2, maxx -= maxw/2; miny += maxh/2; maxy -= maxh/2; sc->mid.x = minx + frand (maxx - minx); if (sc->anim_type == SPIN) sc->mid.y = miny + BELLRAND (maxy - miny); else sc->mid.y = miny + frand (maxy - miny); sc->in.x = BELLRAND(sc->right_margin * 2) - sc->right_margin; sc->out.x = BELLRAND(sc->right_margin * 2) - sc->right_margin; sc->in.y = miny + frand(maxy - miny); sc->out.y = miny + frand(maxy - miny); sc->in.z = minz + frand(maxz - minz); sc->out.z = minz + frand(maxz - minz); sc->mid.z = 0; if (sc->anim_type == SPIN && sc->in.z > 0) sc->in.z /= 4; if (sc->anim_type == SPIN && sc->out.z > 0) sc->out.z /= 4; for (i = 0; i < max_lines; i++) { line *line = make_line (sc, (i == 0)); if (!line) break; /* no text available */ if (i >= min_lines && (!line->text || !*line->text)) /* blank after min */ break; } for (i = 0; i < sc->nlines; i++) { line *line = sc->lines[i]; if (!prev) { line->from.y = sc->bottom_margin; line->to.y = 0; } else { line->from.y = prev->from.y - prev->height; line->to.y = prev->to.y - prev->height; } line->cluster_pos = i; line->cluster_size = sc->nlines; prev = line; } } static void parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a) { XColor c; if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c)) { fprintf (stderr, "%s: can't parse %s color %s", progname, name, s); exit (1); } a[0] = c.red / 65536.0; a[1] = c.green / 65536.0; a[2] = c.blue / 65536.0; a[3] = 1.0; } /* Window management, etc */ ENTRYPOINT void reshape_fliptext (ModeInfo *mi, int width, int height) { fliptext_configuration *sc = &scs[MI_SCREEN(mi)]; GLfloat h = (GLfloat) height / (GLfloat) width; int y = 0; if (width > height * 5) { /* tiny window: show middle */ height = width * 9/16; y = -height/2; h = height / (GLfloat) width; } glViewport (0, y, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (60.0, 1/h, 0.01, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0.0, 0.0, 2.6, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT); sc->right_margin = sc->top_margin / h; sc->left_margin = -sc->right_margin; } ENTRYPOINT void init_fliptext (ModeInfo *mi) { int wire = MI_IS_WIREFRAME(mi); char *s; fliptext_configuration *sc; MI_INIT(mi, scs); sc = &scs[MI_SCREEN(mi)]; sc->lines = (line **) calloc (max_lines+1, sizeof(char *)); sc->dpy = MI_DISPLAY(mi); if ((sc->glx_context = init_GL(mi)) != NULL) { reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */ } { XCharStruct e; int cw, lh, ascent, descent; sc->texfont = load_texture_font (MI_DISPLAY(mi), "font"); check_gl_error ("loading font"); texture_string_metrics (sc->texfont, "n", &e, &ascent, &descent); cw = e.width; lh = ascent + descent; sc->char_width = cw; sc->line_height = lh; } if (!wire) { glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_BLEND); glEnable (GL_ALPHA_TEST); glEnable (GL_TEXTURE_2D); /* "Anistropic filtering helps for quadrilateral-angled textures. A sharper image is accomplished by interpolating and filtering multiple samples from one or more mipmaps to better approximate very distorted textures. This is the next level of filtering after trilinear filtering." */ if (strstr ((char *) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) { GLfloat anisotropic = 0.0; glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropic); if (anisotropic >= 1.0) glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropic); } } /* The default font is (by fiat) "18 points". Interpret the user's font size request relative to that. */ sc->font_scale = 3 * (font_size / 18.0); if (target_columns <= 2) target_columns = 2; /* Figure out what the wrap column should be, in font-coordinate pixels. Compute it from the given -columns value, but don't let it be wider than the screen. */ { GLfloat maxw = 110 * sc->line_height / sc->font_scale; /* magic... */ sc->font_wrap_pixels = target_columns * sc->char_width; if (sc->font_wrap_pixels > maxw || sc->font_wrap_pixels <= 0) sc->font_wrap_pixels = maxw; } sc->buf_size = target_columns * max_lines; sc->buf = (char *) calloc (1, sc->buf_size); alignment_random_p = False; if (!alignment_str || !*alignment_str || !strcasecmp(alignment_str, "left")) sc->alignment = -1; else if (!strcasecmp(alignment_str, "center") || !strcasecmp(alignment_str, "middle")) sc->alignment = 0; else if (!strcasecmp(alignment_str, "right")) sc->alignment = 1; else if (!strcasecmp(alignment_str, "random")) sc->alignment = -1, alignment_random_p = True; else { fprintf (stderr, "%s: alignment must be left/center/right/random, not \"%s\"\n", progname, alignment_str); exit (1); } sc->tc = textclient_open (sc->dpy); if (max_lines < 1) max_lines = 1; min_lines = max_lines * 0.66; if (min_lines > max_lines - 3) min_lines = max_lines - 4; if (min_lines < 1) min_lines = 1; s = get_string_resource(mi->dpy, "foreground", "Foreground"); parse_color (mi, "foreground", s, sc->color); if (s) free (s); sc->top_margin = (sc->char_width * 100); sc->bottom_margin = -sc->top_margin; reshape_fliptext (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); /* compute left/right */ } ENTRYPOINT void draw_fliptext (ModeInfo *mi) { fliptext_configuration *sc = &scs[MI_SCREEN(mi)]; /* XtAppContext app = XtDisplayToApplicationContext (sc->dpy);*/ Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int i; if (!sc->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *sc->glx_context); #if 0 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput)) XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput); #endif glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mi->polygon_count = 0; glPushMatrix(); glRotatef(current_device_rotation(), 0, 0, 1); { GLfloat s = 3.0 / (sc->top_margin - sc->bottom_margin); glScalef(s, s, s); } glRotatef (sc->rotation.x, 1, 0, 0); glRotatef (sc->rotation.y, 0, 1, 0); glRotatef (sc->rotation.z, 0, 0, 1); #if 0 glDisable (GL_TEXTURE_2D); glColor3f (1,1,1); glBegin (GL_LINE_LOOP); glVertex3f (sc->left_margin, sc->top_margin, 0); glVertex3f (sc->right_margin, sc->top_margin, 0); glVertex3f (sc->right_margin, sc->bottom_margin, 0); glVertex3f (sc->left_margin, sc->bottom_margin, 0); glEnd(); glBegin (GL_LINES); glVertex3f (sc->in.x, sc->top_margin, sc->in.z); glVertex3f (sc->in.x, sc->bottom_margin, sc->in.z); glVertex3f (sc->mid.x, sc->top_margin, sc->mid.z); glVertex3f (sc->mid.x, sc->bottom_margin, sc->mid.z); glVertex3f (sc->out.x, sc->top_margin, sc->out.z); glVertex3f (sc->out.x, sc->bottom_margin, sc->out.z); glEnd(); glEnable (GL_TEXTURE_2D); #endif for (i = 0; i < sc->nlines; i++) { line *line = sc->lines[i]; draw_line (mi, line); tick_line (sc, line); } for (i = sc->nlines-1; i >= 0; i--) { line *line = sc->lines[i]; if (line->state == DEAD) free_line (sc, line); } if (sc->nlines == 0) reset_lines (mi); glPopMatrix(); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_fliptext (ModeInfo *mi) { fliptext_configuration *sc = &scs[MI_SCREEN(mi)]; int i; if (!sc->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *sc->glx_context); if (sc->tc) textclient_close (sc->tc); if (sc->texfont) free_texture_font (sc->texfont); for (i = 0; i < sc->nlines; i++) if (sc->lines[i]) { if (sc->lines[i]->text) free (sc->lines[i]->text); free (sc->lines[i]); } if(sc->lines) free(sc->lines); } XSCREENSAVER_MODULE ("FlipText", fliptext) #endif /* USE_GL */