1 /* carousel, Copyright (c) 2005-2018 Jamie Zawinski <jwz@jwz.org>
2  * Loads a sequence of images and rotates them around.
3  *
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
10  * implied warranty.
11  *
12  * Created: 21-Feb-2005
13  */
14 
15 #if defined(HAVE_COCOA) || defined(HAVE_ANDROID)
16 # define DEF_FONT "OCR A Std 48, Lucida Console 48, Monaco 48"
17 #elif 0  /* real X11, XQueryFont() */
18 # define DEF_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*"
19 #else    /* real X11, load_font_retry() */
20 # define DEF_FONT "-*-ocr a std-medium-r-*-*-*-480-*-*-m-*-*-*"
21 #endif
22 
23 #define DEF_TITLE_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*"
24 
25 #define DEFAULTS  "*count:           7         \n" \
26 		  "*delay:           10000     \n" \
27 		  "*wireframe:       False     \n" \
28                   "*showFPS:         False     \n" \
29 	          "*fpsSolid:        True      \n" \
30 	          "*useSHM:          True      \n" \
31 		  "*font:	   " DEF_FONT "\n" \
32 		  "*titleFont:	   " DEF_TITLE_FONT "\n" \
33                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n" \
34 		  "*grabDesktopImages:   False \n" \
35 		  "*chooseRandomImages:  True  \n"
36 
37 # define release_carousel 0
38 # include "xlockmore.h"
39 
40 #undef countof
41 #define countof(x) (sizeof((x))/sizeof((*x)))
42 
43 #ifdef USE_GL
44 
45 # define DEF_SPEED          "1.0"
46 # define DEF_DURATION	    "20"
47 # define DEF_TITLES         "True"
48 # define DEF_ZOOM           "True"
49 # define DEF_TILT           "XY"
50 # define DEF_MIPMAP         "True"
51 # define DEF_DEBUG          "False"
52 
53 #include "rotator.h"
54 #include "gltrackball.h"
55 #include "grab-ximage.h"
56 #include "texfont.h"
57 
58 # ifndef HAVE_JWXYZ
59 #  include <X11/Intrinsic.h>     /* for XrmDatabase in -debug mode */
60 # endif
61 
62 /* Should be in <GL/glext.h> */
63 # ifndef  GL_TEXTURE_MAX_ANISOTROPY_EXT
64 #  define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
65 # endif
66 # ifndef  GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
67 #  define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
68 # endif
69 
70 typedef struct {
71   double x, y, w, h;
72 } rect;
73 
74 typedef enum { EARLY, NORMAL, LOADING, OUT, IN, DEAD } fade_mode;
75 static int fade_ticks = 60;
76 
77 typedef struct {
78   char *title;			/* the filename of this image */
79   int w, h;			/* size in pixels of the image */
80   int tw, th;			/* size in pixels of the texture */
81   XRectangle geom;		/* where in the image the bits are */
82   GLuint texid;
83 } image;
84 
85 typedef struct {
86   ModeInfo *mi;
87   image current, loading;
88   GLfloat r, theta;		/* radius and rotation on the tube */
89   rotator *rot;			/* for zoomery */
90   Bool from_top_p;		/* whether this image drops in or rises up */
91   time_t expires;		/* when this image should be replaced */
92   fade_mode mode;		/* in/out animation state */
93   int mode_tick;
94   Bool loaded_p;		/* whether background load is done */
95 } image_frame;
96 
97 
98 typedef struct {
99   GLXContext *glx_context;
100   GLfloat anisotropic;
101   rotator *rot;
102   trackball_state *trackball;
103   Bool button_down_p;
104   time_t button_down_time;
105 
106   int nframes;			/* how many frames are loaded */
107   int frames_size;
108   image_frame **frames;		/* pointers to the frames */
109 
110   Bool awaiting_first_images_p;
111   int loads_in_progress;
112 
113   texture_font_data *texfont, *titlefont;
114 
115   fade_mode mode;
116   int mode_tick;
117 
118   int loading_sw, loading_sh;
119 
120   time_t last_time, now;
121   int draw_tick;
122 
123 } carousel_state;
124 
125 static carousel_state *sss = NULL;
126 
127 
128 /* Command-line arguments
129  */
130 static GLfloat speed;	    /* animation speed scale factor */
131 static int duration;	    /* reload images after this long */
132 static Bool mipmap_p;	    /* Use mipmaps instead of single textures. */
133 static Bool titles_p;	    /* Display image titles. */
134 static Bool zoom_p;	    /* Throb the images in and out as they spin. */
135 static char *tilt_str;
136 static Bool tilt_x_p;	    /* Tilt axis towards the viewer */
137 static Bool tilt_y_p;	    /* Tilt axis side to side */
138 static Bool debug_p;	    /* Be loud and do weird things. */
139 
140 
141 static XrmOptionDescRec opts[] = {
142   {"-zoom",         ".zoom",          XrmoptionNoArg, "True"  },
143   {"-no-zoom",      ".zoom",          XrmoptionNoArg, "False" },
144   {"-tilt",         ".tilt",          XrmoptionSepArg, 0  },
145   {"-no-tilt",      ".tilt",          XrmoptionNoArg, ""  },
146   {"-titles",       ".titles",        XrmoptionNoArg, "True"  },
147   {"-no-titles",    ".titles",        XrmoptionNoArg, "False" },
148   {"-mipmaps",      ".mipmap",        XrmoptionNoArg, "True"  },
149   {"-no-mipmaps",   ".mipmap",        XrmoptionNoArg, "False" },
150   {"-duration",	    ".duration",      XrmoptionSepArg, 0 },
151   {"-debug",        ".debug",         XrmoptionNoArg, "True"  },
152   {"-font",         ".font",          XrmoptionSepArg, 0 },
153   {"-speed",        ".speed",         XrmoptionSepArg, 0 },
154 };
155 
156 static argtype vars[] = {
157   { &mipmap_p,      "mipmap",       "Mipmap",       DEF_MIPMAP,      t_Bool},
158   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,       t_Bool},
159   { &titles_p,      "titles",       "Titles",       DEF_TITLES,      t_Bool},
160   { &zoom_p,        "zoom",         "Zoom",         DEF_ZOOM,        t_Bool},
161   { &tilt_str,      "tilt",         "Tilt",         DEF_TILT,        t_String},
162   { &speed,         "speed",        "Speed",        DEF_SPEED,       t_Float},
163   { &duration,      "duration",     "Duration",     DEF_DURATION,    t_Int},
164 };
165 
166 ENTRYPOINT ModeSpecOpt carousel_opts = {countof(opts), opts, countof(vars), vars, NULL};
167 
168 
169 /* Allocates a frame structure and stores it in the list.
170  */
171 static image_frame *
alloc_frame(ModeInfo * mi)172 alloc_frame (ModeInfo *mi)
173 {
174   carousel_state *ss = &sss[MI_SCREEN(mi)];
175   image_frame *frame = (image_frame *) calloc (1, sizeof (*frame));
176 
177   frame->mi = mi;
178   frame->mode = EARLY;
179   frame->rot = make_rotator (0, 0, 0, 0, 0.04 * frand(1.0) * speed, False);
180 
181   glGenTextures (1, &frame->current.texid);
182   glGenTextures (1, &frame->loading.texid);
183   if (frame->current.texid <= 0) abort();
184   if (frame->loading.texid <= 0) abort();
185 
186   if (ss->frames_size <= ss->nframes)
187     {
188       ss->frames_size = (ss->frames_size * 1.2) + ss->nframes;
189       ss->frames = (image_frame **)
190         realloc (ss->frames, ss->frames_size * sizeof(*ss->frames));
191       if (! ss->frames)
192         {
193           fprintf (stderr, "%s: out of memory (%d images)\n",
194                    progname, ss->frames_size);
195           exit (1);
196         }
197     }
198 
199   ss->frames[ss->nframes++] = frame;
200 
201   return frame;
202 }
203 
204 
205 static void image_loaded_cb (const char *filename, XRectangle *geom,
206                              int image_width, int image_height,
207                              int texture_width, int texture_height,
208                              void *closure);
209 
210 
211 /* Load a new file into the given image struct.
212  */
213 static void
load_image(ModeInfo * mi,image_frame * frame)214 load_image (ModeInfo *mi, image_frame *frame)
215 {
216   carousel_state *ss = &sss[MI_SCREEN(mi)];
217   int wire = MI_IS_WIREFRAME(mi);
218 
219   if (debug_p && !wire && frame->current.w != 0)
220     fprintf (stderr, "%s:  dropped %4d x %-4d  %4d x %-4d  \"%s\"\n",
221              progname,
222              frame->current.geom.width,
223              frame->current.geom.height,
224              frame->current.tw, frame->current.th,
225              (frame->current.title ? frame->current.title : "(null)"));
226 
227   switch (frame->mode)
228     {
229     case EARLY:  break;
230     case NORMAL: frame->mode = LOADING; break;
231     default: abort();
232     }
233 
234   ss->loads_in_progress++;
235 
236   if (wire)
237     image_loaded_cb (0, 0, 0, 0, 0, 0, frame);
238   else
239     {
240       int w = (MI_WIDTH(mi)  / 2) - 1;
241       int h = (MI_HEIGHT(mi) / 2) - 1;
242       if (w <= 10) w = 10;
243       if (h <= 10) h = 10;
244 
245       if (w > h * 5) {   /* tiny window: use 16:9 boxes */
246         h = w * 9/16;
247       }
248 
249       load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context, w, h,
250                           mipmap_p, frame->loading.texid,
251                           image_loaded_cb, frame);
252     }
253 }
254 
255 
256 /* Callback that tells us that the texture has been loaded.
257  */
258 static void
image_loaded_cb(const char * filename,XRectangle * geom,int image_width,int image_height,int texture_width,int texture_height,void * closure)259 image_loaded_cb (const char *filename, XRectangle *geom,
260                  int image_width, int image_height,
261                  int texture_width, int texture_height,
262                  void *closure)
263 {
264   image_frame *frame = (image_frame *) closure;
265   ModeInfo *mi = frame->mi;
266   carousel_state *ss = &sss[MI_SCREEN(mi)];
267   int wire = MI_IS_WIREFRAME(mi);
268 
269   if (wire)
270     {
271       frame->loading.w = MI_WIDTH (mi) * (0.5 + frand (1.0));
272       frame->loading.h = MI_HEIGHT (mi);
273       frame->loading.geom.width  = frame->loading.w;
274       frame->loading.geom.height = frame->loading.h;
275       goto DONE;
276     }
277 
278   if (image_width == 0 || image_height == 0)
279     exit (1);
280 
281   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
282   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
283                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
284 
285   if (ss->anisotropic >= 1.0)
286     glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
287                      ss->anisotropic);
288 
289   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
290   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
291 
292   frame->loading.w  = image_width;
293   frame->loading.h  = image_height;
294   frame->loading.tw = texture_width;
295   frame->loading.th = texture_height;
296   frame->loading.geom = *geom;
297 
298   if (frame->loading.title)
299     free (frame->loading.title);
300   frame->loading.title = (filename ? strdup (filename) : 0);
301 
302   /* xscreensaver-getimage returns paths relative to the image directory
303      now, so leave the sub-directory part in.  Unless it's an absolute path.
304   */
305   if (frame->loading.title && frame->loading.title[0] == '/')
306     {    /* strip filename to part after last /. */
307       char *s = strrchr (frame->loading.title, '/');
308       if (s) strcpy (frame->loading.title, s+1);
309     }
310 
311   if (debug_p)
312     fprintf (stderr, "%s:   loaded %4d x %-4d  %4d x %-4d  \"%s\"\n",
313              progname,
314              frame->loading.geom.width,
315              frame->loading.geom.height,
316              frame->loading.tw, frame->loading.th,
317              (frame->loading.title ? frame->loading.title : "(null)"));
318 
319  DONE:
320 
321   frame->loaded_p = True;
322 
323   if (ss->loads_in_progress <= 0) abort();
324   ss->loads_in_progress--;
325 
326   /* This image expires N seconds after it finished loading. */
327   frame->expires = time((time_t *) 0) + (duration * MI_COUNT(mi));
328 
329   switch (frame->mode)
330     {
331     case EARLY:		/* part of the initial batch of images */
332       {
333         image swap = frame->current;
334         frame->current = frame->loading;
335         frame->loading = swap;
336       }
337       break;
338     case LOADING:	/* start dropping the old image out */
339       {
340         frame->mode = OUT;
341         frame->mode_tick = fade_ticks / speed;
342         frame->from_top_p = random() & 1;
343       }
344       break;
345     default:
346       abort();
347     }
348 }
349 
350 
351 static void loading_msg (ModeInfo *mi, int n);
352 
353 static Bool
load_initial_images(ModeInfo * mi)354 load_initial_images (ModeInfo *mi)
355 {
356   carousel_state *ss = &sss[MI_SCREEN(mi)];
357   int i;
358   Bool all_loaded_p = True;
359   for (i = 0; i < ss->nframes; i++)
360     if (! ss->frames[i]->loaded_p)
361       all_loaded_p = False;
362 
363   if (all_loaded_p)
364     {
365       if (ss->nframes < MI_COUNT (mi))
366         {
367           /* The frames currently on the list are fully loaded.
368              Start the next one loading.  (We run the image loader
369              asynchronously, but we load them one at a time.)
370            */
371           load_image (mi, alloc_frame (mi));
372         }
373       else
374         {
375           /* The first batch of images are now all loaded!
376              Stagger the expire times so that they don't all drop out at once.
377            */
378           time_t now = time((time_t *) 0);
379           int i;
380 
381           for (i = 0; i < ss->nframes; i++)
382             {
383               image_frame *frame = ss->frames[i];
384               frame->r = 1.0;
385               frame->theta = i * 360.0 / ss->nframes;
386               frame->expires = now + (duration * (i + 1));
387               frame->mode = NORMAL;
388             }
389 
390           /* Instead of always going clockwise, shuffle the expire times
391              of the frames so that they drop out in a random order.
392           */
393           for (i = 0; i < ss->nframes; i++)
394             {
395               image_frame *frame1 = ss->frames[i];
396               image_frame *frame2 = ss->frames[random() % ss->nframes];
397               time_t swap = frame1->expires;
398               frame1->expires = frame2->expires;
399               frame2->expires = swap;
400             }
401 
402           ss->awaiting_first_images_p = False;
403         }
404     }
405 
406   loading_msg (mi, ss->nframes-1);
407 
408   return !ss->awaiting_first_images_p;
409 }
410 
411 
412 ENTRYPOINT void
reshape_carousel(ModeInfo * mi,int width,int height)413 reshape_carousel (ModeInfo *mi, int width, int height)
414 {
415   GLfloat h = (GLfloat) height / (GLfloat) width;
416   int y = 0;
417 
418   if (width > height * 5) {   /* tiny window: show middle */
419     height = width * 9/16;
420     y = -height/2;
421     h = height / (GLfloat) width;
422   }
423 
424   glViewport (0, y, (GLint) width, (GLint) height);
425 
426   glMatrixMode(GL_PROJECTION);
427   glLoadIdentity();
428   gluPerspective (60.0, 1/h, 1.0, 8.0);
429 
430   glMatrixMode(GL_MODELVIEW);
431   glLoadIdentity();
432   gluLookAt( 0.0, 0.0, 2.6,
433              0.0, 0.0, 0.0,
434              0.0, 1.0, 0.0);
435 
436   glClear(GL_COLOR_BUFFER_BIT);
437 }
438 
439 
440 ENTRYPOINT Bool
carousel_handle_event(ModeInfo * mi,XEvent * event)441 carousel_handle_event (ModeInfo *mi, XEvent *event)
442 {
443   carousel_state *ss = &sss[MI_SCREEN(mi)];
444 
445   if (event->xany.type == ButtonPress &&
446       event->xbutton.button == Button1)
447     {
448       if (! ss->button_down_p)
449         ss->button_down_time = time((time_t *) 0);
450     }
451   else if (event->xany.type == ButtonRelease &&
452            event->xbutton.button == Button1)
453     {
454       if (ss->button_down_p)
455         {
456           /* Add the time the mouse was held to the expire times of all
457              frames, so that mouse-dragging doesn't count against
458              image expiration.
459            */
460           int secs = time((time_t *) 0) - ss->button_down_time;
461           int i;
462           for (i = 0; i < ss->nframes; i++)
463             ss->frames[i]->expires += secs;
464         }
465     }
466 
467   if (gltrackball_event_handler (event, ss->trackball,
468                                  MI_WIDTH (mi), MI_HEIGHT (mi),
469                                  &ss->button_down_p))
470     return True;
471   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
472     {
473       int i = random() % ss->nframes;
474       ss->frames[i]->expires  = 0;
475       return True;
476     }
477 
478   return False;
479 }
480 
481 
482 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
483  */
484 static void
hack_resources(Display * dpy)485 hack_resources (Display *dpy)
486 {
487 # ifndef HAVE_JWXYZ
488   char *res = "desktopGrabber";
489   char *val = get_string_resource (dpy, res, "DesktopGrabber");
490   char buf1[255];
491   char buf2[255];
492   XrmValue value;
493   XrmDatabase db = XtDatabase (dpy);
494   sprintf (buf1, "%.100s.%.100s", progname, res);
495   sprintf (buf2, "%.200s -v", val);
496   value.addr = buf2;
497   value.size = strlen(buf2);
498   XrmPutResource (&db, buf1, "String", &value);
499   free (val);
500 # endif /* !HAVE_JWXYZ */
501 }
502 
503 
504 static void
loading_msg(ModeInfo * mi,int n)505 loading_msg (ModeInfo *mi, int n)
506 {
507   carousel_state *ss = &sss[MI_SCREEN(mi)];
508   int wire = MI_IS_WIREFRAME(mi);
509   char text[100];
510 
511   if (wire) return;
512 
513   if (n == 0)
514     sprintf (text, "Loading images...");
515   else
516     sprintf (text, "Loading images...  (%d%%)",
517              (int) (n * 100 / MI_COUNT(mi)));
518 
519   if (ss->loading_sw == 0)
520     {
521       /* only do this once, so that the string doesn't move. */
522       XCharStruct e;
523       texture_string_metrics (ss->titlefont, text, &e, 0, 0);
524       ss->loading_sw = e.width;
525       ss->loading_sh = e.ascent + e.descent;
526     }
527 
528   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
529 
530   glMatrixMode(GL_PROJECTION);
531   glPushMatrix();
532   glLoadIdentity();
533 
534   glMatrixMode(GL_MODELVIEW);
535   glPushMatrix();
536   glLoadIdentity();
537 
538 /*
539   {
540     double rot = current_device_rotation();
541     glRotatef(rot, 0, 0, 1);
542     if ((rot >  45 && rot <  135) ||
543         (rot < -45 && rot > -135))
544       {
545         GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
546         glScalef (s, 1/s, 1);
547       }
548   }
549 */
550 
551 # ifdef HAVE_MOBILE
552   if (MI_WIDTH(mi) < MI_HEIGHT(mi))  /* portrait orientation */
553     {
554       GLfloat s = (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi));
555       glScalef (s, s, s);
556       glTranslatef(-s/2, 0, 0);
557     }
558 # endif
559 
560   glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1);
561   glTranslatef ((MI_WIDTH(mi)  - ss->loading_sw) / 2,
562                 (MI_HEIGHT(mi) - ss->loading_sh) / 2,
563                 0);
564   glColor3f (1, 1, 0);
565   glEnable (GL_TEXTURE_2D);
566   glDisable (GL_DEPTH_TEST);
567   print_texture_string (ss->titlefont, text);
568   glEnable (GL_DEPTH_TEST);
569   glPopMatrix();
570 
571   glMatrixMode(GL_PROJECTION);
572   glPopMatrix();
573 
574   glMatrixMode(GL_MODELVIEW);
575 
576   glFinish();
577   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
578 }
579 
580 
581 ENTRYPOINT void
init_carousel(ModeInfo * mi)582 init_carousel (ModeInfo *mi)
583 {
584   int screen = MI_SCREEN(mi);
585   carousel_state *ss;
586   int wire = MI_IS_WIREFRAME(mi);
587 
588   MI_INIT (mi, sss);
589   ss = &sss[screen];
590 
591   if ((ss->glx_context = init_GL(mi)) != NULL) {
592     reshape_carousel (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
593     clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */
594   } else {
595     MI_CLEARWINDOW(mi);
596   }
597 
598   if (!tilt_str || !*tilt_str)
599     ;
600   else if (!strcasecmp (tilt_str, "0"))
601     ;
602   else if (!strcasecmp (tilt_str, "X"))
603     tilt_x_p = 1;
604   else if (!strcasecmp (tilt_str, "Y"))
605     tilt_y_p = 1;
606   else if (!strcasecmp (tilt_str, "XY"))
607     tilt_x_p = tilt_y_p = 1;
608   else
609     {
610       fprintf (stderr, "%s: tilt must be 'X', 'Y', 'XY' or '', not '%s'\n",
611                progname, tilt_str);
612       exit (1);
613     }
614 
615   {
616     double spin_speed   = speed * 0.2;    /* rotation of tube around axis */
617     double spin_accel   = speed * 0.1;
618     double wander_speed = speed * 0.001;  /* tilting of axis */
619 
620     spin_speed   *= 0.9 + frand(0.2);
621     wander_speed *= 0.9 + frand(0.2);
622 
623     ss->rot = make_rotator (spin_speed, spin_speed, spin_speed,
624                             spin_accel, wander_speed, True);
625 
626     ss->trackball = gltrackball_init (False);
627   }
628 
629   if (strstr ((char *) glGetString(GL_EXTENSIONS),
630               "GL_EXT_texture_filter_anisotropic"))
631     glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &ss->anisotropic);
632   else
633     ss->anisotropic = 0.0;
634 
635   glDisable (GL_LIGHTING);
636   glEnable (GL_DEPTH_TEST);
637   glDisable (GL_CULL_FACE);
638 
639   if (! wire)
640     {
641       glShadeModel (GL_SMOOTH);
642       glEnable (GL_LINE_SMOOTH);
643       /* This gives us a transparent diagonal slice through each image! */
644       /* glEnable (GL_POLYGON_SMOOTH); */
645       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
646       glEnable (GL_BLEND);
647       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
648       glEnable (GL_ALPHA_TEST);
649 
650       glEnable (GL_POLYGON_OFFSET_FILL);
651       glPolygonOffset (1.0, 1.0);
652 
653     }
654 
655   ss->texfont = load_texture_font (MI_DISPLAY(mi), "font");
656   ss->titlefont = load_texture_font (MI_DISPLAY(mi), "titleFont");
657 
658   if (debug_p)
659     hack_resources (MI_DISPLAY (mi));
660 
661   ss->nframes = 0;
662   ss->frames_size = 10;
663   ss->frames = (image_frame **)
664     calloc (1, ss->frames_size * sizeof(*ss->frames));
665 
666   ss->mode = IN;
667   ss->mode_tick = fade_ticks / speed;
668 
669   ss->awaiting_first_images_p = True;
670 }
671 
672 
673 static void
draw_frame(ModeInfo * mi,image_frame * frame,time_t now,Bool body_p)674 draw_frame (ModeInfo *mi, image_frame *frame, time_t now, Bool body_p)
675 {
676   carousel_state *ss = &sss[MI_SCREEN(mi)];
677   int wire = MI_IS_WIREFRAME(mi);
678 
679   GLfloat texw  = frame->current.geom.width  / (GLfloat) frame->current.tw;
680   GLfloat texh  = frame->current.geom.height / (GLfloat) frame->current.th;
681   GLfloat texx1 = frame->current.geom.x / (GLfloat) frame->current.tw;
682   GLfloat texy1 = frame->current.geom.y / (GLfloat) frame->current.th;
683   GLfloat texx2 = texx1 + texw;
684   GLfloat texy2 = texy1 + texh;
685   GLfloat aspect = ((GLfloat) frame->current.geom.height /
686                     (GLfloat) frame->current.geom.width);
687 
688   glBindTexture (GL_TEXTURE_2D, frame->current.texid);
689 
690   glPushMatrix();
691 
692   /* Position this image on the wheel.
693    */
694   glRotatef (frame->theta, 0, 1, 0);
695   glTranslatef (0, 0, frame->r);
696 
697   /* Scale down the image so that all N frames fit on the wheel
698      without bumping in to each other.
699   */
700   {
701     GLfloat t, s;
702     switch (ss->nframes)
703       {
704       case 1:  t = -1.0; s = 1.7; break;
705       case 2:  t = -0.8; s = 1.6; break;
706       case 3:  t = -0.4; s = 1.5; break;
707       case 4:  t = -0.2; s = 1.3; break;
708       default: t =  0.0; s = 6.0 / ss->nframes; break;
709       }
710     glTranslatef (0, 0, t);
711     glScalef (s, s, s);
712   }
713 
714   /* Center this image on the wheel plane.
715    */
716   glTranslatef (-0.5, -(aspect/2), 0);
717 
718   /* Move as per the "zoom in and out" setting.
719    */
720   if (zoom_p)
721     {
722       double x, y, z;
723       /* Only use the Z component of the rotator for in/out position. */
724       get_position (frame->rot, &x, &y, &z, !ss->button_down_p);
725       glTranslatef (0, 0, z/2);
726     }
727 
728   /* Compute the "drop in and out" state.
729    */
730   switch (frame->mode)
731     {
732     case EARLY:
733       abort();
734       break;
735     case NORMAL:
736       if (!ss->button_down_p &&
737           now >= frame->expires &&
738           ss->loads_in_progress == 0)  /* only load one at a time */
739         load_image (mi, frame);
740       break;
741     case LOADING:
742       break;
743     case OUT:
744       if (--frame->mode_tick <= 0) {
745         image swap = frame->current;
746         frame->current = frame->loading;
747         frame->loading = swap;
748 
749         frame->mode = IN;
750         frame->mode_tick = fade_ticks / speed;
751       }
752       break;
753     case IN:
754       if (--frame->mode_tick <= 0)
755         frame->mode = NORMAL;
756       break;
757     default:
758       abort();
759     }
760 
761   /* Now translate for current in/out state.
762    */
763   if (frame->mode == OUT || frame->mode == IN)
764     {
765       GLfloat t = (frame->mode == OUT
766                    ? frame->mode_tick / (fade_ticks / speed)
767                    : (((fade_ticks / speed) - frame->mode_tick + 1) /
768                       (fade_ticks / speed)));
769       t = 5 * (1 - t);
770       if (frame->from_top_p) t = -t;
771       glTranslatef (0, t, 0);
772     }
773 
774   if (body_p)					/* Draw the image quad. */
775     {
776       if (! wire)
777         {
778           glColor3f (1, 1, 1);
779           glNormal3f (0, 0, 1);
780           glEnable (GL_TEXTURE_2D);
781           glBegin (GL_QUADS);
782           glNormal3f (0, 0, 1);
783           glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
784           glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
785           glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0);
786           glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0);
787           glEnd();
788         }
789 
790       /* Draw a box around it.
791        */
792       glLineWidth (2.0);
793       glColor3f (0.5, 0.5, 0.5);
794       glDisable (GL_TEXTURE_2D);
795       glBegin (GL_LINE_LOOP);
796       glVertex3f (0, 0, 0);
797       glVertex3f (1, 0, 0);
798       glVertex3f (1, aspect, 0);
799       glVertex3f (0, aspect, 0);
800       glEnd();
801 
802     }
803   else					/* Draw a title under the image. */
804     {
805       XCharStruct e;
806       int sw, sh;
807       GLfloat scale = 0.05;
808       char *title = frame->current.title ? frame->current.title : "(untitled)";
809       texture_string_metrics (ss->texfont, title, &e, 0, 0);
810       sw = e.width;
811       sh = e.ascent + e.descent;
812 
813       glTranslatef (0, -scale, 0);
814 
815       scale /= sh;
816       glScalef (scale, scale, scale);
817 
818       glTranslatef (((1/scale) - sw) / 2, 0, 0);
819       glColor3f (1, 1, 1);
820 
821       if (!wire)
822         {
823           glEnable (GL_TEXTURE_2D);
824           print_texture_string (ss->texfont, title);
825         }
826       else
827         {
828           glBegin (GL_LINE_LOOP);
829           glVertex3f (0,  0,  0);
830           glVertex3f (sw, 0,  0);
831           glVertex3f (sw, sh, 0);
832           glVertex3f (0,  sh, 0);
833           glEnd();
834         }
835     }
836 
837   glPopMatrix();
838 }
839 
840 
841 ENTRYPOINT void
draw_carousel(ModeInfo * mi)842 draw_carousel (ModeInfo *mi)
843 {
844   carousel_state *ss = &sss[MI_SCREEN(mi)];
845   int i;
846 
847   if (!ss->glx_context)
848     return;
849 
850   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *ss->glx_context);
851 
852   if (ss->awaiting_first_images_p)
853     if (!load_initial_images (mi))
854       return;
855 
856   /* Only check the wall clock every 10 frames */
857   {
858     if (ss->now == 0 || ss->draw_tick++ > 10)
859       {
860         ss->now = time((time_t *) 0);
861         if (ss->last_time == 0) ss->last_time = ss->now;
862         ss->draw_tick = 0;
863       }
864   }
865 
866 
867   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
868 
869   glPushMatrix();
870 
871   glRotatef(current_device_rotation(), 0, 0, 1);
872 
873 
874   /* Run the startup "un-shrink" animation.
875    */
876   switch (ss->mode)
877     {
878     case IN:
879       if (--ss->mode_tick <= 0)
880         {
881           ss->mode = NORMAL;
882           ss->last_time = time((time_t *) 0);
883         }
884       break;
885     case NORMAL:
886       break;
887     default:
888       abort();
889     }
890 
891 
892   /* Scale as per the startup "un-shrink" animation.
893    */
894   if (ss->mode != NORMAL)
895     {
896       GLfloat s = (ss->mode == OUT
897                    ? ss->mode_tick / (fade_ticks / speed)
898                    : (((fade_ticks / speed) - ss->mode_tick + 1) /
899                       (fade_ticks / speed)));
900       glScalef (s, s, s);
901     }
902 
903   /* Rotate and tilt as per the user, and the motion modeller.
904    */
905   {
906     double x, y, z;
907     gltrackball_rotate (ss->trackball);
908 
909     /* Tilt the tube up or down by up to 30 degrees */
910     get_position (ss->rot, &x, &y, &z, !ss->button_down_p);
911     if (tilt_x_p)
912       glRotatef (15 - (x * 30), 1, 0, 0);
913     if (tilt_y_p)
914       glRotatef (7  - (y * 14), 0, 0, 1);
915 
916     /* Only use the Y component of the rotator. */
917     get_rotation (ss->rot, &x, &y, &z, !ss->button_down_p);
918     glRotatef (y * 360, 0, 1, 0);
919   }
920 
921   /* First draw each image, then draw the titles.  GL insists that you
922      draw back-to-front in order to make alpha blending work properly,
923      so we need to draw all of the 100% opaque images before drawing
924      any of the not-100%-opaque titles.
925    */
926   for (i = 0; i < ss->nframes; i++)
927     draw_frame (mi, ss->frames[i], ss->now, True);
928   if (titles_p)
929     for (i = 0; i < ss->nframes; i++)
930       draw_frame (mi, ss->frames[i], ss->now, False);
931 
932   glPopMatrix();
933 
934   if (mi->fps_p) do_fps (mi);
935   glFinish();
936   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
937 }
938 
939 
940 ENTRYPOINT void
free_carousel(ModeInfo * mi)941 free_carousel (ModeInfo *mi)
942 {
943   carousel_state *ss = &sss[MI_SCREEN(mi)];
944   int i;
945   if (!ss->glx_context) return;
946   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *ss->glx_context);
947   if (ss->rot) free_rotator (ss->rot);
948   if (ss->trackball) gltrackball_free (ss->trackball);
949   if (ss->texfont) free_texture_font (ss->texfont);
950   if (ss->titlefont) free_texture_font (ss->titlefont);
951   for (i = 0; i < ss->nframes; i++) {
952     if (ss->frames[i]->current.title) free (ss->frames[i]->current.title);
953     if (ss->frames[i]->loading.title) free (ss->frames[i]->loading.title);
954     if (ss->frames[i]->rot) free_rotator (ss->frames[i]->rot);
955     if (ss->frames[i]->current.texid)
956       glDeleteTextures (1, &ss->frames[i]->current.texid);
957     if (ss->frames[i]->loading.texid)
958       glDeleteTextures (1, &ss->frames[i]->loading.texid);
959   }
960 }
961 
962 XSCREENSAVER_MODULE ("Carousel", carousel)
963 
964 #endif /* USE_GL */
965