http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.23.tar.gz
[xscreensaver] / hacks / apple2-main.c
1 /* xscreensaver, Copyright (c) 1998-2005 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  * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
14  */
15
16 #include <math.h>
17 #include <ctype.h>
18 #include "screenhack.h"
19 #include "apple2.h"
20 #include <X11/Xutil.h>
21 #include <X11/Intrinsic.h>
22
23 #define XK_MISCELLANY
24 #include <X11/keysymdef.h>
25
26 #ifdef HAVE_FORKPTY
27 # include <sys/ioctl.h>
28 # ifdef HAVE_PTY_H
29 #  include <pty.h>
30 # endif
31 # ifdef HAVE_UTIL_H
32 #  include <util.h>
33 # endif
34 #endif /* HAVE_FORKPTY */
35
36 #undef countof
37 #define countof(x) (sizeof((x))/sizeof((*x)))
38
39 #define SCREEN_COLS 40
40 #define SCREEN_ROWS 24
41
42 #define DEBUG
43
44 extern XtAppContext app;
45
46 Time subproc_relaunch_delay = 3000;
47
48
49 /* Given a bitmask, returns the position and width of the field.
50  */
51 static void
52 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
53 {
54   int i;
55   for (i = 0; i < 32; i++)
56     if (mask & (1L << i))
57       {
58         int j = 0;
59         *pos_ret = i;
60         for (; i < 32; i++, j++)
61           if (! (mask & (1L << i)))
62             break;
63         *size_ret = j;
64         return;
65       }
66 }
67
68
69 /* Given a value and a field-width, expands the field to fill out 8 bits.
70  */
71 static unsigned char
72 spread_bits (unsigned char value, unsigned char width)
73 {
74   switch (width)
75     {
76     case 8: return value;
77     case 7: return (value << 1) | (value >> 6);
78     case 6: return (value << 2) | (value >> 4);
79     case 5: return (value << 3) | (value >> 2);
80     case 4: return (value << 4) | (value);
81     case 3: return (value << 5) | (value << 2) | (value >> 2);
82     case 2: return (value << 6) | (value << 4) | (value);
83     default: abort(); break;
84     }
85 }
86
87
88 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
89    Scales it (without dithering) to WxH.
90  */
91 static void
92 scale_image (Display *dpy, Window window, XImage *in,
93              int fromx, int fromy, int fromw, int fromh,
94              unsigned int *out, int w, int h)
95 {
96   float scale;
97   int x, y, i;
98   unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
99   unsigned int rsiz=0, gsiz=0, bsiz=0;
100   unsigned int rmsk=0, gmsk=0, bmsk=0;
101   unsigned char spread_map[3][256];
102   XWindowAttributes xgwa;
103   XColor *colors = 0;
104
105   if (fromx + fromw > in->width ||
106       fromy + fromh > in->height)
107     abort();
108
109   XGetWindowAttributes (dpy, window, &xgwa);
110
111   /* Compute the field offsets for RGB decoding in the XImage,
112      when in TrueColor mode.  Otherwise we use the colormap.
113    */
114   if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
115       visual_class (xgwa.screen, xgwa.visual) == GrayScale)
116     {
117       int ncolors = visual_cells (xgwa.screen, xgwa.visual);
118       colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
119       for (i = 0; i < ncolors; i++)
120         colors[i].pixel = i;
121       XQueryColors (dpy, xgwa.colormap, colors, ncolors);
122     }
123   else
124     {
125       rmsk = xgwa.visual->red_mask;
126       gmsk = xgwa.visual->green_mask;
127       bmsk = xgwa.visual->blue_mask;
128       decode_mask (rmsk, &rpos, &rsiz);
129       decode_mask (gmsk, &gpos, &gsiz);
130       decode_mask (bmsk, &bpos, &bsiz);
131
132       for (i = 0; i < 256; i++)
133         {
134           spread_map[0][i] = spread_bits (i, rsiz);
135           spread_map[1][i] = spread_bits (i, gsiz);
136           spread_map[2][i] = spread_bits (i, bsiz);
137         }
138     }
139
140   scale = (fromw > fromh
141            ? (float) fromw / w
142            : (float) fromh / h);
143
144   /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
145    */
146   for (y = 0; y < h-1; y++)     /* iterate over dest pixels */
147     for (x = 0; x < w-1; x++)
148       {
149         int xx, yy;
150         unsigned int r=0, g=0, b=0;
151
152         int xx1 = x * scale + fromx;
153         int yy1 = y * scale + fromy;
154         int xx2 = (x+1) * scale + fromx;
155         int yy2 = (y+1) * scale + fromy;
156
157         /* Iterate over the source pixels contributing to this one, and sum. */
158         for (xx = xx1; xx < xx2; xx++)
159           for (yy = yy1; yy < yy2; yy++)
160             {
161               unsigned char rr, gg, bb;
162               unsigned long sp = ((xx > in->width || yy > in->height)
163                                   ? 0 : XGetPixel (in, xx, yy));
164               if (colors)
165                 {
166                   rr = colors[sp].red   & 0xFF;
167                   gg = colors[sp].green & 0xFF;
168                   bb = colors[sp].blue  & 0xFF;
169                 }
170               else
171                 {
172                   rr = (sp & rmsk) >> rpos;
173                   gg = (sp & gmsk) >> gpos;
174                   bb = (sp & bmsk) >> bpos;
175                   rr = spread_map[0][rr];
176                   gg = spread_map[1][gg];
177                   bb = spread_map[2][bb];
178                 }
179               r += rr;
180               g += gg;
181               b += bb;
182             }
183
184         /* Scale summed pixel values down to 8/8/8 range */
185         i = (xx2 - xx1) * (yy2 - yy1);
186         if (i < 1) i = 1;
187         r /= i;
188         g /= i;
189         b /= i;
190
191         out[y * w + x] = (r << 16) | (g << 8) | b;
192       }
193 }
194
195
196 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
197    Picks a random sub-image out of the source image, and scales it to WxH.
198  */
199 static void
200 pick_a2_subimage (Display *dpy, Window window, XImage *in,
201                   unsigned int *out, int w, int h)
202 {
203   int fromx, fromy, fromw, fromh;
204   if (in->width <= w || in->height <= h)
205     {
206       fromx = 0;
207       fromy = 0;
208       fromw = in->width;
209       fromh = in->height;
210     }
211   else
212     {
213       int dw, dh;
214       do {
215         double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
216         fromw = w * scale;
217         fromh = h * scale;
218       } while (fromw > in->width ||
219                fromh > in->height);
220
221       dw = (in->width  - fromw) / 2;   /* near the center! */
222       dh = (in->height - fromh) / 2;
223
224       fromx = (random() % dw) + (dw/2);
225       fromy = (random() % dh) + (dh/2);
226     }
227
228   scale_image (dpy, window, in,
229                fromx, fromy, fromw, fromh,
230                out, w, h);
231 }
232
233
234 /* Floyd-Steinberg dither.  Derived from ppmquant.c,
235    Copyright (c) 1989, 1991 by Jef Poskanzer.
236  */
237 static void
238 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
239 {
240   /*
241     Apple ][ color map. Each pixel can only be 1 or 0, but what that
242     means depends on whether it's an odd or even pixel, and whether
243     the high bit in the byte is set or not. If it's 0, it's always
244     black.
245    */
246   static const int a2_cmap[2][2][3] = {
247     {
248       /* hibit=0 */
249       {/* odd pixels = blue */    0x00, 0x80, 0xff},
250       {/* even pixels = red */    0xff, 0x80, 0x00}
251     },
252     {
253       /* hibit=1 */
254       {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
255       {/* odd pixels = green */   0x40, 0xff, 0x40}
256     }
257   };
258
259   int x, y;
260   unsigned int **pixels;
261   unsigned int *pP;
262   int maxval = 255;
263   long *this_rerr;
264   long *next_rerr;
265   long *this_gerr;
266   long *next_gerr;
267   long *this_berr;
268   long *next_berr;
269   long *temp_err;
270   int fs_scale = 1024;
271   int brightness = 75;
272   int fs_direction;
273
274 #if 0
275   {
276     FILE *pipe = popen ("xv -", "w");
277     fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
278     for (y = 0; y < h; y++)
279       for (x = 0; x < w; x++)
280         {
281           unsigned int p = in[y * w + x];
282           unsigned int r = (p >> 16) & 0xFF;
283           unsigned int g = (p >>  8) & 0xFF;
284           unsigned int b = (p      ) & 0xFF;
285           fprintf(pipe, "%c%c%c", r, g, b);
286         }
287     fclose (pipe);
288   }
289 #endif
290
291   /* Initialize Floyd-Steinberg error vectors. */
292   this_rerr = (long *) calloc (w + 2, sizeof(long));
293   next_rerr = (long *) calloc (w + 2, sizeof(long));
294   this_gerr = (long *) calloc (w + 2, sizeof(long));
295   next_gerr = (long *) calloc (w + 2, sizeof(long));
296   this_berr = (long *) calloc (w + 2, sizeof(long));
297   next_berr = (long *) calloc (w + 2, sizeof(long));
298
299
300   /* #### do we really need more than one element of "pixels" at once?
301    */
302   pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
303   for (y = 0; y < h; y++)
304     pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
305
306   for (x = 0; x < w + 2; ++x)
307     {
308       this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
309       this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
310       this_berr[x] = random() % (fs_scale * 2) - fs_scale;
311       /* (random errors in [-1 .. 1]) */
312     }
313   fs_direction = 1;
314
315   for (y = 0; y < h; y++)
316     for (x = 0; x < w; x++)
317       pixels[y][x] = in[y * w + x];
318
319   for (y = 0; y < h; y++)
320     {
321       int xbyte;
322       int err;
323       int prev_byte=0;
324
325       for (x = 0; x < w + 2; x++)
326         next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
327
328       /* It's too complicated to go back and forth on alternate rows,
329          so we always go left-right here. It doesn't change the result
330          very much.
331
332          For each group of 7 pixels, we have to try it both with the
333          high bit=0 and =1. For each high bit value, we add up the
334          total error and pick the best one.
335
336          Because we have to go through each group of bits twice, we
337          don't propagate the error values through this_[rgb]err since
338          it would add them twice. So we keep seperate local_[rgb]err
339          variables for propagating error within the 7-pixel group.
340       */
341
342       pP = pixels[y];
343       for (xbyte=0; xbyte<280; xbyte+=7)
344         {
345           int best_byte=0;
346           int best_error=2000000000;
347           int hibit;
348           int sr, sg, sb;
349           int r2, g2, b2;
350           int local_rerr=0, local_gerr=0, local_berr=0;
351
352           for (hibit=0; hibit<2; hibit++)
353             {
354               int byte = hibit<<7;
355               int tot_error=0;
356
357               for (x=xbyte; x<xbyte+7; x++)
358                 {
359                   int dist0, dist1;
360
361                   /* Use Floyd-Steinberg errors to adjust actual color. */
362                   sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
363                   sg = ((pP[x] >>  8) & 0xFF) * brightness/256;
364                   sb = ((pP[x]      ) & 0xFF) * brightness/256;
365                   sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
366                   sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
367                   sb += (this_berr[x + 1] + local_berr) / fs_scale;
368
369                   if  (sr < 0) sr = 0;
370                   else if  (sr > maxval) sr = maxval;
371                   if  (sg < 0) sg = 0;
372                   else if  (sg > maxval) sg = maxval;
373                   if  (sb < 0) sb = 0;
374                   else if  (sb > maxval) sb = maxval;
375
376                   /* This is the color we'd get if we set the bit 1. For 0,
377                      we get black */
378                   r2=a2_cmap[hibit][x&1][0];
379                   g2=a2_cmap[hibit][x&1][1];
380                   b2=a2_cmap[hibit][x&1][2];
381
382                   /*
383                      dist0 and dist1 are the error (Minkowski 2-metric
384                      distances in the color space) for choosing 0 and
385                      1 respectively. 0 is black, 1 is the color r2,g2,b2.
386                   */
387                   dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
388                   dist0= sr*sr + sg*sg + sb*sb;
389
390                   if (dist1<dist0)
391                     {
392                       byte |= 1 << (x-xbyte);
393                       tot_error += dist1;
394
395                       /* Wanted sr but got r2, so propagate sr-r2 */
396                       local_rerr =  (sr - r2) * fs_scale * 7/16;
397                       local_gerr =  (sg - g2) * fs_scale * 7/16;
398                       local_berr =  (sb - b2) * fs_scale * 7/16;
399                     }
400                   else
401                     {
402                       tot_error += dist0;
403
404                       /* Wanted sr but got 0, so propagate sr */
405                       local_rerr =  sr * fs_scale * 7/16;
406                       local_gerr =  sg * fs_scale * 7/16;
407                       local_berr =  sb * fs_scale * 7/16;
408                     }
409                 }
410
411               if (tot_error < best_error)
412                 {
413                   best_byte = byte;
414                   best_error = tot_error;
415                 }
416             }
417
418           /* Avoid alternating 7f and ff in all-white areas, because it makes
419              regular pink vertical lines */
420           if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
421             best_byte=prev_byte;
422           prev_byte=best_byte;
423
424         /*
425           Now that we've chosen values for all 8 bits of the byte, we
426           have to fill in the real pixel values into pP and propagate
427           all the error terms. We end up repeating a lot of the code
428           above.
429          */
430
431         for (x=xbyte; x<xbyte+7; x++)
432           {
433             int bit=(best_byte>>(x-xbyte))&1;
434             hibit=(best_byte>>7)&1;
435
436             sr = (pP[x] >> 16) & 0xFF;
437             sg = (pP[x] >>  8) & 0xFF;
438             sb = (pP[x]      ) & 0xFF;
439             sr += this_rerr[x + 1] / fs_scale;
440             sg += this_gerr[x + 1] / fs_scale;
441             sb += this_berr[x + 1] / fs_scale;
442
443             if  (sr < 0) sr = 0;
444             else if  (sr > maxval) sr = maxval;
445             if  (sg < 0) sg = 0;
446             else if  (sg > maxval) sg = maxval;
447             if  (sb < 0) sb = 0;
448             else if  (sb > maxval) sb = maxval;
449
450             r2=a2_cmap[hibit][x&1][0] * bit;
451             g2=a2_cmap[hibit][x&1][1] * bit;
452             b2=a2_cmap[hibit][x&1][2] * bit;
453
454             pP[x] = (r2<<16) | (g2<<8) | (b2);
455
456             /* Propagate Floyd-Steinberg error terms. */
457             err =  (sr - r2) * fs_scale;
458             this_rerr[x + 2] +=  (err * 7) / 16;
459             next_rerr[x    ] +=  (err * 3) / 16;
460             next_rerr[x + 1] +=  (err * 5) / 16;
461             next_rerr[x + 2] +=  (err    ) / 16;
462             err =  (sg - g2) * fs_scale;
463             this_gerr[x + 2] +=  (err * 7) / 16;
464             next_gerr[x    ] +=  (err * 3) / 16;
465             next_gerr[x + 1] +=  (err * 5) / 16;
466             next_gerr[x + 2] +=  (err    ) / 16;
467             err =  (sb - b2) * fs_scale;
468             this_berr[x + 2] +=  (err * 7) / 16;
469             next_berr[x    ] +=  (err * 3) / 16;
470             next_berr[x + 1] +=  (err * 5) / 16;
471             next_berr[x + 2] +=  (err    ) / 16;
472           }
473
474         /*
475           And put the actual byte into out.
476         */
477
478         out[y*(w/7) + xbyte/7] = best_byte;
479
480         }
481
482       temp_err  = this_rerr;
483       this_rerr = next_rerr;
484       next_rerr = temp_err;
485       temp_err  = this_gerr;
486       this_gerr = next_gerr;
487       next_gerr = temp_err;
488       temp_err  = this_berr;
489       this_berr = next_berr;
490       next_berr = temp_err;
491     }
492
493   free (this_rerr);
494   free (next_rerr);
495   free (this_gerr);
496   free (next_gerr);
497   free (this_berr);
498   free (next_berr);
499
500   for (y=0; y<h; y++)
501     free (pixels[y]);
502   free (pixels);
503
504 #if 0
505   {
506     /* let's see what we got... */
507     FILE *pipe = popen ("xv -", "w");
508     fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
509     for (y = 0; y < h; y++)
510       for (x = 0; x < w; x++)
511         {
512           unsigned int r = (pixels[y][x]>>16)&0xff;
513           unsigned int g = (pixels[y][x]>>8)&0xff;
514           unsigned int b = (pixels[y][x]>>0)&0xff;
515           fprintf(pipe, "%c%c%c", r, g, b);
516         }
517     fclose (pipe);
518   }
519 #endif
520 }
521
522
523 static unsigned char *
524 load_image (Display *dpy, Window window, char **image_filename_r)
525 {
526   XWindowAttributes xgwa;
527   Pixmap p;
528
529   int w = 280;
530   int h = 192;
531   XImage *image;
532   unsigned int  *buf32 = (unsigned int  *) calloc (w, h * 4);
533   unsigned char *buf8  = (unsigned char *) calloc (w/7, h);
534
535   if (!buf32 || !buf8)
536     {
537       fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
538       exit (1);
539     }
540
541   XGetWindowAttributes (dpy, window, &xgwa);
542   p = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
543   load_random_image (xgwa.screen, window, p, image_filename_r, NULL);
544   image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
545   XFreePixmap (dpy, p);
546   p = 0;
547
548   /* Make sure the window's background is not set to None, and get the
549      grabbed bits (if any) off it as soon as possible. */
550   XSetWindowBackground (dpy, window,
551                         get_pixel_resource ("background", "Background",
552                                             dpy, xgwa.colormap));
553   XClearWindow (dpy, window);
554
555   /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
556      image (regardless of whether it started as TrueColor/PseudoColor.)
557    */
558   pick_a2_subimage (dpy, window, image, buf32, w, h);
559
560   /* Then dither the 32bpp image to a 6-color Apple][ colormap.
561    */
562   a2_dither (buf32, buf8, w, h);
563
564   free (buf32);
565   return buf8;
566 }
567
568 \f
569 char *progclass = "Apple2";
570
571 char *defaults [] = {
572   ".background:            black",
573   ".foreground:            white",
574   "*mode:                  random",
575   "*duration:              20",
576   "*program:               xscreensaver-text --cols 40",
577   "*metaSendsESC:          True",
578   "*swapBSDEL:             True",
579   "*fast:                  False",
580 # ifdef HAVE_FORKPTY
581   "*usePty:                True",
582 #else
583   "*usePty:                False",
584 # endif /* !HAVE_FORKPTY */
585
586   ANALOGTV_DEFAULTS
587   0
588 };
589
590 XrmOptionDescRec options [] = {
591   { "-slideshow",       ".mode",                XrmoptionNoArg,  "slideshow" },
592   { "-basic",           ".mode",                XrmoptionNoArg,  "basic" },
593   { "-text",            ".mode",                XrmoptionNoArg,  "text" },
594   { "-program",         ".program",             XrmoptionSepArg, 0 },
595   { "-duration",        ".duration",            XrmoptionSepArg, 0 },
596   { "-pty",             ".usePty",              XrmoptionNoArg,  "True"  },
597   { "-pipe",            ".usePty",              XrmoptionNoArg,  "False" },
598   { "-meta",            ".metaSendsESC",        XrmoptionNoArg,  "False" },
599   { "-esc",             ".metaSendsESC",        XrmoptionNoArg,  "True"  },
600   { "-bs",              ".swapBSDEL",           XrmoptionNoArg,  "False" },
601   { "-del",             ".swapBSDEL",           XrmoptionNoArg,  "True"  },
602   { "-fast",            ".fast",                XrmoptionNoArg,  "True"  },
603   ANALOGTV_OPTIONS
604   { 0, 0, 0, 0 }
605 };
606
607 /*
608   TODO: this should load 10 images at startup time, then cycle through them
609   to avoid the pause while it loads.
610  */
611
612 void slideshow_controller(apple2_sim_t *sim, int *stepno,
613                           double *next_actiontime)
614 {
615   apple2_state_t *st=sim->st;
616   int i;
617   struct mydata {
618     int slideno;
619     int render_img_lineno;
620     u_char *render_img;
621     char *img_filename;
622   } *mine;
623
624   if (!sim->controller_data)
625     sim->controller_data = calloc(sizeof(struct mydata),1);
626   mine=(struct mydata *) sim->controller_data;
627
628   switch(*stepno) {
629
630   case 0:
631     a2_invalidate(st);
632     a2_clear_hgr(st);
633     a2_cls(st);
634     sim->typing_rate = 0.3;
635     sim->dec->powerup=0.0;
636
637     a2_goto(st, 0, 16);
638     a2_prints(st, "APPLE ][");
639     a2_goto(st,23,0);
640     a2_printc(st,']');
641
642     *next_actiontime += 4.0;
643     *stepno=10;
644
645   case 10:
646     mine->render_img = load_image (sim->dpy, sim->window, &mine->img_filename);
647     if (st->gr_mode) {
648       *stepno=30;
649     } else {
650       *stepno=20;
651     }
652     *next_actiontime += 3.0;
653     break;
654
655   case 20:
656     sim->typing="HGR\n";
657     *stepno=29;
658     break;
659
660   case 29:
661     sim->printing="]";
662     *stepno=30;
663     break;
664
665   case 30:
666     st->gr_mode=A2_GR_HIRES;
667     if (mine->img_filename) {
668       char *basename, *tmp;
669       char *s;
670
671       basename = tmp = strdup (mine->img_filename);
672       while (1)
673         {
674           char *slash = strchr(basename, '/');
675           if (!slash || !slash[1]) break;
676           basename = slash+1;
677         }
678       {
679         char *dot=strchr(basename,'.');
680         if (dot) *dot=0;
681       }
682       if (strlen(basename)>20) basename[20]=0;
683       for (s=basename; *s; s++) {
684         *s = toupper (*s);
685         if (*s <= ' ') *s = '_';
686       }
687       sprintf(sim->typing_buf, "BLOAD %s\n", basename);
688       sim->typing = sim->typing_buf;
689
690       free(tmp);
691     } else {
692       sim->typing = "BLOAD IMAGE\n";
693     }
694     mine->render_img_lineno=0;
695
696     *stepno=35;
697     break;
698
699   case 35:
700     *next_actiontime += 0.7;
701     *stepno=40;
702     break;
703
704   case 40:
705     if (mine->render_img_lineno>=192) {
706       sim->printing="]";
707       sim->typing="POKE 49234,0\n";
708       *stepno=50;
709       return;
710     }
711
712     for (i=0; i<6 && mine->render_img_lineno<192; i++) {
713       a2_display_image_loading(st, mine->render_img,
714                                mine->render_img_lineno++);
715     }
716
717     /* The disk would have to seek every 13 sectors == 78 lines.
718        (This ain't no newfangled 16-sector operating system) */
719     if ((mine->render_img_lineno%78)==0) {
720       *next_actiontime += 0.5;
721     } else {
722       *next_actiontime += 0.08;
723     }
724     break;
725
726   case 50:
727     st->gr_mode |= A2_GR_FULL;
728     *stepno=60;
729     *next_actiontime += sim->delay;
730     break;
731
732   case 60:
733     sim->printing="]";
734     sim->typing="POKE 49235,0\n";
735     *stepno=70;
736     break;
737
738   case 70:
739     sim->printing="]";
740     st->gr_mode &= ~A2_GR_FULL;
741     if (mine->render_img) {
742       free(mine->render_img);
743       mine->render_img=NULL;
744     }
745     if (mine->img_filename) {
746       free(mine->img_filename);
747       mine->img_filename=NULL;
748     }
749     *stepno=10;
750     break;
751
752   case A2CONTROLLER_FREE:
753     free(mine->render_img);
754     free(mine->img_filename);
755     free(mine);
756     return;
757
758   }
759 }
760
761 #define NPAR 16
762
763 struct terminal_controller_data {
764   FILE *pipe;
765   int pipe_id;
766   pid_t pid;
767   int input_available_p;
768   XtIntervalId timeout_id;
769   char curword[256];
770   unsigned char lastc;
771   int fake_nl;
772   double last_emit_time;
773   XComposeStatus compose;
774
775   int escstate;
776   int csiparam[NPAR];
777   int curparam;
778   int cursor_x, cursor_y;
779   int saved_x,  saved_y;
780   union {
781     struct {
782       unsigned int bold : 1;
783       unsigned int blink : 1;
784       unsigned int rev : 1;
785     } bf;
786     int w;
787   } termattrib;
788   Bool meta_sends_esc_p;
789   Bool swap_bs_del_p;
790   Bool fast_p;
791
792 };
793
794 static void
795 subproc_cb (XtPointer closure, int *source, XtInputId *id)
796 {
797   struct terminal_controller_data *mine =
798     (struct terminal_controller_data *) closure;
799   mine->input_available_p = True;
800 }
801
802 static void
803 launch_text_generator (struct terminal_controller_data *mine)
804 {
805   char buf[255];
806   char *oprogram = get_string_resource ("program", "Program");
807   char *program = (char *) malloc (strlen (oprogram) + 10);
808
809   strcpy (program, "( ");
810   strcat (program, oprogram);
811   strcat (program, " ) 2>&1");
812
813   if (mine->pipe) abort();
814
815 # ifdef HAVE_FORKPTY
816   if (get_boolean_resource ("usePty", "Boolean"))
817     {
818       int fd;
819       struct winsize ws;
820
821       ws.ws_col = SCREEN_COLS;
822       ws.ws_row = SCREEN_ROWS;
823       ws.ws_xpixel = ws.ws_col * 6;
824       ws.ws_ypixel = ws.ws_row * 8;
825       
826       mine->pipe = NULL;
827       if((mine->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
828         {
829           /* Unable to fork */
830           sprintf (buf, "%.100s: forkpty", progname);
831           perror(buf);
832         }
833       else if(!mine->pid)
834         {
835           /* This is the child fork. */
836           char *av[10];
837           int i = 0;
838           if (putenv("TERM=vt100"))
839             abort();
840           av[i++] = "/bin/sh";
841           av[i++] = "-c";
842           av[i++] = oprogram;
843           av[i] = 0;
844           execvp (av[0], av);
845           sprintf (buf, "%.100s: %.100s", progname, oprogram);
846           perror(buf);
847           exit(1);
848         }
849       else
850         {
851           /* This is the parent fork. */
852           mine->pipe = fdopen(fd, "r+");
853           mine->pipe_id =
854             XtAppAddInput (app, fileno (mine->pipe),
855                            (XtPointer) (XtInputReadMask | XtInputExceptMask),
856                            subproc_cb, (XtPointer) mine);
857         }
858     }
859   else
860 # endif /* HAVE_FORKPTY */
861
862   if ((mine->pipe = popen (program, "r")))
863     {
864       if (mine->pipe_id) abort();
865       mine->pipe_id =
866         XtAppAddInput (app, fileno (mine->pipe),
867                        (XtPointer) (XtInputReadMask | XtInputExceptMask),
868                        subproc_cb, (XtPointer) mine);
869     }
870   else
871     {
872       sprintf (buf, "%.100s: %.100s", progname, program);
873       perror(buf);
874     }
875 }
876
877 static void
878 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
879 {
880   struct terminal_controller_data *mine =
881     (struct terminal_controller_data *) closure;
882   mine->timeout_id=0;
883   launch_text_generator (mine);
884 }
885
886 static void
887 terminal_closegen(struct terminal_controller_data *mine)
888 {
889   if (mine->pipe_id) {
890     XtRemoveInput (mine->pipe_id);
891     mine->pipe_id = 0;
892   }
893   if (mine->pipe) {
894     pclose (mine->pipe);
895     mine->pipe = 0;
896   }
897   if (mine->timeout_id) {
898     XtRemoveTimeOut(mine->timeout_id);
899     mine->timeout_id=0;
900   }
901 }
902
903 static int
904 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
905 {
906   int rc;
907   if (mine->fake_nl) {
908     buf[0]='\n';
909     mine->fake_nl=0;
910     return 1;
911   }
912
913   if (!mine->input_available_p) return 0;
914
915   rc=read (fileno (mine->pipe), (void *) buf, n);
916   if (rc>0) mine->lastc=buf[rc-1];
917
918   if (rc<=0)
919     {
920       terminal_closegen(mine);
921
922       if (mine->lastc != '\n') { /* add a newline at eof if there wasn't one */
923         mine->fake_nl=1;
924       }
925
926       /* Set up a timer to re-launch the subproc in a bit. */
927       mine->timeout_id =
928         XtAppAddTimeOut(app, subproc_relaunch_delay,
929                         relaunch_generator_timer,
930                         (XtPointer) mine);
931     }
932
933   mine->input_available_p = False;
934
935   return rc;
936 }
937
938
939 /* The interpretation of the ModN modifiers is dependent on what keys
940    are bound to them: Mod1 does not necessarily mean "meta".  It only
941    means "meta" if Meta_L or Meta_R are bound to it.  If Meta_L is on
942    Mod5, then Mod5 is the one that means Meta.  Oh, and Meta and Alt
943    aren't necessarily the same thing.  Icepicks in my forehead!
944  */
945 static unsigned int
946 do_icccm_meta_key_stupidity (Display *dpy)
947 {
948   unsigned int modbits = 0;
949   int i, j, k;
950   XModifierKeymap *modmap = XGetModifierMapping (dpy);
951   for (i = 3; i < 8; i++)
952     for (j = 0; j < modmap->max_keypermod; j++)
953       {
954         int code = modmap->modifiermap[i * modmap->max_keypermod + j];
955         KeySym *syms;
956         int nsyms = 0;
957         if (code == 0) continue;
958         syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
959         for (k = 0; k < nsyms; k++)
960           if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
961               syms[k] == XK_Alt_L  || syms[k] == XK_Alt_R)
962             modbits |= (1 << i);
963         XFree (syms);
964       }
965   XFreeModifiermap (modmap);
966   return modbits;
967 }
968
969 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta". 
970  */
971 static unsigned int
972 meta_modifier (Display *dpy)
973 {
974   static Bool done_once = False;
975   static unsigned int mask = 0;
976   if (!done_once)
977     {
978       /* Really, we are supposed to recompute this if a KeymapNotify
979          event comes in, but fuck it. */
980       done_once = True;
981       mask = do_icccm_meta_key_stupidity (dpy);
982     }
983   return mask;
984 }
985
986
987 static int
988 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
989 {
990   struct terminal_controller_data *mine =
991     (struct terminal_controller_data *) data;
992   KeySym keysym;
993   unsigned char c = 0;
994   XLookupString (&event->xkey, (char *) &c, 1, &keysym, &mine->compose);
995   if (c == 0 || !mine->pipe)
996     return 0;
997
998   if (!mine->swap_bs_del_p) ;
999   else if (c == 127) c = 8;
1000   else if (c == 8)   c = 127;
1001
1002   /* If meta was held down, send ESC, or turn on the high bit. */
1003   if (event->xkey.state & meta_modifier (dpy))
1004     {
1005       if (mine->meta_sends_esc_p)
1006         fputc ('\033', mine->pipe);
1007       else
1008         c |= 0x80;
1009     }
1010
1011   fputc (c, mine->pipe);
1012   fflush (mine->pipe);
1013
1014   event->xany.type = 0;  /* do not process this event further */
1015
1016   return 0;
1017 }
1018
1019
1020 static void
1021 a2_ascii_printc (apple2_state_t *st, unsigned char c,
1022                  Bool bold_p, Bool blink_p, Bool rev_p,
1023                  Bool scroll_p)
1024 {
1025   if (c >= 'a' && c <= 'z')            /* upcase lower-case chars */
1026     {
1027       c &= 0xDF;
1028     }
1029   else if ((c >= 'A'+128) ||                    /* upcase and blink */
1030            (c < ' ' && c != 014 &&              /* high-bit & ctl chrs */
1031             c != '\r' && c != '\n' && c!='\t'))
1032     {
1033       c = (c & 0x1F) | 0x80;
1034     }
1035   else if (c >= 'A' && c <= 'Z')            /* invert upper-case chars */
1036     {
1037       c |= 0x80;
1038     }
1039
1040   if (bold_p)  c |= 0xc0;
1041   if (blink_p) c = (c & ~0x40) | 0x80;
1042   if (rev_p)   c |= 0xc0;
1043
1044   if (scroll_p)
1045     a2_printc(st, c);
1046   else
1047     a2_printc_noscroll(st, c);
1048 }
1049
1050
1051 static void
1052 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
1053                  unsigned char c)
1054 {
1055   apple2_state_t *st=sim->st;
1056   int cols = SCREEN_COLS;
1057   int rows = SCREEN_ROWS;
1058
1059   int i;
1060   int start, end;
1061
1062   switch (state->escstate)
1063     {
1064     case 0:
1065       switch (c)
1066         {
1067         case 7: /* BEL */
1068           /* Dummy case - we don't want the screensaver to beep */
1069           /* #### But maybe this should flash the screen? */
1070           break;
1071         case 8: /* BS */
1072           if (state->cursor_x > 0)
1073             state->cursor_x--;
1074           break;
1075         case 9: /* HT */
1076           if (state->cursor_x < cols - 8)
1077             {
1078               state->cursor_x = (state->cursor_x & ~7) + 8;
1079             }
1080           else
1081             {
1082               state->cursor_x = 0;
1083               if (state->cursor_y < rows - 1)
1084                 state->cursor_y++;
1085               else
1086                 a2_scroll (st);
1087             }
1088           break;
1089         case 10: /* LF */
1090         case 11: /* VT */
1091         case 12: /* FF */
1092           if (state->cursor_y < rows - 1)
1093             state->cursor_y++;
1094           else
1095             a2_scroll (st);
1096           break;
1097         case 13: /* CR */
1098           state->cursor_x = 0;
1099           break;
1100         case 14: /* SO */
1101         case 15: /* SI */
1102           /* Dummy case - there is one and only one font. */
1103           break;
1104         case 24: /* CAN */
1105         case 26: /* SUB */
1106           /* Dummy case - these interrupt escape sequences, so
1107              they don't do anything in this state */
1108           break;
1109         case 27: /* ESC */
1110           state->escstate = 1;
1111           break;
1112         case 127: /* DEL */
1113           /* Dummy case - this is supposed to be ignored */
1114           break;
1115         case 155: /* CSI */
1116           state->escstate = 2;
1117           for(i = 0; i < NPAR; i++)
1118             state->csiparam[i] = 0;
1119           state->curparam = 0;
1120           break;
1121         default:
1122           /* If the cursor is in column 39 and we print a character, then
1123              that character shows up in column 39, and the cursor is no longer
1124              visible on the screen (it's in "column 40".)  If another character
1125              is printed, then that character shows up in column 0, and the
1126              cursor moves to column 1.
1127
1128              This is empirically what xterm and gnome-terminal do, so that must
1129              be the right thing.  (In xterm, the cursor vanishes, whereas; in
1130              gnome-terminal, the cursor overprints the character in col 39.)
1131            */
1132           if (state->cursor_x >= cols)
1133             {
1134               state->cursor_x = 0;
1135               if (state->cursor_y >= rows - 1)
1136                 a2_scroll (st);
1137               else
1138                 state->cursor_y++;
1139             }
1140
1141           a2_goto(st, state->cursor_y, state->cursor_x);  /* clips range */
1142           a2_ascii_printc (st, c,
1143                            state->termattrib.bf.bold,
1144                            state->termattrib.bf.blink,
1145                            state->termattrib.bf.rev,
1146                            False);
1147           state->cursor_x++;
1148
1149           break;
1150         }
1151       break;
1152     case 1:
1153       switch (c)
1154         {
1155         case 24: /* CAN */
1156         case 26: /* SUB */
1157           state->escstate = 0;
1158           break;
1159         case 'c': /* Reset */
1160           a2_cls(st);
1161           state->escstate = 0;
1162           break;
1163         case 'D': /* Linefeed */
1164           if (state->cursor_y < rows - 1)
1165             state->cursor_y++;
1166           else
1167             a2_scroll (st);
1168           state->escstate = 0;
1169           break;
1170         case 'E': /* Newline */
1171           state->cursor_x = 0;
1172           state->escstate = 0;
1173           break;
1174         case 'M': /* Reverse newline */
1175           if (state->cursor_y > 0)
1176             state->cursor_y--;
1177           state->escstate = 0;
1178           break;
1179         case '7': /* Save state */
1180           state->saved_x = state->cursor_x;
1181           state->saved_y = state->cursor_y;
1182           state->escstate = 0;
1183           break;
1184         case '8': /* Restore state */
1185           state->cursor_x = state->saved_x;
1186           state->cursor_y = state->saved_y;
1187           state->escstate = 0;
1188           break;
1189         case '[': /* CSI */
1190           state->escstate = 2;
1191           for(i = 0; i < NPAR; i++)
1192             state->csiparam[i] = 0;
1193           state->curparam = 0;
1194           break;
1195         case '%': /* Select charset */
1196           /* No, I don't support UTF-8, since the apple2 font
1197              isn't even Unicode anyway. We must still catch the
1198              last byte, though. */
1199         case '(':
1200         case ')':
1201           /* I don't support different fonts either - see above
1202              for SO and SI */
1203           state->escstate = 3;
1204           break;
1205         default:
1206           /* Escape sequences not supported:
1207            * 
1208            * H - Set tab stop
1209            * Z - Terminal identification
1210            * > - Keypad change
1211            * = - Other keypad change
1212            * ] - OS command
1213            */
1214           state->escstate = 0;
1215           break;
1216         }
1217       break;
1218     case 2:
1219       switch (c)
1220         {
1221         case 24: /* CAN */
1222         case 26: /* SUB */
1223           state->escstate = 0;
1224           break;
1225         case '0': case '1': case '2': case '3': case '4':
1226         case '5': case '6': case '7': case '8': case '9':
1227           if (state->curparam < NPAR)
1228             state->csiparam[state->curparam] =
1229               (state->csiparam[state->curparam] * 10) + (c - '0');
1230           break;
1231         case ';':
1232           state->csiparam[++state->curparam] = 0;
1233           break;
1234         case '[':
1235           state->escstate = 3;
1236           break;
1237         case '@':
1238           for (i = 0; i < state->csiparam[0]; i++)
1239             {
1240               if(++state->cursor_x > cols)
1241                 {
1242                   state->cursor_x = 0;
1243                   if (state->cursor_y < rows - 1)
1244                     state->cursor_y++;
1245                   else
1246                     a2_scroll (st);
1247                 }
1248             }
1249           state->escstate = 0;
1250           break;
1251         case 'F':
1252           state->cursor_x = 0;
1253         case 'A':
1254           if (state->csiparam[0] == 0)
1255             state->csiparam[0] = 1;
1256           if ((state->cursor_y -= state->csiparam[0]) < 0)
1257             state->cursor_y = 0;
1258           state->escstate = 0;
1259           break;
1260         case 'E':
1261           state->cursor_x = 0;
1262         case 'e':
1263         case 'B':
1264           if (state->csiparam[0] == 0)
1265             state->csiparam[0] = 1;
1266           if ((state->cursor_y += state->csiparam[0]) >= rows)
1267             state->cursor_y = rows - 1;
1268           state->escstate = 0;
1269           break;
1270         case 'a':
1271         case 'C':
1272           if (state->csiparam[0] == 0)
1273             state->csiparam[0] = 1;
1274           if ((state->cursor_x += state->csiparam[0]) >= cols)
1275             state->cursor_x = cols - 1;
1276           state->escstate = 0;
1277           break;
1278         case 'D':
1279           if (state->csiparam[0] == 0)
1280             state->csiparam[0] = 1;
1281           if ((state->cursor_x -= state->csiparam[0]) < 0)
1282             state->cursor_x = 0;
1283           state->escstate = 0;
1284           break;
1285         case 'd':
1286           if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1287             state->cursor_y = rows - 1;
1288           state->escstate = 0;
1289           break;
1290         case '`':
1291         case 'G':
1292           if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1293             state->cursor_x = cols - 1;
1294           state->escstate = 0;
1295           break;
1296         case 'f':
1297         case 'H':
1298           if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1299             state->cursor_y = rows - 1;
1300           if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1301             state->cursor_x = cols - 1;
1302           if(state->cursor_y < 0)
1303             state->cursor_y = 0;
1304           if(state->cursor_x < 0)
1305             state->cursor_x = 0;
1306           state->escstate = 0;
1307           break;
1308         case 'J':
1309           start = 0;
1310           end = rows * cols;
1311           if (state->csiparam[0] == 0)
1312             start = cols * state->cursor_y + state->cursor_x;
1313           if (state->csiparam[0] == 1)
1314             end = cols * state->cursor_y + state->cursor_x;
1315
1316           a2_goto(st, state->cursor_y, state->cursor_x);
1317           for (i = start; i < end; i++)
1318             {
1319               a2_ascii_printc(st, ' ', False, False, False, False);
1320             }
1321           state->escstate = 0;
1322           break;
1323         case 'K':
1324           start = 0;
1325           end = cols;
1326           if (state->csiparam[0] == 0)
1327             start = state->cursor_x;
1328           if (state->csiparam[1] == 1)
1329             end = state->cursor_x;
1330
1331           a2_goto(st, state->cursor_y, state->cursor_x);
1332           for (i = start; i < end; i++)
1333             {
1334               a2_ascii_printc(st, ' ', False, False, False, False);
1335             }
1336           state->escstate = 0;
1337           break;
1338         case 'm': /* Set attributes */
1339           for (i = 0; i <= state->curparam; i++)
1340             {
1341               switch(state->csiparam[i])
1342                 {
1343                 case 0:
1344                   state->termattrib.w = 0;
1345                   break;
1346                 case 1:
1347                   state->termattrib.bf.bold = 1;
1348                   break;
1349                 case 5:
1350                   state->termattrib.bf.blink = 1;
1351                   break;
1352                 case 7:
1353                   state->termattrib.bf.rev = 1;
1354                   break;
1355                 case 21:
1356                 case 22:
1357                   state->termattrib.bf.bold = 0;
1358                   break;
1359                 case 25:
1360                   state->termattrib.bf.blink = 0;
1361                   break;
1362                 case 27:
1363                   state->termattrib.bf.rev = 0;
1364                   break;
1365                 }
1366             }
1367           state->escstate = 0;
1368           break;
1369         case 's': /* Save position */
1370           state->saved_x = state->cursor_x;
1371           state->saved_y = state->cursor_y;
1372           state->escstate = 0;
1373           break;
1374         case 'u': /* Restore position */
1375           state->cursor_x = state->saved_x;
1376           state->cursor_y = state->saved_y;
1377           state->escstate = 0;
1378           break;
1379         case '?': /* DEC Private modes */
1380           if ((state->curparam != 0) || (state->csiparam[0] != 0))
1381             state->escstate = 0;
1382           break;
1383         default:
1384           /* Known unsupported CSIs:
1385            *
1386            * L - Insert blank lines
1387            * M - Delete lines (I don't know what this means...)
1388            * P - Delete characters
1389            * X - Erase characters (difference with P being...?)
1390            * c - Terminal identification
1391            * g - Clear tab stop(s)
1392            * h - Set mode (Mainly due to its complexity and lack of good
1393            docs)
1394            * l - Clear mode
1395            * m - Set mode (Phosphor is, per defenition, green on black)
1396            * n - Status report
1397            * q - Set keyboard LEDs
1398            * r - Set scrolling region (too exhausting - noone uses this,
1399            right?)
1400           */
1401           state->escstate = 0;
1402           break;
1403         }
1404       break;
1405     case 3:
1406       state->escstate = 0;
1407       break;
1408     }
1409   a2_goto(st, state->cursor_y, state->cursor_x);
1410 }
1411
1412
1413 /*
1414   It's fun to put things like "gdb" as the command. For one, it's
1415   amusing how the standard mumble (version, no warranty, it's
1416   GNU/Linux dammit) occupies an entire screen on the Apple ][.
1417 */
1418
1419 void
1420 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1421 {
1422   apple2_state_t *st=sim->st;
1423   int c;
1424   int i;
1425
1426   struct terminal_controller_data *mine;
1427   if (!sim->controller_data)
1428     sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1429   mine=(struct terminal_controller_data *) sim->controller_data;
1430
1431   mine->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
1432   mine->swap_bs_del_p    = get_boolean_resource ("swapBSDEL",    "Boolean");
1433   mine->fast_p           = get_boolean_resource ("fast",         "Boolean");
1434
1435   sim->dec->key_handler = terminal_keypress_handler;
1436   sim->dec->key_data = mine;
1437
1438   switch(*stepno) {
1439
1440   case 0:
1441     if (random()%2)
1442       st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1443                                     showing text */
1444     a2_cls(st);
1445     a2_goto(st,0,16);
1446     a2_prints(st, "APPLE ][");
1447     a2_goto(st,2,0);
1448     mine->cursor_y = 2;
1449
1450     if (! mine->pipe)
1451       launch_text_generator(mine);
1452
1453     if (! mine->fast_p)
1454       *next_actiontime += 4.0;
1455     *stepno = 10;
1456     break;
1457
1458   case 10:
1459     {
1460       unsigned char buf[1024];
1461       int nr,nwant;
1462       double elapsed;
1463
1464       elapsed=sim->curtime - mine->last_emit_time;
1465       mine->last_emit_time=sim->curtime;
1466       nwant=elapsed*25.0;
1467       if (elapsed>1.0) nwant=1;
1468       if (nwant<1) nwant=1;
1469       if (nwant>4) nwant=4;
1470
1471       if (mine->fast_p)
1472         nwant = sizeof(buf)-1;
1473
1474       nr=terminal_read(mine, buf, nwant);
1475       for (i=0; i<nr; i++) {
1476         c=buf[i];
1477
1478         if (mine->pid)
1479           a2_vt100_printc (sim, mine, c);
1480         else
1481           a2_ascii_printc (st, c, False, False, False, True);
1482       }
1483     }
1484     break;
1485
1486   case A2CONTROLLER_FREE:
1487     terminal_closegen(mine);
1488     free(mine);
1489     return;
1490   }
1491 }
1492
1493 struct basic_controller_data {
1494   int prog_line;
1495   int x,y,k;
1496   char **progtext;
1497   int progstep;
1498   char *rep_str;
1499   int rep_pos;
1500   double prog_start_time;
1501   char error_buf[256];
1502 };
1503
1504 /*
1505   Adding more programs is easy. Just add a listing here and to all_programs,
1506   then add the state machine to actually execute it to basic_controller.
1507  */
1508 static char *moire_program[]={
1509   "10 HGR2\n",
1510   "20 FOR Y = 0 TO 190 STEP 2\n",
1511   "30 HCOLOR=4 : REM BLACK\n",
1512   "40 HPLOT 0,191-Y TO 279,Y\n",
1513   "60 HCOLOR=7 : REM WHITE\n",
1514   "80 HPLOT 0,190-Y TO 279,Y+1\n",
1515   "90 NEXT Y\n",
1516   "100 FOR X = 0 TO 278 STEP 3\n",
1517   "110 HCOLOR=4\n",
1518   "120 HPLOT 279-X,0 TO X,191\n",
1519   "140 HCOLOR=7\n",
1520   "150 HPLOT 278-X,0 TO X+1,191\n",
1521   "160 NEXT X\n",
1522   NULL
1523 };
1524
1525 static char *sinewave_program[] = {
1526   "10 HGR\n",
1527   "25 K=0\n",
1528   "30 FOR X = 0 TO 279\n",
1529   "32 HCOLOR= 0\n",
1530   "35 HPLOT X,0 TO X,159\n",
1531   "38 HCOLOR= 3\n",
1532   "40 Y = 80 + SIN(15*(X-K)/279) * 40\n",
1533   "50 HPLOT X,Y\n",
1534   "60 NEXT X\n",
1535   "70 K=K+4\n",
1536   "80 GOTO 30\n",
1537   NULL
1538 };
1539
1540 #if 0
1541 static char *dumb_program[]={
1542   "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1543   "20 GOTO 10\n",
1544   NULL
1545 };
1546 #endif
1547
1548 static char *random_lores_program[]={
1549   "1 REM APPLE ][ SCREEN SAVER\n",
1550   "10 GR\n",
1551   "100 COLOR= RND(1)*16\n",
1552
1553   "110 X=RND(1)*40\n",
1554   "120 Y1=RND(1)*40\n",
1555   "130 Y2=RND(1)*40\n",
1556   "140 FOR Y = Y1 TO Y2\n",
1557   "150 PLOT X,Y\n",
1558   "160 NEXT Y\n",
1559
1560   "210 Y=RND(1)*40\n",
1561   "220 X1=RND(1)*40\n",
1562   "230 X2=RND(1)*40\n",
1563   "240 FOR X = X1 TO X2\n",
1564   "250 PLOT X,Y\n",
1565   "260 NEXT X\n",
1566   "300 GOTO 100\n",
1567
1568   NULL
1569 };
1570
1571 static char typo_map[256];
1572
1573 int make_typo(char *out_buf, char *orig, char *err_buf)
1574 {
1575   int i,j;
1576   int errc;
1577   int success=0;
1578   err_buf[0]=0;
1579
1580   typo_map['A']='Q';
1581   typo_map['S']='A';
1582   typo_map['D']='S';
1583   typo_map['F']='G';
1584   typo_map['G']='H';
1585   typo_map['H']='J';
1586   typo_map['J']='H';
1587   typo_map['K']='L';
1588   typo_map['L']=';';
1589
1590   typo_map['Q']='1';
1591   typo_map['W']='Q';
1592   typo_map['E']='3';
1593   typo_map['R']='T';
1594   typo_map['T']='Y';
1595   typo_map['Y']='U';
1596   typo_map['U']='Y';
1597   typo_map['I']='O';
1598   typo_map['O']='P';
1599   typo_map['P']='[';
1600
1601   typo_map['Z']='X';
1602   typo_map['X']='C';
1603   typo_map['C']='V';
1604   typo_map['V']='C';
1605   typo_map['B']='N';
1606   typo_map['N']='B';
1607   typo_map['M']='N';
1608   typo_map[',']='.';
1609   typo_map['.']=',';
1610
1611   typo_map['!']='1';
1612   typo_map['@']='2';
1613   typo_map['#']='3';
1614   typo_map['$']='4';
1615   typo_map['%']='5';
1616   typo_map['^']='6';
1617   typo_map['&']='7';
1618   typo_map['*']='8';
1619   typo_map['(']='9';
1620   typo_map[')']='0';
1621
1622   typo_map['1']='Q';
1623   typo_map['2']='W';
1624   typo_map['3']='E';
1625   typo_map['4']='R';
1626   typo_map['5']='T';
1627   typo_map['6']='Y';
1628   typo_map['7']='U';
1629   typo_map['8']='I';
1630   typo_map['9']='O';
1631   typo_map['0']='-';
1632
1633   strcpy(out_buf, orig);
1634   for (i=0; out_buf[i]; i++) {
1635     char *p = out_buf+i;
1636
1637     if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1638       break;
1639
1640     if (isalpha(p[0]) &&
1641         isalpha(p[1]) &&
1642         p[0] != p[1] &&
1643         random()%15==0)
1644       {
1645         int tmp=p[1];
1646         p[1]=p[0];
1647         p[0]=tmp;
1648         success=1;
1649         sprintf(err_buf,"?SYNTAX ERROR\n");
1650         break;
1651       }
1652
1653     if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(u_char)p[0]])) {
1654       int remain=strlen(p);
1655       int past=random()%(remain-2)+1;
1656       memmove(p+past+past, p, remain+1);
1657       p[0]=errc;
1658       for (j=0; j<past; j++) {
1659         p[past+j]=010;
1660       }
1661       break;
1662     }
1663   }
1664   return success;
1665 }
1666
1667 struct {
1668   char **progtext;
1669   int progstep;
1670 } all_programs[]={
1671   {moire_program, 100},
1672   /*{dumb_program, 200}, */
1673   {sinewave_program, 400},
1674   {random_lores_program, 500},
1675 };
1676
1677 void
1678 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1679 {
1680   apple2_state_t *st=sim->st;
1681   int i;
1682
1683   struct basic_controller_data *mine;
1684   if (!sim->controller_data)
1685     sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1686   mine=(struct basic_controller_data *) sim->controller_data;
1687
1688   switch (*stepno) {
1689   case 0:
1690     st->gr_mode=0;
1691     a2_cls(st);
1692     a2_goto(st,0,16);
1693     a2_prints(st, "APPLE ][");
1694     a2_goto(st,23,0);
1695     a2_printc(st,']');
1696     sim->typing_rate=0.2;
1697
1698     i=random()%countof(all_programs);
1699     mine->progtext=all_programs[i].progtext;
1700     mine->progstep=all_programs[i].progstep;
1701     mine->prog_line=0;
1702
1703     *next_actiontime += 1.0;
1704     *stepno=10;
1705     break;
1706
1707   case 10:
1708     if (st->cursx==0) a2_printc(st,']');
1709     if (mine->progtext[mine->prog_line]) {
1710       if (random()%4==0) {
1711         int err=make_typo(sim->typing_buf,
1712                           mine->progtext[mine->prog_line],
1713                           mine->error_buf);
1714         sim->typing=sim->typing_buf;
1715         if (err) {
1716           *stepno=11;
1717         } else {
1718           mine->prog_line++;
1719         }
1720       } else {
1721         sim->typing=mine->progtext[mine->prog_line++];
1722       }
1723     } else {
1724       *stepno=15;
1725     }
1726     break;
1727
1728   case 11:
1729     sim->printing=mine->error_buf;
1730     *stepno=12;
1731     break;
1732
1733   case 12:
1734     if (st->cursx==0) a2_printc(st,']');
1735     *next_actiontime+=1.0;
1736     *stepno=10;
1737     break;
1738
1739   case 15:
1740     sim->typing="RUN\n";
1741     mine->y=0;
1742     mine->x=0;
1743     mine->k=0;
1744     mine->prog_start_time=*next_actiontime;
1745     *stepno=mine->progstep;
1746     break;
1747
1748     /* moire_program */
1749   case 100:
1750     st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1751     for (i=0; i<24 && mine->y<192; i++)
1752       {
1753         a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1754         a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1755         mine->y += 2;
1756       }
1757     if (mine->y>=192) {
1758       mine->x = 0;
1759       *stepno = 110;
1760     }
1761     break;
1762
1763   case 110:
1764     for (i=0; i<24 && mine->x<280; i++)
1765       {
1766         a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1767         a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1768         mine->x+=3;
1769       }
1770     if (mine->x >= 280) *stepno=120;
1771     break;
1772
1773   case 120:
1774     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1775     break;
1776
1777     /* dumb_program */
1778   case 200:
1779     mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1780     for (i=0; i<30; i++) {
1781       a2_prints(st, mine->rep_str);
1782     }
1783     *stepno=210;
1784     break;
1785
1786   case 210:
1787     i=random()%strlen(mine->rep_str);
1788     while (mine->rep_pos != i) {
1789       a2_printc(st, mine->rep_str[mine->rep_pos]);
1790       mine->rep_pos++;
1791       if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1792     }
1793     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1794     break;
1795
1796     /* sinewave_program */
1797   case 400:
1798     st->gr_mode=A2_GR_HIRES;
1799     *stepno=410;
1800     break;
1801
1802   case 410:
1803     for (i=0; i<48; i++) {
1804       int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1805       a2_hline(st, 0, mine->x, 0, mine->x, 159);
1806       a2_hplot(st, 3, mine->x, y);
1807       mine->x += 1;
1808       if (mine->x>=279) {
1809         mine->x=0;
1810         mine->k+=4;
1811       }
1812     }
1813     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1814     break;
1815
1816   case 420:
1817     a2_prints(st, "]");
1818     *stepno=999;
1819     break;
1820
1821     /* random_lores_program */
1822   case 500:
1823     st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1824     a2_clear_gr(st);
1825     *stepno=510;
1826
1827   case 510:
1828     for (i=0; i<10; i++) {
1829       int color,x,y,x1,x2,y1,y2;
1830
1831       color=random()%15;
1832       x=random()%40;
1833       y1=random()%48;
1834       y2=random()%48;
1835       for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1836
1837       x1=random()%40;
1838       x2=random()%40;
1839       y=random()%48;
1840       for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1841     }
1842     if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1843     break;
1844
1845   case 999:
1846     *stepno=0;
1847     break;
1848
1849   case A2CONTROLLER_FREE:
1850     free(mine);
1851     break;
1852   }
1853
1854 }
1855
1856 void (*controllers[])(apple2_sim_t *sim, int *stepno,
1857                       double *next_actiontime) = {
1858   slideshow_controller,
1859   terminal_controller,
1860   basic_controller
1861 };
1862
1863 void
1864 screenhack (Display *dpy, Window window)
1865 {
1866   int duration = get_integer_resource ("duration", "Integer");
1867   char *s;
1868   void (*controller)(apple2_sim_t *sim, int *stepno, double *next_actiontime);
1869   Bool random_p = False;
1870
1871   controller = 0;
1872   if (duration < 1) duration = 1;
1873
1874   if (!get_boolean_resource ("root", "Boolean"))
1875     {
1876       XWindowAttributes xgwa;
1877       XGetWindowAttributes (dpy, window, &xgwa);
1878       XSelectInput (dpy, window,
1879                     xgwa.your_event_mask |
1880                     KeyPressMask | ButtonPressMask | ExposureMask);
1881     }
1882
1883   s = get_string_resource ("mode", "Mode");
1884   if (!s || !*s || !strcasecmp(s, "random"))
1885     random_p = True;
1886   else if (!strcasecmp(s, "text"))
1887      controller = terminal_controller;
1888   else if (!strcasecmp(s, "slideshow"))
1889      controller = slideshow_controller;
1890   else if (!strcasecmp(s, "basic"))
1891      controller = basic_controller;
1892   else
1893     {
1894       fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1895                progname, s);
1896       exit (1);
1897     }
1898   if (s) free (s);
1899
1900   if (controller == terminal_controller)
1901     duration = 999999;  /* this one runs "forever" */
1902
1903   while (1)
1904     {
1905       if (random_p)
1906         controller = controllers[random() % (countof(controllers))];
1907
1908       apple2 (dpy, window, duration, controller);
1909       XSync (dpy, False);
1910     }
1911 }