X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=hacks%2Ffluidballs.c;h=7fc43bd2c9d5ae8afb63c668b7de7dbfe8f28a97;hb=aa75c7476aeaa84cf3abc192b376a8b03c325213;hp=b60e4395788b756ff44596461c4010cb83d0d1ff;hpb=4cecfc89e5e889c7232693897c06168fb378bd5c;p=xscreensaver diff --git a/hacks/fluidballs.c b/hacks/fluidballs.c index b60e4395..7fc43bd2 100644 --- a/hacks/fluidballs.c +++ b/hacks/fluidballs.c @@ -11,13 +11,17 @@ * Ported to X11 and xscreensaver by jwz, 27-Feb-2002. * * http://astronomy.swin.edu.au/~pbourke/modelling/fluid/ + * + * Some physics improvements by Steven Barker */ -/* cjb notes - * - * Future ideas: +/* Future ideas: * Specifying a distribution in the ball sizes (with a gamma curve, possibly). * Brownian motion, for that extra touch of realism. + * + * It would be nice to detect when there are more balls than fit in + * the window, and scale the number of balls back. Useful for the + * xscreensaver-demo preview, which is often too tight by default. */ #include @@ -32,10 +36,12 @@ typedef struct { Display *dpy; Window window; XWindowAttributes xgwa; + int delay; Pixmap b, ba; /* double-buffer to reduce flicker */ #ifdef HAVE_DOUBLE_BUFFER_EXTENSION XdbeBackBuffer backb; + Bool dbeclear_p; #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ GC draw_gc; /* most of the balls */ @@ -60,13 +66,12 @@ typedef struct { float *r; /* ball radiuses */ float *m; /* ball mass, precalculated */ - float e; /* coefficient of friction, I think? */ + float e; /* coeficient of elasticity */ float max_radius; /* largest radius of any ball */ Bool random_sizes_p; /* Whether balls should be various sizes up to max. */ Bool shake_p; /* Whether to mess with gravity when things settle. */ Bool dbuf; /* Whether we're using double buffering. */ - Bool dbeclear_p; /* ? */ float shake_threshold; int time_since_shake; @@ -78,6 +83,9 @@ typedef struct { int collision_count; char fps_str[1024]; + int time_tick; + struct timeval last_time; + } b_state; @@ -86,10 +94,11 @@ static void draw_fps_string (b_state *state) { XFillRectangle (state->dpy, state->b, state->erase_gc, - 0, state->xgwa.height - state->font_height, - state->xgwa.width, state->font_height); + 0, state->xgwa.height - state->font_height*3 - 20, + state->xgwa.width, state->font_height*3 + 20); XDrawImageString (state->dpy, state->b, state->font_gc, - 0, state->xgwa.height - state->font_baseline, + 10, state->xgwa.height - state->font_height*2 - + state->font_baseline - 10, state->fps_str, strlen(state->fps_str)); } @@ -99,32 +108,8 @@ draw_fps_string (b_state *state) static void window_origin (Display *dpy, Window window, int *x, int *y) { - Window root, parent, *kids; - unsigned int nkids; - XWindowAttributes xgwa; - int wx, wy; - XGetWindowAttributes (dpy, window, &xgwa); - - wx = xgwa.x; - wy = xgwa.y; - - kids = 0; - *x = 0; - *y = 0; - - if (XQueryTree (dpy, window, &root, &parent, &kids, &nkids)) - { - if (parent && parent != root) - { - int px, py; - window_origin (dpy, parent, &px, &py); - wx += px; - wy += py; - } - } - if (kids) XFree (kids); - *x = wx; - *y = wy; + XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen (dpy)), + 0, 0, x, y, &window); } @@ -148,7 +133,8 @@ check_window_moved (b_state *state) state->xmin = wx; state->ymin = wy; state->xmax = state->xmin + state->xgwa.width; - state->ymax = state->ymin + state->xgwa.height - state->font_height; + state->ymax = state->ymin + state->xgwa.height - (state->font_height*3) - + (state->font_height ? 22 : 0); if (state->dbuf && (state->ba)) { @@ -170,8 +156,8 @@ check_window_moved (b_state *state) XClearWindow (state->dpy, state->window); else if (state->fps_p && oymax != state->ymax) XFillRectangle (state->dpy, state->b, state->erase_gc, - 0, state->xgwa.height - state->font_height, - state->xgwa.width, state->font_height); + 0, state->xgwa.height - state->font_height*3, + state->xgwa.width, state->font_height*3); } } @@ -226,8 +212,8 @@ recolor (b_state *state) /* Initialize the state structure and various X data. */ -static b_state * -init_balls (Display *dpy, Window window) +static void * +fluidballs_init (Display *dpy, Window window) { int i; float extx, exty; @@ -235,17 +221,21 @@ init_balls (Display *dpy, Window window) XGCValues gcv; state->dpy = dpy; - state->window = window; + state->delay = get_integer_resource (dpy, "delay", "Integer"); check_window_moved (state); - state->dbuf = get_boolean_resource ("doubleBuffer", "Boolean"); - state->dbeclear_p = get_boolean_resource ("useDBEClear", "Boolean"); + state->dbuf = get_boolean_resource (dpy, "doubleBuffer", "Boolean"); + +# ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */ + state->dbuf = False; +# endif if (state->dbuf) { #ifdef HAVE_DOUBLE_BUFFER_EXTENSION + state->dbeclear_p = get_boolean_resource (dpy, "useDBEClear", "Boolean"); if (state->dbeclear_p) state->b = xdbe_get_backbuffer (dpy, window, XdbeBackground); else @@ -268,19 +258,21 @@ init_balls (Display *dpy, Window window) /* Select ButtonRelease events on the external window, if no other app has already selected it (only one app can select it at a time: BadAccess. */ +#if 0 if (! (state->xgwa.all_event_masks & ButtonReleaseMask)) XSelectInput (state->dpy, state->window, state->xgwa.your_event_mask | ButtonReleaseMask); +#endif - gcv.foreground = get_pixel_resource("foreground", "Foreground", - state->dpy, state->xgwa.colormap); - gcv.background = get_pixel_resource("background", "Background", - state->dpy, state->xgwa.colormap); + gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap, + "foreground", "Foreground"); + gcv.background = get_pixel_resource(state->dpy, state->xgwa.colormap, + "background", "Background"); state->draw_gc = XCreateGC (state->dpy, state->b, GCForeground|GCBackground, &gcv); - gcv.foreground = get_pixel_resource("mouseForeground", "MouseForeground", - state->dpy, state->xgwa.colormap); + gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap, + "mouseForeground", "MouseForeground"); state->draw_gc2 = XCreateGC (state->dpy, state->b, GCForeground|GCBackground, &gcv); @@ -298,43 +290,63 @@ init_balls (Display *dpy, Window window) extx = state->xmax - state->xmin; exty = state->ymax - state->ymin; - state->count = get_integer_resource ("count", "Count"); + state->count = get_integer_resource (dpy, "count", "Count"); if (state->count < 1) state->count = 20; - state->max_radius = get_float_resource ("size", "Size") / 2; + state->max_radius = get_float_resource (dpy, "size", "Size") / 2; if (state->max_radius < 1.0) state->max_radius = 1.0; - state->random_sizes_p = get_boolean_resource ("random", "Random"); + state->random_sizes_p = get_boolean_resource (dpy, "random", "Random"); - state->accx = get_float_resource ("wind", "Wind"); + /* If the initial window size is too small to hold all these balls, + make fewer of them... + */ + { + float r = (state->random_sizes_p + ? state->max_radius * 0.7 + : state->max_radius); + float ball_area = M_PI * r * r; + float balls_area = state->count * ball_area; + float window_area = state->xgwa.width * state->xgwa.height; + window_area *= 0.75; /* don't pack it completely full */ + if (balls_area > window_area) + state->count = window_area / ball_area; + } + + state->accx = get_float_resource (dpy, "wind", "Wind"); if (state->accx < -1.0 || state->accx > 1.0) state->accx = 0; - state->accy = get_float_resource ("gravity", "Gravity"); + state->accy = get_float_resource (dpy, "gravity", "Gravity"); if (state->accy < -1.0 || state->accy > 1.0) state->accy = 0.01; - state->e = get_float_resource ("friction", "Friction"); + state->e = get_float_resource (dpy, "elasticity", "Elacitcity"); if (state->e < 0.2 || state->e > 1.0) state->e = 0.97; - state->tc = get_float_resource ("timeScale", "TimeScale"); + state->tc = get_float_resource (dpy, "timeScale", "TimeScale"); if (state->tc <= 0 || state->tc > 10) state->tc = 1.0; - state->shake_p = get_boolean_resource ("shake", "Shake"); - state->shake_threshold = get_float_resource ("shakeThreshold", + state->shake_p = get_boolean_resource (dpy, "shake", "Shake"); + state->shake_threshold = get_float_resource (dpy, "shakeThreshold", "ShakeThreshold"); + state->time_tick = 999999; + +# ifdef HAVE_MOBILE /* Always obey real-world gravity */ + state->shake_p = False; +# endif + - state->fps_p = get_boolean_resource ("doFPS", "DoFPS"); + state->fps_p = get_boolean_resource (dpy, "doFPS", "DoFPS"); if (state->fps_p) { XFontStruct *font; - char *fontname = get_string_resource ("font", "Font"); - const char *def_font = "fixed"; - if (!fontname || !*fontname) fontname = (char *)def_font; + char *fontname = get_string_resource (dpy, "fpsFont", "Font"); + if (!fontname) fontname = "-*-courier-bold-r-normal-*-180-*"; font = XLoadQueryFont (dpy, fontname); - if (!font) font = XLoadQueryFont (dpy, def_font); + if (!font) font = XLoadQueryFont (dpy, "fixed"); if (!font) exit(-1); gcv.font = font->fid; - gcv.foreground = get_pixel_resource("textColor", "Foreground", - state->dpy, state->xgwa.colormap); + gcv.foreground = get_pixel_resource(state->dpy, state->xgwa.colormap, + "textColor", "Foreground"); state->font_gc = XCreateGC(dpy, state->b, GCFont|GCForeground|GCBackground, &gcv); state->font_height = font->ascent + font->descent; @@ -366,6 +378,9 @@ init_balls (Display *dpy, Window window) state->m[i] = pow(state->r[i],3) * M_PI * 1.3333; } + memcpy (state->opx, state->px, sizeof (*state->opx) * (state->count + 1)); + memcpy (state->opy, state->py, sizeof (*state->opx) * (state->count + 1)); + return state; } @@ -413,13 +428,11 @@ shake (b_state *state) static void check_wall_clock (b_state *state, float max_d) { - static int tick = 0; state->frame_count++; - if (tick++ > 20) /* don't call gettimeofday() too often -- it's slow. */ + if (state->time_tick++ > 20) /* don't call gettimeofday() too often -- it's slow. */ { struct timeval now; - static struct timeval last = {0, }; # ifdef GETTIMEOFDAY_TWO_ARGS struct timezone tzp; gettimeofday(&now, &tzp); @@ -427,32 +440,46 @@ check_wall_clock (b_state *state, float max_d) gettimeofday(&now); # endif - if (last.tv_sec == 0) - last = now; + if (state->last_time.tv_sec == 0) + state->last_time = now; - tick = 0; - if (now.tv_sec == last.tv_sec) + state->time_tick = 0; + if (now.tv_sec == state->last_time.tv_sec) return; - state->time_since_shake += (now.tv_sec - last.tv_sec); + state->time_since_shake += (now.tv_sec - state->last_time.tv_sec); + +# ifdef HAVE_MOBILE /* Always obey real-world gravity */ + { + float a = fabs (fabs(state->accx) > fabs(state->accy) + ? state->accx : state->accy); + int rot = current_device_rotation(); + switch (rot) { + case 0: case 360: state->accx = 0; state->accy = a; break; + case -90: state->accx = -a; state->accy = 0; break; + case 90: state->accx = a; state->accy = 0; break; + case 180: case -180: state->accx = 0; state->accy = -a; break; + default: break; + } + } +# endif /* HAVE_MOBILE */ if (state->fps_p) { float elapsed = ((now.tv_sec + (now.tv_usec / 1000000.0)) - - (last.tv_sec + (last.tv_usec / 1000000.0))); + (state->last_time.tv_sec + (state->last_time.tv_usec / 1000000.0))); float fps = state->frame_count / elapsed; float cps = state->collision_count / elapsed; - sprintf (state->fps_str, - " FPS: %.2f Collisions: %.3f/frame Max motion: %.3f", - fps, cps/fps, max_d); + sprintf (state->fps_str, "Collisions: %.3f/frame Max motion: %.3f", + cps/fps, max_d); draw_fps_string(state); } state->frame_count = 0; state->collision_count = 0; - last = now; + state->last_time = now; } } @@ -462,28 +489,35 @@ static void repaint_balls (b_state *state) { int a; +# ifndef HAVE_JWXYZ int x1a, x2a, y1a, y2a; +# endif int x1b, x2b, y1b, y2b; float max_d = 0; +#ifdef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */ + XClearWindow (state->dpy, state->b); +#endif + for (a=1; a <= state->count; a++) { GC gc; +# ifndef HAVE_JWXYZ x1a = (state->opx[a] - state->r[a] - state->xmin); y1a = (state->opy[a] - state->r[a] - state->ymin); x2a = (state->opx[a] + state->r[a] - state->xmin); y2a = (state->opy[a] + state->r[a] - state->ymin); +# endif x1b = (state->px[a] - state->r[a] - state->xmin); y1b = (state->py[a] - state->r[a] - state->ymin); x2b = (state->px[a] + state->r[a] - state->xmin); y2b = (state->py[a] + state->r[a] - state->ymin); - if (!state->dbeclear_p || +#ifndef HAVE_JWXYZ /* Don't second-guess Quartz's double-buffering */ #ifdef HAVE_DOUBLE_BUFFER_EXTENSION - !state->backb + if (!state->dbeclear_p || !state->backb) #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ - ) { /* if (x1a != x1b || y1a != y1b) -- leaves turds if we optimize this */ { @@ -493,6 +527,8 @@ repaint_balls (b_state *state) 0, 360*64); } } +#endif /* !HAVE_JWXYZ */ + if (state->mouse_ball == a) gc = state->draw_gc2; else @@ -516,7 +552,11 @@ repaint_balls (b_state *state) state->opy[a] = state->py[a]; } - if (state->fps_p && state->dbeclear_p) + if (state->fps_p +#ifdef HAVE_DOUBLE_BUFFER_EXTENSION + && (state->backb ? state->dbeclear_p : 1) +#endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ + ) draw_fps_string(state); #ifdef HAVE_DOUBLE_BUFFER_EXTENSION @@ -556,7 +596,7 @@ update_balls (b_state *state) { int a, b; float d, vxa, vya, vxb, vyb, dd, cdx, cdy; - float ma, mb, vela, velb, vela1, velb1; + float ma, mb, vca, vcb, dva, dvb; float dee2; check_window_moved (state); @@ -580,71 +620,69 @@ update_balls (b_state *state) } /* For each ball, compute the influence of every other ball. */ - for (a=1; a <= state->count; a++) - if (a != state->mouse_ball) - for (b=1; b <= state->count; b++) - if (a != b) - { - d = ((state->px[a] - state->px[b]) * - (state->px[a] - state->px[b]) + - (state->py[a] - state->py[b]) * - (state->py[a] - state->py[b])); - dee2 = (state->r[a] + state->r[b]) * - (state->r[a] + state->r[b]); - if (d < dee2) - { - state->collision_count++; - d = sqrt(d); - dd = state->r[a] + state->r[b] - d; - /* A pair of balls that have already collided in this - * current frame (and therefore touching each other) - * should not have another collision calculated, hence - * the fallthru if "dd ~= 0.0". - */ - if ((dd < -0.01) || (dd > 0.01)) - { - cdx = (state->px[b] - state->px[a]) / d; - cdy = (state->py[b] - state->py[a]) / d; - - /* Move each ball apart from the other by half the - * 'collision' distance. - */ - state->px[a] -= 0.5 * dd * cdx; - state->py[a] -= 0.5 * dd * cdy; - state->px[b] += 0.5 * dd * cdx; - state->py[b] += 0.5 * dd * cdy; - - ma = state->m[a]; - mb = state->m[b]; - vxa = state->vx[a]; - vya = state->vy[a]; - vxb = state->vx[b]; - vyb = state->vy[b]; - - vela = sqrt((vxa * vxa) + (vya * vya)); - velb = sqrt((vxb * vxb) + (vyb * vyb)); - - vela1 = vela * ((ma - mb) / (ma + mb)) + - velb * ((2 * mb) / (ma + mb)); - velb1 = vela * ((2 * ma) / (ma + mb)) + - velb * ((mb - ma) / (ma + mb)); - - vela1 *= state->e; /* "air resistance" */ - velb1 *= state->e; + for (a=1; a <= state->count - 1; a++) + for (b=a + 1; b <= state->count; b++) + { + d = ((state->px[a] - state->px[b]) * + (state->px[a] - state->px[b]) + + (state->py[a] - state->py[b]) * + (state->py[a] - state->py[b])); + dee2 = (state->r[a] + state->r[b]) * + (state->r[a] + state->r[b]); + if (d < dee2) + { + state->collision_count++; + d = sqrt(d); + dd = state->r[a] + state->r[b] - d; + + cdx = (state->px[b] - state->px[a]) / d; + cdy = (state->py[b] - state->py[a]) / d; + + /* Move each ball apart from the other by half the + * 'collision' distance. + */ + state->px[a] -= 0.5 * dd * cdx; + state->py[a] -= 0.5 * dd * cdy; + state->px[b] += 0.5 * dd * cdx; + state->py[b] += 0.5 * dd * cdy; + + ma = state->m[a]; + mb = state->m[b]; + + vxa = state->vx[a]; + vya = state->vy[a]; + vxb = state->vx[b]; + vyb = state->vy[b]; + + vca = vxa * cdx + vya * cdy; /* the component of each velocity */ + vcb = vxb * cdx + vyb * cdy; /* along the axis of the collision */ + + /* elastic collison */ + dva = (vca * (ma - mb) + vcb * 2 * mb) / (ma + mb) - vca; + dvb = (vcb * (mb - ma) + vca * 2 * ma) / (ma + mb) - vcb; + + dva *= state->e; /* some energy lost to inelasticity */ + dvb *= state->e; + #if 0 - vela1 += (frand (50) - 25) / ma; /* brownian motion */ - velb1 += (frand (50) - 25) / mb; + dva += (frand (50) - 25) / ma; /* q: why are elves so chaotic? */ + dvb += (frand (50) - 25) / mb; /* a: brownian motion. */ #endif - state->vx[a] = -cdx * vela1; - state->vy[a] = -cdy * vela1; - state->vx[b] = cdx * velb1; - state->vy[b] = cdy * velb1; - } - } - } - /* Force all balls to be on screen. - */ + vxa += dva * cdx; + vya += dva * cdy; + vxb += dvb * cdx; + vyb += dvb * cdy; + + state->vx[a] = vxa; + state->vy[a] = vya; + state->vx[b] = vxb; + state->vy[b] = vyb; + } + } + + /* Force all balls to be on screen. + */ for (a=1; a <= state->count; a++) { if (state->px[a] <= (state->xmin + state->r[a])) @@ -684,68 +722,94 @@ update_balls (b_state *state) /* Handle X events, specifically, allow a ball to be picked up with the mouse. */ -static void -handle_events (b_state *state) +static Bool +fluidballs_event (Display *dpy, Window window, void *closure, XEvent *event) { - XSync (state->dpy, False); - while (XPending (state->dpy)) + b_state *state = (b_state *) closure; + + if (event->xany.type == ButtonPress) { - XEvent event; - XNextEvent (state->dpy, &event); - if (event.xany.type == ButtonPress) + int i, rx, ry; + XTranslateCoordinates (dpy, window, RootWindow (dpy, DefaultScreen(dpy)), + event->xbutton.x, event->xbutton.y, &rx, &ry, + &window); + + if (state->mouse_ball != 0) /* second down-click? drop the ball. */ + { + state->mouse_ball = 0; + return True; + } + else { - int i; - float fmx = event.xbutton.x_root; - float fmy = event.xbutton.y_root; - if (state->mouse_ball != 0) /* second down-click? drop the ball. */ - { - state->mouse_ball = 0; - return; - } - else - for (i=1; i <= state->count; i++) + /* When trying to pick up a ball, first look for a click directly + inside the ball; but if we don't find it, expand the radius + outward until we find something nearby. + */ + float max = state->max_radius * 4; + float step = max / 10; + float r2; + for (r2 = step; r2 < max; r2 += step) { + for (i = 1; i <= state->count; i++) { - float d = ((state->px[i] - fmx) * (state->px[i] - fmx) + - (state->py[i] - fmy) * (state->py[i] - fmy)); + float d = ((state->px[i] - rx) * (state->px[i] - rx) + + (state->py[i] - ry) * (state->py[i] - ry)); float r = state->r[i]; + if (r2 > r) r = r2; if (d < r*r) { state->mouse_ball = i; - return; + return True; } } + } } - else if (event.xany.type == ButtonRelease) /* drop the ball */ - { - state->mouse_ball = 0; - return; - } - else if (event.xany.type == ConfigureNotify) - { - /* This is redundant, since we poll this every iteration. */ - check_window_moved (state); - } - - screenhack_handle_event (state->dpy, &event); + return True; } + else if (event->xany.type == ButtonRelease) /* drop the ball */ + { + state->mouse_ball = 0; + return True; + } + + return False; +} + +static unsigned long +fluidballs_draw (Display *dpy, Window window, void *closure) +{ + b_state *state = (b_state *) closure; + repaint_balls(state); + update_balls(state); + return state->delay; +} + +static void +fluidballs_reshape (Display *dpy, Window window, void *closure, + unsigned int w, unsigned int h) +{ } - -char *progclass = "FluidBalls"; +static void +fluidballs_free (Display *dpy, Window window, void *closure) +{ + b_state *state = (b_state *) closure; + free (state); +} -char *defaults [] = { + +static const char *fluidballs_defaults [] = { ".background: black", + ".foreground: yellow", ".textColor: yellow", - ".font: -*-helvetica-*-r-*-*-*-180-*-*-p-*-*-*", + "*mouseForeground: white", "*delay: 10000", "*count: 300", "*size: 25", "*random: True", "*gravity: 0.01", "*wind: 0.00", - "*friction: 0.8", + "*elasticity: 0.97", "*timeScale: 1.0", - "*doFPS: False", "*shake: True", "*shakeThreshold: 0.015", "*doubleBuffer: True", @@ -753,41 +817,29 @@ char *defaults [] = { "*useDBE: True", "*useDBEClear: True", #endif /* HAVE_DOUBLE_BUFFER_EXTENSION */ +#ifdef HAVE_MOBILE + "*ignoreRotation: True", +#endif 0 }; -XrmOptionDescRec options [] = { +static XrmOptionDescRec fluidballs_options [] = { { "-delay", ".delay", XrmoptionSepArg, 0 }, { "-count", ".count", XrmoptionSepArg, 0 }, { "-size", ".size", XrmoptionSepArg, 0 }, { "-count", ".count", XrmoptionSepArg, 0 }, { "-gravity", ".gravity", XrmoptionSepArg, 0 }, { "-wind", ".wind", XrmoptionSepArg, 0 }, - { "-friction", ".friction", XrmoptionSepArg, 0 }, - { "-fps", ".doFPS", XrmoptionNoArg, "True" }, - { "-no-fps", ".doFPS", XrmoptionNoArg, "False" }, + { "-elasticity", ".elasticity", XrmoptionSepArg, 0 }, { "-shake", ".shake", XrmoptionNoArg, "True" }, { "-no-shake", ".shake", XrmoptionNoArg, "False" }, { "-random", ".random", XrmoptionNoArg, "True" }, + { "-no-random", ".random", XrmoptionNoArg, "False" }, { "-nonrandom", ".random", XrmoptionNoArg, "False" }, { "-db", ".doubleBuffer", XrmoptionNoArg, "True" }, { "-no-db", ".doubleBuffer", XrmoptionNoArg, "False" }, { 0, 0, 0, 0 } }; -void -screenhack (Display *dpy, Window window) -{ - b_state *state = init_balls(dpy, window); - int delay = get_integer_resource ("delay", "Integer"); - - while (1) - { - repaint_balls(state); - update_balls(state); - XSync (dpy, False); - handle_events (state); - if (delay) usleep (delay); - } -} +XSCREENSAVER_MODULE ("FluidBalls", fluidballs)