+/* -copyright-
+#-# Copyright © 2021 Eric Bina, Dave Black, TJ Phan,
+#-# Vincent Renardias, Willem Vermin
+#-#
+#-# Permission is hereby granted, free of charge, to any person
+#-# obtaining a copy of this software and associated documentation
+#-# files (the “Software”), to deal in the Software without
+#-# restriction, including without limitation the rights to use,
+#-# copy, modify, merge, publish, distribute, sublicense, and/or
+#-# sell copies of the Software, and to permit persons to whom
+#-# the Software is furnished to do so, subject to the following
+#-# conditions:
+#-#
+#-# The above copyright notice and this permission notice shall
+#-# be included in all copies or substantial portions of the Software.
+#-#
+#-# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+#-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+#-# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+#-# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+#-# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+#-# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+#-# OTHER DEALINGS IN THE SOFTWARE.
+#-#
+*/
+
+/*
+
+ * Original Author Unknow.
+
+ * 8/10/88 - Ported from X10 to X11R3 by:
+
+ Jonathan Greenblatt (jonnyg@rover.umd.edu)
+
+ * Cleaned up by Dave Lemke (lemke@sun.com)
+
+ * Ported to monocrome by Jonathan Greenblatt (jonnyg@rover.umd.edu)
+
+ * 05/02/1996 Added TrueColor support by TJ Phan (phan@aur.alcatel.com)
+
+*/
+
+#include <sys/types.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <signal.h>
+#include <gtk/gtk.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/xpm.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "xfishtank.h"
+#include "vroot.h"
+#include "bubbles.h"
+#include "debug.h"
+#include "transwindow.h"
+#include "utils.h"
+#include "fishes.h"
+#include "ixpm.h"
+#include "ui.h"
+
+
+#ifdef DOUBLE_BUFFER
+#include <X11/extensions/Xdbe.h>
+static int UseXdbe;
+static XdbeSwapAction BMETHOD;
+static int XdbeAvailable = -42;
+#endif
+
+extern unsigned char *ReadBitmap();
+
+/* externals for pixmap and bimaps from xfishy.h */
+
+
+/* typedefs for bubble and fish structures, also caddr_t (not used in X.h) */
+typedef struct _bubble {
+ float x;
+ float y;
+ int size; // size of bubble
+ float bstep; // increment in placement
+} bubble;
+
+typedef struct _fish {
+ float x;
+ float y;
+ int direction; // direction: 0: r->l
+ int frame; // animation frames: 0 .. NUM_FRAMES
+ int type;
+ float fstep; // increment in placement
+ int animtime; // # drawings between change of frame
+} fish;
+
+typedef struct _fishtype {
+ Pixmap pix;
+ GC gc;
+ int w;
+ int h;
+} fishtype;
+
+
+static int binc[] = { 0, 64, 56, 48, 40, 32, 24, 16, 8 }; /* bubble increment and yes check tables */
+static int DoubleBuf = 0; /* Should we use double buffering */
+static int width; /* width of initial window in pixels */
+static int height; /* height of initial window in pixels */
+static int screen; /* Default screen of this display */
+static int inxscreensaver = 0; /* are we running in xscreensaver? */
+static float smooth = 0.02; /* smoothness increment multiplier */
+static bubble *binfo = NULL; /* bubble info structures, allocated dynamically */
+static fish *finfo = NULL; /* fish info structures, allocated dynamically */
+static Window root_window;
+static fishtype xfish[NUM_FISH][2][NUM_FRAMES]; /* [type][left/right][frame] */
+
+static Pixmap xbubbles[9]; /* bubbles bitmaps (1 to 8, by size in pixels) */
+static Window wid; /* aquarium window */
+static Window wid1;
+static Window UserWindow = 0;
+static Pixel white;
+static Pixel black;
+static Pixel bcolor;
+static Pixel bgcolor;
+//static GC pgc;
+//static GC gc;
+static GC bgc;
+//static GC draw_gc;
+static int xfishtank_trans = 0;
+//static int rwidth[NUM_FISH];
+//static int rheight[NUM_FISH];
+static XdbeBackBuffer backbuf = 0;
+static int WantXdbe = 1;
+static const int maxanimtime = 20;
+static int nomenu = 0;
+static int ForceRoot = 0;
+static int HaltedByInterrupt = 0;
+static int Done = 0;
+
+static Pixel AllocNamedColor(Display *display, char *colorName, Pixel dfltPix);
+static char *display_name=NULL;
+static int do_move(void *dummy);
+static int do_testfish(void *dummy);
+static int do_write_flags(void *dummy);
+static void erasebubble(bubble *b);
+static void erasefish(fish *f);
+static void init_pixmap(void);
+static void init_signals(void);
+static void initialize(void);
+static void move_fish(void);
+static void movefish(fish *f);
+static void new_bubble(bubble *b, int init);
+static void new_fish(fish *f0, int init);
+static void parse(int argc, char **argv);
+static void putbubble(bubble *b, unsigned long c);
+static void remove_all_bubbles(void);
+static void remove_all_fishes(void);
+static void setanimtime(fish*f);
+static void step_bubbles(void);
+static void Usage(void);
+static void Thanks(void);
+static void SigHandler(int signum);
+static void print_changelog(void);
+static void selfrep(void);
+
+
+Display *Dpy = NULL;
+Window xfishtankWin;
+int counter;
+int blimit = 32; /* bubble limit */
+int flimit = 10; /* fish limit */
+char *bcolorstring = NULL;
+char *bgcolorstring = NULL;
+float speedfactor;
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ parse command line
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void parse(int argc, char *argv[])
+{
+
+ int p = 0;
+
+#define CHECK do{if(++p >= argc){ Usage(); Thanks(); exit(1);}}while(0)
+ while (++p < argc)
+ {
+ P("argv[p] %s\n",argv[p]);
+ if (!strcmp(argv[p],"-b"))
+ {
+ CHECK;
+ blimit = strtod(argv[p],NULL);
+ }
+ else if (!strcmp(argv[p],"-f"))
+ {
+ CHECK;
+ flimit = strtod(argv[p],NULL);
+ }
+ else if (!strcmp(argv[p],"-display"))
+ {
+ CHECK;
+ display_name = strdup(argv[p]);
+ }
+ else if (!strcmp(argv[p],"-bc"))
+ {
+ CHECK;
+ if(bcolorstring)
+ free(bcolorstring);
+ bcolorstring = strdup(argv[p]);
+ }
+ else if (!strcmp(argv[p],"-bgc"))
+ {
+ CHECK;
+ if(bgcolorstring)
+ free(bgcolorstring);
+ bgcolorstring = strdup(argv[p]);
+ }
+ else if (!strcmp(argv[p],"-double"))
+ {
+ CHECK;
+ WantXdbe = (strtod(argv[p],NULL)>0);
+ }
+ else if (!strcmp(argv[p],"-speed"))
+ {
+ CHECK;
+ speedfactor = (strtod(argv[p],NULL)/100.0);
+ }
+ else if (!strcmp(argv[p],"-h"))
+ {
+ Usage();
+ Thanks();
+ }
+ else if (!strcmp(argv[p],"-defaults"))
+ {
+ set_defaults();
+ }
+ else if (!strcmp(argv[p],"-nomenu"))
+ {
+ nomenu = 1;
+ }
+ else if (!strcmp(argv[p],"-root"))
+ {
+ ForceRoot = 1;
+ }
+ else if (!strcmp(argv[p],"-window-id"))
+ {
+ CHECK;
+ UserWindow = strtod(argv[p],NULL);
+ }
+ else if (!strcmp(argv[p],"-changelog"))
+ {
+ print_changelog();
+ Thanks();
+ }
+#ifdef SELFREP
+ else if (!strcmp(argv[p],"-selfrep"))
+ {
+ selfrep();
+ exit(0);
+ }
+#endif
+ else
+ {
+ printf("Not understood: %s\n",argv[p]);
+ Usage();
+ Thanks();
+ }
+ }
+}
+
+void set_defaults()
+{
+ flimit = 10;
+ blimit = 32;
+ if(bcolorstring)
+ free(bcolorstring);
+ bcolorstring = strdup("lightblue");
+ if(bgcolorstring)
+ free(bgcolorstring);
+ bgcolorstring = strdup("darkblue");
+ speedfactor = 1.0;
+}
+
+void Usage()
+{
+ printf("\nUsage: xfishtank [options]\n\n");
+ printf("Options:\n");
+ printf(" -b n number of bubbles (default 32)\n");
+ printf(" -f n number of fish (default 10)\n");
+ printf(" -bc color color of bubbles (default \"lightblue\")\n");
+ printf(" -double n 1: double buffering, 0: do not use (default: 1)\n");
+ printf(" -speed speed of fishes (default: 100)\n");
+ printf(" -defaults all options to default\n");
+ printf(" -nomenu do not show menu\n");
+ printf(" -display DISPLAY to draw on (default \"\")\n");
+ printf(" -root use root-window or xscreensaver-provided window to draw in\n");
+ printf(" -window-id window to draw on\n");
+ printf(" -h show this info\n");
+ printf(" -changelog show ChangeLog and exit\n");
+ printf(" -selfrep output gzipped tarfile of the source and exit\n");
+ printf("\n");
+}
+
+int do_write_flags(void *dummy)
+{
+ if (FlagsChanged)
+ {
+ FlagsChanged = 0;
+ WriteFlags();
+ }
+ return TRUE;
+ (void)dummy;
+}
+
+
+void erasefish(fish *f)
+{
+ /*
+ * for something as small as a bubble, it was never worth the
+ * effort of using clipmasks to only turn of the bubble itself, so
+ * we just clear the whole rectangle.
+ */
+ //XClearArea(Dpy, wid, f->x, f->y, rwidth[f->type], rheight[f->type], False);
+ fishtype *ft = &xfish[f->type][f->direction][f->frame];
+ XClearArea(Dpy, wid, f->x, f->y, ft->w, ft->h, False);
+}
+
+
+/*
+ * This function can only be called if DoClipping is True. It is used to
+ * move a clipmasked fish. First the area under the fish is cleared,
+ * and then the new fish is masked in.
+ * The parameters x, y, amd d are from the old fish that is being
+ * erased before the new fish is drawn.
+ */
+void movefish(fish *f)
+{
+ fishtype *ft = &xfish[f->type][f->direction][f->frame];
+ XSetClipOrigin(Dpy, ft->gc, f->x, f->y);
+ XCopyArea(Dpy, ft->pix, wid,ft->gc,0,0, ft->w, ft->h, f->x, f->y);
+
+ P("movefish: %#lx %d %d %d %d %d %d %d\n",wid,f->type,f->direction,f->frame,(int)f->x,(int)f->y,ft->w,ft->h);
+ f->animtime --;
+ if (f->animtime <= 0)
+ {
+ setanimtime(f);
+ f->frame = (f->frame + 1)%NUM_FRAMES;
+ }
+}
+
+void setanimtime(fish*f)
+{
+ f->animtime = 0.75*(drand48()+0.333)*maxanimtime;
+}
+
+void erasebubble(bubble *b)
+{
+ XClearArea(Dpy, wid, b->x, b->y, b->size, b->size, 0);
+ P("erasebubble: %d %d %d\n",b->x, b->y, b->size);
+}
+
+
+void putbubble(bubble *b, unsigned long c)
+{
+ XGCValues gcv;
+
+ int s = b->size;
+ gcv.foreground = c;
+ gcv.clip_mask = xbubbles[s];
+ gcv.clip_x_origin = b->x;
+ gcv.clip_y_origin = b->y;
+ XChangeGC(Dpy, bgc, GCForeground | GCClipMask | GCClipXOrigin | GCClipYOrigin, &gcv);
+ XFillRectangle(Dpy, wid, bgc, b->x, b->y, s, s);
+ P("putbubble: %d %d\n", b->x, b->y);
+}
+
+
+/*
+ Allocate a color by name.
+ */
+Pixel AllocNamedColor(Display *display, char *colorName, Pixel dfltPix)
+{
+ Pixel pix;
+ XColor exactcolor;
+ XColor scrncolor;
+
+ if (XAllocNamedColor(display,
+ DefaultColormap(display, DefaultScreen(Dpy)),
+ colorName,
+ &scrncolor,
+ &exactcolor))
+ pix = scrncolor.pixel;
+ else
+ pix = dfltPix;
+
+ return pix;
+}
+
+
+
+//static unsigned char bits[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Calibrate the pixmaps and bitmaps. The right-fish data is coded in xfishy.h,
+ this is transformed to create the left-fish. The eight bubbles are coded
+ in bubbles.h as a two dimensional array.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void init_pixmap()
+{
+
+ XWindowAttributes attr;
+ XGetWindowAttributes(Dpy, wid, &attr);
+
+ XpmAttributes attributes;
+ attributes.valuemask = (
+ XpmExactColors | XpmCloseness | XpmDepth);
+
+ attributes.exactColors = False; // does not matter, true or false
+ attributes.closeness = 40000; // recommended in xpm manual
+ attributes.depth = attr.depth; // essential
+
+ int k;
+ // debugging : print all fishes
+ if(0)
+ for (k=0; k<NUM_FISH*2; k++)
+ {
+ int w,h,c,m,i;
+ sscanf(fishes[k][0],"%d %d %d %d",&w,&h,&c,&m);
+ printf("k %d %d %d %d %d\n",k,w,h,c,m);
+ for (i=0; i<h+c+1; i++)
+ printf("k i: %d %d %s\n",k,i,fishes[k][i]);
+ }
+ //XGCValues gc_values;
+ //gc_values.function = GXcopy;
+ //gc_values.graphics_exposures = False;
+ //gc_values.fill_style = FillTiled;
+ //int gmask = GCFunction | GCFillStyle | GCGraphicsExposures;
+ //(void)gmask;
+ //(void)gc_values;
+ for (k = 0; k < NUM_FISH; k++)
+ {
+ int i;
+ for (i=0; i<2; i++)
+ {
+ int j;
+ for(j=0; j<NUM_FRAMES; j++)
+ {
+ Pixmap pix;
+ fishtype *ft = &xfish[k][i][j];
+ iXpmCreatePixmapFromData(Dpy, wid, fishes[2*k+j], &ft->pix, &pix, &attributes, i);
+ ft->gc = XCreateGC(Dpy, wid, 0, NULL);
+ //XChangeGC(Dpy, draw_gc, gmask, &gc_values);
+ XSetClipMask(Dpy, ft->gc, pix);
+ XFreePixmap(Dpy,pix);
+ sscanf(fishes[2*k][0],"%d %d",&(ft->w),&(ft->h));
+ }
+ }
+ }
+
+ int i;
+ for (i = 1; i <= 8; i++)
+ xbubbles[i] = XCreateBitmapFromData(Dpy, wid, (char *) xbBits[i], i, i);
+}
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Initialize signal so that SIGUSR1 causes secure mode to toggle.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void init_signals()
+{
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Variety of initialization calls, including getting the window up and running.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void initialize()
+{
+ XWindowAttributes winfo;
+ XSetWindowAttributes attr;
+ (void)attr;
+ XGCValues vals;
+ int i,j,k;
+
+ root_window = VirtualRootWindowOfScreen(DefaultScreenOfDisplay(Dpy));
+
+ XGetWindowAttributes(Dpy, root_window, &winfo);
+ width = winfo.width;
+ height = winfo.height;
+
+ DoubleBuf=0;
+
+ int x,y;
+ unsigned int w,h,b,depth;
+ Window root, searchWin;
+ GtkWidget *gtkwin;
+ XGetGeometry(Dpy,root_window,&root,
+ &x, &y, &w, &h, &b, &depth);
+ gtkwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title (GTK_WINDOW(gtkwin),"Xfishtank-A");
+ gtk_window_set_skip_taskbar_hint (GTK_WINDOW(gtkwin),TRUE);
+ gtk_window_set_skip_pager_hint (GTK_WINDOW(gtkwin),TRUE);
+
+ if (ForceRoot)
+ {
+ searchWin = DefaultRootWindow(Dpy);
+ // Are we started by xscreensaver?
+ if (getenv("XSCREENSAVER_WINDOW"))
+ {
+ searchWin = strtol(getenv("XSCREENSAVER_WINDOW"),NULL,0);
+ inxscreensaver = 1;
+ }
+ }
+ else if(UserWindow)
+ {
+ searchWin = UserWindow;
+ }
+ else
+ {
+ int HaveTrans = make_trans_window(gtkwin,
+ 1 /*fullscreen*/,
+ 1 /*sticky*/,
+ 1 /* below*/,
+ 1 /* dock*/ ,
+ NULL,
+ &searchWin);
+ if (HaveTrans)
+ {
+ xfishtank_trans = 1;
+ }
+ else
+ {
+ searchWin = root_window;
+ // Maybe, it is LXDE: find window with name pcmanfm
+ char *DesktopSession = NULL;
+ if (getenv("DESKTOP_SESSION"))
+ {
+ DesktopSession = strdup(getenv("DESKTOP_SESSION"));
+ char *a = DesktopSession;
+ while (*a) { *a = toupper(*a); a++; }
+ if (!strncmp(DesktopSession,"LXDE",4))
+ {
+ Window w = Window_With_Name(Dpy, root_window, "pcmanfm");
+ if(w)
+ {
+ searchWin = w;
+ printf("LXDE session found, using window pcmanfm\n");
+ }
+ }
+ }
+ if(DesktopSession)
+ free(DesktopSession);
+ }
+ if(xfishtank_trans)
+ {
+ XMoveWindow(Dpy,searchWin,0,0);
+ XSetWindowBackground(Dpy,searchWin,0);
+ }
+ }
+
+ P("searchWin: %#lx\n",searchWin);
+
+ wid = searchWin;
+
+ xfishtankWin = searchWin;
+
+
+ vals.foreground = vals.background = bcolor;
+ vals.graphics_exposures = False;
+ //gc = XCreateGC(Dpy, wid, GCForeground | GCBackground | GCGraphicsExposures, &vals);
+ //pgc = XCreateGC(Dpy, wid, GCForeground | GCBackground | GCGraphicsExposures, &vals);
+ bgc = XCreateGC(Dpy, wid, GCForeground | GCBackground | GCGraphicsExposures, &vals);
+ //draw_gc = XCreateGC(Dpy, wid, 0, NULL);
+ //XGCValues gc_values;
+ //gc_values.function = GXcopy;
+ //gc_values.graphics_exposures = False;
+ //gc_values.fill_style = FillTiled;
+ //XChangeGC(Dpy, draw_gc, GCFunction | GCFillStyle | GCGraphicsExposures, &gc_values);
+
+ for (i = 0; i < NUM_FISH; i++)
+ for (j=0; j<2; j++)
+ for (k=0; k<NUM_FRAMES; k++)
+ {
+ fishtype *ft = &xfish[i][j][k];
+ ft->pix = 0;
+ ft->gc = 0;
+ ft->w = 42;
+ ft->h = 42;
+ }
+
+ init_pixmap();
+ init_signals();
+
+ binfo = (bubble *) malloc(blimit * sizeof(bubble));
+ //finfo = (fish *) malloc(flimit * sizeof(fish));
+
+ wid1 = wid;
+#ifdef DOUBLE_BUFFER
+ int xdbemajor;
+ int xdbeminor;
+ if (XdbeAvailable == -42)
+ XdbeAvailable = XdbeQueryExtension(Dpy,&xdbemajor,&xdbeminor);
+#else
+ XdbeAvailable = 0;
+#endif
+ UseXdbe = XdbeAvailable && WantXdbe;
+
+#ifdef DOUBLE_BUFFER
+ //BMETHOD = XdbeBackground;
+ BMETHOD = XdbeBackground;
+ // BMETHOD = XdbeUndefined;
+ // BMETHOD = XdbeUntouched;
+ // BMETHOD = XdbeCopied; // use this to check if dbe works; fishes and bubbles will not be erased
+
+ if (UseXdbe)
+ {
+ if (backbuf)
+ XdbeDeallocateBackBufferName(Dpy,backbuf);
+ backbuf = XdbeAllocateBackBufferName(Dpy, wid1, BMETHOD);
+ wid = backbuf;
+ printf("Using double buffer: %#lx window: %#lx\n",backbuf,wid1);
+ }
+ else
+ printf("Using window: %#lx\n",wid);
+#endif
+
+ if(xfishtank_trans)
+ printf("This is a new transparent window.\n");
+ else
+ {
+ if(UserWindow)
+ printf("This is an existing window.\n");
+ else
+ printf("This is an existing window, probably the root window.\n");
+ }
+ XClearWindow(Dpy,wid1);
+
+ // drawing on wid, XClearWindow, XSetWindowBackground on wid1
+}
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Create a new bubble. Placement along the x axis is random, as is the size of
+ the bubble. Increment value is determined by speed.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void new_bubble(bubble *b, int init) // init=0: postions at bottom, else random
+{
+ int s;
+
+ b->x = 5+(width-10) * drand48();
+ if (init)
+ b->y = (height / 16) * (16*drand48() + 1) - 1;
+ else
+ b->y = height - 1;
+ b->size = s = 1.0 + 8*drand48();
+ if ((b->bstep = smooth * height / (float) binc[s]) == 0)
+ b->bstep = 1;
+ P("newbubble %d %d\n",b->x, b->y);
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Erase old bubbles, move and draw new bubbles. Random left-right factor
+ can move bubble one size-unit in either direction.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void step_bubbles()
+{
+ int i, j, s;
+ (void)j;
+ (void)s;
+ bubble *b;
+
+ for (i = 0; i < blimit; i++)
+ {
+ b = &binfo[i];
+ s = b->size;
+ /* clear */
+ /*
+ if ((b->y > 0) && (b->erased == 0))
+ {
+ erasebubble(b, s);
+ }
+ */
+ if ((b->y -= b->bstep) > 0)
+ {
+ double r = drand48();
+ if (r < 0.25) {
+ b->x -= 1;
+ } else if (r > 0.75) {
+ b->x += 1;
+ }
+ putbubble(b, bcolor);
+ } else {
+ if (drand48() < 0.25) {
+ new_bubble(b,0);
+ }
+ }
+ }
+ XFlush(Dpy);
+}
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Create a new fish. Placement along the y axis is random, as is the side
+ >from which the fish appears. Direction is determined from side. Increment
+ is also random.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void new_fish(fish *f0, int init) // init 0: position at edge, else position at random
+{
+ int i, collide;
+ fish *f = f0;
+
+ f->type = drand48() * NUM_FISH;
+ fishtype *ft = &xfish[f->type][0][0];
+ for (i = 0, collide = 1; (i < 16) && (collide); i++)
+ {
+ f->y = (height - ft->h) * drand48();
+ if ((f->fstep = smooth * width / (8.0 * (1.0 + 8*drand48()))) == 0)
+ f->fstep = 1;
+ }
+ if (drand48() < 0.5)
+ {
+ f->direction = 0;
+ if (init)
+ f->x = ft->w + drand48()*width;
+ else
+ f->x = width;
+ }
+ else
+ {
+ f->direction = 1;
+ if (init)
+ f->x = drand48()*width-ft->w;
+ else
+ f->x = -ft->w;
+ }
+
+ f->frame = 0;
+ setanimtime(f);
+ P("newfish %d %d\n",(int)f->x,(int)f->y);
+}
+
+void remove_all_fishes()
+{
+ int i;
+ for (i = 0; i < flimit; i++)
+ erasefish(&finfo[i]);
+ XFlush(Dpy);
+}
+
+void remove_all_bubbles()
+{
+ int i;
+ for (i = 0; i < blimit; i++)
+ erasebubble(&binfo[i]);
+ XFlush(Dpy);
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ Move all the fish. Clearing old fish is accomplished by masking only the
+ exposed areas of the old fish. Random up-down factor can move fish 1/4 a
+ fish height in either direction, if no collisions are caused.
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+void move_fish()
+{
+ int i, y, done = 0;
+ fish *f;
+
+ for (i = 0; i < flimit; i++)
+ {
+ f = &finfo[i];
+ fishtype *ft = &xfish[f->type][f->direction][f->frame];
+ if (f->direction == 0)
+ done = ((f->x -= speedfactor*f->fstep) < -ft->w);
+ else if (f->direction == 1)
+ done = ((f->x += speedfactor*f->fstep) > width);
+
+ if(0) // spontaneous change direction now and then
+ if (f->x > ft->w && f->x < width - 2*ft->w && drand48() < 0.001)
+ {
+ f->direction = 1-f->direction;
+ P("direction: %d\n",f->direction);
+ }
+ if (1)
+ {
+ if (!done)
+ {
+ double r = drand48();
+ if (r < 0.25)
+ y = f->fstep / 4;
+ else if (r > 0.75)
+ y = f->fstep / -4;
+ else
+ y = 0;
+
+ if (y)
+ f->y += y;
+ movefish(f);
+ }
+ else
+ {
+ erasefish(f);
+ new_fish(f,0);
+ }
+ }
+ }
+ XFlush(Dpy);
+}
+
+int do_move(void *dummy)
+{
+ (void)dummy;
+
+ if (Done)
+ {
+ gtk_main_quit();
+ return FALSE;
+ }
+
+#ifdef DOUBLE_BUFFER
+ if (UseXdbe)
+ {
+ XdbeSwapInfo swapInfo;
+ swapInfo.swap_window = wid1;
+ swapInfo.swap_action = BMETHOD;
+ XdbeSwapBuffers(Dpy, &swapInfo, 1);
+ if(xfishtank_trans)
+ XSetWindowBackground(Dpy,wid1,0);
+ }
+ else
+ {
+ XFlush(Dpy);
+ remove_all_bubbles();
+ remove_all_fishes();
+ }
+#else
+ XFlush(Dpy);
+ remove_all_bubbles();
+ remove_all_fishes();
+#endif
+ move_fish();
+ step_bubbles();
+ return TRUE;
+
+}
+
+int do_testfish(void *dummy)
+{
+ (void)dummy;
+
+ static int x = 100;
+ static int y = 100;
+ int w = 64;
+ int h = 41;
+ w= 64;
+ h=59;
+ XClearArea(Dpy, wid, x,y,w,h,0);
+ x += 1;
+ y += 1;
+ if (x > 500)
+ {
+ x = 100;
+ y = 100;
+ }
+
+ GC gc = xfish[0][1][0].gc;
+ XSetClipOrigin(Dpy, gc, x, y);
+ XCopyArea(Dpy, xfish[0][1][0].pix, wid, gc,0,0,w,h,x,y);
+ XFlush(Dpy);
+ return TRUE;
+}
+
+void create_fishes(int n)
+{
+ int i;
+ static int prev = 0;
+ flimit = n;
+ finfo = (fish *)realloc(finfo,sizeof(fish)*flimit);
+ for (i = prev; i < flimit; i++)
+ new_fish(&finfo[i],1);
+ prev = flimit;
+}
+
+void create_bubbles(int n)
+{
+ int i;
+ static int prev = 0;
+ blimit = n;
+ binfo = (bubble *)realloc(binfo,sizeof(bubble)*blimit);
+ for (i = prev; i < blimit; i++)
+ new_bubble(&binfo[i],1);
+ prev = blimit;
+}
+
+void setspeed(float s)
+{
+ speedfactor = s;
+}
+
+void setbcolor()
+{
+ bcolor = AllocNamedColor(Dpy,bcolorstring,white)|0xff000000;
+}
+
+void setbgcolor()
+{
+ if (inxscreensaver && !xfishtank_trans && bgcolorstring && strlen(bgcolorstring))
+ {
+ P("bgcolorstring '%s'\n",bgcolorstring);
+ bgcolor = AllocNamedColor(Dpy,bgcolorstring,black)|0xff000000;
+ XSetWindowBackground(Dpy, wid1, bgcolor);
+ XClearWindow(Dpy,wid1);
+ }
+}
+
+
+void Thanks()
+{
+ if (HaltedByInterrupt)
+ printf("\nxfishtank: Caught signal %d\n",HaltedByInterrupt);
+ if (Dpy)
+ {
+ XClearWindow(Dpy,wid1);
+ XFlush(Dpy);
+ }
+ printf("\nThank you for using xfishtank\n");
+ exit(0);
+}
+
+void SigHandler(int signum)
+{
+ HaltedByInterrupt = signum;
+ Done = 1;
+}
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+int main(int argc, char *argv[])
+{
+ // Circumvent wayland problems:before starting gtk: make sure that the
+ // gdk-x11 backend is used.
+
+ setenv("GDK_BACKEND","x11",1);
+ signal(SIGINT, SigHandler);
+ signal(SIGTERM, SigHandler);
+ signal(SIGHUP, SigHandler);
+
+ parse(argc, argv);
+ printf("xfishtank version %s\n",VERSION);
+ printf("Comments to: %s\n",PACKAGE_BUGREPORT);
+ printf("Url: %s\n",PACKAGE_URL);
+ srand48(time(NULL));
+ counter = 0;
+ gtk_init(&argc, &argv);
+ set_defaults();
+ ReadFlags();
+ Dpy = XOpenDisplay(display_name);
+ if(!Dpy)
+ {
+ char *name = display_name;
+ if (!display_name)
+ {
+ if(getenv("DISPLAY"))
+ name = getenv("DISPLAY");
+ }
+ printf("Cannot open display: '%s'\n",name);
+ Thanks();
+ exit(1);
+ }
+ screen = DefaultScreen(Dpy);
+
+ white = WhitePixel(Dpy, screen);
+ black = BlackPixel(Dpy, screen);
+
+ initialize();
+
+ setbcolor();
+ setbgcolor();
+
+ srand((unsigned) getpid());
+
+ create_bubbles(blimit);
+
+ create_fishes(flimit);
+
+ ui();
+
+ g_timeout_add_full(G_PRIORITY_DEFAULT, 30, do_move ,NULL, NULL);
+ g_timeout_add_full(G_PRIORITY_DEFAULT, 500, do_write_flags ,NULL, NULL);
+
+ if(0)
+ g_timeout_add_full(G_PRIORITY_DEFAULT, 40, do_testfish ,NULL, NULL);
+
+ if (nomenu)
+ iconify();
+ gtk_main();
+
+ Thanks();
+
+ return 0;
+}
+
+void print_changelog()
+{
+#include "changelog.inc"
+}
+
+#ifdef SELFREP
+static unsigned char tarfile[] = {
+#include "tarfile.inc"
+};
+void selfrep()
+{
+ if(sizeof(tarfile) > 1000 && isatty(fileno(stdout)))
+ {
+ printf("Not sending tar file to terminal.\n");
+ printf("Try redirecting to a file (e.g: xpenguins -selfrep > xpenguins.tar.gz),\n");
+ printf("or use a pipe (e.g: xpenguins -selfrep | tar zxf -).\n");
+ }
+ else
+ {
+ ssize_t rc = mywrite(fileno(stdout),tarfile,sizeof(tarfile));
+ if (rc < 0)
+ fprintf(stderr,"xpenguins: Problems encountered during production of the tar ball.\n");
+ }
+}
+#endif
+