From http://www.jwz.org/xscreensaver/xscreensaver-5.22.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 "screenhack.h"
26
27 #define MINX 0.0
28 #define MINY 0.0
29 #define X_PERIOD 15000.0
30 #define Y_PERIOD 12000.0
31
32 struct state {
33   Display *dpy;
34   Window window;
35   Screen *screen;
36
37   int sizex, sizey; /* screen size */
38   int delay;
39   int duration;
40   time_t start_time;
41   int first_time;
42   GC window_gc;
43 #ifdef DEBUG
44   GC white_gc;
45 #endif
46   GC buffer_gc;     /* draw in buffer, then flush to screen
47                        to avoid flicker */
48   int radius;       /* radius of spotlight in pixels */
49
50   Pixmap pm;        /* pixmap grabbed from screen */
51   Pixmap buffer;    /* pixmap for the buffer */
52
53   int x, y, s;      /* x & y coords of buffer (upper left corner) */
54   /* s is the width of the buffer */
55
56   int off;      /* random offset from currentTimeInMs(), so that
57                    two concurrent copies of spotlight have different
58                    behavior. */
59
60   int oldx, oldy, max_x_speed, max_y_speed;
61   /* used to keep the new buffer position
62      over the old spotlight image to make sure 
63      the old image is completely erased */
64
65   Bool first_p;
66   async_load_state *img_loader;
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 #ifdef GETTIMEOFDAY_TWO_ARGS
77   struct timezone tz = {0,0};
78   gettimeofday(&curTime, &tz);
79 #else
80   gettimeofday(&curTime);
81 #endif
82   return curTime.tv_sec*1000 + curTime.tv_usec/1000.0;
83 }
84
85
86 static void *
87 spotlight_init (Display *dpy, Window window)
88 {
89   struct state *st = (struct state *) calloc (1, sizeof(*st));
90   XGCValues gcv;
91   XWindowAttributes xgwa;
92   long gcflags;
93   Colormap cmap;
94   unsigned long bg;
95   GC clip_gc;
96   Pixmap clip_pm;
97
98   st->dpy = dpy;
99   st->window = window;
100   st->first_p = True;
101
102   XGetWindowAttributes (st->dpy, st->window, &xgwa);
103   st->screen = xgwa.screen;
104   st->sizex = xgwa.width;
105   st->sizey = xgwa.height;
106   cmap = xgwa.colormap;
107   bg = get_pixel_resource (st->dpy, cmap, "background", "Background");
108
109   /* read parameters, keep em sane */
110   st->delay = get_integer_resource (st->dpy, "delay", "Integer");
111   if (st->delay < 1) st->delay = 1;
112   st->duration = get_integer_resource (st->dpy, "duration", "Seconds");
113   if (st->duration < 1) st->duration = 1;
114   st->radius = get_integer_resource (st->dpy, "radius", "Integer");
115   if (st->radius < 0) st->radius = 125;
116
117   /* Don't let the spotlight be bigger than the window */
118   while (st->radius > xgwa.width * 0.45)
119     st->radius /= 2;
120   while (st->radius > xgwa.height * 0.45)
121     st->radius /= 2;
122
123   if (st->radius < 4)
124     st->radius = 4;
125
126   /* do the dance */
127   gcv.function = GXcopy;
128   gcv.subwindow_mode = IncludeInferiors;
129   gcflags = GCForeground | GCFunction;
130   gcv.foreground = bg;
131
132 #ifdef NOPE
133   if (use_subwindow_mode_p(xgwa.screen, st->window)) /* see grabscreen.c */
134     gcflags |= GCSubwindowMode;
135 #endif
136   st->window_gc = XCreateGC(st->dpy, st->window, gcflags, &gcv);
137
138   st->pm = XCreatePixmap(st->dpy, st->window, st->sizex, st->sizey, xgwa.depth);
139   XClearWindow(st->dpy, st->window);
140
141   st->first_time = 1;
142
143   /* create buffer to reduce flicker */
144 #ifdef HAVE_COCOA       /* Don't second-guess Quartz's double-buffering */
145   st->buffer = 0;
146 #else
147   st->buffer = XCreatePixmap(st->dpy, st->window, st->sizex, st->sizey, xgwa.depth);
148 #endif
149
150   st->buffer_gc = XCreateGC(st->dpy, (st->buffer ? st->buffer : window), gcflags, &gcv);
151   if (st->buffer)
152     XFillRectangle(st->dpy, st->buffer, st->buffer_gc, 0, 0, st->sizex, st->sizey);
153
154   /* create clip mask (so it's a circle, not a square) */
155   clip_pm = XCreatePixmap(st->dpy, st->window, st->radius*4, st->radius*4, 1);
156   st->img_loader = load_image_async_simple (0, xgwa.screen, st->window, st->pm,
157                                             0, 0);
158   st->start_time = time ((time_t) 0);
159
160   gcv.foreground = 0L;
161   clip_gc = XCreateGC(st->dpy, clip_pm, gcflags, &gcv);
162   XFillRectangle(st->dpy, clip_pm, clip_gc, 0, 0, st->radius*4, st->radius*4);
163
164   XSetForeground(st->dpy, clip_gc, 1L);
165   XFillArc(st->dpy, clip_pm, clip_gc, st->radius , st->radius,
166            st->radius*2, st->radius*2, 0, 360*64);
167
168   /* set buffer's clip mask to the one we just made */
169   XSetClipMask(st->dpy, st->buffer_gc, clip_pm);
170
171   /* free everything */
172   XFreeGC(st->dpy, clip_gc);
173   XFreePixmap(st->dpy, clip_pm);
174
175   /* avoid remants */
176   st->max_x_speed = st->max_y_speed = st->radius;
177   
178   st->off = random();
179
180 #ifdef DEBUG
181   /* create GC with white fg */
182   gcv.foreground = fg;
183   st->white_gc = XCreateGC(st->dpy, st->window, gcflags, &gcv);
184 #endif
185
186   /* blank out screen */
187   XFillRectangle(st->dpy, st->window, st->window_gc, 0, 0, st->sizex, st->sizey);
188
189   return st;
190 }
191
192
193 /*
194  * perform one iteration
195  */
196 static void
197 onestep (struct state *st, Bool first_p)
198 {
199   long now;
200
201   if (st->img_loader)   /* still loading */
202     {
203       st->img_loader = load_image_async_simple (st->img_loader, 0, 0, 0, 0, 0);
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, 0);
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 = currentTimeInMs(st) + st->off;
225
226   /* find new x,y */
227   st->x = ((1 + sin(((double)now) / X_PERIOD * 2. * M_PI))/2.0) 
228     * (st->sizex - st->s/2) -st->s/4  + MINX;
229   st->y = ((1 + sin(((double)now) / Y_PERIOD * 2. * M_PI))/2.0) 
230     * (st->sizey - st->s/2) -st->s/4  + MINY;
231     
232   if (!st->first_p)
233     {
234       /* limit change in x and y to buffer width */
235       if ( st->x < (st->oldx - st->max_x_speed) ) st->x = st->oldx - st->max_x_speed;
236       if ( st->x > (st->oldx + st->max_x_speed) ) st->x = st->oldx + st->max_x_speed;
237       if ( st->y < (st->oldy - st->max_y_speed) ) st->y = st->oldy - st->max_y_speed;
238       if ( st->y > (st->oldy + st->max_y_speed) ) st->y = st->oldy + st->max_y_speed;
239     }
240
241   if (! st->buffer)
242     {
243       XClearWindow (st->dpy, st->window);
244       XSetClipOrigin(st->dpy, st->buffer_gc, st->x,st->y);
245       XCopyArea(st->dpy, st->pm, st->window, st->buffer_gc, st->x, st->y, st->s, st->s, st->x, st->y);
246     }
247   else
248     {
249       /* clear buffer */
250       XFillRectangle(st->dpy, st->buffer, st->buffer_gc, st->x, st->y, st->s, st->s);
251
252       /* copy area of screen image (pm) to buffer
253          Clip to a circle */
254       XSetClipOrigin(st->dpy, st->buffer_gc, st->x,st->y);
255       XCopyArea(st->dpy, st->pm, st->buffer, st->buffer_gc, st->x, st->y, st->s, st->s, st->x, st->y);
256
257       if (st->first_time) {
258         /* blank out screen */
259         XFillRectangle(st->dpy, st->window, st->window_gc, 0, 0, st->sizex, st->sizey);
260         st->first_time = 0;
261       }
262
263       /* copy buffer to screen (window) */
264       XCopyArea(st->dpy, st->buffer, st->window, st->window_gc, st->x , st->y, st->s, st->s, st->x, st->y);
265     }
266
267 #ifdef DEBUG
268   /* draw a box around the buffer */
269   XDrawRectangle(st->dpy, st->window, st->white_gc, st->x , st->y, st->s, st->s);
270 #endif
271
272 }
273
274
275 static unsigned long
276 spotlight_draw (Display *dpy, Window window, void *closure)
277 {
278   struct state *st = (struct state *) closure;
279   onestep(st, st->first_p);
280   st->first_p = False;
281   return st->delay;
282 }
283   
284 static void
285 spotlight_reshape (Display *dpy, Window window, void *closure, 
286                  unsigned int w, unsigned int h)
287 {
288 }
289
290 static Bool
291 spotlight_event (Display *dpy, Window window, void *closure, XEvent *event)
292 {
293   return False;
294 }
295
296 static void
297 spotlight_free (Display *dpy, Window window, void *closure)
298 {
299   struct state *st = (struct state *) closure;
300   XFreeGC (dpy, st->window_gc);
301   XFreeGC (dpy, st->buffer_gc);
302   if (st->pm) XFreePixmap (dpy, st->pm);
303   if (st->buffer) XFreePixmap (dpy, st->buffer);
304   free (st);
305 }
306
307
308 \f
309
310 static const char *spotlight_defaults [] = {
311   ".background:                 black",
312   ".foreground:                 white",
313   "*dontClearRoot:              True",
314   "*fpsSolid:                   true",
315
316 #ifdef __sgi    /* really, HAVE_READ_DISPLAY_EXTENSION */
317   "*visualID:                   Best",
318 #endif
319
320   "*delay:                      10000",
321   "*duration:                   120",
322   "*radius:                     125",
323 #ifdef USE_IPHONE
324   "*ignoreRotation:             True",
325 #endif
326   0
327 };
328
329 static XrmOptionDescRec spotlight_options [] = {
330   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
331   { "-duration",        ".duration",            XrmoptionSepArg, 0 },
332   { "-radius",          ".radius",              XrmoptionSepArg, 0 },
333   { 0, 0, 0, 0 }
334 };
335
336 XSCREENSAVER_MODULE ("Spotlight", spotlight)