ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-5.01.tar.gz
[xscreensaver] / hacks / glx / gltext.c
1 /* gltext, Copyright (c) 2001-2006 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        20000        \n" \
13                         "*showFPS:      False        \n" \
14                         "*wireframe:    False        \n" \
15
16 # define refresh_text 0
17 # define release_text 0
18 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
19
20 #ifdef SMOOTH_TUBE
21 # define TUBE_FACES  12   /* how densely to render tubes */
22 #else
23 # define TUBE_FACES  8
24 #endif
25
26
27 #undef countof
28 #define countof(x) (sizeof((x))/sizeof((*x)))
29
30 #include "xlockmore.h"
31 #include "colors.h"
32 #include "tube.h"
33 #include "rotator.h"
34 #include "gltrackball.h"
35
36 #include <ctype.h>
37
38 #ifdef USE_GL /* whole file */
39
40 #ifdef HAVE_COCOA
41 # define DEF_TEXT       "%A%n%d %b %Y%n%r"
42 #else
43 # define DEF_TEXT        "(default)"
44 #endif
45
46 #define DEF_PROGRAM     "(default)"
47 #define DEF_SPIN        "XYZ"
48 #define DEF_WANDER      "True"
49 #define DEF_FRONT       "True"
50
51 #ifdef HAVE_UNAME
52 # include <sys/utsname.h>
53 #endif /* HAVE_UNAME */
54
55 #include "glutstroke.h"
56 #include "glut_roman.h"
57 #define GLUT_FONT (&glutStrokeRoman)
58
59
60 typedef struct {
61   GLXContext *glx_context;
62   rotator *rot, *rot2;
63   trackball_state *trackball;
64   Bool button_down_p;
65   Bool spinx, spiny, spinz;
66
67   GLuint text_list;
68
69   int ncolors;
70   XColor *colors;
71   int ccolor;
72
73   char *text;
74   int reload;
75
76   time_t last_update;
77
78 } text_configuration;
79
80 static text_configuration *tps = NULL;
81
82 static char *text_fmt;
83 static char *program_str;
84 static char *do_spin;
85 static Bool do_wander;
86 static Bool face_front_p;
87
88 static XrmOptionDescRec opts[] = {
89   { "-text",    ".text",      XrmoptionSepArg, 0 },
90   { "-program", ".program",   XrmoptionSepArg, 0 },
91   { "-spin",    ".spin",      XrmoptionSepArg, 0 },
92   { "+spin",    ".spin",      XrmoptionNoArg, "" },
93   { "-wander",  ".wander",    XrmoptionNoArg, "True" },
94   { "+wander",  ".wander",    XrmoptionNoArg, "False" },
95   { "-front",   ".faceFront", XrmoptionNoArg, "True" },
96   { "+front",   ".faceFront", XrmoptionNoArg, "False" }
97 };
98
99 static argtype vars[] = {
100   {&text_fmt,     "text",      "Text",      DEF_TEXT,    t_String},
101   {&program_str,  "program",   "Program",   DEF_PROGRAM, t_String},
102   {&do_spin,      "spin",      "Spin",      DEF_SPIN,    t_String},
103   {&do_wander,    "wander",    "Wander",    DEF_WANDER,  t_Bool},
104   {&face_front_p, "faceFront", "FaceFront", DEF_FRONT,   t_Bool},
105 };
106
107 ENTRYPOINT ModeSpecOpt text_opts = {countof(opts), opts, countof(vars), vars, NULL};
108
109
110 /* Window management, etc
111  */
112 ENTRYPOINT void
113 reshape_text (ModeInfo *mi, int width, int height)
114 {
115   GLfloat h = (GLfloat) height / (GLfloat) width;
116
117   glViewport (0, 0, (GLint) width, (GLint) height);
118
119   glMatrixMode(GL_PROJECTION);
120   glLoadIdentity();
121   gluPerspective (30.0, 1/h, 1.0, 100.0);
122
123   glMatrixMode(GL_MODELVIEW);
124   glLoadIdentity();
125   gluLookAt( 0.0, 0.0, 30.0,
126              0.0, 0.0, 0.0,
127              0.0, 1.0, 0.0);
128
129   glClear(GL_COLOR_BUFFER_BIT);
130 }
131
132
133 static void
134 gl_init (ModeInfo *mi)
135 {
136   text_configuration *tp = &tps[MI_SCREEN(mi)];
137   int wire = MI_IS_WIREFRAME(mi);
138
139   static const GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
140
141   if (!wire)
142     {
143       glLightfv(GL_LIGHT0, GL_POSITION, pos);
144       glEnable(GL_CULL_FACE);
145       glEnable(GL_LIGHTING);
146       glEnable(GL_LIGHT0);
147       glEnable(GL_DEPTH_TEST);
148     }
149
150   tp->text_list = glGenLists (1);
151   glNewList (tp->text_list, GL_COMPILE);
152   glEndList ();
153 }
154
155
156 /* The GLUT font only has ASCII characters in them, so do what we can to
157    convert Latin1 characters to the nearest ASCII equivalent... 
158  */
159 static void
160 latin1_to_ascii (char *s)
161 {
162   unsigned char *us = (unsigned char *) s;
163   const unsigned char ascii[95] = {
164     '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
165     '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
166     '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
167     'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
168     'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
169     'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
170     'u', 'u', 'y', 'p', 'y' };
171   while (*us)
172     {
173       if (*us >= 161)
174         *us = ascii[*us - 161];
175       else if (*us > 127)
176         *us = '?';
177       us++;
178     }
179 }
180
181
182 static void
183 parse_text (ModeInfo *mi)
184 {
185   text_configuration *tp = &tps[MI_SCREEN(mi)];
186
187   if (tp->text) free (tp->text);
188
189   if (program_str && *program_str && !!strcmp(program_str, "(default)"))
190     {
191       FILE *p;
192       int i;
193       char buf[1024];
194       sprintf (buf, "( %.900s ) 2>&1", program_str);
195       p = popen (buf, "r");
196       if (! p)
197         sprintf (buf, "error running '%.900s'", program_str);
198       else
199         {
200           char *out = buf;
201           char *end = out + sizeof(buf) - 1;
202           int n;
203           do {
204             n = fread (out, 1, end - out, p);
205             if (n > 0)
206               out += n;
207             *out = 0;
208           } while (n > 0);
209           fclose (p);
210         }
211
212       /* Truncate it to 10 lines */
213       {
214         char *s = buf;
215         for (i = 0; i < 10; i++)
216           if (s && (s = strchr (s, '\n')))
217             s++;
218         if (s) *s = 0;
219       }
220
221       tp->text = strdup (buf);
222       tp->reload = 5;
223     }
224   else if (!text_fmt || !*text_fmt || !strcmp(text_fmt, "(default)"))
225     {
226 # ifdef HAVE_UNAME
227       struct utsname uts;
228
229       if (uname (&uts) < 0)
230         {
231           tp->text = strdup("uname() failed");
232         }
233       else
234         {
235           char *s;
236           if ((s = strchr(uts.nodename, '.')))
237             *s = 0;
238           tp->text = (char *) malloc(strlen(uts.nodename) +
239                                      strlen(uts.sysname) +
240                                      strlen(uts.version) +
241                                      strlen(uts.release) + 10);
242 #  if defined(_AIX)
243           sprintf(tp->text, "%s\n%s %s.%s",
244                   uts.nodename, uts.sysname, uts.version, uts.release);
245 #  elif defined(__APPLE__)  /* MacOS X + XDarwin */
246           sprintf(tp->text, "%s\n%s %s\n%s",
247                   uts.nodename, uts.sysname, uts.release, uts.machine);
248 #  else
249           sprintf(tp->text, "%s\n%s %s",
250                   uts.nodename, uts.sysname, uts.release);
251 #  endif /* special system types */
252         }
253 # else  /* !HAVE_UNAME */
254 #  ifdef VMS
255       tp->text = strdup(getenv("SYS$NODE"));
256 #  else
257       tp->text = strdup("*  *\n*  *  *\nxscreensaver\n*  *  *\n*  *");
258 #  endif
259 # endif /* !HAVE_UNAME */
260     }
261   else if (!strchr (text_fmt, '%'))
262     {
263       tp->text = strdup (text_fmt);
264     }
265   else
266     {
267       time_t now = time ((time_t *) 0);
268       struct tm *tm = localtime (&now);
269       int L = strlen(text_fmt) + 100;
270       tp->text = (char *) malloc (L);
271       *tp->text = 0;
272       strftime (tp->text, L-1, text_fmt, tm);
273       if (!*tp->text)
274         sprintf (tp->text, "strftime error:\n%s", text_fmt);
275       tp->reload = 1;
276     }
277
278   latin1_to_ascii (tp->text);
279 }
280
281
282 ENTRYPOINT Bool
283 text_handle_event (ModeInfo *mi, XEvent *event)
284 {
285   text_configuration *tp = &tps[MI_SCREEN(mi)];
286
287   if (event->xany.type == ButtonPress &&
288       event->xbutton.button == Button1)
289     {
290       tp->button_down_p = True;
291       gltrackball_start (tp->trackball,
292                          event->xbutton.x, event->xbutton.y,
293                          MI_WIDTH (mi), MI_HEIGHT (mi));
294       return True;
295     }
296   else if (event->xany.type == ButtonRelease &&
297            event->xbutton.button == Button1)
298     {
299       tp->button_down_p = False;
300       return True;
301     }
302   else if (event->xany.type == ButtonPress &&
303            (event->xbutton.button == Button4 ||
304             event->xbutton.button == Button5))
305     {
306       gltrackball_mousewheel (tp->trackball, event->xbutton.button, 10,
307                               !!event->xbutton.state);
308       return True;
309     }
310   else if (event->xany.type == MotionNotify &&
311            tp->button_down_p)
312     {
313       gltrackball_track (tp->trackball,
314                          event->xmotion.x, event->xmotion.y,
315                          MI_WIDTH (mi), MI_HEIGHT (mi));
316       return True;
317     }
318
319   return False;
320 }
321
322
323 ENTRYPOINT void 
324 init_text (ModeInfo *mi)
325 {
326   text_configuration *tp;
327   int i;
328
329   if (!tps) {
330     tps = (text_configuration *)
331       calloc (MI_NUM_SCREENS(mi), sizeof (text_configuration));
332     if (!tps) {
333       fprintf(stderr, "%s: out of memory\n", progname);
334       exit(1);
335     }
336
337     tp = &tps[MI_SCREEN(mi)];
338   }
339
340   tp = &tps[MI_SCREEN(mi)];
341
342   if ((tp->glx_context = init_GL(mi)) != NULL) {
343     gl_init(mi);
344     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
345   }
346
347   {
348     double spin_speed   = 0.5;
349     double wander_speed = 0.02;
350     double tilt_speed   = 0.03;
351     double spin_accel   = 0.5;
352
353     char *s = do_spin;
354     while (*s)
355       {
356         if      (*s == 'x' || *s == 'X') tp->spinx = True;
357         else if (*s == 'y' || *s == 'Y') tp->spiny = True;
358         else if (*s == 'z' || *s == 'Z') tp->spinz = True;
359         else if (*s == '0') ;
360         else
361           {
362             fprintf (stderr,
363          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
364                      progname, do_spin);
365             exit (1);
366           }
367         s++;
368       }
369
370     tp->rot = make_rotator (tp->spinx ? spin_speed : 0,
371                             tp->spiny ? spin_speed : 0,
372                             tp->spinz ? spin_speed : 0,
373                             spin_accel,
374                             do_wander ? wander_speed : 0,
375                             False);
376     tp->rot2 = (face_front_p
377                 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
378                 : 0);
379     tp->trackball = gltrackball_init ();
380   }
381
382   tp->ncolors = 255;
383   tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
384   make_smooth_colormap (0, 0, 0,
385                         tp->colors, &tp->ncolors,
386                         False, 0, False);
387
388   /* brighter colors, please... */
389   for (i = 0; i < tp->ncolors; i++)
390     {
391       tp->colors[i].red   = (tp->colors[i].red   / 2) + 32767;
392       tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
393       tp->colors[i].blue  = (tp->colors[i].blue  / 2) + 32767;
394     }
395
396   parse_text (mi);
397
398 }
399
400
401 static int
402 fill_character (GLUTstrokeFont font, int c, Bool wire)
403 {
404   GLfloat tube_width = 10;
405
406   const StrokeCharRec *ch;
407   const StrokeRec *stroke;
408   const CoordRec *coord;
409   StrokeFontPtr fontinfo;
410   int i, j;
411
412   fontinfo = (StrokeFontPtr) font;
413
414   if (c < 0 || c >= fontinfo->num_chars)
415     return 0;
416   ch = &(fontinfo->ch[c]);
417   if (ch)
418     {
419       GLfloat lx=0, ly=0;
420       for (i = ch->num_strokes, stroke = ch->stroke;
421            i > 0; i--, stroke++) {
422         for (j = stroke->num_coords, coord = stroke->coord;
423              j > 0; j--, coord++)
424           {
425 # ifdef SMOOTH_TUBE
426             int smooth = True;
427 # else
428             int smooth = False;
429 # endif
430             if (j != stroke->num_coords)
431               tube (lx,       ly,       0,
432                     coord->x, coord->y, 0,
433                     tube_width,
434                     tube_width * 0.15,
435                     TUBE_FACES, smooth, True, wire);
436             lx = coord->x;
437             ly = coord->y;
438           }
439       }
440       return (int) (ch->right + tube_width);
441     }
442   return 0;
443 }
444
445
446 static int
447 text_extents (const char *string, int *wP, int *hP)
448 {
449   const char *s, *start;
450   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
451   int lines = 0;
452   *wP = 0;
453   *hP = 0;
454   start = string;
455   s = start;
456   while (1)
457     if (*s == '\n' || *s == 0)
458       {
459         int w = 0;
460         while (start < s)
461           {
462             w += glutStrokeWidth(GLUT_FONT, *start);
463             start++;
464           }
465         start = s+1;
466
467         if (w > *wP) *wP = w;
468         *hP += line_height;
469         lines++;
470         if (*s == 0) break;
471         s++;
472       }
473     else
474       s++;
475
476   return lines;
477 }
478
479
480 static void
481 fill_string (const char *string, Bool wire)
482 {
483   const char *s, *start;
484   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
485   int off;
486   GLfloat x = 0, y = 0;
487   int lines;
488
489   int ow, oh;
490   lines = text_extents (string, &ow, &oh);
491
492   y = oh / 2 - line_height;
493
494   start = string;
495   s = start;
496   while (1)
497     if (*s == '\n' || *s == 0)
498       {
499         int line_w = 0;
500         const char *s2;
501         const char *lstart = start;
502         const char *lend = s;
503
504         /* strip off whitespace at beginning and end of line
505            (since we're centering.) */
506         while (lend > lstart && isspace(lend[-1]))
507           lend--;
508         while (lstart < lend && isspace(*lstart))
509           lstart++;
510
511         for (s2 = lstart; s2 < lend; s2++)
512           line_w += glutStrokeWidth (GLUT_FONT, *s2);
513
514         x = (-ow/2) + ((ow-line_w)/2);
515         for (s2 = lstart; s2 < lend; s2++)
516           {
517             glPushMatrix();
518             glTranslatef(x, y, 0);
519             off = fill_character (GLUT_FONT, *s2, wire);
520             x += off;
521             glPopMatrix();
522           }
523
524         start = s+1;
525
526         y -= line_height;
527         if (*s == 0) break;
528         s++;
529       }
530     else
531       s++;
532 }
533
534
535 ENTRYPOINT void
536 draw_text (ModeInfo *mi)
537 {
538   text_configuration *tp = &tps[MI_SCREEN(mi)];
539   Display *dpy = MI_DISPLAY(mi);
540   Window window = MI_WINDOW(mi);
541   int wire = MI_IS_WIREFRAME(mi);
542
543   GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
544   GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
545
546   if (!tp->glx_context)
547     return;
548
549   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tp->glx_context));
550
551   if (tp->reload)
552     {
553       if (time ((time_t *) 0) >= tp->last_update + tp->reload)
554         {
555           parse_text (mi);
556           tp->last_update = time ((time_t *) 0);
557         }
558     }
559
560   glShadeModel(GL_SMOOTH);
561
562   glEnable(GL_DEPTH_TEST);
563   glEnable(GL_NORMALIZE);
564   glEnable(GL_CULL_FACE);
565
566
567   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
568
569   glPushMatrix ();
570
571   glScalef(1.1, 1.1, 1.1);
572
573   {
574     double x, y, z;
575     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
576     glTranslatef((x - 0.5) * 8,
577                  (y - 0.5) * 8,
578                  (z - 0.5) * 8);
579
580     gltrackball_rotate (tp->trackball);
581
582     if (face_front_p)
583       {
584         double max = 90;
585         get_position (tp->rot2, &x, &y, &z, !tp->button_down_p);
586         if (tp->spinx) glRotatef (max/2 - x*max, 1, 0, 0);
587         if (tp->spiny) glRotatef (max/2 - y*max, 0, 1, 0);
588         if (tp->spinz) glRotatef (max/2 - z*max, 0, 0, 1);
589       }
590     else
591       {
592         get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
593         glRotatef (x * 360, 1, 0, 0);
594         glRotatef (y * 360, 0, 1, 0);
595         glRotatef (z * 360, 0, 0, 1);
596       }
597   }
598
599
600   glColor4fv (white);
601
602   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
603   color[1] = tp->colors[tp->ccolor].green / 65536.0;
604   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
605   tp->ccolor++;
606   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
607
608   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
609
610   glScalef(0.01, 0.01, 0.01);
611
612   fill_string(tp->text, wire);
613
614   glPopMatrix ();
615
616   if (mi->fps_p) do_fps (mi);
617   glFinish();
618
619   glXSwapBuffers(dpy, window);
620 }
621
622 XSCREENSAVER_MODULE_2 ("GLText", gltext, text)
623
624 #endif /* USE_GL */