1 /* gltext, Copyright (c) 2001-2021 Jamie Zawinski <jwz@jwz.orgq2
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 #define DEFAULTS "*delay: 20000 \n" \
13 "*showFPS: False \n" \
14 "*wireframe: False \n" \
17 # define release_text 0
18 #define SMOOTH_TUBE /* whether to have smooth or faceted tubes */
21 # define TUBE_FACES 12 /* how densely to render tubes */
27 #include "xlockmore.h"
32 #include "gltrackball.h"
33 #include "textclient.h"
38 #ifdef USE_GL /* whole file */
40 #define DEF_TEXT "(default)"
41 #define DEF_PROGRAM "xscreensaver-text --date --cols 20 --lines 3"
42 #define DEF_SCALE_FACTOR "0.01"
43 #define DEF_WANDER_SPEED "0.02"
44 #define DEF_MAX_LINES "8"
45 #define DEF_SPIN "XYZ"
46 #define DEF_WANDER "True"
47 #define DEF_FACE_FRONT "True"
48 #define DEF_USE_MONOSPACE "False"
50 #include "glutstroke.h"
51 #include "glut_roman.h"
52 #include "glut_mroman.h"
53 #define GLUT_VARI_FONT (&glutStrokeRoman)
54 #define GLUT_MONO_FONT (&glutStrokeMonoRoman)
55 #define GLUT_FONT ((use_monospace) ? GLUT_MONO_FONT : GLUT_VARI_FONT)
59 GLXContext *glx_context;
61 trackball_state *trackball;
63 Bool spinx, spiny, spinz;
79 static text_configuration *tps = NULL;
81 static char *text_fmt;
82 static char *program_str;
83 static float scale_factor;
84 static int max_no_lines;
85 static float wander_speed;
87 static Bool do_wander;
88 static Bool face_front_p;
89 static Bool use_monospace;
91 static XrmOptionDescRec opts[] = {
92 { "-text", ".text", XrmoptionSepArg, 0 },
93 { "-program", ".program", XrmoptionSepArg, 0 },
94 { "-scale", ".scaleFactor", XrmoptionSepArg, 0 },
95 { "-maxlines", ".maxLines", XrmoptionSepArg, 0 },
96 { "-wander-speed", ".wanderSpeed", XrmoptionSepArg, 0 },
97 { "-spin", ".spin", XrmoptionSepArg, 0 },
98 { "+spin", ".spin", XrmoptionNoArg, "" },
99 { "-wander", ".wander", XrmoptionNoArg, "True" },
100 { "+wander", ".wander", XrmoptionNoArg, "False" },
101 { "-front", ".faceFront", XrmoptionNoArg, "True" },
102 { "+front", ".faceFront", XrmoptionNoArg, "False" },
103 { "-mono", ".useMonoSpace", XrmoptionNoArg, "True" },
104 { "+mono", ".useMonoSpace", XrmoptionNoArg, "False" }
107 static argtype vars[] = {
108 {&text_fmt, "text", "Text", DEF_TEXT, t_String},
109 /* This happens to be what utils/textclient.c reads */
110 {&program_str, "program", "Program", DEF_PROGRAM, t_String},
111 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
112 {&scale_factor, "scaleFactor", "ScaleFactor", DEF_SCALE_FACTOR, t_Float},
113 {&max_no_lines, "maxLines", "MaxLines", DEF_MAX_LINES, t_Int},
114 {&wander_speed, "wanderSpeed", "WanderSpeed", DEF_WANDER_SPEED, t_Float},
115 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
116 {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool},
117 {&use_monospace, "useMonoSpace", "UseMonoSpace", DEF_USE_MONOSPACE, t_Bool},
120 ENTRYPOINT ModeSpecOpt text_opts = {countof(opts), opts, countof(vars), vars, NULL};
123 /* Window management, etc
126 reshape_text (ModeInfo *mi, int width, int height)
128 GLfloat h = (GLfloat) height / (GLfloat) width;
131 if (width > height * 5) { /* tiny window: show middle */
132 height = width * 9/16;
134 h = height / (GLfloat) width;
137 glViewport (0, y, (GLint) width, (GLint) height);
139 glMatrixMode(GL_PROJECTION);
141 gluPerspective (30.0, 1/h, 1.0, 100.0);
143 glMatrixMode(GL_MODELVIEW);
145 gluLookAt( 0.0, 0.0, 30.0,
150 GLfloat s = (MI_WIDTH(mi) < MI_HEIGHT(mi)
151 ? (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi))
156 glClear(GL_COLOR_BUFFER_BIT);
161 gl_init (ModeInfo *mi)
163 text_configuration *tp = &tps[MI_SCREEN(mi)];
164 int wire = MI_IS_WIREFRAME(mi);
166 static const GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
170 glLightfv(GL_LIGHT0, GL_POSITION, pos);
171 glEnable(GL_CULL_FACE);
172 glEnable(GL_LIGHTING);
174 glEnable(GL_DEPTH_TEST);
177 tp->text_list = glGenLists (1);
178 glNewList (tp->text_list, GL_COMPILE);
184 parse_text (ModeInfo *mi)
186 text_configuration *tp = &tps[MI_SCREEN(mi)];
187 char *old = tp->text;
190 (text_fmt && *text_fmt && !!strcmp(text_fmt, "(default)")
193 (program_str && *program_str && !!strcmp(program_str, "(default)")
196 /* We used to do some "#ifdef HAVE_UNAME" stuff in here, but
197 "xscreensaver-text --date" does a much better job of that
198 by reading random files from /etc/ and such.
201 if (tt && !strchr (tt, '%')) /* Static text with no formatting */
203 tp->text = strdup (tt);
206 else if (tt) /* Format string */
208 time_t now = time ((time_t *) 0);
209 struct tm *tm = localtime (&now);
210 int L = strlen(text_fmt) + 100;
211 tp->text = (char *) malloc (L);
213 strftime (tp->text, L-1, text_fmt, tm);
215 sprintf (tp->text, "strftime error:\n%s", text_fmt);
216 tp->reload = 1; /* Clock ticks every second */
220 int max_lines = max_no_lines;
226 /* This runs 'pr' because it reads the same "program" resource. */
227 tp->tc = textclient_open (mi->dpy);
229 while (p < buf + sizeof(buf) - 1 &&
232 int c = textclient_getc (tp->tc);
241 if (lines == 0 && buf[0])
244 tp->text = strdup (buf);
247 tp->reload = 1; /* No output, try again right away */
248 else if (!strncmp (pr, "xscreensaver-text --date", 24))
250 /* If it's the default, and we have results, there's no need
255 tp->reload = 7; /* Linger a bit */
261 /* The GLUT font only has ASCII characters. */
262 char *s1 = utf8_to_latin1 (tp->text, True);
267 /* If we had text before but got no text this time, hold on to the
268 old one, to avoid flickering.
270 if (old && *old && !*tp->text)
281 text_handle_event (ModeInfo *mi, XEvent *event)
283 text_configuration *tp = &tps[MI_SCREEN(mi)];
285 if (gltrackball_event_handler (event, tp->trackball,
286 MI_WIDTH (mi), MI_HEIGHT (mi),
295 init_text (ModeInfo *mi)
297 text_configuration *tp;
302 tp = &tps[MI_SCREEN(mi)];
304 if ((tp->glx_context = init_GL(mi)) != NULL) {
306 reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
310 double spin_speed = 0.5;
311 double wander_speed = 0.02;
312 double tilt_speed = 0.03;
313 double spin_accel = 0.5;
318 if (*s == 'x' || *s == 'X') tp->spinx = True;
319 else if (*s == 'y' || *s == 'Y') tp->spiny = True;
320 else if (*s == 'z' || *s == 'Z') tp->spinz = True;
321 else if (*s == '0') ;
325 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
332 tp->rot = make_rotator (tp->spinx ? spin_speed : 0,
333 tp->spiny ? spin_speed : 0,
334 tp->spinz ? spin_speed : 0,
336 do_wander ? wander_speed : 0,
338 tp->rot2 = (face_front_p
339 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
341 tp->trackball = gltrackball_init (False);
345 tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
346 make_smooth_colormap (0, 0, 0,
347 tp->colors, &tp->ncolors,
350 /* brighter colors, please... */
351 for (i = 0; i < tp->ncolors; i++)
353 tp->colors[i].red = (tp->colors[i].red / 2) + 32767;
354 tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
355 tp->colors[i].blue = (tp->colors[i].blue / 2) + 32767;
364 fill_character (GLUTstrokeFont font, int c, Bool wire, int *polysP)
366 GLfloat tube_width = 10;
368 const StrokeCharRec *ch;
369 const StrokeRec *stroke;
370 const CoordRec *coord;
371 StrokeFontPtr fontinfo;
374 fontinfo = (StrokeFontPtr) font;
376 if (c < 0 || c >= fontinfo->num_chars)
378 ch = &(fontinfo->ch[c]);
382 for (i = ch->num_strokes, stroke = ch->stroke;
383 i > 0; i--, stroke++) {
384 for (j = stroke->num_coords, coord = stroke->coord;
393 if (j != stroke->num_coords)
394 *polysP += tube (lx, ly, 0,
395 coord->x, coord->y, 0,
398 TUBE_FACES, smooth, False, wire);
402 /* Put a sphere at the endpoint of every line segment. Wasteful
403 on curves like "0" but necessary on corners like "4". */
407 glTranslatef (lx, ly, 0);
408 glScalef (tube_width, tube_width, tube_width);
409 *polysP += unit_sphere (TUBE_FACES, TUBE_FACES, wire);
414 return (int) (ch->right + tube_width);
421 text_extents (const char *string, int *wP, int *hP)
423 const char *s, *start;
424 int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
431 if (*s == '\n' || *s == 0)
436 w += glutStrokeWidth(GLUT_FONT, *start);
441 if (w > *wP) *wP = w;
455 fill_string (const char *string, Bool wire)
458 const char *s, *start;
459 int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
461 GLfloat x = 0, y = 0;
464 text_extents (string, &ow, &oh);
466 y = oh / 2 - line_height;
471 if (*s == '\n' || *s == 0)
475 const char *lstart = start;
476 const char *lend = s;
478 /* strip off whitespace at beginning and end of line
479 (since we're centering.) */
480 while (lend > lstart && isspace(lend[-1]))
482 while (lstart < lend && isspace(*lstart))
485 for (s2 = lstart; s2 < lend; s2++)
486 line_w += glutStrokeWidth (GLUT_FONT, *s2);
488 x = (-ow/2) + ((ow-line_w)/2);
489 for (s2 = lstart; s2 < lend; s2++)
492 glTranslatef(x, y, 0);
493 off = fill_character (GLUT_FONT, *s2, wire, &polys);
511 draw_text (ModeInfo *mi)
513 text_configuration *tp = &tps[MI_SCREEN(mi)];
514 Display *dpy = MI_DISPLAY(mi);
515 Window window = MI_WINDOW(mi);
516 int wire = MI_IS_WIREFRAME(mi);
518 GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
519 GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
521 if (!tp->glx_context)
524 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *tp->glx_context);
528 if (time ((time_t *) 0) >= tp->last_update + tp->reload)
531 tp->last_update = time ((time_t *) 0);
535 glShadeModel(GL_SMOOTH);
537 glEnable(GL_DEPTH_TEST);
538 glEnable(GL_NORMALIZE);
539 glEnable(GL_CULL_FACE);
542 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
545 glScalef(1.1, 1.1, 1.1);
546 glRotatef(current_device_rotation(), 0, 0, 1);
550 get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
551 glTranslatef((x - 0.5) * 8,
555 gltrackball_rotate (tp->trackball);
560 get_position (tp->rot2, &x, &y, &z, !tp->button_down_p);
561 if (tp->spinx) glRotatef (max/2 - x*max, 1, 0, 0);
562 if (tp->spiny) glRotatef (max/2 - y*max, 0, 1, 0);
563 if (tp->spinz) glRotatef (max/2 - z*max, 0, 0, 1);
567 get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
568 glRotatef (x * 360, 1, 0, 0);
569 glRotatef (y * 360, 0, 1, 0);
570 glRotatef (z * 360, 0, 0, 1);
577 color[0] = tp->colors[tp->ccolor].red / 65536.0;
578 color[1] = tp->colors[tp->ccolor].green / 65536.0;
579 color[2] = tp->colors[tp->ccolor].blue / 65536.0;
581 if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
583 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
585 glScalef(scale_factor, scale_factor, scale_factor);
587 mi->polygon_count = fill_string(tp->text, wire);
591 if (mi->fps_p) do_fps (mi);
594 glXSwapBuffers(dpy, window);
598 free_text(ModeInfo * mi)
600 text_configuration *tp = &tps[MI_SCREEN(mi)];
602 if (!tp->glx_context) return;
603 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *tp->glx_context);
606 textclient_close (tp->tc);
607 if (tp->text) free (tp->text);
608 if (tp->trackball) gltrackball_free (tp->trackball);
609 if (tp->rot) free_rotator (tp->rot);
610 if (tp->rot2) free_rotator (tp->rot2);
611 if (tp->colors) free (tp->colors);
612 if (glIsList(tp->text_list)) glDeleteLists(tp->text_list, 1);
616 XSCREENSAVER_MODULE_2 ("GLText", gltext, text)