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