From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / slidescreen.c
1 /* xscreensaver, Copyright (c) 1992-2018 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 #include "screenhack.h"
13
14 enum { DOWN = 0, LEFT, UP, RIGHT };
15 enum { VERTICAL, HORIZONTAL };
16
17 struct state {
18   Display *dpy;
19   Window window;
20
21   int grid_size;
22   int pix_inc;
23   int hole_x, hole_y;
24   int bitmap_w, bitmap_h;
25   int xoff, yoff;
26   int grid_w, grid_h;
27   int delay, delay2;
28   int duration;
29   GC gc;
30   unsigned long fg, bg;
31   int max_width, max_height;
32   int early_i;
33
34   int draw_rnd, draw_i;
35   int draw_x, draw_y, draw_ix, draw_iy, draw_dx, draw_dy;
36   int draw_dir, draw_w, draw_h, draw_size, draw_inc;
37   int draw_last;
38   int draw_initted;
39
40   time_t start_time;
41   async_load_state *img_loader;
42 };
43
44
45 static void *
46 slidescreen_init (Display *dpy, Window window)
47 {
48   struct state *st = (struct state *) calloc (1, sizeof(*st));
49   XWindowAttributes xgwa;
50   XGCValues gcv;
51   long gcflags;
52
53   st->dpy = dpy;
54   st->window = window;
55   XGetWindowAttributes (st->dpy, st->window, &xgwa);
56   st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
57                                             st->window, 0, 0);
58   st->start_time = time ((time_t *) 0);
59
60   st->max_width = xgwa.width;
61   st->max_height = xgwa.height;
62
63   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
64   st->delay2 = get_integer_resource (st->dpy, "delay2", "Integer");
65   st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
66   st->grid_size = get_integer_resource (st->dpy, "gridSize", "Integer");
67   st->pix_inc = get_integer_resource (st->dpy, "pixelIncrement", "Integer");
68
69   if (xgwa.width > 2560) st->grid_size *= 2;  /* Retina displays */
70
71
72   /* Don't let the grid be smaller than 5x5 */
73   while (st->grid_size > xgwa.width / 5)
74     st->grid_size /= 2;
75   while (st->grid_size > xgwa.height / 5)
76     st->grid_size /= 2;
77
78   if (st->delay < 0) st->delay = 0;
79   if (st->delay2 < 0) st->delay2 = 0;
80   if (st->duration < 1) st->duration = 1;
81   if (st->pix_inc < 1) st->pix_inc = 1;
82   if (st->grid_size < 1) st->grid_size = 1;
83
84
85   {
86     XColor fgc, bgc;
87     char *fgs = get_string_resource(st->dpy, "background", "Background");
88     char *bgs = get_string_resource(st->dpy, "foreground", "Foreground");
89     Bool fg_ok, bg_ok;
90     if (!XParseColor (st->dpy, xgwa.colormap, fgs, &fgc))
91       XParseColor (st->dpy, xgwa.colormap, "black", &bgc);
92     if (!XParseColor (st->dpy, xgwa.colormap, bgs, &bgc))
93       XParseColor (st->dpy, xgwa.colormap, "gray", &fgc);
94
95     fg_ok = XAllocColor (st->dpy, xgwa.colormap, &fgc);
96     bg_ok = XAllocColor (st->dpy, xgwa.colormap, &bgc);
97
98     /* If we weren't able to allocate the two colors we want from the
99        colormap (which is likely if the screen has been grabbed on an
100        8-bit SGI visual -- don't ask) then just go through the map
101        and find the closest color to the ones we wanted, and use those
102        pixels without actually allocating them.
103      */
104     if (fg_ok)
105       st->fg = fgc.pixel;
106     else
107       st->fg = 0;
108
109     if (bg_ok)
110       st->bg = bgc.pixel;
111     else
112       st->bg = 1;
113
114 #ifndef HAVE_JWXYZ
115     if (!fg_ok || bg_ok)
116       {
117         int i;
118         unsigned long fgd = ~0;
119         unsigned long bgd = ~0;
120         int max = visual_cells (xgwa.screen, xgwa.visual);
121         XColor *all = (XColor *) calloc(sizeof (*all), max);
122         for (i = 0; i < max; i++)
123           {
124             all[i].flags = DoRed|DoGreen|DoBlue;
125             all[i].pixel = i;
126           }
127         XQueryColors (st->dpy, xgwa.colormap, all, max);
128         for(i = 0; i < max; i++)
129           {
130             long rd, gd, bd;
131             unsigned long dd;
132             if (!fg_ok)
133               {
134                 rd = (all[i].red   >> 8) - (fgc.red   >> 8);
135                 gd = (all[i].green >> 8) - (fgc.green >> 8);
136                 bd = (all[i].blue  >> 8) - (fgc.blue  >> 8);
137                 if (rd < 0) rd = -rd;
138                 if (gd < 0) gd = -gd;
139                 if (bd < 0) bd = -bd;
140                 dd = (rd << 1) + (gd << 2) + bd;
141                 if (dd < fgd)
142                   {
143                     fgd = dd;
144                     st->fg = all[i].pixel;
145                     if (dd == 0)
146                       fg_ok = True;
147                   }
148               }
149
150             if (!bg_ok)
151               {
152                 rd = (all[i].red   >> 8) - (bgc.red   >> 8);
153                 gd = (all[i].green >> 8) - (bgc.green >> 8);
154                 bd = (all[i].blue  >> 8) - (bgc.blue  >> 8);
155                 if (rd < 0) rd = -rd;
156                 if (gd < 0) gd = -gd;
157                 if (bd < 0) bd = -bd;
158                 dd = (rd << 1) + (gd << 2) + bd;
159                 if (dd < bgd)
160                   {
161                     bgd = dd;
162                     st->bg = all[i].pixel;
163                     if (dd == 0)
164                       bg_ok = True;
165                   }
166               }
167
168             if (fg_ok && bg_ok)
169               break;
170           }
171         XFree(all);
172       }
173 #endif /* !HAVE_JWXYZ */
174   }
175
176   gcv.foreground = st->fg;
177   gcv.function = GXcopy;
178   gcv.subwindow_mode = IncludeInferiors;
179   gcflags = GCForeground |GCFunction;
180   if (use_subwindow_mode_p(xgwa.screen, st->window)) /* see grabscreen.c */
181     gcflags |= GCSubwindowMode;
182   st->gc = XCreateGC (st->dpy, st->window, gcflags, &gcv);
183
184   return st;
185 }
186
187 static void
188 draw_grid (struct state *st)
189 {
190   int i;
191   Drawable d;
192   int border;
193   XWindowAttributes xgwa;
194
195   XGetWindowAttributes (st->dpy, st->window, &xgwa);
196
197   border = get_integer_resource (st->dpy, "internalBorderWidth",
198                                  "InternalBorderWidth");
199
200   XGetWindowAttributes (st->dpy, st->window, &xgwa);
201   st->bitmap_w = xgwa.width;
202   st->bitmap_h = xgwa.height;
203
204   if (xgwa.width < 50 || xgwa.height < 50) /* tiny window */
205     {
206       int s = (xgwa.width < xgwa.height ? xgwa.width : xgwa.height);
207       border = 1;
208       st->grid_size = s / 2;
209       if (st->grid_size < 16) st->grid_size = 16;
210       if (st->bitmap_w < st->grid_size*2) st->bitmap_w = st->grid_size*2;
211       if (st->bitmap_h < st->grid_size*2) st->bitmap_h = st->grid_size*2;
212     }
213
214   if (xgwa.width > 2560) border *= 2;  /* Retina displays */
215
216   st->grid_w = st->bitmap_w / st->grid_size;
217   st->grid_h = st->bitmap_h / st->grid_size;
218   st->hole_x = random () % st->grid_w;
219   st->hole_y = random () % st->grid_h;
220   st->xoff = (st->bitmap_w - (st->grid_w * st->grid_size)) / 2;
221   st->yoff = (st->bitmap_h - (st->grid_h * st->grid_size)) / 2;
222
223   d = st->window;
224
225   st->early_i = -10;
226   st->draw_last = -1;
227
228   if (border)
229     {
230       int half = border/2;
231       int half2 = (border & 1 ? half+1 : half);
232       XSetForeground(st->dpy, st->gc, st->bg);
233       for (i = 0; i < st->bitmap_w; i += st->grid_size)
234         {
235           int j;
236           for (j = 0; j < st->bitmap_h; j += st->grid_size)
237             XDrawRectangle (st->dpy, d, st->gc,
238                             st->xoff+i+half2, st->yoff+j+half2,
239                             st->grid_size-border-1, st->grid_size-border-1);
240         }
241
242       XSetForeground(st->dpy, st->gc, st->fg);
243       for (i = 0; i <= st->bitmap_w; i += st->grid_size)
244         XFillRectangle (st->dpy, d, st->gc, st->xoff+i-half, st->yoff, border, st->bitmap_h);
245       for (i = 0; i <= st->bitmap_h; i += st->grid_size)
246         XFillRectangle (st->dpy, d, st->gc, st->xoff, st->yoff+i-half, st->bitmap_w, border);
247     }
248
249   if (st->xoff)
250     {
251       XFillRectangle (st->dpy, d, st->gc, 0, 0, st->xoff, st->bitmap_h);
252       XFillRectangle (st->dpy, d, st->gc, st->bitmap_w - st->xoff, 0, st->xoff, st->bitmap_h);
253     }
254   if (st->yoff)
255     {
256       XFillRectangle (st->dpy, d, st->gc, 0, 0, st->bitmap_w, st->yoff);
257       XFillRectangle (st->dpy, d, st->gc, 0, st->bitmap_h - st->yoff, st->bitmap_w, st->yoff);
258     }
259 }
260
261
262 static int
263 slidescreen_draw_early (struct state *st)
264 {
265   while (st->early_i < 0)
266     {
267       st->early_i++;
268       return 1;
269     }
270
271   /* for (early_i = 0; early_i < grid_size; early_i += pix_inc) */
272    {
273      XPoint points [3];
274      points[0].x = st->xoff + st->grid_size * st->hole_x;
275      points[0].y = st->yoff + st->grid_size * st->hole_y;
276      points[1].x = points[0].x + st->grid_size;
277      points[1].y = points[0].y;
278      points[2].x = points[0].x;
279      points[2].y = points[0].y + st->early_i;
280      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
281
282      points[1].x = points[0].x;
283      points[1].y = points[0].y + st->grid_size;
284      points[2].x = points[0].x + st->early_i;
285      points[2].y = points[0].y + st->grid_size;
286      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
287
288      points[0].x = points[1].x + st->grid_size;
289      points[0].y = points[1].y;
290      points[2].x = points[0].x;
291      points[2].y = points[0].y - st->early_i;
292      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
293
294      points[1].x = points[0].x;
295      points[1].y = points[0].y - st->grid_size;
296      points[2].x = points[1].x - st->early_i;
297      points[2].y = points[1].y;
298      XFillPolygon (st->dpy, st->window, st->gc, points, 3, Convex, CoordModeOrigin);
299    }
300
301    st->early_i += st->pix_inc;
302    if (st->early_i < st->grid_size)
303      return 1;
304
305    XFillRectangle (st->dpy, st->window, st->gc,
306                    st->xoff + st->grid_size * st->hole_x,
307                    st->yoff + st->grid_size * st->hole_y,
308                    st->grid_size, st->grid_size);
309    return 0;
310 }
311
312
313 static unsigned long
314 slidescreen_draw (Display *dpy, Window window, void *closure)
315 {
316   struct state *st = (struct state *) closure;
317   int this_delay = st->delay;
318
319   /* this code is a total kludge, but who cares, it works... */
320
321   if (st->img_loader)   /* still loading */
322     {
323       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
324       if (! st->img_loader) {  /* just finished */
325         st->start_time = time ((time_t *) 0);
326         draw_grid (st);
327       }
328       return st->delay;
329     }
330
331   if (!st->img_loader &&
332       st->start_time + st->duration < time ((time_t *) 0)) {
333     XWindowAttributes xgwa;
334     XGetWindowAttributes(st->dpy, st->window, &xgwa);
335     st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
336                                               st->window, 0, 0);
337     st->start_time = time ((time_t *) 0);
338     st->draw_initted = 0;
339     return st->delay;
340   }
341
342   if (! st->draw_initted)
343     {
344       if (!slidescreen_draw_early (st))
345         {
346           st->draw_initted = 1;
347           return st->delay2;
348         }
349       else
350         return st->delay;
351     }
352
353  if (st->draw_i == 0)
354    {
355      if (st->draw_last == -1) st->draw_last = random () % 2;
356
357      /* alternate between horizontal and vertical slides */
358      /* note that draw_dir specifies the direction the _hole_ moves, not the tiles */
359      if (st->draw_last == VERTICAL) {
360        if (((st->grid_w > 1) ? st->draw_rnd = random () % (st->grid_w - 1) : 0)
361            < st->hole_x) {
362          st->draw_dx = -1; st->draw_dir = LEFT;  st->hole_x -= st->draw_rnd;
363        } else {
364          st->draw_dx =  1; st->draw_dir = RIGHT; st->draw_rnd -= st->hole_x;
365        }
366        st->draw_dy = 0; st->draw_w = st->draw_size = st->draw_rnd + 1; st->draw_h = 1;
367        st->draw_last = HORIZONTAL;
368      } else {
369        if (((st->grid_h > 1) ? st->draw_rnd = random () % (st->grid_h - 1) : 0)
370            < st->hole_y) {
371          st->draw_dy = -1; st->draw_dir = UP;    st->hole_y -= st->draw_rnd;
372        } else {
373          st->draw_dy =  1; st->draw_dir = DOWN;  st->draw_rnd -= st->hole_y;
374        }
375        st->draw_dx = 0; st->draw_h = st->draw_size = st->draw_rnd + 1; st->draw_w = 1;
376        st->draw_last = VERTICAL;
377      }
378  
379      st->draw_ix = st->draw_x = st->xoff + (st->hole_x + st->draw_dx) * st->grid_size;
380      st->draw_iy = st->draw_y = st->yoff + (st->hole_y + st->draw_dy) * st->grid_size;
381      st->draw_inc = st->pix_inc;
382
383    }
384
385  /* for (draw_i = 0; draw_i < grid_size; draw_i += draw_inc) */
386    {
387      int fx, fy, tox, toy;
388      if (st->draw_inc + st->draw_i > st->grid_size)
389        st->draw_inc = st->grid_size - st->draw_i;
390      tox = st->draw_x - st->draw_dx * st->draw_inc;
391      toy = st->draw_y - st->draw_dy * st->draw_inc;
392
393      fx = (st->draw_x < 0 ? 0 : st->draw_x > st->max_width  ? st->max_width  : st->draw_x);
394      fy = (st->draw_y < 0 ? 0 : st->draw_y > st->max_height ? st->max_height : st->draw_y);
395      tox = (tox < 0 ? 0 : tox > st->max_width  ? st->max_width  : tox);
396      toy = (toy < 0 ? 0 : toy > st->max_height ? st->max_height : toy);
397
398      XCopyArea (st->dpy, st->window, st->window, st->gc,
399                 fx, fy,
400                 st->grid_size * st->draw_w, st->grid_size * st->draw_h,
401                 tox, toy);
402
403      st->draw_x -= st->draw_dx * st->draw_inc;
404      st->draw_y -= st->draw_dy * st->draw_inc;
405      switch (st->draw_dir)
406        {
407        case DOWN: XFillRectangle (st->dpy, st->window, st->gc,
408                                st->draw_ix, st->draw_y + st->grid_size * st->draw_h, st->grid_size * st->draw_w, st->draw_iy - st->draw_y);
409          break;
410        case LEFT: XFillRectangle (st->dpy, st->window, st->gc, st->draw_ix, st->draw_iy, st->draw_x - st->draw_ix, st->grid_size * st->draw_h);
411          break;
412        case UP: XFillRectangle (st->dpy, st->window, st->gc, st->draw_ix, st->draw_iy, st->grid_size * st->draw_w, st->draw_y - st->draw_iy);
413          break;
414        case RIGHT: XFillRectangle (st->dpy, st->window, st->gc,
415                                st->draw_x + st->grid_size * st->draw_w, st->draw_iy, st->draw_ix - st->draw_x, st->grid_size * st->draw_h);
416          break;
417        }
418    }
419
420    st->draw_i += st->draw_inc;
421    if (st->draw_i >= st->grid_size)
422      {
423        st->draw_i = 0;
424
425        switch (st->draw_dir)
426          {
427          case DOWN: st->hole_y += st->draw_size; break;
428          case LEFT: st->hole_x--; break;
429          case UP: st->hole_y--; break;
430          case RIGHT: st->hole_x += st->draw_size; break;
431          }
432
433        this_delay = st->delay2;
434      }
435
436    return this_delay;
437 }
438
439 static void
440 slidescreen_reshape (Display *dpy, Window window, void *closure, 
441                  unsigned int w, unsigned int h)
442 {
443   struct state *st = (struct state *) closure;
444   st->max_width = w;
445   st->max_height = h;
446   if (! st->img_loader) {
447     XWindowAttributes xgwa;
448     XGetWindowAttributes (st->dpy, st->window, &xgwa);
449     st->img_loader = load_image_async_simple (0, xgwa.screen, st->window,
450                                               st->window, 0, 0);
451     st->start_time = time ((time_t *) 0);
452   }
453 }
454
455 static Bool
456 slidescreen_event (Display *dpy, Window window, void *closure, XEvent *event)
457 {
458   struct state *st = (struct state *) closure;
459   if (screenhack_event_helper (dpy, window, event))
460     {
461       st->start_time = 0;
462       return True;
463     }
464   return False;
465 }
466
467 static void
468 slidescreen_free (Display *dpy, Window window, void *closure)
469 {
470   struct state *st = (struct state *) closure;
471   XFreeGC (dpy, st->gc);
472   free (st);
473 }
474
475 \f
476
477 static const char *slidescreen_defaults [] = {
478   "*dontClearRoot:              True",
479   "*fpsSolid:                   true",
480
481 #ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
482   "*visualID:                   Best",
483 #endif
484
485   ".background:                 Black",
486   ".foreground:                 #BEBEBE",
487   "*gridSize:                   70",
488   "*pixelIncrement:             10",
489   "*internalBorderWidth:        4",
490   "*delay:                      50000",
491   "*delay2:                     1000000",
492   "*duration:                   120",
493 #ifdef HAVE_MOBILE
494   "*ignoreRotation:             True",
495   "*rotateImages:               True",
496 #endif
497   0
498 };
499
500 static XrmOptionDescRec slidescreen_options [] = {
501   { "-grid-size",       ".gridSize",            XrmoptionSepArg, 0 },
502   { "-ibw",             ".internalBorderWidth", XrmoptionSepArg, 0 },
503   { "-increment",       ".pixelIncrement",      XrmoptionSepArg, 0 },
504   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
505   { "-delay2",          ".delay2",              XrmoptionSepArg, 0 },
506   {"-duration",         ".duration",            XrmoptionSepArg, 0 },
507   { 0, 0, 0, 0 }
508 };
509
510 XSCREENSAVER_MODULE ("SlideScreen", slidescreen)