xenocara/app/fvwm/modules/FvwmAnimate/FvwmAnimate.c

1278 lines
46 KiB
C

/*
* FvwmAnimate! Animation module for Fvwm
*
* Copyright (c) 1997 Frank Scheelen <scheelen@worldonline.nl>
* Copyright (c) 1996 Alfredo Kengi Kojima (kojima@inf.ufrgs.br)
* Copyright (c) 1996 Kaj Groner <kajg@mindspring.com>
* Added .steprc parsing and twisty iconify.
*
* Copyright (c) 1998 Dan Espen <dane@mk.bellcore.com>
* Changed to run under fvwm2. Lots of changes made.
* This used to only animate iconify on M_CONFIGURE_WINDOW.
* This module no longer reads M_CONFIGURE_WINDOW.
* I added args to M_ICONIFY so iconification takes one message.
* The arg parsing is completely redone using library functions.
* I also added args to M_DEICONIFY to eliminate the need to read the
* window size.
* Added AnimateResizeLines animation effect.
* Changed option "resize" to "effect", (resize still works).
* Changed effect "zoom" to "frame", (zoom still works).
* Added myfprintf double parens debugging trick.
* Changed so that commands can be sent to this module while it is
* running.
* Changed so that this module creates its own built in menu.
* Added Stop, Save commands.
* Changed so this this module uses FvwmForm for complete control on all
* settings.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <signal.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h> /* for O_WRONLY */
#include <sys/times.h> /* For timing things time() */
#include <limits.h> /* For time() */
#include "../../fvwm/module.h"
#include "../../libs/fvwmlib.h"
#include "FvwmAnimate.h"
#define AS_PI 3.14159265358979323846
static Display *dpy;
static int scr;
static Window root;
static GC DrawGC;
static char *MyName;
static int MyNameLen;
static int Channel[2];
static XColor xcol;
static unsigned long color; /* color for animation */
static XGCValues gcv;
static int animate_none = 0; /* count bypassed animations */
static char stop_recvd = 'n'; /* got stop command */
static char prior_color = 'n'; /* in case color changes */
static char running = 'n'; /* whether we are initiialized or not */
static char custom_recvd = 'n'; /* got custom command */
/* here is the old double parens trick. */
/* #define DEBUG */
#ifdef DEBUG
#define myfprintf(X) \
fprintf X;\
fflush (stderr);
#else
#define myfprintf(X)
#endif
/* #define DEBUG_ANIMATION */
#ifdef DEBUG_ANIMATION
#define myaprintf(X) \
fprintf X;\
fflush (stderr);
#else
#define myaprintf(X)
#endif
/* Macros for creating and sending commands:
CMD1X - Myname+1
CMD10 - Myname+1,MyName
CMD11 - Myname+1,MyName+1 */
#define CMD1X(TEXT) \
sprintf(cmd,TEXT,MyName+1);\
SendText(Channel,cmd,0);
#define CMD10(TEXT) \
sprintf(cmd,TEXT,MyName+1,MyName);\
SendText(Channel,cmd,0);
#define CMD11(TEXT) \
sprintf(cmd,TEXT,MyName+1,MyName+1);\
SendText(Channel,cmd,0);
static void Loop();
static void ParseOptions();
static void ParseConfigLine(char *);
static void CreateDrawGC();
static void DefineMe();
static void SaveConfig();
static void StopCmd();
static void AnimateResizeZoom(int, int, int, int, int, int, int, int);
static void AnimateResizeLines(int, int, int, int, int, int, int, int);
static void AnimateResizeFlip(int, int, int, int, int, int, int, int);
static void AnimateResizeTurn(int, int, int, int, int, int, int, int);
static void AnimateResizeRandom(int, int, int, int, int, int, int, int);
static void AnimateResizeZoom3D(int, int, int, int, int, int, int, int);
static void AnimateResizeNone(int, int, int, int, int, int, int, int);
static void AnimateResizeTwist(int, int, int, int, int, int, int, int);
static void DefineForm();
struct ASAnimate Animate = { NULL, ANIM_ITERATIONS, ANIM_DELAY,
ANIM_TWIST, ANIM_WIDTH,
AnimateResizeTwist, ANIM_TIME };
/* We now have so many effects, that I feel the need for a table. */
typedef struct AnimateEffects {
char *name;
char *alias;
void (*function)(int, int, int, int, int, int, int, int);
char *button; /* used to set custom form */
} ae;
/* Note None and Random must be the first 2 entries. */
struct AnimateEffects effects[] = {
{"None",0,AnimateResizeNone},
{"Random",0,AnimateResizeRandom},
{"Flip",0,AnimateResizeFlip},
{"Frame","Zoom",AnimateResizeZoom},
{"Frame3D","Zoom3D",AnimateResizeZoom3D},
{"Lines",0,AnimateResizeLines},
{"Turn",0,AnimateResizeTurn},
{"Twist",0,AnimateResizeTwist}
};
#define NUM_EFFECTS sizeof(effects) / sizeof(struct AnimateEffects)
/*
* This makes a twisty iconify/deiconify animation for a window, similar to
* MacOS. Parameters specify the position and the size of the initial
* window and the final window
*/
static void AnimateResizeTwist(int x, int y, int w, int h,
int fx, int fy, int fw, int fh)
{
float cx, cy, cw, ch;
float xstep, ystep, wstep, hstep;
XPoint points[5];
float angle, angle_finite, a, d;
x += w/2;
y += h/2;
fx += fw/2;
fy += fh/2;
xstep = (float)(fx-x)/Animate.iterations;
ystep = (float)(fy-y)/Animate.iterations;
wstep = (float)(fw-w)/Animate.iterations;
hstep = (float)(fh-h)/Animate.iterations;
cx = (float)x;
cy = (float)y;
cw = (float)w;
ch = (float)h;
a = atan(ch/cw);
d = sqrt((cw/2)*(cw/2)+(ch/2)*(ch/2));
angle_finite = 2*AS_PI*Animate.twist;
for (angle=0;; angle+=(float)(2*AS_PI*Animate.twist/Animate.iterations)) {
if (angle > angle_finite)
angle = angle_finite;
points[0].x = cx+cos(angle-a)*d;
points[0].y = cy+sin(angle-a)*d;
points[1].x = cx+cos(angle+a)*d;
points[1].y = cy+sin(angle+a)*d;
points[2].x = cx+cos(angle-a+AS_PI)*d;
points[2].y = cy+sin(angle-a+AS_PI)*d;
points[3].x = cx+cos(angle+a+AS_PI)*d;
points[3].y = cy+sin(angle+a+AS_PI)*d;
points[4].x = cx+cos(angle-a)*d;
points[4].y = cy+sin(angle-a)*d;
XGrabServer(dpy);
XDrawLines(dpy, root, DrawGC, points, 5, CoordModeOrigin);
XFlush(dpy);
usleep(Animate.delay*1000);
XDrawLines(dpy, root, DrawGC, points, 5, CoordModeOrigin);
XUngrabServer(dpy);
cx+=xstep;
cy+=ystep;
cw+=wstep;
ch+=hstep;
a = atan(ch/cw);
d = sqrt((cw/2)*(cw/2)+(ch/2)*(ch/2));
if (angle >= angle_finite)
break;
}
XFlush(dpy);
}
/*
* Add even more 3D feel to AfterStep by doing a flipping iconify.
* Parameters specify the position and the size of the initial and the
* final window.
*
* Idea: how about texture mapped, user definable free 3D movement
* during a resize? That should get X on its knees all right! :)
*/
void AnimateResizeFlip(int x, int y, int w, int h, int fx, int fy, int fw, int fh)
{
float cx, cy, cw, ch;
float xstep, ystep, wstep, hstep;
XPoint points[5];
float distortx;
float distortch;
float midy;
float angle, angle_finite;
xstep = (float) (fx - x) / Animate.iterations;
ystep = (float) (fy - y) / Animate.iterations;
wstep = (float) (fw - w) / Animate.iterations;
hstep = (float) (fh - h) / Animate.iterations;
cx = (float) x;
cy = (float) y;
cw = (float) w;
ch = (float) h;
angle_finite = 2 * AS_PI * Animate.twist;
for (angle = 0;; angle += (float) (2 * AS_PI * Animate.twist / Animate.iterations)) {
if (angle > angle_finite)
angle = angle_finite;
distortx = (cw / 10) - ((cw / 5) * sin(angle));
distortch = (ch / 2) * cos(angle);
midy = cy + (ch / 2);
points[0].x = cx + distortx;
points[0].y = midy - distortch;
points[1].x = cx + cw - distortx;
points[1].y = points[0].y;
points[2].x = cx + cw + distortx;
points[2].y = midy + distortch;
points[3].x = cx - distortx;
points[3].y = points[2].y;
points[4].x = points[0].x;
points[4].y = points[0].y;
XGrabServer(dpy);
XDrawLines(dpy, root, DrawGC, points, 5, CoordModeOrigin);
XFlush(dpy);
usleep(Animate.delay * 1000);
XDrawLines(dpy, root, DrawGC, points, 5, CoordModeOrigin);
XUngrabServer(dpy);
cx += xstep;
cy += ystep;
cw += wstep;
ch += hstep;
if (angle >= angle_finite)
break;
}
XFlush(dpy);
}
/*
* And another one, this time around the Y-axis.
*/
void AnimateResizeTurn(int x, int y, int w, int h, int fx, int fy, int fw, int fh)
{
float cx, cy, cw, ch;
float xstep, ystep, wstep, hstep;
XPoint points[5];
float distorty;
float distortcw;
float midx;
float angle, angle_finite;
xstep = (float) (fx - x) / Animate.iterations;
ystep = (float) (fy - y) / Animate.iterations;
wstep = (float) (fw - w) / Animate.iterations;
hstep = (float) (fh - h) / Animate.iterations;
cx = (float) x;
cy = (float) y;
cw = (float) w;
ch = (float) h;
angle_finite = 2 * AS_PI * Animate.twist;
for (angle = 0;; angle += (float) (2 * AS_PI * Animate.twist / Animate.iterations)) {
if (angle > angle_finite)
angle = angle_finite;
distorty = (ch / 10) - ((ch / 5) * sin(angle));
distortcw = (cw / 2) * cos(angle);
midx = cx + (cw / 2);
points[0].x = midx - distortcw;
points[0].y = cy + distorty;
points[1].x = midx + distortcw;
points[1].y = cy - distorty;
points[2].x = points[1].x;
points[2].y = cy + ch + distorty;
points[3].x = points[0].x;
points[3].y = cy + ch - distorty;
points[4].x = points[0].x;
points[4].y = points[0].y;
XGrabServer(dpy);
XDrawLines(dpy, root, DrawGC, points, 5, CoordModeOrigin);
XFlush(dpy);
usleep(Animate.delay * 1000);
XDrawLines(dpy, root, DrawGC, points, 5, CoordModeOrigin);
XUngrabServer(dpy);
cx += xstep;
cy += ystep;
cw += wstep;
ch += hstep;
if (angle >= angle_finite)
break;
}
XFlush(dpy);
}
/*
* This makes a zooming iconify/deiconify animation for a window, like most
* any other icon animation out there. Parameters specify the position and
* the size of the initial window and the final window
*/
static void AnimateResizeZoom(int x, int y, int w, int h,
int fx, int fy, int fw, int fh)
{
float cx, cy, cw, ch;
float xstep, ystep, wstep, hstep;
int i;
xstep = (float)(fx-x)/Animate.iterations;
ystep = (float)(fy-y)/Animate.iterations;
wstep = (float)(fw-w)/Animate.iterations;
hstep = (float)(fh-h)/Animate.iterations;
cx = (float)x;
cy = (float)y;
cw = (float)w;
ch = (float)h;
for (i=0; i<Animate.iterations; i++) {
XGrabServer(dpy);
XDrawRectangle(dpy, root, DrawGC, (int)cx, (int)cy, (int)cw, (int)ch);
XFlush(dpy);
usleep(Animate.delay*1000);
XDrawRectangle(dpy, root, DrawGC, (int)cx, (int)cy, (int)cw, (int)ch);
XUngrabServer(dpy);
cx+=xstep;
cy+=ystep;
cw+=wstep;
ch+=hstep;
}
XFlush(dpy);
}
/*
* The effect of this is similar to AnimateResizeZoom but this time we
* add lines to create a 3D effect. The gotcha is that we have to do
* something different depending on the direction we are zooming in.
*
* Andy Parker <parker_andy@hotmail.com>
*/
void AnimateResizeZoom3D(int x, int y, int w, int h, int fx, int fy, int fw, int fh)
{
float cx, cy, cw, ch;
float xstep, ystep, wstep, hstep, srca, dsta;
int i;
xstep = (float) (fx - x) / Animate.iterations;
ystep = (float) (fy - y) / Animate.iterations;
wstep = (float) (fw - w) / Animate.iterations;
hstep = (float) (fh - h) / Animate.iterations;
dsta = (float) (fw + fh);
srca = (float) (w + h);
cx = (float) x;
cy = (float) y;
cw = (float) w;
ch = (float) h;
if (dsta <= srca)
/* We are going from a Window to an Icon */
{
for (i = 0; i < Animate.iterations; i++) {
XGrabServer(dpy);
XDrawRectangle(dpy, root, DrawGC, (int) cx, (int) cy, (int) cw,
(int) ch);
XDrawRectangle(dpy, root, DrawGC, (int) fx, (int) fy, (int) fw,
(int) fh);
XDrawLine(dpy, root, DrawGC, (int) cx, (int) cy, fx, fy);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw), (int) cy,
(fx + fw), fy);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw),
((int) cy + (int) ch), (fx + fw), (fy + fh));
XDrawLine(dpy, root, DrawGC, (int) cx, ((int) cy + (int) ch), fx,
(fy + fh));
XFlush(dpy);
usleep(Animate.delay);
XDrawRectangle(dpy, root, DrawGC, (int) cx, (int) cy, (int) cw,
(int) ch);
XDrawRectangle(dpy, root, DrawGC, (int) fx, (int) fy, (int) fw,
(int) fh);
XDrawLine(dpy, root, DrawGC, (int) cx, (int) cy, fx, fy);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw), (int) cy,
(fx + fw), fy);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw),
((int) cy + (int) ch), (fx + fw), (fy + fh));
XDrawLine(dpy, root, DrawGC, (int) cx, ((int) cy + (int) ch), fx,
(fy + fh));
XUngrabServer(dpy);
cx += xstep;
cy += ystep;
cw += wstep;
ch += hstep;
}
}
if (dsta > srca) {
/* We are going from an Icon to a Window */
for (i = 0; i < Animate.iterations; i++) {
XGrabServer(dpy);
XDrawRectangle(dpy, root, DrawGC, (int) cx, (int) cy, (int) cw,
(int) ch);
XDrawRectangle(dpy, root, DrawGC, x, y, w, h);
XDrawLine(dpy, root, DrawGC, (int) cx, (int) cy, x, y);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw), (int) cy,
(x + w), y);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw), ((int) cy +
(int) ch), (x + w), (y + h));
XDrawLine(dpy, root, DrawGC, (int) cx, ((int) cy + (int) ch), x,
(y + h));
XFlush(dpy);
usleep(Animate.delay);
XDrawRectangle(dpy, root, DrawGC, (int) cx, (int) cy, (int) cw,
(int) ch);
XDrawRectangle(dpy, root, DrawGC, x, y, w, h);
XDrawLine(dpy, root, DrawGC, (int) cx, (int) cy, x, y);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw), (int) cy,
(x + w), y);
XDrawLine(dpy, root, DrawGC, ((int) cx + (int) cw),
((int) cy + (int) ch), (x + w), (y + h));
XDrawLine(dpy, root, DrawGC, (int) cx, ((int) cy + (int) ch), x,
(y + h));
XUngrabServer(dpy);
cx += xstep;
cy += ystep;
cw += wstep;
ch += hstep;
}
}
XFlush(dpy);
}
/*
* This picks one of the animations and calls it.
*/
void AnimateResizeRandom(int x, int y, int w, int h, int fx, int fy, int fw, int fh)
{
/* Note, first 2 effects "None" and "Random" should never be chosen */
effects[(rand() + (x * y + w * h + fx)) % (NUM_EFFECTS - 2) + 2].function
(x, y, w, h, fx, fy, fw, fh);
}
/*
* This animation creates 4 lines from each corner of the initial window,
* to each corner of the final window.
*
* Parameters specify the position and the size of the initial window and
* the final window.
*
* Starting with x/y w/h, need to draw sets of 4 line segs from initial
* to final window.
*
* The variable "ants" controls whether each iteration is drawn and then
* erased vs. draw all the segments and then come back and erase them.
*
* Currently I have this hardcoded as the later. The word "ants" is
* used, because if "ants" is set to 0 and the number of iterations is
* high, it looks a little like ants crawling across the screen.
*/
static void AnimateResizeLines(int x, int y, int w, int h,
int fx, int fy, int fw, int fh) {
int i, j;
int ants = 1, ant_ctr;
typedef struct {
XSegment seg[4]; /* draw 4 unconnected lines */
XSegment incr[4]; /* x/y increments */
} Endpoints;
Endpoints ends[2];
/* define the array occurances */
#define UR seg[0]
#define UL seg[1]
#define LR seg[2]
#define LL seg[3]
#define BEG ends[0]
#define INC ends[1]
if (ants == 1) { /* if draw then erase */
XGrabServer(dpy); /* grab for whole animation */
}
for (ant_ctr=0;ant_ctr<=ants;ant_ctr++) {
/* Put args into arrays: */
BEG.UR.x1 = x; /* upper left */
BEG.UR.y1 = y;
/* Temporarily put width and height in Lower Left slot.
Changed to Lower Left x/y later. */
BEG.LL.x1 = w;
BEG.LL.y1 = h;
/* Use final positions to calc increments. */
INC.UR.x1 = fx;
INC.UR.y1 = fy;
INC.LL.x1 = fw;
INC.LL.y1 = fh;
/* The lines look a little better if they start and end a little in
from the edges. Allowing tuning might be overkill. */
for (i=0;i<2;i++) { /* for begin and endpoints */
if (ends[i].LL.x1 > 40) { /* if width > 40 */
ends[i].LL.x1 -=16; /* reduce width a little */
ends[i].UR.x1 += 8; /* move in a little */
}
if (ends[i].LL.y1 > 40) { /* if height > 40 */
ends[i].LL.y1 -=16; /* reduce height a little */
ends[i].UR.y1 += 8; /* move down a little */
}
/* Upper Left, Use x from Upper Right + width */
ends[i].UL.x1 = ends[i].UR.x1 + ends[i].LL.x1;
ends[i].UL.y1 = ends[i].UR.y1; /* copy y */
/* Lower Right, Use y from Upper Right + height */
ends[i].LR.x1 = ends[i].UR.x1; /* copy x */
ends[i].LR.y1 = ends[i].UR.y1 + ends[i].LL.y1;
/* Now width and height have been used, change LL to endpoints. */
ends[i].LL.x1 += ends[i].UR.x1;
ends[i].LL.y1 += ends[i].UR.y1;
}
/* Now put the increment in the end x/y slots */
for (i=0;i<4;i++) { /* for each of 4 line segs */
INC.seg[i].x2 = (INC.seg[i].x1 - BEG.seg[i].x1)/Animate.iterations;
INC.seg[i].y2 = (INC.seg[i].y1 - BEG.seg[i].y1)/Animate.iterations;
}
for (i=0; i<Animate.iterations; i++) {
for (j=0;j<4;j++) { /* all 4 line segs */
BEG.seg[j].x2 = BEG.seg[j].x1 + INC.seg[j].x2; /* calc end points */
BEG.seg[j].y2 = BEG.seg[j].y1 + INC.seg[j].y2; /* calc end points */
}
myaprintf((stderr,
"Lines %dx%d-%dx%d, %dx%d-%dx%d, %dx%d-%dx%d, %dx%d-%dx%d,\
ant_ctr %d\n",
BEG.UR.x1, BEG.UR.y1, BEG.UR.x2, BEG.UR.y2,
BEG.UL.x1, BEG.UL.y1, BEG.UL.x2, BEG.UL.y2,
BEG.LR.x1, BEG.LR.y1, BEG.LR.x2, BEG.LR.y2,
BEG.LL.x1, BEG.LL.y1, BEG.LL.x2, BEG.LL.y2, ant_ctr));
if (ants==0) {
XGrabServer(dpy);
}
XDrawSegments(dpy, root, DrawGC, BEG.seg, 4);
XFlush(dpy);
if (ant_ctr == 0) { /* only pause on draw cycle */
usleep(Animate.delay*1000);
}
if (ants==0) {
XDrawSegments(dpy, root, DrawGC, BEG.seg, 4);
XUngrabServer(dpy);
}
for (j=0;j<4;j++) { /* all 4 lines segs */
BEG.seg[j].x1 += INC.seg[j].x2; /* calc new starting point */
BEG.seg[j].y1 += INC.seg[j].y2; /* calc new starting point */
} /* end 4 lines segs */
} /* end iterations */
} /* end for ants */
if (ants == 1) { /* if draw then erase */
XUngrabServer(dpy); /* end grab for whole animation */
myaprintf((stderr,"Did ungrab\n"));
}
XFlush(dpy);
myaprintf((stderr,"Done animating\n"));
}
/*
* Animate None is set on during configuration. When set on, it causes
* this module to exit after some number of animation events. (If it
* just exited immediately, you couldn't use this module to turn it back
* on.)
*/
static void AnimateResizeNone(int x, int y, int w, int h,
int fx, int fy, int fw, int fh) {
++animate_none;
return;
}
#if 0
/*
* This makes a animation that looks like that light effect
* when you turn off an old TV.
* Used for window destruction
*
* This was commented out in Afterstep, I don't know why yet. dje.
*/
static void AnimateClose(int x, int y, int w, int h)
{
int i, step;
if (h>4) {
step = h*4/Animate.iterations;
if (step==0) {
step = 2;
}
for (i=h; i>=2; i-=step) {
XDrawRectangle(dpy, root, DrawGC, x, y, w, i);
XFlush(dpy);
usleep(ANIM_DELAY2*600);
XDrawRectangle(dpy, root, DrawGC, x, y, w, i);
y+=step/2;
}
}
if (w<2) return;
step = w*4/Animate.iterations;
if (step==0) {
step = 2;
}
for (i=w; i>=0; i-=step) {
XDrawRectangle(dpy, root, DrawGC, x, y, i, 2);
XFlush(dpy);
usleep(ANIM_DELAY2*1000);
XDrawRectangle(dpy, root, DrawGC, x, y, i, 2);
x+=step/2;
}
usleep(100000);
XFlush(dpy);
}
#endif
void DeadPipe(int foo) {
myfprintf((stderr,"Dead Pipe, errno %d\n",foo));
exit(0);
}
int main(int argc, char **argv) {
char *s;
char mask_mesg[20];
char cmd[200]; /* really big area for a command */
/* Save our program name - for error events */
if ((s=strrchr(argv[0], '/'))) /* strip path */
s++;
else /* no slash */
s = argv[0];
if(argc==7) /* if override name */
s = argv[6]; /* use arg as name */
MyNameLen=strlen(s)+1; /* account for '*' */
MyName = safemalloc(MyNameLen+1); /* account for \0 */
*MyName='*';
strcpy(MyName+1, s); /* append name */
myfprintf((stderr,"%s: Starting, argv[0] is %s, len %d\n",MyName+1,
argv[0],MyNameLen));
if ((argc != 6)&&(argc != 7)) { /* Now MyName is defined */
fprintf(stderr,"%s Version "VERSION" should only be executed by fvwm!\n",
MyName+1);
exit(1);
}
signal (SIGPIPE, DeadPipe); /* Dead pipe == Fvwm died */
Channel[0] = atoi(argv[1]);
Channel[1] = atoi(argv[2]);
dpy = XOpenDisplay("");
if (dpy==NULL) {
fprintf(stderr,"%s: could not open display\n",MyName+1);
exit(1);
}
root = DefaultRootWindow(dpy);
scr = DefaultScreen(dpy);
sprintf(cmd,"read .%s Quiet",MyName+1); /* read quiet modules config */
SendText(Channel,cmd,0);
ParseOptions(); /* get cmds fvwm2 has parsed */
sprintf(mask_mesg,"SET_MASK %lu\n",(unsigned long)
(M_ICONIFY|M_DEICONIFY
|M_LOCKONSEND|M_SENDCONFIG|M_CONFIG_INFO));
SendInfo(Channel, mask_mesg, 0); /* tell fvwm about our mask */
CreateDrawGC(); /* create initial GC */
SendText(Channel,"Nop",0);
DefineMe();
running = 'y'; /* out of initialization phase */
Loop(); /* start running */
exit (0); /* Never gets here! */
}
/*
* Wait for some event like iconify, deiconify and stuff.
*/
static void Loop() {
unsigned long header[HEADER_SIZE], *body;
int c; /* size of message body */
clock_t time_start; /* for time() */
clock_t time_end; /* for time() */
clock_t time_accum;
struct tms time_buffer; /* for time() */
char cmd[200];
myfprintf((stderr,"Starting event loop\n"));
while (1) {
c=ReadFvwmPacket(Channel[1], header, &body);
myfprintf((stderr,"Header[2] is %d, read %d\n", (int)header[2],c));
if (c>0) { /* if message has body */
switch (header[1]) { /* check message type */
case M_DEICONIFY:
if (header[2] < 15 /* If not all info needed, */
|| (int)body[5] == 0) { /* or a "noicon" icon */
break; /* don't animate it */
}
if (Animate.time != 0) {
time_start = times(&time_buffer);
}
Animate.resize((int)body[3], /* t->icon_x_loc */
(int)body[4], /* t->icon_y_loc */
(int)body[5], /* t->icon_p_width */
(int)body[6], /* t->icon_p_height */
(int)body[7], /* t->frame_x */
(int)body[8], /* t->frame_y */
(int)body[9], /* t->frame_width */
(int)body[10]); /* t->frame_height */
if (Animate.time != 0) {
time_end = times(&time_buffer);
time_accum = time_end - time_start;
}
myaprintf((stderr,
"DE_Iconify, args %d+%d+%dx%d %d+%d+%dx%d. took %dx%d\n",
(int)body[3], /* t->icon_x_loc */
(int)body[4], /* t->icon_y_loc */
(int)body[5], /* t->icon_p_width */
(int)body[6], /* t->icon_p_height */
(int)body[7], /* t->frame_x */
(int)body[8], /* t->frame_y */
(int)body[9], /* t->frame_width */
(int)body[10], /* t->frame_height */
(int)time_accum,1));
#if 0
/* So far, clk_tck seems to be non-portable...dje */
/* (int)time_accum,(int)CLK_TCK)); */
#endif
break;
case M_ICONIFY:
/* In Afterstep, this logic waited for M_CONFIGURE_WINDOW
before animating. To this time, I don't know why.
(One is sent right after the other.)
*/
if (header[2] < 15 /* if not enough info */
|| (int)body[3] == -10000 /* or a transient window */
|| (int)body[5] == 0) { /* or a "noicon" icon */
break; /* don't animate it */
}
if (Animate.time != 0) {
time_start = times(&time_buffer);
}
Animate.resize((int)body[7], /* t->frame_x */
(int)body[8], /* t->frame_y */
(int)body[9], /* t->frame_width */
(int)body[10], /* t->frame_height */
(int)body[3], /* t->icon_x_loc */
(int)body[4], /* t->icon_y_loc */
(int)body[5], /* t->icon_p_width */
(int)body[6]); /* t->icon_p_height */
if (Animate.time != 0) {
time_end = times(&time_buffer);
time_accum = time_end - time_start;
}
myaprintf((stderr,
"Iconify, args %d+%d+%dx%d %d+%d+%dx%d. Took %d\n",
(int)body[7], /* t->frame_x */
(int)body[8], /* t->frame_y */
(int)body[9], /* t->frame_width */
(int)body[10], /* t->frame_height */
(int)body[3], /* t->icon_x_loc */
(int)body[4], /* t->icon_y_loc */
(int)body[5], /* t->icon_p_width */
(int)body[6],
(int)time_accum));
break;
case M_CONFIG_INFO:
myfprintf((stderr,"Got command: %s\n", (char *)&body[3]));
ParseConfigLine((char *)&body[3]);
break;
} /* end switch header */
free(body); /* get rid of message body */
} /* end message had a body */
myfprintf((stderr,"Sending unlock\n"));
SendInfo(Channel, "UNLOCK 1\n", 0); /* fvwm can continue now! */
if ((Animate.resize == AnimateResizeNone /* If no animation desired */
&& animate_none >= 1) /* and 1 animation(s) */
|| (stop_recvd == 'y')) { /* or stop cmd */
/* This still isn't perfect, if the user turns off animation,
they would expect the menu to change on the spot.
On the otherhand, the menu shouldn't change if the module is
still running.
This logic is dependent on fvwm sending iconify messages
to make this module exit. Currently it is sending messages
even when "Style NoIcon" is on for everything.
*/
StopCmd(); /* update menu */
myfprintf((stderr,"Exiting, animate none count %d, stop recvd %c\n",
animate_none, (int)stop_recvd));
exit (0); /* and stop */
} /* end stopping */
/* The execution of the custom command has to be delayed,
because we first had to send the UNLOCK response.
*/
if (custom_recvd == 'y') {
custom_recvd = 'n';
DefineForm();
CMD1X("Module FvwmForm Form%s");
}
} /* end while forever */
}
/*****************************************************************************
*
* This routine is responsible for reading and parsing the config file
* Used FvwmEvent as a model.
*
****************************************************************************/
#include <ModParse.h>
static char *table[]= {
"Color",
#define Color_arg 0
"Custom",
#define Custom_arg 1
"Delay",
#define Delay_arg 2
"Effect",
#define Effect_arg 3
"Iterations",
#define Iterations_arg 4
"Resize",
#define Resize_arg 5
"Save",
#define Save_arg 6
"Stop",
#define Stop_arg 7
"Time",
#define Time_arg 8
"Twist",
#define Twist_arg 9
"Width"
#define Width_arg 10
}; /* Keep list in alphabetic order, using binary search! */
static void ParseOptions() {
char *buf;
myfprintf((stderr,"Reading options\n"));
while (GetConfigLine(Channel,&buf), buf != NULL) {
ParseConfigLine(buf);
} /* end config lines */
} /* end function */
void ParseConfigLine(char *buf) {
char **e, *p, *q;
unsigned seed;
long curtime;
int i;
if (buf[strlen(buf)-1] == '\n') { /* if line ends with newline */
buf[strlen(buf)-1] = '\0'; /* strip off \n */
}
/* Search for MyName (normally *FvwmAnimate) */
if (strncasecmp(buf, MyName, MyNameLen) == 0) {/* If its for me */
myfprintf((stderr,"Found line for me: %s\n", buf));
p = buf+MyNameLen; /* starting point */
q = NULL;
if ((e = FindToken(p,table,char *))) { /* config option ? */
if ((strcasecmp(*e,"Stop") != 0)
&& (strcasecmp(*e,"Custom") != 0)
&& (strcasecmp(*e,"Save") != 0)) { /* no arg commands */
p+=strlen(*e); /* skip matched token */
q=GetArgument(&p);
if (!q) { /* If arg not found */
fprintf(stderr,"%s: %s%s needs a parameter\n",
MyName+1, MyName+1,*e);
return;
}
}
switch (e - (char**)table) {
case Stop_arg: /* Stop command */
if (running == 'y') { /* if not a stored command */
stop_recvd = 'y'; /* remember to stop */
}
break;
case Save_arg: /* Save command */
SaveConfig();
break;
case Custom_arg: /* Custom command */
if (running == 'y') { /* if not a stored command */
custom_recvd = 'y'; /* remember someone asked */
}
break;
case Color_arg: /* Color */
if (Animate.color) {
free(Animate.color); /* release storage holding color name */
Animate.color = 0; /* show its gone */
}
if ((strcasecmp(q,"None") != 0) /* If not color "none" */
&& (strcasecmp(q,"Black^White") != 0)
&& (strcasecmp(q,"White^Black") != 0)) {
Animate.color = (char *)strdup(q); /* make copy of name */
}
CreateDrawGC(); /* update GC */
break;
case Delay_arg: /* Delay */
Animate.delay = atoi(q);
break;
case Iterations_arg: /* Iterations */
Animate.iterations = atoi(q);
break;
case Effect_arg: /* Effect */
case Resize_arg: /* -or - Resize */
for (i=0; i < NUM_EFFECTS; i++) {
if (strcasecmp(q, effects[i].name)==0
|| (effects[i].alias
&& strcasecmp(q, effects[i].alias)==0)) {
Animate.resize = effects[i].function;
break;
} /* end match */
} /* end all effects */
if (i > NUM_EFFECTS) { /* If not found */
fprintf(stderr, "%s: Unknown Effect '%s'\n", MyName+1, q);
}
/* Logically, you would only reset these when you got a command
of None, or Random, but it doesn't really matter. */
animate_none = 0;
curtime = time(NULL);
seed = (unsigned) curtime % INT_MAX;
srand(seed);
break;
case Time_arg: /* Time in milliseconds */
Animate.time = (clock_t)atoi(q);
break;
case Twist_arg: /* Twist */
Animate.twist = atof(q);
break;
case Width_arg: /* Width */
Animate.width = atoi(q);
CreateDrawGC(); /* update GC */
break;
default:
fprintf(stderr,"%s: unknown action %s\n",MyName+1,*e);
break;
}
} else { /* Match Myname, but a space */
fprintf(stderr,"%s: unknown command: %s\n",MyName+1,buf);
}
if(q) { /* if parsed an arg */
free(q); /* free its memory */
}
} /* config line for me */
} /* end function */
/* create GC for drawing the various animations */
/* Called during start-up, and whenever the color or line width changes. */
static void CreateDrawGC() {
myfprintf((stderr,"Creating GC\n"));
if (DrawGC != NULL) {
XFreeGC(dpy,DrawGC); /* free old GC */
}
if (prior_color == 'y') { /* if there was a previous color */
/*
* Prior color if allocated is always freed. Don't worry about the
* fact that the color may not have changed.
*/
XFreeColors(dpy, DefaultColormap(dpy,scr),
&xcol.pixel,
1, /* # of pixels */
0); /* # of planes */
prior_color = 'n'; /* no more prior color */
}
color = (BlackPixel(dpy,scr) ^ WhitePixel(dpy,scr)); /* From builtins.c: */
gcv.function = GXxor; /* default is to xor the lines */
if (Animate.color) { /* if color called for */
if (XParseColor(dpy,DefaultColormap(dpy,scr),Animate.color, &xcol)) {
if (XAllocColor(dpy, DefaultColormap(dpy,scr), &xcol)) {
prior_color = 'y'; /* remember to free it */
color = xcol.pixel;
/* gcv.function = GXequiv; Afterstep used this. */
} else {
fprintf(stderr,"%s: could not allocate color '%s'\n",
MyName+1,Animate.color);
}
} else {
fprintf(stderr,"%s: could not parse color '%s'\n",
MyName+1,Animate.color);
}
}
gcv.line_width = Animate.width;
gcv.foreground = color;
gcv.background = color;
myfprintf((stderr,"Color is %ld\n",gcv.foreground));
gcv.subwindow_mode = IncludeInferiors;
DrawGC=XCreateGC(dpy, root, GCFunction|GCForeground|GCLineWidth|GCBackground
|GCSubwindowMode, &gcv);
}
/*
* Send commands to fvwm to define this modules menus.
*
* When I first wrote this, I thought it might be a good idea to call the
* menu "FvwmAnimate", just like the module name. To my surprise, I
* found that fvwm2 treats menus just like functions. In fact I could no
* longer start FvwmAnimate because it kept finding the menu instead of
* the function. This probably should be fixed, but for now, the
* generated menu is called "MenuFvwmAnimate", or "Menu<ModuleAlias>".
* dje, 10/11/98.
*/
static void DefineMe() {
char cmd[200]; /* really big area for a command */
myfprintf((stderr,"defining menu\n"));
CMD1X("DestroyMenu Menu%s");
CMD1X("DestroyMenu MenuIterations%s");
CMD1X("DestroyMenu MenuEffects%s");
CMD1X("DestroyMenu MenuWidth%s");
CMD1X("DestroyMenu MenuTwist%s");
CMD1X("DestroyMenu MenuDelay%s");
CMD1X("DestroyMenu MenuColor%s");
CMD1X("AddToMenu Menu%s \"Animation Main Menu\" Title");
CMD11("AddToMenu Menu%s \"&E. Effects\" Popup MenuEffects%s");
CMD11("AddToMenu Menu%s \"&I. Iterations\" Popup MenuIterations%s");
CMD11("AddToMenu Menu%s \"&T. Twists\" Popup MenuTwist%s");
CMD11("AddToMenu Menu%s \"&L. Line Width\" Popup MenuWidth%s");
CMD11("AddToMenu Menu%s \"&D. Delays\" Popup MenuDelay%s");
CMD11("AddToMenu Menu%s \"&X. Color for XOR\" Popup MenuColor%s");
CMD10("AddToMenu Menu%s \"&C. Custom Settings\" %sCustom");
CMD10("AddToMenu Menu%s \"&S. Save Config\" %sSave");
CMD10("AddToMenu Menu%s \"&Z. Stop Animation\" %sStop");
CMD11("AddToMenu Menu%s \"&R. Restart Animation\" FuncRestart%s");
/* Define function for stopping and restarting Animation. */
CMD1X("DestroyFunc FuncRestart%s");
CMD10("AddToFunc FuncRestart%s \"I\" %sStop");
CMD11("AddToFunc FuncRestart%s \"I\" Module FvwmAnimate %s");
/* Define the sub menus. */
CMD1X("AddToMenu MenuIterations%s \"Iterations\" Title");
CMD10("AddToMenu MenuIterations%s \"&1. Iterations 1\" %sIterations 1");
CMD10("AddToMenu MenuIterations%s \"&2. Iterations 2\" %sIterations 2");
CMD10("AddToMenu MenuIterations%s \"&3. Iterations 4\" %sIterations 4");
CMD10("AddToMenu MenuIterations%s \"&4. Iterations 8\" %sIterations 8");
CMD10("AddToMenu MenuIterations%s \"&5. Iterations 16\" %sIterations 16");
CMD10("AddToMenu MenuIterations%s \"&6. Iterations 32\" %sIterations 32");
CMD1X("AddToMenu MenuWidth%s \"Line Width\" Title");
CMD10("AddToMenu MenuWidth%s \"&1. Line Width 0 (fast)\" %sWidth 0");
CMD10("AddToMenu MenuWidth%s \"&2. Line Width 1\" %sWidth 1");
CMD10("AddToMenu MenuWidth%s \"&3. Line Width 2\" %sWidth 2");
CMD10("AddToMenu MenuWidth%s \"&4. Line Width 4\" %sWidth 4");
CMD10("AddToMenu MenuWidth%s \"&5. Line Width 6\" %sWidth 6");
CMD10("AddToMenu MenuWidth%s \"&6. Line Width 8\" %sWidth 8");
CMD1X("AddToMenu MenuTwist%s \"Twists (Twist, Turn, Flip only)\" Title");
CMD10("AddToMenu MenuTwist%s \"&1. Twist .25\" %sTwist .25");
CMD10("AddToMenu MenuTwist%s \"&2. Twist .50\" %sTwist .50");
CMD10("AddToMenu MenuTwist%s \"&3. Twist 1\" %sTwist 1");
CMD10("AddToMenu MenuTwist%s \"&4. Twist 2\" %sTwist 2");
CMD1X("AddToMenu MenuDelay%s \"Delays\" Title");
CMD10("AddToMenu MenuDelay%s \"&1. Delay 1/1000 sec\" %sDelay 1");
CMD10("AddToMenu MenuDelay%s \"&2. Delay 1/100 sec\" %sDelay 10");
CMD10("AddToMenu MenuDelay%s \"&3. Delay 1/10 sec\" %sDelay 100");
/* Same as the colors at the front of the colorlimiting table */
CMD1X("AddToMenu MenuColor%s \"Colors\" Title");
CMD10("AddToMenu MenuColor%s \"&1. Color Black^White\" %sColor None");
CMD10("AddToMenu MenuColor%s \"&2. Color White\" %sColor white");
CMD10("AddToMenu MenuColor%s \"&3. Color Black\" %sColor black");
CMD10("AddToMenu MenuColor%s \"&4. Color Grey\" %sColor grey");
CMD10("AddToMenu MenuColor%s \"&5. Color Green\" %sColor green");
CMD10("AddToMenu MenuColor%s \"&6. Color Blue\" %sColor blue");
CMD10("AddToMenu MenuColor%s \"&7. Color Red\" %sColor red");
CMD10("AddToMenu MenuColor%s \"&8. Color Cyan\" %sColor cyan");
CMD10("AddToMenu MenuColor%s \"&9. Color Yellow\" %sColor yellow");
CMD10("AddToMenu MenuColor%s \"&A. Color Magenta\" %sColor magenta");
CMD10("AddToMenu MenuColor%s \"&B. Color DodgerBlue\" %sColor DodgerBlue");
CMD10("AddToMenu MenuColor%s \"&C. Color SteelBlue\" %sColor SteelBlue");
CMD10("AddToMenu MenuColor%s \"&D. Color Chartreuse\" %sColor chartreuse");
CMD10("AddToMenu MenuColor%s \"&E. Color Wheat\" %sColor wheat");
CMD10("AddToMenu MenuColor%s \"&F. Color Turquoise\" %sColor turquoise");
CMD1X("AddToMenu MenuEffects%s \"Effects\" Title");
CMD10("AddToMenu MenuEffects%s \"&1. Effect Random\" %sEffect Random");
CMD10("AddToMenu MenuEffects%s \"&2. Effect Flip\" %sEffect Flip");
CMD10("AddToMenu MenuEffects%s \"&3. Effect Frame\" %sEffect Frame");
CMD10("AddToMenu MenuEffects%s \"&4. Effect Frame3D\" %sEffect Frame3D");
CMD10("AddToMenu MenuEffects%s \"&5. Effect Lines\" %sEffect Lines");
CMD10("AddToMenu MenuEffects%s \"&6. Effect Turn\" %sEffect Turn");
CMD10("AddToMenu MenuEffects%s \"&7. Effect Twist\" %sEffect Twist");
CMD10("AddToMenu MenuEffects%s \"&N. Effect None\" %sEffect None");
/* Still to be done:
Use of FvwmForms for Help. (Need to fix line spacing in FvwmForms first).
*/
}
/* Write the current config into a file. */
static void SaveConfig() {
FILE *config_file;
int i;
char filename[100]; /* more than enough room */
char msg[200]; /* even more room for msg */
/* Need to use logic to create fully qualified file name same as in
read.c, right now, this logic only works well if fvwm is started
from the users home directory.
*/
sprintf(filename,".%s",MyName+1);
config_file = fopen(filename,"w");
if (config_file == NULL) {
sprintf(msg,
"%s: Open config file <%s> for write failed. \
Save not done! Error\n",
MyName+1, filename);
perror(msg);
return;
}
fprintf(config_file,"# This file created by %s\n\n",MyName+1);
for (i=0; i < NUM_EFFECTS; i++) {
if (effects[i].function == Animate.resize) {
fprintf(config_file,"*%sEffect %s\n",MyName+1,effects[i].name);
break;
} /* found match */
} /* all possible effects */
fprintf(config_file,"*%sIterations %d\n",MyName+1,Animate.iterations);
fprintf(config_file,"*%sWidth %d\n",MyName+1,Animate.width);
fprintf(config_file,"*%sTwist %f\n",MyName+1,Animate.twist);
fprintf(config_file,"*%sDelay %d\n",MyName+1,Animate.delay);
if (Animate.color) {
fprintf(config_file,"*%sColor %s\n",MyName+1,Animate.color);
}
/* Note, add "time" if that ever works. dje. 10/14/98 */
fclose(config_file);
}
/* Stop is different than KillModule in that it gives this module a chance to
alter the builtin menu before it exits.
*/
static void StopCmd() {
char cmd[200];
myfprintf((stderr,"%s: Defining startup menu in preparation for stop\n",
MyName+1));
CMD1X("DestroyMenu Menu%s");
CMD11("AddToMenu Menu%s \"%s\" Title");
CMD11("AddToMenu Menu%s \"&0. Start FvwmAnimate\" Module %s");
}
static void DefineForm() {
int i;
char cmd[200];
myfprintf((stderr,"Defining form Form%s\n", MyName+1));
CMD1X("DestroyModuleConfig Form%s*");
CMD1X("*Form%sWarpPointer");
CMD1X("*Form%sFont 9x15bold");
CMD1X("*Form%sButtonFont 8x13bold");
CMD1X("*Form%sInputFont 9x15bold");
CMD1X("*Form%sFore white");
CMD1X("*Form%sBack cornflowerblue");
CMD1X("*Form%sItemFore green");
CMD1X("*Form%sLine center");
CMD11("*Form%sText \"Custom settings for %s\"");
CMD1X("*Form%sLine left");
CMD1X("*Form%sText \"\"");
CMD1X("*Form%sLine left");
CMD1X("*Form%sFore Black");
CMD1X("*Form%sText \"Effect:\"");
CMD1X("*Form%sSelection meth single");
for (i=0; i < NUM_EFFECTS; i++) { /* for all effects */
effects[i].button="off"; /* init the button setting */
if (Animate.resize == effects[i].function) { /* compare to curr setting */
effects[i].button="on"; /* turn on one button */
} /* end if curr setting */
} /* end all buttons */
/* Macro for a command with one var */
#define CMD1V(TEXT,VAR) \
sprintf(cmd,TEXT,MyName+1,VAR);\
SendText(Channel,cmd,0);
/* There is a cleaner way (using the array) for this...dje */
CMD1V("*Form%sChoice RANDOM RANDOM %s \"Random\"",effects[1].button);
CMD1V("*Form%sChoice FLIP FLIP %s \"Flip\"",effects[2].button);
CMD1V("*Form%sChoice FRAME FRAME %s \"Frame\"",effects[3].button);
CMD1V("*Form%sChoice FRAME3D FRAME3D %s \"Frame3d\"",effects[4].button);
CMD1V("*Form%sChoice LINES LINES %s \"Lines\"",effects[5].button);
CMD1V("*Form%sChoice TURN TURN %s \"Turn\"",effects[6].button);
CMD1V("*Form%sChoice TWIST TWIST %s \"Twist\"",effects[7].button);
CMD1X("*Form%sLine left");
CMD1X("*Form%sText \"\"");
CMD1X("*Form%sLine left");
CMD1X("*Form%sText \"Iterations:\"");
CMD1V("*Form%sInput Iterations 5 \"%d\"",Animate.iterations);
CMD1X("*Form%sText \"Twists:\"");
CMD1V("*Form%sInput Twists 10 \"%f\"",Animate.twist);
CMD1X("*Form%sText \"Linewidth:\"");
CMD1V("*Form%sInput Linewidth 3 \"%d\"",Animate.width);
CMD1X("*Form%sText \"Delays:\"");
CMD1V("*Form%sInput Delays 5 \"%d\"",Animate.delay);
CMD1X("*Form%sLine left");
CMD1X("*Form%sText \"\"");
CMD1X("*Form%sLine left");
CMD1X("*Form%sText \"Color:\"");
CMD1V("*Form%sInput Color 20 \"%s\"",
Animate.color ? Animate.color : "Black^White");
CMD1X("*Form%sLine left");
CMD1X("*Form%sText \"\"");
/*
F1 - Apply, F2 - Apply and Save, F3 - Reset, F4 - Dismiss
*/
CMD1X("*Form%sLine expand");
CMD1X("*Form%sButton continue \"F1 - Apply\" F1");
CMD11("*Form%sCommand *%sIterations $(Iterations)");
CMD11("*Form%sCommand *%sTwist $(Twists)");
CMD11("*Form%sCommand *%sWidth $(Linewidth)");
CMD11("*Form%sCommand *%sDelay $(Delays)");
CMD11("*Form%sCommand *%sColor $(Color)");
CMD11("*Form%sCommand *%sEffect $(RANDOM?Random)\
$(FLIP?Flip)\
$(FRAME?Frame)\
$(FRAME3D?Frame3d)\
$(LINES?Lines)\
$(TURN?Turn)\
$(TWIST?Twist)");
CMD1X("*Form%sButton continue \"F2 - Apply & Save\" F2");
CMD11("*Form%sCommand *%sIterations $(Iterations)");
CMD11("*Form%sCommand *%sTwist $(Twists)");
CMD11("*Form%sCommand *%sWidth $(Linewidth)");
CMD11("*Form%sCommand *%sColor $(Color)");
CMD11("*Form%sCommand *%sEffect $(RANDOM?Random)\
$(FLIP?Flip)\
$(FRAME?Frame)\
$(FRAME3D?Frame3d)\
$(LINES?Lines)\
$(TURN?Turn)\
$(TWIST?Twist)");
CMD11("*Form%sCommand *%sSave");
CMD1X("*Form%sButton restart \"F3 - Reset\" F3");
CMD1X("*Form%sButton quit \"F4 - Dismiss\" F4");
CMD1X("*Form%sCommand Nop");
}