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