1 /* -copyright-
2 #-#
3 #-# xsnow: let it snow on your desktop
4 #-# Copyright (C) 1984,1988,1990,1993-1995,2000-2001 Rick Jansen
5 #-# 	      2019,2020,2021 Willem Vermin
6 #-#
7 #-# This program is free software: you can redistribute it and/or modify
8 #-# it under the terms of the GNU General Public License as published by
9 #-# the Free Software Foundation, either version 3 of the License, or
10 #-# (at your option) any later version.
11 #-#
12 #-# This program is distributed in the hope that it will be useful,
13 #-# but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #-# GNU General Public License for more details.
16 #-#
17 #-# You should have received a copy of the GNU General Public License
18 #-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #-#
20 */
21 
22 #include <stdio.h>
23 #include <gtk/gtk.h>
24 #include <stdlib.h>
25 #include <math.h>
26 #include <X11/Intrinsic.h>
27 #include <X11/xpm.h>
28 #include "Santa.h"
29 #include "pixmaps.h"
30 #include "debug.h"
31 #include "windows.h"
32 #include "flags.h"
33 #include "utils.h"
34 #include "wind.h"
35 #include "ixpm.h"
36 #include "moon.h"
37 
38 #define NOTACTIVE \
39    (Flags.BirdsOnly || !WorkspaceActive())
40 static int    do_usanta(void *);
41 //static void   InitSantaPixmaps(void);
42 static void   init_Santa_surfaces(void);
43 static Region RegionCreateRectangle(int x, int y, int w, int h);
44 static void   ResetSanta(void);
45 static void   SetSantaSizeSpeed(void);
46 static void   setSantaRegions(void);
47 
48 static int          CurrentSanta;
49 static Pixmap       SantaMaskPixmap[PIXINANIMATION];
50 static Pixmap       SantaPixmap[PIXINANIMATION];
51 static Region       SantaRegion = 0;
52 static float        SantaSpeed;
53 static float        SantaXr;
54 static float        SantaYr;
55 static int          SantaYStep;
56 static int          OldSantaX  = 0;  // the x value of Santa when he was last drawn
57 static int          OldSantaY  = 0;  // the y value of Santa when he was last drawn
58 static const float  LocalScale = 0.6;
59 static int          MoonSeeking = 1;
60 
61 static cairo_surface_t *Santa_surfaces[MAXSANTA+1][2][PIXINANIMATION];
62 
63 /* Speed for each Santa  in pixels/second*/
64 static float Speed[] = {SANTASPEED0,  /* Santa 0 */
65    SANTASPEED1,  /* Santa 1 */
66    SANTASPEED2,  /* Santa 2 */
67    SANTASPEED3,  /* Santa 3 */
68    SANTASPEED4,  /* Santa 4 */
69 };
70 
Santa_ui()71 void Santa_ui()
72 {
73    UIDO(SantaSize,        SetSantaSizeSpeed(););
74    UIDO(Rudolf,           SetSantaSizeSpeed(););
75    UIDO(NoSanta,                              );
76    UIDO(SantaSpeedFactor, SetSantaSizeSpeed(););
77 
78    static int prev = 100;
79    if(ScaleChanged(&prev))
80    {
81       P("%d Santa_scale \n",global.counter);
82       SetSantaSizeSpeed();
83    }
84 }
85 
Santa_draw(cairo_t * cr)86 int Santa_draw(cairo_t *cr)
87 {
88    if (Flags.NoSanta)
89       return TRUE;
90    P("Santa_draw %d\n",global.counter++);
91    cairo_surface_t *surface;
92    surface = Santa_surfaces[Flags.SantaSize][Flags.Rudolf][CurrentSanta];
93    cairo_set_source_surface (cr, surface, global.SantaX, global.SantaY);
94    my_cairo_paint_with_alpha(cr,ALPHA);
95    OldSantaX = global.SantaX;
96    OldSantaY = global.SantaY;
97    return TRUE;
98 }
99 
Santa_erase(cairo_t * cr)100 void Santa_erase(cairo_t *cr)
101 {
102    P("Santa_erase %d %d\n",OldSantaX,OldSantaY);
103    (void)cr;
104    myXClearArea(global.display, global.SnowWin,
105 	 OldSantaX, OldSantaY,
106 	 global.SantaWidth+1,global.SantaHeight,
107 	 global.xxposures);
108 }
109 
Santa_init()110 void Santa_init()
111 {
112    P("Santa_init\n");
113    int i;
114    for (i=0; i<PIXINANIMATION; i++)
115    {
116       SantaPixmap[i]     = 0;
117       SantaMaskPixmap[i] = 0;
118    }
119    int j,k;
120    for (i=0; i<MAXSANTA+1; i++)
121       for(j=0; j<2; j++)
122 	 for (k=0; k<PIXINANIMATION; k++)
123 	    Santa_surfaces[i][j][k] = NULL;
124 
125    SantaRegion            = XCreateRegion();
126    global.SantaPlowRegion = XCreateRegion();
127    SetSantaSizeSpeed();
128    ResetSanta();
129    add_to_mainloop(PRIORITY_HIGH, time_usanta, do_usanta);
130 }
131 
init_Santa_surfaces()132 void init_Santa_surfaces()
133 {
134    GdkPixbuf *pixbuf;
135    int i,j,k;
136    for(i=0; i<MAXSANTA+1; i++)
137       for (j=0; j<2; j++)
138 	 for (k=0; k<PIXINANIMATION; k++)
139 	 {
140 	    int w,h;
141 	    sscanf(Santas[i][j][k][0],"%d %d",&w,&h);
142 	    w *= 0.01*Flags.Scale*LocalScale*global.WindowScale;
143 	    h *= 0.01*Flags.Scale*LocalScale*global.WindowScale;
144 	    P("%d init_Santa_surfaces %d %d %d %d %d\n",global.counter++,i,j,k,w,h);
145 	    pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)Santas[i][j][k]);
146 	    if(w < 1) w = 1;
147 	    if(h < 1) h = 1;
148 	    if (w == 1 && h == 1) h = 2;
149 	    GdkPixbuf *pixbufscaled  = gdk_pixbuf_scale_simple(pixbuf,w,h,GDK_INTERP_HYPER);
150 	    if(Santa_surfaces[i][j][k])
151 	       cairo_surface_destroy(Santa_surfaces[i][j][k]);
152 
153 	    Santa_surfaces[i][j][k] = gdk_cairo_surface_create_from_pixbuf (pixbufscaled, 0, NULL);
154 	    g_clear_object(&pixbuf);
155 	    g_clear_object(&pixbufscaled);
156 	 }
157    int ok = 1;
158    char *path[PIXINANIMATION];
159    const char *filenames[] =
160    {
161       "xsnow/pixmaps/santa1.xpm",
162       "xsnow/pixmaps/santa2.xpm",
163       "xsnow/pixmaps/santa3.xpm",
164       "xsnow/pixmaps/santa4.xpm",
165    };
166    for (i=0; i<PIXINANIMATION; i++)
167    {
168       path[i] = NULL;
169       FILE *f = HomeOpen(filenames[i],"r",&path[i]);
170       if(!f){ ok = 0; if (path[i]) free(path[i]); break; }
171       fclose(f);
172    }
173    if (ok)
174    {
175       printf("Using external Santa: %s.\n",path[0]);
176       if (!Flags.NoMenu)
177 	 printf("Disabling menu.\n");
178       Flags.NoMenu = 1;
179       int rc,i;
180       char **santaxpm;
181       for (i=0; i<PIXINANIMATION; i++)
182       {
183 	 rc = XpmReadFileToData(path[i],&santaxpm);
184 	 if(rc == XpmSuccess)
185 	 {
186 	    int w,h;
187 	    sscanf(santaxpm[0],"%d %d",&w,&h);
188 	    w *= 0.01*Flags.Scale*LocalScale*global.WindowScale;
189 	    h *= 0.01*Flags.Scale*LocalScale*global.WindowScale;
190 	    if(w < 1) w = 1;
191 	    if(h < 1) h = 1;
192 	    if (w == 1 && h == 1) h = 2;
193 	    pixbuf = gdk_pixbuf_new_from_xpm_data((const char **)santaxpm);
194 	    GdkPixbuf *pixbufscaled  = gdk_pixbuf_scale_simple(pixbuf,w,h,GDK_INTERP_HYPER);
195 	    cairo_surface_destroy( Santa_surfaces[0][0][i]);
196 	    Santa_surfaces[0][0][i] = gdk_cairo_surface_create_from_pixbuf(pixbufscaled,0,NULL);
197 	    XpmFree(santaxpm);
198 	    g_clear_object(&pixbuf);
199 	    g_clear_object(&pixbufscaled);
200 	 }
201 	 else
202 	 {
203 	    printf("Invalid external xpm for Santa given: %s\n",path[i]);
204 	    exit(1);
205 	 }
206 	 free(path[i]);
207       }
208       Flags.SantaSize = 0;
209       Flags.Rudolf    = 0;
210    }
211    setSantaRegions();
212 }
213 
SetSantaSizeSpeed()214 void SetSantaSizeSpeed()
215 {
216    SantaSpeed = Speed[Flags.SantaSize];
217    if (Flags.SantaSpeedFactor < 10)
218       SantaSpeed = 0.1*SantaSpeed;
219    else
220       SantaSpeed = 0.01*Flags.SantaSpeedFactor*SantaSpeed;
221    global.ActualSantaSpeed               = SantaSpeed;
222    init_Santa_surfaces();
223    cairo_surface_t *surface = Santa_surfaces[Flags.SantaSize][Flags.Rudolf][CurrentSanta];
224    global.SantaWidth        = cairo_image_surface_get_width(surface);
225    global.SantaHeight       = cairo_image_surface_get_height(surface);
226    setSantaRegions();
227 }
228 
229 
230 // update santa's coordinates and speed
do_usanta(void * d)231 int do_usanta(void *d)
232 {
233    (void) d;
234    P("do_usanta %d\n",counter++);
235    if (Flags.Done)
236       return FALSE;
237 #define RETURN do { return TRUE ; } while(0)
238    if (NOTACTIVE)
239       RETURN;
240    if(Flags.NoSanta)
241       RETURN;
242    double         yspeed;
243    static int yspeeddir  = 0;
244    static double sdt     = 0;
245    static double dtt     = 0;
246 
247    int oldx = global.SantaX;
248    int oldy = global.SantaY;
249 
250    double dt = time_usanta;
251 
252    double santayrmin = 0;
253    double santayrmax = global.SnowWinHeight*0.33;
254 
255    global.ActualSantaSpeed += dt*(SANTASENS*global.NewWind+SantaSpeed - global.ActualSantaSpeed);
256    if (global.ActualSantaSpeed>3*SantaSpeed)
257       global.ActualSantaSpeed = 3*SantaSpeed;
258    else if (global.ActualSantaSpeed < -2*SantaSpeed)
259       global.ActualSantaSpeed = -2*SantaSpeed;
260 
261    SantaXr += dt*global.ActualSantaSpeed;
262    P("SantaXr: %ld global.SnowWinWidth: %d\n",lrint(SantaXr),global.SnowWinWidth);
263    if (SantaXr >= global.SnowWinWidth)
264    {
265       ResetSanta();
266       oldx = global.SantaX;
267       oldy = global.SantaY;
268    }
269    if (SantaXr < -global.SantaWidth-global.ActualSantaSpeed)
270       SantaXr = -global.SantaWidth - global.ActualSantaSpeed;
271    global.SantaX = lrintf(SantaXr);
272    dtt += dt;
273    if (dtt > 0.1 && fabs(global.ActualSantaSpeed) > 3)
274    {
275       dtt = 0;
276       CurrentSanta++;
277       if (CurrentSanta >= PIXINANIMATION) CurrentSanta = 0;
278    }
279 
280    yspeed = global.ActualSantaSpeed/4;
281    sdt += dt;
282    if (sdt > 2.0*50.0/SantaSpeed || sdt > 2.0)
283    {
284       // time to change yspeed
285       sdt = 0;
286       yspeeddir = randint(3)-1;  //  -1, 0, 1
287       if (SantaYr < santayrmin + 20)
288 	 yspeeddir = 2;
289 
290       if (SantaYr > santayrmax - 20)
291 	 yspeeddir = -2;
292       int mooncy = global.moonY+Flags.MoonSize/2;
293       if (MoonSeeking &&
294 	    Flags.Moon &&
295 	    global.SantaX+global.SantaWidth < global.moonX+Flags.MoonSize &&
296 	    global.SantaX+global.SantaWidth > global.moonX-300) // Santa likes to hover the moon
297       {
298 	 int dy = global.SantaY+global.SantaHeight/2 - mooncy;
299 	 if (dy < 0)
300 	    yspeeddir = 1;
301 	 else
302 	    yspeeddir = -1;
303 	 if (dy < -global.moonR/2)  // todo
304 	    yspeeddir = 3;
305 	 else if (dy > Flags.MoonSize/2)
306 	    yspeeddir = -3;
307 	 P("moon seeking %f %f %d %f\n",SantaYr, moonY, yspeeddir,SantaSpeed);
308       }
309    }
310 
311    SantaYr += dt*yspeed*yspeeddir;
312    if (SantaYr < santayrmin)
313       SantaYr = 0;
314 
315    if (SantaYr > santayrmax)
316       SantaYr = santayrmax;
317 
318    global.SantaY = lrintf(SantaYr);
319    XOffsetRegion(SantaRegion,            global.SantaX - oldx, global.SantaY - oldy);
320    XOffsetRegion(global.SantaPlowRegion, global.SantaX - oldx, global.SantaY - oldy);
321 
322    RETURN;
323 }
324 
ResetSanta()325 void ResetSanta()
326 {
327    global.SantaX  = -global.SantaWidth - global.ActualSantaSpeed;
328    SantaXr = global.SantaX;
329    global.SantaY  = randint(global.SnowWinHeight / 3)+40;
330    // sometimes Santa is moon seeking, sometimes not
331    MoonSeeking = drand48() > 0.5;
332    P("MoonSeeking: %d\n",MoonSeeking);
333    if (MoonSeeking && Flags.Moon && global.moonX < 400)
334    {
335       P("moon seeking at start\n");
336       global.SantaY = randint(Flags.MoonSize + 40)+global.moonY-20;
337    }
338    else
339       global.SantaY  = randint(global.SnowWinHeight / 3)+40;
340    SantaYr = global.SantaY;
341    SantaYStep = 1;
342    CurrentSanta = 0;
343    SetSantaSizeSpeed();
344 }
345 
setSantaRegions()346 void setSantaRegions()
347 {
348    P("setSantaRegions %d %d %d %d\n",global.SantaX,global.SantaY,global.SantaWidth,global.SantaHeight);
349    if(SantaRegion)
350       XDestroyRegion(SantaRegion);
351    SantaRegion = RegionCreateRectangle(
352 	 global.SantaX,global.SantaY,global.SantaWidth,global.SantaHeight);
353 
354    if(global.SantaPlowRegion)
355       XDestroyRegion(global.SantaPlowRegion);
356    global.SantaPlowRegion = RegionCreateRectangle(
357 	 global.SantaX + global.SantaWidth, global.SantaY, 1, global.SantaHeight);
358 }
359 
RegionCreateRectangle(int x,int y,int w,int h)360 Region RegionCreateRectangle(int x, int y, int w, int h)
361 {
362    XPoint p[5];
363    p[0].x =          x;   p[0].y = y;
364    p[1].x =          x+w; p[1].y = y;
365    p[2].x =          x+w; p[2].y = y+h;
366    p[3].x =          x;   p[3].y = y+h;
367    p[4].x =          x;   p[4].y = y;
368    return XPolygonRegion(p, 5, EvenOddRule);
369 }
370