http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.tar.gz
[xscreensaver] / hacks / apple2-main.c
1 /* xscreensaver, Copyright (c) 1998-2003 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12  * with additional work by Jamie Zawinski <jwz@jwz.org>
13  */
14
15 #include <math.h>
16 #include "screenhack.h"
17 #include "apple2.h"
18 #include <X11/Xutil.h>
19 #include <X11/Intrinsic.h>
20 #include <ctype.h>
21
22 #undef countof
23 #define countof(x) (sizeof((x))/sizeof((*x)))
24
25 #define DEBUG
26
27 extern XtAppContext app;
28
29 Time subproc_relaunch_delay = 3000;
30
31
32 /* Given a bitmask, returns the position and width of the field.
33  */
34 static void
35 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
36 {
37   int i;
38   for (i = 0; i < 32; i++)
39     if (mask & (1L << i))
40       {
41         int j = 0;
42         *pos_ret = i;
43         for (; i < 32; i++, j++)
44           if (! (mask & (1L << i)))
45             break;
46         *size_ret = j;
47         return;
48       }
49 }
50
51
52 /* Given a value and a field-width, expands the field to fill out 8 bits.
53  */
54 static unsigned char
55 spread_bits (unsigned char value, unsigned char width)
56 {
57   switch (width)
58     {
59     case 8: return value;
60     case 7: return (value << 1) | (value >> 6);
61     case 6: return (value << 2) | (value >> 4);
62     case 5: return (value << 3) | (value >> 2);
63     case 4: return (value << 4) | (value);
64     case 3: return (value << 5) | (value << 2) | (value >> 2);
65     case 2: return (value << 6) | (value << 4) | (value);
66     default: abort(); break;
67     }
68 }
69
70
71 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
72    Scales it (without dithering) to WxH.
73  */
74 static void
75 scale_image (Display *dpy, Window window, XImage *in,
76              int fromx, int fromy, int fromw, int fromh,
77              unsigned int *out, int w, int h)
78 {
79   float scale;
80   int x, y, i;
81   unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
82   unsigned int rsiz=0, gsiz=0, bsiz=0;
83   unsigned int rmsk=0, gmsk=0, bmsk=0;
84   unsigned char spread_map[3][256];
85   XWindowAttributes xgwa;
86   XColor *colors = 0;
87
88   if (fromx + fromw > in->width ||
89       fromy + fromh > in->height)
90     abort();
91
92   XGetWindowAttributes (dpy, window, &xgwa);
93
94   /* Compute the field offsets for RGB decoding in the XImage,
95      when in TrueColor mode.  Otherwise we use the colormap.
96    */
97   if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
98       visual_class (xgwa.screen, xgwa.visual) == GrayScale)
99     {
100       int ncolors = visual_cells (xgwa.screen, xgwa.visual);
101       colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
102       for (i = 0; i < ncolors; i++)
103         colors[i].pixel = i;
104       XQueryColors (dpy, xgwa.colormap, colors, ncolors);
105     }
106   else
107     {
108       rmsk = xgwa.visual->red_mask;
109       gmsk = xgwa.visual->green_mask;
110       bmsk = xgwa.visual->blue_mask;
111       decode_mask (rmsk, &rpos, &rsiz);
112       decode_mask (gmsk, &gpos, &gsiz);
113       decode_mask (bmsk, &bpos, &bsiz);
114
115       for (i = 0; i < 256; i++)
116         {
117           spread_map[0][i] = spread_bits (i, rsiz);
118           spread_map[1][i] = spread_bits (i, gsiz);
119           spread_map[2][i] = spread_bits (i, bsiz);
120         }
121     }
122
123   scale = (fromw > fromh
124            ? (float) fromw / w
125            : (float) fromh / h);
126
127   /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
128    */
129   for (y = 0; y < h-1; y++)     /* iterate over dest pixels */
130     for (x = 0; x < w-1; x++)
131       {
132         int xx, yy;
133         unsigned int r=0, g=0, b=0;
134
135         int xx1 = x * scale + fromx;
136         int yy1 = y * scale + fromy;
137         int xx2 = (x+1) * scale + fromx;
138         int yy2 = (y+1) * scale + fromy;
139
140         /* Iterate over the source pixels contributing to this one, and sum. */
141         for (xx = xx1; xx < xx2; xx++)
142           for (yy = yy1; yy < yy2; yy++)
143             {
144               unsigned char rr, gg, bb;
145               unsigned long sp = ((xx > in->width || yy > in->height)
146                                   ? 0 : XGetPixel (in, xx, yy));
147               if (colors)
148                 {
149                   rr = colors[sp].red   & 0xFF;
150                   gg = colors[sp].green & 0xFF;
151                   bb = colors[sp].blue  & 0xFF;
152                 }
153               else
154                 {
155                   rr = (sp & rmsk) >> rpos;
156                   gg = (sp & gmsk) >> gpos;
157                   bb = (sp & bmsk) >> bpos;
158                   rr = spread_map[0][rr];
159                   gg = spread_map[1][gg];
160                   bb = spread_map[2][bb];
161                 }
162               r += rr;
163               g += gg;
164               b += bb;
165             }
166
167         /* Scale summed pixel values down to 8/8/8 range */
168         i = (xx2 - xx1) * (yy2 - yy1);
169         if (i < 1) i = 1;
170         r /= i;
171         g /= i;
172         b /= i;
173
174         out[y * w + x] = (r << 16) | (g << 8) | b;
175       }
176 }
177
178
179 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
180    Picks a random sub-image out of the source image, and scales it to WxH.
181  */
182 static void
183 pick_a2_subimage (Display *dpy, Window window, XImage *in,
184                   unsigned int *out, int w, int h)
185 {
186   int fromx, fromy, fromw, fromh;
187   if (in->width <= w || in->height <= h)
188     {
189       fromx = 0;
190       fromy = 0;
191       fromw = in->width;
192       fromh = in->height;
193     }
194   else
195     {
196       int dw, dh;
197       do {
198         double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
199         fromw = w * scale;
200         fromh = h * scale;
201       } while (fromw > in->width ||
202                fromh > in->height);
203
204       dw = (in->width  - fromw) / 2;   /* near the center! */
205       dh = (in->height - fromh) / 2;
206
207       fromx = (random() % dw) + (dw/2);
208       fromy = (random() % dh) + (dh/2);
209     }
210
211   scale_image (dpy, window, in,
212                fromx, fromy, fromw, fromh,
213                out, w, h);
214 }
215
216
217 /* Floyd-Steinberg dither.  Derived from ppmquant.c,
218    Copyright (c) 1989, 1991 by Jef Poskanzer.
219  */
220 static void
221 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
222 {
223   /*
224     Apple ][ color map. Each pixel can only be 1 or 0, but what that
225     means depends on whether it's an odd or even pixel, and whether
226     the high bit in the byte is set or not. If it's 0, it's always
227     black.
228    */
229   static const int a2_cmap[2][2][3] = {
230     {
231       /* hibit=0 */
232       {/* odd pixels = blue */    0x00, 0x80, 0xff},
233       {/* even pixels = red */    0xff, 0x80, 0x00}
234     },
235     {
236       /* hibit=1 */
237       {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
238       {/* odd pixels = green */   0x40, 0xff, 0x40}
239     }
240   };
241
242   int x, y;
243   unsigned int **pixels;
244   unsigned int *pP;
245   int maxval = 255;
246   long *this_rerr;
247   long *next_rerr;
248   long *this_gerr;
249   long *next_gerr;
250   long *this_berr;
251   long *next_berr;
252   long *temp_err;
253   int fs_scale = 1024;
254   int brightness = 75;
255   int fs_direction;
256
257 #if 0
258   {
259     FILE *pipe = popen ("xv -", "w");
260     fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
261     for (y = 0; y < h; y++)
262       for (x = 0; x < w; x++)
263         {
264           unsigned int p = in[y * w + x];
265           unsigned int r = (p >> 16) & 0xFF;
266           unsigned int g = (p >>  8) & 0xFF;
267           unsigned int b = (p      ) & 0xFF;
268           fprintf(pipe, "%c%c%c", r, g, b);
269         }
270     fclose (pipe);
271   }
272 #endif
273
274   /* Initialize Floyd-Steinberg error vectors. */
275   this_rerr = (long *) calloc (w + 2, sizeof(long));
276   next_rerr = (long *) calloc (w + 2, sizeof(long));
277   this_gerr = (long *) calloc (w + 2, sizeof(long));
278   next_gerr = (long *) calloc (w + 2, sizeof(long));
279   this_berr = (long *) calloc (w + 2, sizeof(long));
280   next_berr = (long *) calloc (w + 2, sizeof(long));
281
282
283   /* #### do we really need more than one element of "pixels" at once?
284    */
285   pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
286   for (y = 0; y < h; y++)
287     pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
288
289   for (x = 0; x < w + 2; ++x)
290     {
291       this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
292       this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
293       this_berr[x] = random() % (fs_scale * 2) - fs_scale;
294       /* (random errors in [-1 .. 1]) */
295     }
296   fs_direction = 1;
297
298   for (y = 0; y < h; y++)
299     for (x = 0; x < w; x++)
300       pixels[y][x] = in[y * w + x];
301
302   for (y = 0; y < h; y++)
303     {
304       int xbyte;
305       int err;
306       int prev_byte=0;
307
308       for (x = 0; x < w + 2; x++)
309         next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
310
311       /* It's too complicated to go back and forth on alternate rows,
312          so we always go left-right here. It doesn't change the result
313          very much.
314
315          For each group of 7 pixels, we have to try it both with the
316          high bit=0 and =1. For each high bit value, we add up the
317          total error and pick the best one.
318
319          Because we have to go through each group of bits twice, we
320          don't propagate the error values through this_[rgb]err since
321          it would add them twice. So we keep seperate local_[rgb]err
322          variables for propagating error within the 7-pixel group.
323       */
324
325       pP = pixels[y];
326       for (xbyte=0; xbyte<280; xbyte+=7)
327         {
328           int best_byte=0;
329           int best_error=2000000000;
330           int hibit;
331           int sr, sg, sb;
332           int r2, g2, b2;
333           int local_rerr=0, local_gerr=0, local_berr=0;
334
335           for (hibit=0; hibit<2; hibit++)
336             {
337               int byte = hibit<<7;
338               int tot_error=0;
339
340               for (x=xbyte; x<xbyte+7; x++)
341                 {
342                   int dist0, dist1;
343
344                   /* Use Floyd-Steinberg errors to adjust actual color. */
345                   sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
346                   sg = ((pP[x] >>  8) & 0xFF) * brightness/256;
347                   sb = ((pP[x]      ) & 0xFF) * brightness/256;
348                   sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
349                   sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
350                   sb += (this_berr[x + 1] + local_berr) / fs_scale;
351
352                   if  (sr < 0) sr = 0;
353                   else if  (sr > maxval) sr = maxval;
354                   if  (sg < 0) sg = 0;
355                   else if  (sg > maxval) sg = maxval;
356                   if  (sb < 0) sb = 0;
357                   else if  (sb > maxval) sb = maxval;
358
359                   /* This is the color we'd get if we set the bit 1. For 0,
360                      we get black */
361                   r2=a2_cmap[hibit][x&1][0];
362                   g2=a2_cmap[hibit][x&1][1];
363                   b2=a2_cmap[hibit][x&1][2];
364
365                   /*
366                      dist0 and dist1 are the error (Minkowski 2-metric
367                      distances in the color space) for choosing 0 and
368                      1 respectively. 0 is black, 1 is the color r2,g2,b2.
369                   */
370                   dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
371                   dist0= sr*sr + sg*sg + sb*sb;
372
373                   if (dist1<dist0)
374                     {
375                       byte |= 1 << (x-xbyte);
376                       tot_error += dist1;
377
378                       /* Wanted sr but got r2, so propagate sr-r2 */
379                       local_rerr =  (sr - r2) * fs_scale * 7/16;
380                       local_gerr =  (sg - g2) * fs_scale * 7/16;
381                       local_berr =  (sb - b2) * fs_scale * 7/16;
382                     }
383                   else
384                     {
385                       tot_error += dist0;
386
387                       /* Wanted sr but got 0, so propagate sr */
388                       local_rerr =  sr * fs_scale * 7/16;
389                       local_gerr =  sg * fs_scale * 7/16;
390                       local_berr =  sb * fs_scale * 7/16;
391                     }
392                 }
393
394               if (tot_error < best_error)
395                 {
396                   best_byte = byte;
397                   best_error = tot_error;
398                 }
399             }
400
401           /* Avoid alternating 7f and ff in all-white areas, because it makes
402              regular pink vertical lines */
403           if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
404             best_byte=prev_byte;
405           prev_byte=best_byte;
406
407         /*
408           Now that we've chosen values for all 8 bits of the byte, we
409           have to fill in the real pixel values into pP and propagate
410           all the error terms. We end up repeating a lot of the code
411           above.
412          */
413
414         for (x=xbyte; x<xbyte+7; x++)
415           {
416             int bit=(best_byte>>(x-xbyte))&1;
417             hibit=(best_byte>>7)&1;
418
419             sr = (pP[x] >> 16) & 0xFF;
420             sg = (pP[x] >>  8) & 0xFF;
421             sb = (pP[x]      ) & 0xFF;
422             sr += this_rerr[x + 1] / fs_scale;
423             sg += this_gerr[x + 1] / fs_scale;
424             sb += this_berr[x + 1] / fs_scale;
425
426             if  (sr < 0) sr = 0;
427             else if  (sr > maxval) sr = maxval;
428             if  (sg < 0) sg = 0;
429             else if  (sg > maxval) sg = maxval;
430             if  (sb < 0) sb = 0;
431             else if  (sb > maxval) sb = maxval;
432
433             r2=a2_cmap[hibit][x&1][0] * bit;
434             g2=a2_cmap[hibit][x&1][1] * bit;
435             b2=a2_cmap[hibit][x&1][2] * bit;
436
437             pP[x] = (r2<<16) | (g2<<8) | (b2);
438
439             /* Propagate Floyd-Steinberg error terms. */
440             err =  (sr - r2) * fs_scale;
441             this_rerr[x + 2] +=  (err * 7) / 16;
442             next_rerr[x    ] +=  (err * 3) / 16;
443             next_rerr[x + 1] +=  (err * 5) / 16;
444             next_rerr[x + 2] +=  (err    ) / 16;
445             err =  (sg - g2) * fs_scale;
446             this_gerr[x + 2] +=  (err * 7) / 16;
447             next_gerr[x    ] +=  (err * 3) / 16;
448             next_gerr[x + 1] +=  (err * 5) / 16;
449             next_gerr[x + 2] +=  (err    ) / 16;
450             err =  (sb - b2) * fs_scale;
451             this_berr[x + 2] +=  (err * 7) / 16;
452             next_berr[x    ] +=  (err * 3) / 16;
453             next_berr[x + 1] +=  (err * 5) / 16;
454             next_berr[x + 2] +=  (err    ) / 16;
455           }
456
457         /*
458           And put the actual byte into out.
459         */
460
461         out[y*(w/7) + xbyte/7] = best_byte;
462
463         }
464
465       temp_err  = this_rerr;
466       this_rerr = next_rerr;
467       next_rerr = temp_err;
468       temp_err  = this_gerr;
469       this_gerr = next_gerr;
470       next_gerr = temp_err;
471       temp_err  = this_berr;
472       this_berr = next_berr;
473       next_berr = temp_err;
474     }
475
476   free (this_rerr);
477   free (next_rerr);
478   free (this_gerr);
479   free (next_gerr);
480   free (this_berr);
481   free (next_berr);
482
483   for (y=0; y<h; y++)
484     free (pixels[y]);
485   free (pixels);
486
487 #if 0
488   {
489     /* let's see what we got... */
490     FILE *pipe = popen ("xv -", "w");
491     fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
492     for (y = 0; y < h; y++)
493       for (x = 0; x < w; x++)
494         {
495           unsigned int r = (pixels[y][x]>>16)&0xff;
496           unsigned int g = (pixels[y][x]>>8)&0xff;
497           unsigned int b = (pixels[y][x]>>0)&0xff;
498           fprintf(pipe, "%c%c%c", r, g, b);
499         }
500     fclose (pipe);
501   }
502 #endif
503 }
504
505
506 static unsigned char *
507 load_image (Display *dpy, Window window, char **image_filename_r)
508 {
509   XWindowAttributes xgwa;
510   Pixmap p;
511
512   int w = 280;
513   int h = 192;
514   XImage *image;
515   unsigned int  *buf32 = (unsigned int  *) calloc (w, h * 4);
516   unsigned char *buf8  = (unsigned char *) calloc (w/7, h);
517
518   if (!buf32 || !buf8)
519     {
520       fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
521       exit (1);
522     }
523
524   XGetWindowAttributes (dpy, window, &xgwa);
525   p = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
526   load_random_image (xgwa.screen, window, p, image_filename_r);
527   image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
528   XFreePixmap (dpy, p);
529   p = 0;
530
531   /* Make sure the window's background is not set to None, and get the
532      grabbed bits (if any) off it as soon as possible. */
533   XSetWindowBackground (dpy, window,
534                         get_pixel_resource ("background", "Background",
535                                             dpy, xgwa.colormap));
536   XClearWindow (dpy, window);
537
538   /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
539      image (regardless of whether it started as TrueColor/PseudoColor.)
540    */
541   pick_a2_subimage (dpy, window, image, buf32, w, h);
542
543   /* Then dither the 32bpp image to a 6-color Apple][ colormap.
544    */
545   a2_dither (buf32, buf8, w, h);
546
547   free (buf32);
548   return buf8;
549 }
550
551 \f
552 char *progclass = "Apple2";
553
554 char *defaults [] = {
555   "*mode:                  random",
556   "*duration:              20",
557   ANALOGTV_DEFAULTS
558   0
559 };
560
561 XrmOptionDescRec options [] = {
562   { "-slideshow",       ".mode",                XrmoptionNoArg, "slideshow" },
563   { "-basic",           ".mode",                XrmoptionNoArg, "basic" },
564   { "-text",            ".mode",                XrmoptionNoArg, "text" },
565   { "-program",         ".program",             XrmoptionSepArg, 0 },
566   { "-duration",        ".duration",            XrmoptionSepArg, 0 },
567   ANALOGTV_OPTIONS
568   { 0, 0, 0, 0 }
569 };
570
571 /*
572   TODO: this should load 10 images at startup time, then cycle through them
573   to avoid the pause while it loads.
574  */
575
576 void slideshow_controller(apple2_sim_t *sim, int *stepno,
577                           double *next_actiontime)
578 {
579   apple2_state_t *st=sim->st;
580   int i;
581   struct mydata {
582     int slideno;
583     int render_img_lineno;
584     u_char *render_img;
585     char *img_filename;
586   } *mine;
587
588   if (!sim->controller_data)
589     sim->controller_data = calloc(sizeof(struct mydata),1);
590   mine=(struct mydata *) sim->controller_data;
591
592   switch(*stepno) {
593
594   case 0:
595     a2_invalidate(st);
596     a2_clear_hgr(st);
597     a2_cls(st);
598     sim->typing_rate = 0.3;
599     sim->dec->powerup=0.0;
600
601     a2_goto(st, 0, 16);
602     a2_prints(st, "APPLE ][");
603     a2_goto(st,23,0);
604     a2_printc(st,']');
605
606     *next_actiontime += 4.0;
607     *stepno=10;
608
609   case 10:
610     mine->render_img = load_image (sim->dpy, sim->window, &mine->img_filename);
611     if (st->gr_mode) {
612       *stepno=30;
613     } else {
614       *stepno=20;
615     }
616     *next_actiontime += 3.0;
617     break;
618
619   case 20:
620     sim->typing="HGR\n";
621     *stepno=29;
622     break;
623
624   case 29:
625     sim->printing="]";
626     *stepno=30;
627     break;
628
629   case 30:
630     st->gr_mode=A2_GR_HIRES;
631     if (mine->img_filename) {
632       char *basename, *tmp;
633       char *s;
634
635       basename = tmp = strdup (mine->img_filename);
636       while (1)
637         {
638           char *slash = strchr(basename, '/');
639           if (!slash || !slash[1]) break;
640           basename = slash+1;
641         }
642       {
643         char *dot=strchr(basename,'.');
644         if (dot) *dot=0;
645       }
646       if (strlen(basename)>20) basename[20]=0;
647       for (s=basename; *s; s++) *s = toupper (*s);
648       sprintf(sim->typing_buf, "BLOAD %s\n", basename);
649       sim->typing = sim->typing_buf;
650
651       free(tmp);
652     } else {
653       sim->typing = "BLOAD IMAGE\n";
654     }
655     mine->render_img_lineno=0;
656
657     *stepno=35;
658     break;
659
660   case 35:
661     *next_actiontime += 0.7;
662     *stepno=40;
663     break;
664
665   case 40:
666     if (mine->render_img_lineno>=192) {
667       sim->printing="]";
668       sim->typing="POKE 49234,0\n";
669       *stepno=50;
670       return;
671     }
672
673     for (i=0; i<6 && mine->render_img_lineno<192; i++) {
674       a2_display_image_loading(st, mine->render_img,
675                                mine->render_img_lineno++);
676     }
677
678     /* The disk would have to seek every 13 sectors == 78 lines.
679        (This ain't no newfangled 16-sector operating system) */
680     if ((mine->render_img_lineno%78)==0) {
681       *next_actiontime += 0.5;
682     } else {
683       *next_actiontime += 0.08;
684     }
685     break;
686
687   case 50:
688     st->gr_mode |= A2_GR_FULL;
689     *stepno=60;
690     *next_actiontime += sim->delay;
691     break;
692
693   case 60:
694     sim->printing="]";
695     sim->typing="POKE 49235,0\n";
696     *stepno=70;
697     break;
698
699   case 70:
700     sim->printing="]";
701     st->gr_mode &= ~A2_GR_FULL;
702     if (mine->render_img) {
703       free(mine->render_img);
704       mine->render_img=NULL;
705     }
706     if (mine->img_filename) {
707       free(mine->img_filename);
708       mine->img_filename=NULL;
709     }
710     *stepno=10;
711     break;
712
713   case A2CONTROLLER_FREE:
714     free(mine->render_img);
715     free(mine->img_filename);
716     free(mine);
717     return;
718
719   }
720 }
721
722 struct terminal_controller_data {
723   FILE *pipe;
724   int pipe_id;
725   int input_available_p;
726   XtIntervalId timeout_id;
727   char curword[256];
728   unsigned char lastc;
729   int fake_nl;
730   double last_emit_time;
731 };
732
733 static void
734 subproc_cb (XtPointer closure, int *source, XtInputId *id)
735 {
736   struct terminal_controller_data *mine =
737     (struct terminal_controller_data *) closure;
738   mine->input_available_p = True;
739 }
740
741 static void
742 launch_text_generator (struct terminal_controller_data *mine)
743 {
744   char *oprogram = get_string_resource ("program", "Program");
745   char *program;
746
747   if (!oprogram || !*oprogram)
748     oprogram = FORTUNE_PROGRAM;
749
750   program = (char *) malloc (strlen (oprogram) + 10);
751
752   strcpy (program, "( ");
753   strcat (program, oprogram);
754   strcat (program, " ) 2>&1");
755
756   if (mine->pipe) abort();
757   if ((mine->pipe = popen (program, "r")))
758     {
759       if (mine->pipe_id) abort();
760       mine->pipe_id =
761         XtAppAddInput (app, fileno (mine->pipe),
762                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
763                        subproc_cb, (XtPointer) mine);
764     }
765   else
766     {
767       perror (program);
768     }
769 }
770
771 static void
772 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
773 {
774   struct terminal_controller_data *mine =
775     (struct terminal_controller_data *) closure;
776   mine->timeout_id=0;
777   launch_text_generator (mine);
778 }
779
780 static void
781 terminal_closegen(struct terminal_controller_data *mine)
782 {
783   if (mine->pipe_id) {
784     XtRemoveInput (mine->pipe_id);
785     mine->pipe_id = 0;
786   }
787   if (mine->pipe) {
788     pclose (mine->pipe);
789     mine->pipe = 0;
790   }
791   if (mine->timeout_id) {
792     XtRemoveTimeOut(mine->timeout_id);
793     mine->timeout_id=0;
794   }
795 }
796
797 static int
798 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
799 {
800   int rc;
801   if (mine->fake_nl) {
802     buf[0]='\n';
803     mine->fake_nl=0;
804     return 1;
805   }
806
807   if (!mine->input_available_p) return 0;
808
809   rc=read (fileno (mine->pipe), (void *) buf, n);
810   if (rc>0) mine->lastc=buf[rc-1];
811
812   if (rc<=0)
813     {
814       terminal_closegen(mine);
815
816       if (mine->lastc != '\n') { /* add a newline at eof if there wasn't one */
817         mine->fake_nl=1;
818       }
819
820       /* Set up a timer to re-launch the subproc in a bit. */
821       mine->timeout_id =
822         XtAppAddTimeOut(app, subproc_relaunch_delay,
823                         relaunch_generator_timer,
824                         (XtPointer) mine);
825     }
826
827   mine->input_available_p = False;
828
829   return rc;
830 }
831
832
833 /*
834   It's fun to put things like "gdb" as the command. For one, it's
835   amusing how the standard mumble (version, no warranty, it's
836   GNU/Linux dammit) occupies an entire screen on the Apple ][.
837 */
838
839 void
840 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
841 {
842   apple2_state_t *st=sim->st;
843   int c;
844   int i;
845
846   struct terminal_controller_data *mine;
847   if (!sim->controller_data)
848     sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
849   mine=(struct terminal_controller_data *) sim->controller_data;
850
851   switch(*stepno) {
852
853   case 0:
854     if (random()%2)
855       st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
856                                     showing text */
857     a2_cls(st);
858     a2_goto(st,0,16);
859     a2_prints(st, "APPLE ][");
860     a2_goto(st,2,0);
861
862     if (! mine->pipe)
863       launch_text_generator(mine);
864
865     *next_actiontime += 4.0;
866     *stepno = 10;
867     break;
868
869   case 10:
870     {
871       unsigned char buf[5];
872       int nr,nwant;
873       double elapsed;
874
875       elapsed=sim->curtime - mine->last_emit_time;
876       mine->last_emit_time=sim->curtime;
877       nwant=elapsed*25.0;
878       if (elapsed>1.0) nwant=1;
879       if (nwant<1) nwant=1;
880       if (nwant>4) nwant=4;
881
882       nr=terminal_read(mine, buf, nwant);
883       for (i=0; i<nr; i++) {
884         c=buf[i];
885         if (c < 0)
886           ;
887         else if (c >= 'a' && c <= 'z')            /* upcase lower-case chars */
888           {
889             a2_printc(st, c&0xDF);
890           }
891         else if ((c >= 'A'+128) ||                    /* upcase and blink */
892                  (c < ' ' && c != 014 &&              /* high-bit & ctl chrs */
893                   c != '\r' && c != '\n' && c!='\t'))
894           {
895             a2_printc(st, (c & 0x1F) | 0x80);
896           }
897         else if (c >= 'A' && c <= 'Z')            /* invert upper-case chars */
898           {
899             a2_printc(st, c | 0x80);
900           }
901         else {
902           a2_printc(st, c);
903         }
904       }
905     }
906     break;
907
908   case A2CONTROLLER_FREE:
909     terminal_closegen(mine);
910     free(mine);
911     return;
912   }
913 }
914
915 struct basic_controller_data {
916   int prog_line;
917   int x,y,k;
918   char **progtext;
919   int progstep;
920   char *rep_str;
921   int rep_pos;
922   double prog_start_time;
923   char error_buf[256];
924 };
925
926 /*
927   Adding more programs is easy. Just add a listing here and to all_programs,
928   then add the state machine to actually execute it to basic_controller.
929  */
930 static char *moire_program[]={
931   "10 HGR2\n",
932   "20 FOR Y = 0 TO 191 STEP 2\n",
933   "30 HCOLOR=4 : REM BLACK\n",
934   "40 HLINE 0,191-Y TO 279,Y\n",
935   "60 HCOLOR=7 : REM WHITE\n",
936   "80 HLINE 0,190-Y TO 279,Y+1\n",
937   "90 NEXT Y\n",
938   "100 FOR X = 0 TO 279 STEP 3\n",
939   "110 HCOLOR=4\n",
940   "120 HLINE 279-X,0 TO X,192\n",
941   "140 HCOLOR=7\n",
942   "150 HLINE 278-X,0 TO X+1,192\n",
943   "160 NEXT X\n",
944   NULL
945 };
946
947 static char *sinewave_program[] = {
948   "10 HGR\n",
949   "25 K=0\n",
950   "30 FOR X = 0 TO 279\n",
951   "32 HCOLOR= 0\n",
952   "35 HLINE X,0 TO X,159\n",
953   "38 HCOLOR= 3\n",
954   "40 Y = 80 + SIN(15*(X-K)/279)\n",
955   "50 HPLOT X,Y\n",
956   "60 NEXT X\n",
957   "70 K=K+4\n",
958   "80 GOTO 30\n",
959   NULL
960 };
961
962 #if 0
963 static char *dumb_program[]={
964   "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
965   "20 GOTO 10\n",
966   NULL
967 };
968 #endif
969
970 static char *random_lores_program[]={
971   "1 REM APPLE ][ SCREEN SAVER\n",
972   "10 GR\n",
973   "100 COLOR= RND(1)*16\n",
974
975   "110 X=RND(1)*40\n",
976   "120 Y1=RND(1)*48\n",
977   "130 Y2=RND(1)*48\n",
978   "140 FOR Y = Y1 TO Y2\n",
979   "150 PLOT X,Y\n",
980   "160 NEXT Y\n",
981
982   "210 Y=RND(1)*48\n",
983   "220 X1=RND(1)*40\n",
984   "230 X2=RND(1)*40\n",
985   "240 FOR X = X1 TO X2\n",
986   "250 PLOT X,Y\n",
987   "260 NEXT X\n",
988   "300 GOTO 100\n",
989
990   NULL
991 };
992
993 static char typo_map[256];
994
995 int make_typo(char *out_buf, char *orig, char *err_buf)
996 {
997   int i,j;
998   int errc;
999   int success=0;
1000   err_buf[0]=0;
1001
1002   typo_map['A']='Q';
1003   typo_map['S']='A';
1004   typo_map['D']='S';
1005   typo_map['F']='G';
1006   typo_map['G']='H';
1007   typo_map['H']='J';
1008   typo_map['J']='H';
1009   typo_map['K']='L';
1010   typo_map['L']=';';
1011
1012   typo_map['Q']='1';
1013   typo_map['W']='Q';
1014   typo_map['E']='3';
1015   typo_map['R']='T';
1016   typo_map['T']='Y';
1017   typo_map['Y']='U';
1018   typo_map['U']='Y';
1019   typo_map['I']='O';
1020   typo_map['O']='P';
1021   typo_map['P']='[';
1022
1023   typo_map['Z']='X';
1024   typo_map['X']='C';
1025   typo_map['C']='V';
1026   typo_map['V']='C';
1027   typo_map['B']='N';
1028   typo_map['N']='B';
1029   typo_map['M']='N';
1030   typo_map[',']='.';
1031   typo_map['.']=',';
1032
1033   typo_map['!']='1';
1034   typo_map['@']='2';
1035   typo_map['#']='3';
1036   typo_map['$']='4';
1037   typo_map['%']='5';
1038   typo_map['^']='6';
1039   typo_map['&']='7';
1040   typo_map['*']='8';
1041   typo_map['(']='9';
1042   typo_map[')']='0';
1043
1044   typo_map['1']='Q';
1045   typo_map['2']='W';
1046   typo_map['3']='E';
1047   typo_map['4']='R';
1048   typo_map['5']='T';
1049   typo_map['6']='Y';
1050   typo_map['7']='U';
1051   typo_map['8']='I';
1052   typo_map['9']='O';
1053   typo_map['0']='-';
1054
1055   strcpy(out_buf, orig);
1056   for (i=0; out_buf[i]; i++) {
1057     char *p = out_buf+i;
1058
1059     if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1060       break;
1061
1062     if (isalpha(p[0]) &&
1063         isalpha(p[1]) &&
1064         p[0] != p[1] &&
1065         random()%15==0)
1066       {
1067         int tmp=p[1];
1068         p[1]=p[0];
1069         p[0]=tmp;
1070         success=1;
1071         sprintf(err_buf,"?SYNTAX ERROR\n");
1072         break;
1073       }
1074
1075     if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(u_char)p[0]])) {
1076       int remain=strlen(p);
1077       int past=random()%(remain-2)+1;
1078       memmove(p+past+past, p, remain+1);
1079       p[0]=errc;
1080       for (j=0; j<past; j++) {
1081         p[past+j]=010;
1082       }
1083       break;
1084     }
1085   }
1086   return success;
1087 }
1088
1089 struct {
1090   char **progtext;
1091   int progstep;
1092 } all_programs[]={
1093   {moire_program, 100},
1094   /*{dumb_program, 200}, */
1095   {sinewave_program, 400},
1096   {random_lores_program, 500},
1097 };
1098
1099 void
1100 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1101 {
1102   apple2_state_t *st=sim->st;
1103   int i;
1104
1105   struct basic_controller_data *mine;
1106   if (!sim->controller_data)
1107     sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1108   mine=(struct basic_controller_data *) sim->controller_data;
1109
1110   switch (*stepno) {
1111   case 0:
1112     st->gr_mode=0;
1113     a2_cls(st);
1114     a2_goto(st,0,16);
1115     a2_prints(st, "APPLE ][");
1116     a2_goto(st,23,0);
1117     a2_printc(st,']');
1118     sim->typing_rate=0.2;
1119
1120     i=random()%countof(all_programs);
1121     mine->progtext=all_programs[i].progtext;
1122     mine->progstep=all_programs[i].progstep;
1123     mine->prog_line=0;
1124
1125     *next_actiontime += 1.0;
1126     *stepno=10;
1127     break;
1128
1129   case 10:
1130     if (st->cursx==0) a2_printc(st,']');
1131     if (mine->progtext[mine->prog_line]) {
1132       if (random()%4==0) {
1133         int err=make_typo(sim->typing_buf,
1134                           mine->progtext[mine->prog_line],
1135                           mine->error_buf);
1136         sim->typing=sim->typing_buf;
1137         if (err) {
1138           *stepno=11;
1139         } else {
1140           mine->prog_line++;
1141         }
1142       } else {
1143         sim->typing=mine->progtext[mine->prog_line++];
1144       }
1145     } else {
1146       *stepno=15;
1147     }
1148     break;
1149
1150   case 11:
1151     sim->printing=mine->error_buf;
1152     *stepno=12;
1153     break;
1154
1155   case 12:
1156     if (st->cursx==0) a2_printc(st,']');
1157     *next_actiontime+=1.0;
1158     *stepno=10;
1159     break;
1160
1161   case 15:
1162     sim->typing="RUN\n";
1163     mine->y=0;
1164     mine->x=0;
1165     mine->k=0;
1166     mine->prog_start_time=*next_actiontime;
1167     *stepno=mine->progstep;
1168     break;
1169
1170     /* moire_program */
1171   case 100:
1172     st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1173     for (i=0; i<24 && mine->y<192; i++)
1174       {
1175         a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1176         a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1177         mine->y += 2;
1178       }
1179     if (mine->y>=192) {
1180       mine->x = 0;
1181       *stepno = 110;
1182     }
1183     break;
1184
1185   case 110:
1186     for (i=0; i<24 && mine->x<280; i++)
1187       {
1188         a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1189         a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1190         mine->x+=3;
1191       }
1192     if (mine->x >= 280) *stepno=120;
1193     break;
1194
1195   case 120:
1196     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1197     break;
1198
1199     /* dumb_program */
1200   case 200:
1201     mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1202     for (i=0; i<30; i++) {
1203       a2_prints(st, mine->rep_str);
1204     }
1205     *stepno=210;
1206     break;
1207
1208   case 210:
1209     i=random()%strlen(mine->rep_str);
1210     while (mine->rep_pos != i) {
1211       a2_printc(st, mine->rep_str[mine->rep_pos]);
1212       mine->rep_pos++;
1213       if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1214     }
1215     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1216     break;
1217
1218     /* sinewave_program */
1219   case 400:
1220     st->gr_mode=A2_GR_HIRES;
1221     *stepno=410;
1222     break;
1223
1224   case 410:
1225     for (i=0; i<48; i++) {
1226       int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1227       a2_hline(st, 0, mine->x, 0, mine->x, 159);
1228       a2_hplot(st, 3, mine->x, y);
1229       mine->x += 1;
1230       if (mine->x>=279) {
1231         mine->x=0;
1232         mine->k+=4;
1233       }
1234     }
1235     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1236     break;
1237
1238   case 420:
1239     a2_prints(st, "]");
1240     *stepno=999;
1241     break;
1242
1243     /* random_lores_program */
1244   case 500:
1245     st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1246     a2_clear_gr(st);
1247     *stepno=510;
1248
1249   case 510:
1250     for (i=0; i<10; i++) {
1251       int color,x,y,x1,x2,y1,y2;
1252
1253       color=random()%15;
1254       x=random()%40;
1255       y1=random()%48;
1256       y2=random()%48;
1257       for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1258
1259       x1=random()%40;
1260       x2=random()%40;
1261       y=random()%48;
1262       for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1263     }
1264     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1265     break;
1266
1267   case 999:
1268     *stepno=0;
1269     break;
1270
1271   case A2CONTROLLER_FREE:
1272     free(mine);
1273     break;
1274   }
1275
1276 }
1277
1278 void (*controllers[])(apple2_sim_t *sim, int *stepno,
1279                       double *next_actiontime) = {
1280   slideshow_controller,
1281   terminal_controller,
1282   basic_controller
1283 };
1284
1285 void
1286 screenhack (Display *dpy, Window window)
1287 {
1288   int duration = get_integer_resource ("duration", "Integer");
1289   char *s;
1290   void (*controller)(apple2_sim_t *sim, int *stepno, double *next_actiontime);
1291
1292   if (duration < 1) duration = 1;
1293
1294   s = get_string_resource ("mode", "Mode");
1295   if (!s || !*s || !strcasecmp(s, "random"))
1296     controller = controllers[random() % (countof(controllers))];
1297   else if (!strcasecmp(s, "text"))
1298      controller = terminal_controller;
1299   else if (!strcasecmp(s, "slideshow"))
1300      controller = slideshow_controller;
1301   else if (!strcasecmp(s, "basic"))
1302      controller = basic_controller;
1303   else
1304     {
1305       fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1306                progname, s);
1307       exit (1);
1308     }
1309
1310   if (!get_boolean_resource ("root", "Boolean"))
1311     {
1312       XWindowAttributes xgwa;
1313       XGetWindowAttributes (dpy, window, &xgwa);
1314       XSelectInput (dpy, window,
1315                     xgwa.your_event_mask |
1316                     KeyPressMask | ButtonPressMask | ExposureMask);
1317     }
1318
1319   apple2 (dpy, window, duration, controller);
1320   XSync (dpy, False);
1321 }