source: trunk/third/xscreensaver/hacks/apple2-main.c @ 20148

Revision 20148, 33.0 KB checked in by ghudson, 21 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r20147, which included commits to RCS files with non-trunk default branches.
Line 
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
27extern XtAppContext app;
28
29Time subproc_relaunch_delay = 3000;
30
31
32/* Given a bitmask, returns the position and width of the field.
33 */
34static void
35decode_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 */
54static unsigned char
55spread_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 */
74static void
75scale_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 */
182static void
183pick_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 */
220static void
221a2_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
506static unsigned char *
507load_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
552char *progclass = "Apple2";
553
554char *defaults [] = {
555  "*mode:                  random",
556  "*duration:              20",
557  ANALOGTV_DEFAULTS
558  0
559};
560
561XrmOptionDescRec 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
576void 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
722struct 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
733static void
734subproc_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
741static void
742launch_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
771static void
772relaunch_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
780static void
781terminal_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
797static int
798terminal_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
839void
840terminal_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
915struct 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 */
930static 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
947static 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
963static char *dumb_program[]={
964  "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
965  "20 GOTO 10\n",
966  NULL
967};
968#endif
969
970static 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
993static char typo_map[256];
994
995int 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
1089struct {
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
1099void
1100basic_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
1278void (*controllers[])(apple2_sim_t *sim, int *stepno,
1279                      double *next_actiontime) = {
1280  slideshow_controller,
1281  terminal_controller,
1282  basic_controller
1283};
1284
1285void
1286screenhack (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}
Note: See TracBrowser for help on using the repository browser.