text: /*
   *  Copyright (C) 2000 James Macnicol
   *
   * 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.
   */
  
  #include <math.h>
  #include <limits.h>
  #include "screenhack.h"
  
  #ifndef MIN
  #define MIN(a, b) (((a) < (b))?(a):(b))
  #endif
  
  #ifndef MAX
  #define MAX(a, b) (((a) > (b))?(a):(b))
  #endif
  
  #define MINX 0.0
  #define MINY 0.0
  /* This should be *way* slower than the spotlight hack was */
  #define X_PERIOD (45000.0 * 3)
  #define Y_PERIOD (36000.0 * 3)
  
  struct state {
    Display *dpy;
    Window window;
    Screen *screen;
  
    int sizex, sizey;
  
    int delay;
    int duration;
    int pixwidth, pixheight, pixspacex, pixspacey, lensoffsetx, lensoffsety;
    Bool lenses;
  
    GC window_gc;
  
    XImage *orig_map;
    Pixmap pm;
  
    int tlx, tly, s;
  
    int sinusoid_offset;
  
    time_t start_time;
    async_load_state *img_loader;
  };
  
  
  static long currentTimeInMs(struct state *st)
  { 
    struct timeval curTime;
    unsigned long ret_unsigned;
  #ifdef GETTIMEOFDAY_TWO_ARGS
    struct timezone tz = {0,0};
    gettimeofday(&curTime, &tz);
  #else
    gettimeofday(&curTime);
  #endif
    ret_unsigned = curTime.tv_sec *1000U + curTime.tv_usec / 1000;
    return (ret_unsigned <= LONG_MAX) ? ret_unsigned : -1 - (long)(ULONG_MAX - ret_unsigned);
  }
  
  static void *
  zoom_init (Display *dpy, Window window)
  {
    struct state *st = (struct state *) calloc (1, sizeof(*st));
    XGCValues gcv;
    XWindowAttributes xgwa;
    Colormap cmap;
    unsigned long bg;
    long gcflags;
    int nblocksx, nblocksy;
  
    st->dpy = dpy;
    st->window = window;
    XGetWindowAttributes(st->dpy, st->window, &xgwa);
    st->screen = xgwa.screen;
    st->sizex = xgwa.width;
    st->sizey = xgwa.height;
    cmap = xgwa.colormap;
    bg = get_pixel_resource(st->dpy, cmap, "background", "Background");
  
    st->delay = get_integer_resource(st->dpy, "delay", "Integer");
    if (st->delay < 1)
      st->delay = 1;
    st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
    if (st->duration < 1)
      st->duration = 1;
    st->pixwidth = get_integer_resource(st->dpy, "pixwidth", "Integer");
    if (st->pixwidth < 1)
      st->pixwidth = 1;
    st->pixheight = get_integer_resource(st->dpy, "pixheight", "Integer");
    if (st->pixheight < 1)
      st->pixheight = 1;
    st->pixspacex = get_integer_resource(st->dpy, "pixspacex", "Integer");
    if (st->pixspacex < 0)
      st->pixspacex = 0;
    st->pixspacey = get_integer_resource(st->dpy, "pixspacey", "Integer");
    if (st->pixspacey < 0)
      st->pixspacey = 0;
  
    if (st->sizex < 50 || st->sizey < 50) {  /* tiny window */
      st->pixwidth = 10;
      st->pixheight = 10;
    }
  
    st->lenses = get_boolean_resource(st->dpy, "lenses", "Boolean");
    st->lensoffsetx = get_integer_resource(st->dpy, "lensoffsetx", "Integer");
    st->lensoffsetx = MAX(0, MIN(st->pixwidth, st->lensoffsetx));
    st->lensoffsety = get_integer_resource(st->dpy, "lensoffsety", "Integer");
    st->lensoffsety = MAX(0, MIN(st->pixwidth, st->lensoffsety));
  
    gcv.function = GXcopy;
    gcv.subwindow_mode = IncludeInferiors;
    gcflags = GCForeground|GCFunction;
    gcv.foreground = bg;
    if (!st->lenses && use_subwindow_mode_p(xgwa.screen, st->window))       /* see grabscreen.c */
      gcflags |= GCSubwindowMode;
    st->window_gc = XCreateGC(st->dpy, st->window, gcflags, &gcv);
  
  
    st->orig_map = NULL;
    st->pm = XCreatePixmap(st->dpy, st->window, st->sizex, st->sizey, xgwa.depth);
  
    XFillRectangle(st->dpy, st->window, st->window_gc, 0, 0, st->sizex, st->sizey);
    XSetWindowBackground(st->dpy, st->window, bg);
  
    st->start_time = time ((time_t *) 0);
    st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
                                              st->pm, 0, 0);
  
    /* We might have needed this to grab the image, but if we leave this set
       to GCSubwindowMode, then we'll *draw* right over subwindows too. */
    XSetSubwindowMode (st->dpy, st->window_gc, ClipByChildren);
  
  
    nblocksx = (int)ceil((double)st->sizex / (double)(st->pixwidth + st->pixspacex));
    nblocksy = (int)ceil((double)st->sizey / (double)(st->pixheight + st->pixspacey));
    if (st->lenses)
      st->s = MAX((nblocksx - 1) * st->lensoffsetx + st->pixwidth, 
  	    (nblocksy - 1) * st->lensoffsety + st->pixheight) * 2;
    else
      st->s = MAX(nblocksx, nblocksy) * 2;
  
    st->sinusoid_offset = random();
  
    return st;
  }
  
  static unsigned long
  zoom_draw (Display *dpy, Window window, void *closure)
  {
    struct state *st = (struct state *) closure;
    unsigned x, y, i, j;
  
    long now;
    unsigned long now_unsigned;
  
    if (st->img_loader)   /* still loading */
      {
        st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
        if (! st->img_loader) {  /* just finished */
          XClearWindow (st->dpy, st->window);
          st->start_time = time ((time_t *) 0);
          if (!st->lenses) {
            st->orig_map = XGetImage(st->dpy, st->pm, 0, 0, st->sizex, st->sizey, ~0L, ZPixmap);
  /*          XFreePixmap(st->dpy, st->pm);
            st->pm = 0;*/
          }
        }
        return st->delay;
      }
  
    if (!st->img_loader &&
        st->start_time + st->duration < time ((time_t *) 0)) {
      st->img_loader = load_image_async_simple (0, st->screen, st->window,
                                                st->pm, 0, 0);
      return st->delay;
    }
  
  #define nrnd(x) (random() % (x))
  
    now = currentTimeInMs(st);
    now_unsigned = (unsigned long) now + st->sinusoid_offset;  /* don't run multiple screens in lock-step */
    now = (now_unsigned <= LONG_MAX) ? now_unsigned : -1 - (long)(ULONG_MAX - now_unsigned);
  
    /* find new x,y */
    st->tlx = ((1. + sin(((double)now) / X_PERIOD * 2. * M_PI))/2.0)
      * (st->sizex - st->s/2) /* -s/4 */ + MINX;
    st->tly = ((1. + sin(((double)now) / Y_PERIOD * 2. * M_PI))/2.0)
      * (st->sizey - st->s/2) /* -s/4 */ + MINY;
  
    if (st->lenses) {
      for (x = i = 0; x < st->sizex; x += (st->pixwidth + st->pixspacex), ++i)
        for (y = j = 0; y < st->sizey; y += (st->pixheight + st->pixspacey), ++j) {
  	XCopyArea(st->dpy, st->pm /* src */, st->window /* dest */, st->window_gc,
  		  st->tlx + i * st->lensoffsetx /* src_x */, 
  		  st->tly + j * st->lensoffsety /* src_y */,
  		  st->pixwidth, st->pixheight,
  		  x /* dest_x */, y /* dest_y */);
        }
    } else {
      for (x = i = 0; x < st->sizex; x += (st->pixwidth + st->pixspacex), ++i)
        for (y = j = 0; y < st->sizey; y += (st->pixheight + st->pixspacey), ++j) {
  	XSetForeground(st->dpy, st->window_gc, XGetPixel(st->orig_map, st->tlx+i, st->tly+j));
  	XFillRectangle(st->dpy, st->window, st->window_gc, 
  		       i * (st->pixwidth + st->pixspacex),
  		       j * (st->pixheight + st->pixspacey), st->pixwidth, st->pixheight);
        }
    }
  
    return st->delay;
  }
  
  static void
  zoom_reshape (Display *dpy, Window window, void *closure, 
                   unsigned int w, unsigned int h)
  {
  }
  
  static Bool
  zoom_event (Display *dpy, Window window, void *closure, XEvent *event)
  {
    struct state *st = (struct state *) closure;
    if (screenhack_event_helper (dpy, window, event))
      {
        st->start_time = 0;
        return True;
      }
    return False;
  }
  
  static void
  zoom_free (Display *dpy, Window window, void *closure)
  {
    struct state *st = (struct state *) closure;
    XFreeGC (st->dpy, st->window_gc);
    if (st->orig_map) XDestroyImage (st->orig_map);
    if (st->pm) XFreePixmap (st->dpy, st->pm);
    free (st);
  }
  
  
  static const char *zoom_defaults[] = {
    "*dontClearRoot: True",
    ".foreground: white",
    ".background: #111111",
    ".lowrez: true",
    "*fpsSolid:	true",
  #ifdef __sgi /* really, HAVE_READ_DISPLAY_EXTENSION */
    "*visualID: Best",
  #endif
    "*lenses:      true",
    "*delay:       10000",
    "*duration:    120",
    "*pixwidth:    40",
    "*pixheight:   40",
    "*pixspacex:   2",
    "*pixspacey:   2",
    "*lensoffsetx: 5",
    "*lensoffsety: 5",
  #ifdef HAVE_MOBILE
    "*ignoreRotation: True",
    "*rotateImages:   True",
  #endif
    0
  };
  
  static XrmOptionDescRec zoom_options[] = {
    { "-lenses", ".lenses", XrmoptionNoArg, "true" },
    { "-no-lenses", ".lenses", XrmoptionNoArg, "false" },
    { "-delay", ".delay", XrmoptionSepArg, 0 },
    { "-duration",  ".duration", XrmoptionSepArg, 0 },
    { "-pixwidth", ".pixwidth", XrmoptionSepArg, 0 },
    { "-pixheight", ".pixheight", XrmoptionSepArg, 0 },
    { "-pixspacex", ".pixspacex", XrmoptionSepArg, 0 },
    { "-pixspacey", ".pixspacey", XrmoptionSepArg, 0 },
    { "-lensoffsetx", ".lensoffsetx", XrmoptionSepArg, 0 },
    { "-lensoffsety", ".lensoffsety", XrmoptionSepArg, 0 },
    { 0, 0, 0, 0 }
  };
  
  XSCREENSAVER_MODULE ("Zoom", zoom)
  