From http://www.jwz.org/xscreensaver/xscreensaver-5.39.tar.gz
[xscreensaver] / hacks / spotlight.c
1 /*
2  * spotlight - an xscreensaver module
3  * Copyright (c) 1999, 2001 Rick Schultz <rick.schultz@gmail.com>
4  *
5  * loosely based on the BackSpace module "StefView" by Darcy Brockbank
6  */
7
8 /* modified from a module from the xscreensaver distribution */
9
10 /*
11  * xscreensaver, Copyright (c) 1992-2006 Jamie Zawinski <jwz@jwz.org>
12  *
13  * Permission to use, copy, modify, distribute, and sell this software and its
14  * documentation for any purpose is hereby granted without fee, provided that
15  * the above copyright notice appear in all copies and that both that
16  * copyright notice and this permission notice appear in supporting
17  * documentation.  No representations are made about the suitability of this
18  * software for any purpose.  It is provided "as is" without express or 
19  * implied warranty.
20  */
21
22
23 /* #define DEBUG */
24 #include <math.h>
25 #include <limits.h>
26 #include "screenhack.h"
27
28 #define X_PERIOD 15000.0
29 #define Y_PERIOD 12000.0
30
31 struct state {
32   Display *dpy;
33   Window window;
34   Screen *screen;
35
36   int sizex, sizey; /* screen size */
37   int delay;
38   int duration;
39   time_t start_time;
40   int first_time;
41   GC window_gc;
42 #ifdef DEBUG
43   GC white_gc;
44 #endif
45   GC buffer_gc;     /* draw in buffer, then flush to screen
46                        to avoid flicker */
47   int radius;       /* radius of spotlight in pixels */
48
49   Pixmap pm;        /* pixmap grabbed from screen */
50   Pixmap buffer;    /* pixmap for the buffer */
51
52   int x, y, s;      /* x & y coords of buffer (upper left corner) */
53   /* s is the width of the buffer */
54
55   int off;      /* random offset from currentTimeInMs(), so that
56                    two concurrent copies of spotlight have different
57                    behavior. */
58
59   int oldx, oldy, max_x_speed, max_y_speed;
60   /* used to keep the new buffer position
61      over the old spotlight image to make sure 
62      the old image is completely erased */
63
64   Bool first_p;
65   async_load_state *img_loader;
66   XRectangle geom;
67 };
68
69
70 /* The path the spotlight follows around the screen is sinusoidal.
71    This function is fed to sin() to get the x & y coords */
72 static long
73 currentTimeInMs(struct state *st)
74 {
75   struct timeval curTime;
76   unsigned long ret_unsigned;
77 #ifdef GETTIMEOFDAY_TWO_ARGS
78   struct timezone tz = {0,0};
79   gettimeofday(&curTime, &tz);
80 #else
81   gettimeofday(&curTime);
82 #endif
83   ret_unsigned = curTime.tv_sec *1000U + curTime.tv_usec / 1000;
84   return (ret_unsigned <= LONG_MAX) ? ret_unsigned : -1 - (long)(ULONG_MAX - ret_unsigned);
85 }
86
87
88 static void *
89 spotlight_init (Display *dpy, Window window)
90 {
91   struct state *st = (struct state *) calloc (1, sizeof(*st));
92   XGCValues gcv;
93   XWindowAttributes xgwa;
94   long gcflags;
95   Colormap cmap;
96   unsigned long bg;
97   GC clip_gc;
98   Pixmap clip_pm;
99
100   st->dpy = dpy;
101   st->window = window;
102   st->first_p = True;
103
104   XGetWindowAttributes (st->dpy, st->window, &xgwa);
105   st->screen = xgwa.screen;
106   st->sizex = xgwa.width;
107   st->sizey = xgwa.height;
108   cmap = xgwa.colormap;
109   bg = get_pixel_resource (st->dpy, cmap, "background", "Background");
110
111   /* read parameters, keep em sane */
112   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
113   if (st->delay < 1) st->delay = 1;
114   st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
115   if (st->duration < 1) st->duration = 1;
116   st->radius = get_integer_resource (st->dpy, "radius", "Integer");
117   if (st->radius < 0) st->radius = 125;
118
119   if (xgwa.width > 2560) st->radius *= 2;  /* Retina displays */
120
121   /* Don't let the spotlight be bigger than the window */
122   while (st->radius > xgwa.width * 0.45)
123     st->radius /= 2;
124   while (st->radius > xgwa.height * 0.45)
125     st->radius /= 2;
126
127   if (st->radius < 4)
128     st->radius = 4;
129
130   /* do the dance */
131   gcv.function = GXcopy;
132   gcv.subwindow_mode = IncludeInferiors;
133   gcflags = GCForeground | GCFunction;
134   gcv.foreground = bg;
135
136 #ifdef NOPE
137   if (use_subwindow_mode_p(xgwa.screen, st->window)) /* see grabscreen.c */
138     gcflags |= GCSubwindowMode;
139 #endif
140   st->window_gc = XCreateGC(st->dpy, st->window, gcflags, &gcv);
141
142   st->pm = XCreatePixmap(st->dpy, st->window, st->sizex, st->sizey, xgwa.depth);
143   XClearWindow(st->dpy, st->window);
144
145   st->first_time = 1;
146
147   /* create buffer to reduce flicker */
148 #ifdef HAVE_JWXYZ       /* Don't second-guess Quartz's double-buffering */
149   st->buffer = 0;
150 #else
151   st->buffer = XCreatePixmap(st->dpy, st->window, st->sizex, st->sizey, xgwa.depth);
152 #endif
153
154   st->buffer_gc = XCreateGC(st->dpy, (st->buffer ? st->buffer : window), gcflags, &gcv);
155   if (st->buffer)
156     XFillRectangle(st->dpy, st->buffer, st->buffer_gc, 0, 0, st->sizex, st->sizey);
157
158   /* create clip mask (so it's a circle, not a square) */
159   clip_pm = XCreatePixmap(st->dpy, st->window, st->radius*4, st->radius*4, 1);
160   st->img_loader = load_image_async_simple (0, xgwa.screen, st->window, st->pm,
161                                             0, &st->geom);
162   st->start_time = time ((time_t *) 0);
163
164   gcv.foreground = 0L;
165   clip_gc = XCreateGC(st->dpy, clip_pm, gcflags, &gcv);
166   XFillRectangle(st->dpy, clip_pm, clip_gc, 0, 0, st->radius*4, st->radius*4);
167
168   XSetForeground(st->dpy, clip_gc, 1L);
169   XFillArc(st->dpy, clip_pm, clip_gc, st->radius , st->radius,
170            st->radius*2, st->radius*2, 0, 360*64);
171
172   /* set buffer's clip mask to the one we just made */
173   XSetClipMask(st->dpy, st->buffer_gc, clip_pm);
174
175   /* free everything */
176   XFreeGC(st->dpy, clip_gc);
177   XFreePixmap(st->dpy, clip_pm);
178
179   /* avoid remants */
180   st->max_x_speed = st->max_y_speed = st->radius;
181   
182   st->off = random();
183
184   /* blank out screen */
185   XFillRectangle(st->dpy, st->window, st->window_gc, 0, 0, st->sizex, st->sizey);
186
187   return st;
188 }
189
190
191 /*
192  * perform one iteration
193  */
194 static void
195 onestep (struct state *st, Bool first_p)
196 {
197   long now;
198   unsigned long now_unsigned;
199
200   if (st->img_loader)   /* still loading */
201     {
202       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 
203                                                 &st->geom);
204       if (! st->img_loader) {  /* just finished */
205         st->start_time = time ((time_t *) 0);
206       }
207       return;
208     }
209
210   if (!st->img_loader &&
211       st->start_time + st->duration < time ((time_t *) 0)) {
212     st->img_loader = load_image_async_simple (0, st->screen, st->window,
213                                               st->pm, 0, &st->geom);
214     return;
215   }
216
217 #define nrnd(x) (random() % (x))
218
219   st->oldx = st->x;
220   st->oldy = st->y;
221
222   st->s = st->radius *4 ;   /* s = width of buffer */
223
224   now_unsigned = (unsigned long) currentTimeInMs(st) + st->off;
225   now = (now_unsigned <= LONG_MAX) ? now_unsigned : -1 - (long)(ULONG_MAX - now_unsigned);
226
227   /* find new x,y */
228   st->x = st->geom.x +
229     ((1 + sin(((double)now) / X_PERIOD * 2. * M_PI))/2.0) 
230     * (st->geom.width - st->s/2) -st->s/4;
231   st->y = st->geom.y +
232     ((1 + sin(((double)now) / Y_PERIOD * 2. * M_PI))/2.0) 
233     * (st->geom.height - st->s/2) -st->s/4;
234     
235   if (!st->first_p)
236     {
237       /* limit change in x and y to buffer width */
238       if ( st->x < (st->oldx - st->max_x_speed) ) st->x = st->oldx - st->max_x_speed;
239       if ( st->x > (st->oldx + st->max_x_speed) ) st->x = st->oldx + st->max_x_speed;
240       if ( st->y < (st->oldy - st->max_y_speed) ) st->y = st->oldy - st->max_y_speed;
241       if ( st->y > (st->oldy + st->max_y_speed) ) st->y = st->oldy + st->max_y_speed;
242     }
243
244   if (! st->buffer)
245     {
246       XClearWindow (st->dpy, st->window);
247       XSetClipOrigin(st->dpy, st->buffer_gc, st->x,st->y);
248       XCopyArea(st->dpy, st->pm, st->window, st->buffer_gc, st->x, st->y, st->s, st->s, st->x, st->y);
249     }
250   else
251     {
252       /* clear buffer */
253       XFillRectangle(st->dpy, st->buffer, st->buffer_gc, st->x, st->y, st->s, st->s);
254
255       /* copy area of screen image (pm) to buffer
256          Clip to a circle */
257       XSetClipOrigin(st->dpy, st->buffer_gc, st->x,st->y);
258       XCopyArea(st->dpy, st->pm, st->buffer, st->buffer_gc, st->x, st->y, st->s, st->s, st->x, st->y);
259
260       if (st->first_time) {
261         /* blank out screen */
262         XFillRectangle(st->dpy, st->window, st->window_gc, 0, 0, st->sizex, st->sizey);
263         st->first_time = 0;
264       }
265
266       /* copy buffer to screen (window) */
267       XCopyArea(st->dpy, st->buffer, st->window, st->window_gc, st->x , st->y, st->s, st->s, st->x, st->y);
268
269 # if 0
270       XSetForeground (st->dpy, st->window_gc,
271                       WhitePixel (st->dpy, DefaultScreen (st->dpy)));
272       XDrawRectangle(st->dpy, st->window, st->window_gc,
273                      st->geom.x, st->geom.y, st->geom.width, st->geom.height);
274 # endif
275     }
276
277 #ifdef DEBUG
278   /* draw a box around the buffer */
279   XDrawRectangle(st->dpy, st->window, st->white_gc, st->x , st->y, st->s, st->s);
280 #endif
281
282 }
283
284
285 static unsigned long
286 spotlight_draw (Display *dpy, Window window, void *closure)
287 {
288   struct state *st = (struct state *) closure;
289   onestep(st, st->first_p);
290   st->first_p = False;
291   return st->delay;
292 }
293   
294 static void
295 spotlight_reshape (Display *dpy, Window window, void *closure, 
296                  unsigned int w, unsigned int h)
297 {
298 }
299
300 static Bool
301 spotlight_event (Display *dpy, Window window, void *closure, XEvent *event)
302 {
303   struct state *st = (struct state *) closure;
304   if (screenhack_event_helper (dpy, window, event))
305     {
306       st->start_time = 0;
307       return True;
308     }
309   return False;
310 }
311
312 static void
313 spotlight_free (Display *dpy, Window window, void *closure)
314 {
315   struct state *st = (struct state *) closure;
316   XFreeGC (dpy, st->window_gc);
317   XFreeGC (dpy, st->buffer_gc);
318   if (st->pm) XFreePixmap (dpy, st->pm);
319   if (st->buffer) XFreePixmap (dpy, st->buffer);
320   free (st);
321 }
322
323
324 \f
325
326 static const char *spotlight_defaults [] = {
327   ".background:                 black",
328   ".foreground:                 white",
329   "*dontClearRoot:              True",
330   "*fpsSolid:                   true",
331
332 #ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
333   "*visualID:                   Best",
334 #endif
335
336   "*delay:                      10000",
337   "*duration:                   120",
338   "*radius:                     125",
339 #ifdef HAVE_MOBILE
340   "*ignoreRotation:             True",
341   "*rotateImages:               True",
342 #endif
343   0
344 };
345
346 static XrmOptionDescRec spotlight_options [] = {
347   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
348   { "-duration",        ".duration",            XrmoptionSepArg, 0 },
349   { "-radius",          ".radius",              XrmoptionSepArg, 0 },
350   { 0, 0, 0, 0 }
351 };
352
353 XSCREENSAVER_MODULE ("Spotlight", spotlight)