/* carousel, Copyright (c) 2005-2018 Jamie Zawinski * Loads a sequence of images and rotates them around. * * 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. * * Created: 21-Feb-2005 */ #if defined(HAVE_COCOA) || defined(HAVE_ANDROID) # define DEF_FONT "OCR A Std 48, Lucida Console 48, Monaco 48" #elif 0 /* real X11, XQueryFont() */ # define DEF_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*" #else /* real X11, load_font_retry() */ # define DEF_FONT "-*-ocr a std-medium-r-*-*-*-480-*-*-m-*-*-*" #endif #define DEF_TITLE_FONT "-*-helvetica-bold-r-normal-*-*-480-*-*-*-*-*-*" #define DEFAULTS "*count: 7 \n" \ "*delay: 10000 \n" \ "*wireframe: False \n" \ "*showFPS: False \n" \ "*fpsSolid: True \n" \ "*useSHM: True \n" \ "*font: " DEF_FONT "\n" \ "*titleFont: " DEF_TITLE_FONT "\n" \ "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \ "*grabDesktopImages: False \n" \ "*chooseRandomImages: True \n" # define release_carousel 0 # include "xlockmore.h" #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #ifdef USE_GL # define DEF_SPEED "1.0" # define DEF_DURATION "20" # define DEF_TITLES "True" # define DEF_ZOOM "True" # define DEF_TILT "XY" # define DEF_MIPMAP "True" # define DEF_DEBUG "False" #include "rotator.h" #include "gltrackball.h" #include "grab-ximage.h" #include "texfont.h" # ifndef HAVE_JWXYZ # include /* for XrmDatabase in -debug mode */ # endif /* 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 typedef struct { double x, y, w, h; } rect; typedef enum { EARLY, NORMAL, LOADING, OUT, IN, DEAD } fade_mode; static int fade_ticks = 60; typedef struct { char *title; /* the filename of this image */ int w, h; /* size in pixels of the image */ int tw, th; /* size in pixels of the texture */ XRectangle geom; /* where in the image the bits are */ GLuint texid; } image; typedef struct { ModeInfo *mi; image current, loading; GLfloat r, theta; /* radius and rotation on the tube */ rotator *rot; /* for zoomery */ Bool from_top_p; /* whether this image drops in or rises up */ time_t expires; /* when this image should be replaced */ fade_mode mode; /* in/out animation state */ int mode_tick; Bool loaded_p; /* whether background load is done */ } image_frame; typedef struct { GLXContext *glx_context; GLfloat anisotropic; rotator *rot; trackball_state *trackball; Bool button_down_p; time_t button_down_time; int nframes; /* how many frames are loaded */ int frames_size; image_frame **frames; /* pointers to the frames */ Bool awaiting_first_images_p; int loads_in_progress; texture_font_data *texfont, *titlefont; fade_mode mode; int mode_tick; int loading_sw, loading_sh; time_t last_time, now; int draw_tick; } carousel_state; static carousel_state *sss = NULL; /* Command-line arguments */ static GLfloat speed; /* animation speed scale factor */ static int duration; /* reload images after this long */ static Bool mipmap_p; /* Use mipmaps instead of single textures. */ static Bool titles_p; /* Display image titles. */ static Bool zoom_p; /* Throb the images in and out as they spin. */ static char *tilt_str; static Bool tilt_x_p; /* Tilt axis towards the viewer */ static Bool tilt_y_p; /* Tilt axis side to side */ static Bool debug_p; /* Be loud and do weird things. */ static XrmOptionDescRec opts[] = { {"-zoom", ".zoom", XrmoptionNoArg, "True" }, {"-no-zoom", ".zoom", XrmoptionNoArg, "False" }, {"-tilt", ".tilt", XrmoptionSepArg, 0 }, {"-no-tilt", ".tilt", XrmoptionNoArg, "" }, {"-titles", ".titles", XrmoptionNoArg, "True" }, {"-no-titles", ".titles", XrmoptionNoArg, "False" }, {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" }, {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" }, {"-duration", ".duration", XrmoptionSepArg, 0 }, {"-debug", ".debug", XrmoptionNoArg, "True" }, {"-font", ".font", XrmoptionSepArg, 0 }, {"-speed", ".speed", XrmoptionSepArg, 0 }, }; static argtype vars[] = { { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool}, { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool}, { &titles_p, "titles", "Titles", DEF_TITLES, t_Bool}, { &zoom_p, "zoom", "Zoom", DEF_ZOOM, t_Bool}, { &tilt_str, "tilt", "Tilt", DEF_TILT, t_String}, { &speed, "speed", "Speed", DEF_SPEED, t_Float}, { &duration, "duration", "Duration", DEF_DURATION, t_Int}, }; ENTRYPOINT ModeSpecOpt carousel_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Allocates a frame structure and stores it in the list. */ static image_frame * alloc_frame (ModeInfo *mi) { carousel_state *ss = &sss[MI_SCREEN(mi)]; image_frame *frame = (image_frame *) calloc (1, sizeof (*frame)); frame->mi = mi; frame->mode = EARLY; frame->rot = make_rotator (0, 0, 0, 0, 0.04 * frand(1.0) * speed, False); glGenTextures (1, &frame->current.texid); glGenTextures (1, &frame->loading.texid); if (frame->current.texid <= 0) abort(); if (frame->loading.texid <= 0) abort(); if (ss->frames_size <= ss->nframes) { ss->frames_size = (ss->frames_size * 1.2) + ss->nframes; ss->frames = (image_frame **) realloc (ss->frames, ss->frames_size * sizeof(*ss->frames)); if (! ss->frames) { fprintf (stderr, "%s: out of memory (%d images)\n", progname, ss->frames_size); exit (1); } } ss->frames[ss->nframes++] = frame; return frame; } static void image_loaded_cb (const char *filename, XRectangle *geom, int image_width, int image_height, int texture_width, int texture_height, void *closure); /* Load a new file into the given image struct. */ static void load_image (ModeInfo *mi, image_frame *frame) { carousel_state *ss = &sss[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); if (debug_p && !wire && frame->current.w != 0) fprintf (stderr, "%s: dropped %4d x %-4d %4d x %-4d \"%s\"\n", progname, frame->current.geom.width, frame->current.geom.height, frame->current.tw, frame->current.th, (frame->current.title ? frame->current.title : "(null)")); switch (frame->mode) { case EARLY: break; case NORMAL: frame->mode = LOADING; break; default: abort(); } ss->loads_in_progress++; if (wire) image_loaded_cb (0, 0, 0, 0, 0, 0, frame); else { int w = (MI_WIDTH(mi) / 2) - 1; int h = (MI_HEIGHT(mi) / 2) - 1; if (w <= 10) w = 10; if (h <= 10) h = 10; if (w > h * 5) { /* tiny window: use 16:9 boxes */ h = w * 9/16; } load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context, w, h, mipmap_p, frame->loading.texid, image_loaded_cb, frame); } } /* Callback that tells us that the texture has been loaded. */ static void image_loaded_cb (const char *filename, XRectangle *geom, int image_width, int image_height, int texture_width, int texture_height, void *closure) { image_frame *frame = (image_frame *) closure; ModeInfo *mi = frame->mi; carousel_state *ss = &sss[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); if (wire) { frame->loading.w = MI_WIDTH (mi) * (0.5 + frand (1.0)); frame->loading.h = MI_HEIGHT (mi); frame->loading.geom.width = frame->loading.w; frame->loading.geom.height = frame->loading.h; goto DONE; } if (image_width == 0 || image_height == 0) exit (1); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR); if (ss->anisotropic >= 1.0) glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, ss->anisotropic); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); frame->loading.w = image_width; frame->loading.h = image_height; frame->loading.tw = texture_width; frame->loading.th = texture_height; frame->loading.geom = *geom; if (frame->loading.title) free (frame->loading.title); frame->loading.title = (filename ? strdup (filename) : 0); /* xscreensaver-getimage returns paths relative to the image directory now, so leave the sub-directory part in. Unless it's an absolute path. */ if (frame->loading.title && frame->loading.title[0] == '/') { /* strip filename to part after last /. */ char *s = strrchr (frame->loading.title, '/'); if (s) strcpy (frame->loading.title, s+1); } if (debug_p) fprintf (stderr, "%s: loaded %4d x %-4d %4d x %-4d \"%s\"\n", progname, frame->loading.geom.width, frame->loading.geom.height, frame->loading.tw, frame->loading.th, (frame->loading.title ? frame->loading.title : "(null)")); DONE: frame->loaded_p = True; if (ss->loads_in_progress <= 0) abort(); ss->loads_in_progress--; /* This image expires N seconds after it finished loading. */ frame->expires = time((time_t *) 0) + (duration * MI_COUNT(mi)); switch (frame->mode) { case EARLY: /* part of the initial batch of images */ { image swap = frame->current; frame->current = frame->loading; frame->loading = swap; } break; case LOADING: /* start dropping the old image out */ { frame->mode = OUT; frame->mode_tick = fade_ticks / speed; frame->from_top_p = random() & 1; } break; default: abort(); } } static void loading_msg (ModeInfo *mi, int n); static Bool load_initial_images (ModeInfo *mi) { carousel_state *ss = &sss[MI_SCREEN(mi)]; int i; Bool all_loaded_p = True; for (i = 0; i < ss->nframes; i++) if (! ss->frames[i]->loaded_p) all_loaded_p = False; if (all_loaded_p) { if (ss->nframes < MI_COUNT (mi)) { /* The frames currently on the list are fully loaded. Start the next one loading. (We run the image loader asynchronously, but we load them one at a time.) */ load_image (mi, alloc_frame (mi)); } else { /* The first batch of images are now all loaded! Stagger the expire times so that they don't all drop out at once. */ time_t now = time((time_t *) 0); int i; for (i = 0; i < ss->nframes; i++) { image_frame *frame = ss->frames[i]; frame->r = 1.0; frame->theta = i * 360.0 / ss->nframes; frame->expires = now + (duration * (i + 1)); frame->mode = NORMAL; } /* Instead of always going clockwise, shuffle the expire times of the frames so that they drop out in a random order. */ for (i = 0; i < ss->nframes; i++) { image_frame *frame1 = ss->frames[i]; image_frame *frame2 = ss->frames[random() % ss->nframes]; time_t swap = frame1->expires; frame1->expires = frame2->expires; frame2->expires = swap; } ss->awaiting_first_images_p = False; } } loading_msg (mi, ss->nframes-1); return !ss->awaiting_first_images_p; } ENTRYPOINT void reshape_carousel (ModeInfo *mi, int width, int height) { 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, 1.0, 8.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); } ENTRYPOINT Bool carousel_handle_event (ModeInfo *mi, XEvent *event) { carousel_state *ss = &sss[MI_SCREEN(mi)]; if (event->xany.type == ButtonPress && event->xbutton.button == Button1) { if (! ss->button_down_p) ss->button_down_time = time((time_t *) 0); } else if (event->xany.type == ButtonRelease && event->xbutton.button == Button1) { if (ss->button_down_p) { /* Add the time the mouse was held to the expire times of all frames, so that mouse-dragging doesn't count against image expiration. */ int secs = time((time_t *) 0) - ss->button_down_time; int i; for (i = 0; i < ss->nframes; i++) ss->frames[i]->expires += secs; } } if (gltrackball_event_handler (event, ss->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &ss->button_down_p)) return True; else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event)) { int i = random() % ss->nframes; ss->frames[i]->expires = 0; return True; } return False; } /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode */ static void hack_resources (Display *dpy) { # ifndef HAVE_JWXYZ char *res = "desktopGrabber"; char *val = get_string_resource (dpy, res, "DesktopGrabber"); char buf1[255]; char buf2[255]; XrmValue value; XrmDatabase db = XtDatabase (dpy); sprintf (buf1, "%.100s.%.100s", progname, res); sprintf (buf2, "%.200s -v", val); value.addr = buf2; value.size = strlen(buf2); XrmPutResource (&db, buf1, "String", &value); free (val); # endif /* !HAVE_JWXYZ */ } static void loading_msg (ModeInfo *mi, int n) { carousel_state *ss = &sss[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); char text[100]; if (wire) return; if (n == 0) sprintf (text, "Loading images..."); else sprintf (text, "Loading images... (%d%%)", (int) (n * 100 / MI_COUNT(mi))); if (ss->loading_sw == 0) { /* only do this once, so that the string doesn't move. */ XCharStruct e; texture_string_metrics (ss->titlefont, text, &e, 0, 0); ss->loading_sw = e.width; ss->loading_sh = e.ascent + e.descent; } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); /* { double rot = current_device_rotation(); glRotatef(rot, 0, 0, 1); if ((rot > 45 && rot < 135) || (rot < -45 && rot > -135)) { GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi); glScalef (s, 1/s, 1); } } */ # ifdef HAVE_MOBILE if (MI_WIDTH(mi) < MI_HEIGHT(mi)) /* portrait orientation */ { GLfloat s = (MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi)); glScalef (s, s, s); glTranslatef(-s/2, 0, 0); } # endif glOrtho(0, MI_WIDTH(mi), 0, MI_HEIGHT(mi), -1, 1); glTranslatef ((MI_WIDTH(mi) - ss->loading_sw) / 2, (MI_HEIGHT(mi) - ss->loading_sh) / 2, 0); glColor3f (1, 1, 0); glEnable (GL_TEXTURE_2D); glDisable (GL_DEPTH_TEST); print_texture_string (ss->titlefont, text); glEnable (GL_DEPTH_TEST); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glFinish(); glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi)); } ENTRYPOINT void init_carousel (ModeInfo *mi) { int screen = MI_SCREEN(mi); carousel_state *ss; int wire = MI_IS_WIREFRAME(mi); MI_INIT (mi, sss); ss = &sss[screen]; if ((ss->glx_context = init_GL(mi)) != NULL) { reshape_carousel (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); clear_gl_error(); /* WTF? sometimes "invalid op" from glViewport! */ } else { MI_CLEARWINDOW(mi); } if (!tilt_str || !*tilt_str) ; else if (!strcasecmp (tilt_str, "0")) ; else if (!strcasecmp (tilt_str, "X")) tilt_x_p = 1; else if (!strcasecmp (tilt_str, "Y")) tilt_y_p = 1; else if (!strcasecmp (tilt_str, "XY")) tilt_x_p = tilt_y_p = 1; else { fprintf (stderr, "%s: tilt must be 'X', 'Y', 'XY' or '', not '%s'\n", progname, tilt_str); exit (1); } { double spin_speed = speed * 0.2; /* rotation of tube around axis */ double spin_accel = speed * 0.1; double wander_speed = speed * 0.001; /* tilting of axis */ spin_speed *= 0.9 + frand(0.2); wander_speed *= 0.9 + frand(0.2); ss->rot = make_rotator (spin_speed, spin_speed, spin_speed, spin_accel, wander_speed, True); ss->trackball = gltrackball_init (False); } if (strstr ((char *) glGetString(GL_EXTENSIONS), "GL_EXT_texture_filter_anisotropic")) glGetFloatv (GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &ss->anisotropic); else ss->anisotropic = 0.0; glDisable (GL_LIGHTING); glEnable (GL_DEPTH_TEST); glDisable (GL_CULL_FACE); if (! wire) { glShadeModel (GL_SMOOTH); glEnable (GL_LINE_SMOOTH); /* This gives us a transparent diagonal slice through each image! */ /* glEnable (GL_POLYGON_SMOOTH); */ glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_ALPHA_TEST); glEnable (GL_POLYGON_OFFSET_FILL); glPolygonOffset (1.0, 1.0); } ss->texfont = load_texture_font (MI_DISPLAY(mi), "font"); ss->titlefont = load_texture_font (MI_DISPLAY(mi), "titleFont"); if (debug_p) hack_resources (MI_DISPLAY (mi)); ss->nframes = 0; ss->frames_size = 10; ss->frames = (image_frame **) calloc (1, ss->frames_size * sizeof(*ss->frames)); ss->mode = IN; ss->mode_tick = fade_ticks / speed; ss->awaiting_first_images_p = True; } static void draw_frame (ModeInfo *mi, image_frame *frame, time_t now, Bool body_p) { carousel_state *ss = &sss[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); GLfloat texw = frame->current.geom.width / (GLfloat) frame->current.tw; GLfloat texh = frame->current.geom.height / (GLfloat) frame->current.th; GLfloat texx1 = frame->current.geom.x / (GLfloat) frame->current.tw; GLfloat texy1 = frame->current.geom.y / (GLfloat) frame->current.th; GLfloat texx2 = texx1 + texw; GLfloat texy2 = texy1 + texh; GLfloat aspect = ((GLfloat) frame->current.geom.height / (GLfloat) frame->current.geom.width); glBindTexture (GL_TEXTURE_2D, frame->current.texid); glPushMatrix(); /* Position this image on the wheel. */ glRotatef (frame->theta, 0, 1, 0); glTranslatef (0, 0, frame->r); /* Scale down the image so that all N frames fit on the wheel without bumping in to each other. */ { GLfloat t, s; switch (ss->nframes) { case 1: t = -1.0; s = 1.7; break; case 2: t = -0.8; s = 1.6; break; case 3: t = -0.4; s = 1.5; break; case 4: t = -0.2; s = 1.3; break; default: t = 0.0; s = 6.0 / ss->nframes; break; } glTranslatef (0, 0, t); glScalef (s, s, s); } /* Center this image on the wheel plane. */ glTranslatef (-0.5, -(aspect/2), 0); /* Move as per the "zoom in and out" setting. */ if (zoom_p) { double x, y, z; /* Only use the Z component of the rotator for in/out position. */ get_position (frame->rot, &x, &y, &z, !ss->button_down_p); glTranslatef (0, 0, z/2); } /* Compute the "drop in and out" state. */ switch (frame->mode) { case EARLY: abort(); break; case NORMAL: if (!ss->button_down_p && now >= frame->expires && ss->loads_in_progress == 0) /* only load one at a time */ load_image (mi, frame); break; case LOADING: break; case OUT: if (--frame->mode_tick <= 0) { image swap = frame->current; frame->current = frame->loading; frame->loading = swap; frame->mode = IN; frame->mode_tick = fade_ticks / speed; } break; case IN: if (--frame->mode_tick <= 0) frame->mode = NORMAL; break; default: abort(); } /* Now translate for current in/out state. */ if (frame->mode == OUT || frame->mode == IN) { GLfloat t = (frame->mode == OUT ? frame->mode_tick / (fade_ticks / speed) : (((fade_ticks / speed) - frame->mode_tick + 1) / (fade_ticks / speed))); t = 5 * (1 - t); if (frame->from_top_p) t = -t; glTranslatef (0, t, 0); } if (body_p) /* Draw the image quad. */ { if (! wire) { glColor3f (1, 1, 1); glNormal3f (0, 0, 1); glEnable (GL_TEXTURE_2D); glBegin (GL_QUADS); glNormal3f (0, 0, 1); glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0); glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0); glTexCoord2f (texx2, texy1); glVertex3f (1, aspect, 0); glTexCoord2f (texx1, texy1); glVertex3f (0, aspect, 0); glEnd(); } /* Draw a box around it. */ glLineWidth (2.0); glColor3f (0.5, 0.5, 0.5); glDisable (GL_TEXTURE_2D); glBegin (GL_LINE_LOOP); glVertex3f (0, 0, 0); glVertex3f (1, 0, 0); glVertex3f (1, aspect, 0); glVertex3f (0, aspect, 0); glEnd(); } else /* Draw a title under the image. */ { XCharStruct e; int sw, sh; GLfloat scale = 0.05; char *title = frame->current.title ? frame->current.title : "(untitled)"; texture_string_metrics (ss->texfont, title, &e, 0, 0); sw = e.width; sh = e.ascent + e.descent; glTranslatef (0, -scale, 0); scale /= sh; glScalef (scale, scale, scale); glTranslatef (((1/scale) - sw) / 2, 0, 0); glColor3f (1, 1, 1); if (!wire) { glEnable (GL_TEXTURE_2D); print_texture_string (ss->texfont, title); } else { glBegin (GL_LINE_LOOP); glVertex3f (0, 0, 0); glVertex3f (sw, 0, 0); glVertex3f (sw, sh, 0); glVertex3f (0, sh, 0); glEnd(); } } glPopMatrix(); } ENTRYPOINT void draw_carousel (ModeInfo *mi) { carousel_state *ss = &sss[MI_SCREEN(mi)]; int i; if (!ss->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *ss->glx_context); if (ss->awaiting_first_images_p) if (!load_initial_images (mi)) return; /* Only check the wall clock every 10 frames */ { if (ss->now == 0 || ss->draw_tick++ > 10) { ss->now = time((time_t *) 0); if (ss->last_time == 0) ss->last_time = ss->now; ss->draw_tick = 0; } } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glRotatef(current_device_rotation(), 0, 0, 1); /* Run the startup "un-shrink" animation. */ switch (ss->mode) { case IN: if (--ss->mode_tick <= 0) { ss->mode = NORMAL; ss->last_time = time((time_t *) 0); } break; case NORMAL: break; default: abort(); } /* Scale as per the startup "un-shrink" animation. */ if (ss->mode != NORMAL) { GLfloat s = (ss->mode == OUT ? ss->mode_tick / (fade_ticks / speed) : (((fade_ticks / speed) - ss->mode_tick + 1) / (fade_ticks / speed))); glScalef (s, s, s); } /* Rotate and tilt as per the user, and the motion modeller. */ { double x, y, z; gltrackball_rotate (ss->trackball); /* Tilt the tube up or down by up to 30 degrees */ get_position (ss->rot, &x, &y, &z, !ss->button_down_p); if (tilt_x_p) glRotatef (15 - (x * 30), 1, 0, 0); if (tilt_y_p) glRotatef (7 - (y * 14), 0, 0, 1); /* Only use the Y component of the rotator. */ get_rotation (ss->rot, &x, &y, &z, !ss->button_down_p); glRotatef (y * 360, 0, 1, 0); } /* First draw each image, then draw the titles. GL insists that you draw back-to-front in order to make alpha blending work properly, so we need to draw all of the 100% opaque images before drawing any of the not-100%-opaque titles. */ for (i = 0; i < ss->nframes; i++) draw_frame (mi, ss->frames[i], ss->now, True); if (titles_p) for (i = 0; i < ss->nframes; i++) draw_frame (mi, ss->frames[i], ss->now, False); glPopMatrix(); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi)); } ENTRYPOINT void free_carousel (ModeInfo *mi) { carousel_state *ss = &sss[MI_SCREEN(mi)]; int i; if (!ss->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *ss->glx_context); if (ss->rot) free_rotator (ss->rot); if (ss->trackball) gltrackball_free (ss->trackball); if (ss->texfont) free_texture_font (ss->texfont); if (ss->titlefont) free_texture_font (ss->titlefont); for (i = 0; i < ss->nframes; i++) { if (ss->frames[i]->current.title) free (ss->frames[i]->current.title); if (ss->frames[i]->loading.title) free (ss->frames[i]->loading.title); if (ss->frames[i]->rot) free_rotator (ss->frames[i]->rot); if (ss->frames[i]->current.texid) glDeleteTextures (1, &ss->frames[i]->current.texid); if (ss->frames[i]->loading.texid) glDeleteTextures (1, &ss->frames[i]->loading.texid); } } XSCREENSAVER_MODULE ("Carousel", carousel) #endif /* USE_GL */