http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.tar.gz
[xscreensaver] / hacks / apple2.c
1 /* xscreensaver, Copyright (c) 1998-2003 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or
9  * implied warranty.
10  *
11  * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12  * with additional work by Jamie Zawinski <jwz@jwz.org>
13  */
14
15 #include <math.h>
16 #include "screenhack.h"
17 #include "apple2.h"
18 #include <time.h>
19 #include <sys/time.h>
20 #include <X11/Intrinsic.h>
21
22 #ifdef HAVE_XSHM_EXTENSION
23 #include "xshm.h"
24 #endif
25
26 #define DEBUG
27
28 extern XtAppContext app;
29
30 /*
31  * Implementation notes
32  *
33  * The A2 had 3 display modes: text, lores, and hires. Text was 40x24, and it
34  * disabled color in the TV. Lores gave you 40x48 graphics blocks, using the
35  * same memory as the text screen. Each could be one of 16 colors. Hires gave
36  * you 280x192 pixels. Odd pixels were blue or purple, and even pixels were
37  * orange or green depending on the setting of the high bit in each byte.
38  *
39  * The graphics modes could also have 4 lines of text at the bottom. This was
40  * fairly unreadable if you had a color monitor.
41  *
42  * Each mode had 2 different screens using different memory space. In hires
43  * mode this was sometimes used for double buffering, but more often the lower
44  * screen was full of code/data and the upper screen was used for display, so
45  * you got random garbage on the screen.
46  *
47  * The text font is based on X's standard 6x10 font, with a few tweaks like
48  * putting a slash across the zero.
49  *
50  * To use this, you'll call apple2(display, window, duration,
51  * controller) where the function controller defines what will happen.
52  * See bsod.c and apple2-main.c for example controllers. The
53  * controller function gets called whenever the machine ready to start
54  * something new. By setting sim->printing or sim->typing, it'll be
55  * busy for some time spitting characters out one at a time. By
56  * setting *next_actiontime+=X.X, it'll pause and just update the screen
57  * for that long before calling the controller function again.
58  *
59  * By setting stepno to A2CONTROLLER_DONE, the loop will end. It will also end
60  * after the time specified by the delay parameter. In either case, it calls
61  * the controller with stepno==A2CONTROLLER_FREE to allow it to release any
62  * memory.
63  *
64  * The void* apple2_sim_t::controller_data is for the use of the controller.
65  * It will be initialize to NULL, and the controller can store its own state
66  * there.
67  *
68  */
69
70 void
71 a2_scroll(apple2_state_t *st)
72 {
73   int i;
74   for (i=0; i<23; i++) {
75     memcpy(st->textlines[i],st->textlines[i+1],40);
76   }
77   memset(st->textlines[23],0xe0,40);
78 }
79
80 void
81 a2_printc(apple2_state_t *st, char c)
82 {
83   st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
84
85   if (c == '\n')                      /* ^J == NL */
86     {
87       if (st->cursy==23)
88         {
89           a2_scroll(st);
90         }
91       else
92         {
93           st->cursy++;
94         }
95       st->cursx=0;
96     }
97   else if (c == 014)                  /* ^L == CLS, Home */
98     {
99       a2_cls(st);
100       a2_goto(st,0,0);
101     }
102   else if (c == '\t')                 /* ^I == tab */
103     {
104       a2_goto(st, st->cursy, (st->cursx+8)&~7);
105     }
106   else if (c == 010)                  /* ^H == backspace */
107     {
108       st->textlines[st->cursy][st->cursx]=0xe0;
109       a2_goto(st, st->cursy, st->cursx-1);
110     }
111   else if (c == '\r')                 /* ^M == CR */
112     {
113       st->cursx=0;
114     }
115   else
116     {
117       st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
118       st->cursx++;
119       if (st->cursx==40) {
120         if (st->cursy==23) {
121           a2_scroll(st);
122         } else {
123           st->cursy++;
124         }
125         st->cursx=0;
126       }
127     }
128
129   st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
130 }
131
132 void
133 a2_prints(apple2_state_t *st, char *s)
134 {
135   while (*s) a2_printc(st, *s++);
136 }
137
138 void
139 a2_goto(apple2_state_t *st, int r, int c)
140 {
141   st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
142   st->cursy=r;
143   st->cursx=c;
144   st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
145 }
146
147 void
148 a2_cls(apple2_state_t *st)
149 {
150   int i;
151   for (i=0; i<24; i++) {
152     memset(st->textlines[i],0xe0,40);
153   }
154 }
155
156 void
157 a2_clear_gr(apple2_state_t *st)
158 {
159   int i;
160   for (i=0; i<24; i++) {
161     memset(st->textlines[i],0x00,40);
162   }
163 }
164
165 void
166 a2_clear_hgr(apple2_state_t *st)
167 {
168   int i;
169   for (i=0; i<192; i++) {
170     memset(st->hireslines[i],0,40);
171   }
172 }
173
174 void
175 a2_invalidate(apple2_state_t *st)
176 {
177 }
178
179 void
180 a2_poke(apple2_state_t *st, int addr, int val)
181 {
182
183   if (addr>=0x400 && addr<0x800) {
184     /* text memory */
185     int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
186     int col=(addr&0x7f)%0x28;
187     if (row<24 && col<40) {
188       st->textlines[row][col]=val;
189       if (!(st->gr_mode&(A2_GR_HIRES)) ||
190           (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
191       }
192     }
193   }
194   else if (addr>=0x2000 && addr<0x4000) {
195     int row=(((addr&0x1c00) / 0x400) * 1 +
196              ((addr&0x0380) / 0x80) * 8 +
197              ((addr&0x0078) / 0x28) * 64);
198     int col=((addr&0x07f)%0x28);
199     if (row<192 && col<40) {
200       st->hireslines[row][col]=val;
201       if (st->gr_mode&A2_GR_HIRES) {
202       }
203     }
204   }
205 }
206
207 void
208 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
209 {
210   int highbit,run;
211
212   highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
213
214   if (y<0 || y>=192 || x<0 || x>=280) return;
215
216   for (run=0; run<2 && x<280; run++) {
217     u_char *vidbyte = &st->hireslines[y][x/7];
218     u_char whichbit=1<<(x%7);
219     int masked_bit;
220
221     *vidbyte = (*vidbyte & 0x7f) | highbit;
222
223     /* use either bit 0 or 1 of hcolor for odd or even pixels */
224     masked_bit = (hcolor>>(1-(x&1)))&1;
225
226     /* Set whichbit to 1 or 0 depending on color */
227     *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
228
229     x++;
230   }
231 }
232
233 void
234 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
235 {
236   int dx,dy,incx,incy,x,y,balance;
237
238   /* Bresenham's line drawing algorithm */
239
240   if (x2>=x1) {
241     dx=x2-x1;
242     incx=1;
243   } else {
244     dx=x1-x2;
245     incx=-1;
246   }
247   if (y2>=y1) {
248     dy=y2-y1;
249     incy=1;
250   } else {
251     dy=y1-y2;
252     incy=-1;
253   }
254
255   x=x1; y=y1;
256
257   if (dx>=dy) {
258     dy*=2;
259     balance=dy-dx;
260     dx*=2;
261     while (x!=x2) {
262       a2_hplot(st, hcolor, x, y);
263       if (balance>=0) {
264         y+=incy;
265         balance-=dx;
266       }
267       balance+=dy;
268       x+=incx;
269     }
270     a2_hplot(st, hcolor, x, y);
271   } else {
272     dx*=2;
273     balance=dx-dy;
274     dy*=2;
275     while (y!=y2) {
276       a2_hplot(st, hcolor, x, y);
277       if (balance>=0) {
278         x+=incx;
279         balance-=dy;
280       }
281       balance+=dx;
282       y+=incy;
283     }
284     a2_hplot(st, hcolor, x, y);
285   }
286 }
287
288 void
289 a2_plot(apple2_state_t *st, int color, int x, int y)
290 {
291   int textrow=y/2;
292   u_char byte;
293
294   if (x<0 || x>=40 || y<0 || y>=48) return;
295
296   byte=st->textlines[textrow][x];
297   if (y&1) {
298     byte = (byte&0xf0) | (color&0x0f);
299   } else {
300     byte = (byte&0x0f) | ((color&0x0f)<<4);
301   }
302   st->textlines[textrow][x]=byte;
303 }
304
305 void
306 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
307                          int lineno)
308 {
309   /*
310     When loading images,it would normally just load the big binary
311     dump into screen memory while you watched. Because of the way
312     screen memory was laid out, it wouldn't load from the top down,
313     but in a funny interleaved way. You should call this with lineno
314     increasing from 0 thru 191 over a period of a few seconds.
315   */
316
317   int row=(((lineno / 24) % 8) * 1 +
318            ((lineno / 3 ) % 8) * 8 +
319            ((lineno / 1 ) % 3) * 64);
320
321   memcpy (st->hireslines[row], &image[row * 40], 40);
322 }
323
324 /*
325   Simulate plausible initial memory contents for running a program.
326 */
327 void
328 a2_init_memory_active(apple2_sim_t *sim)
329 {
330   int i,j,x,y,c;
331   int addr=0;
332   apple2_state_t *st=sim->st;
333
334   while (addr<0x4000) {
335     int n;
336
337     switch (random()%4) {
338     case 0:
339     case 1:
340       n=random()%500;
341       for (i=0; i<n && addr<0x4000; i++) {
342         u_char rb=((random()%6==0 ? 0 : random()%16) |
343                    ((random()%5==0 ? 0 : random()%16)<<4));
344         a2_poke(st, addr++, rb);
345       }
346       break;
347
348     case 2:
349       /* Simulate shapes stored in memory. We use the font since we have it.
350          Unreadable, since rows of each character are stored in consecutive
351          bytes. It was typical to store each of the 7 possible shifts of
352          bitmaps, for fastest blitting to the screen. */
353       x=random()%(sim->text_im->width);
354       for (i=0; i<100; i++) {
355         for (y=0; y<8; y++) {
356           c=0;
357           for (j=0; j<8; j++) {
358             c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
359           }
360           a2_poke(st, addr++, c);
361         }
362         x=(x+1)%(sim->text_im->width);
363       }
364       break;
365
366     case 3:
367       if (addr>0x2000) {
368         n=random()%200;
369         for (i=0; i<n && addr<0x4000; i++) {
370           a2_poke(st, addr++, 0);
371         }
372       }
373       break;
374
375     }
376   }
377 }
378
379
380 /* This table lists fixes for characters that differ from the standard 6x10
381    font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
382    where value is 0 for white and 1 for black. */
383 static unsigned short a2_fixfont[] = {
384   /* Fix $ */  0x8421, 0x941d,
385   /* Fix % */  0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
386                0x1824, 0x9828,
387   /* Fix * */  0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
388                0x9449, 0x9849,
389   /* Fix , */  0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
390   /* Fix . */  0x1465, 0x1864, 0x1866, 0x1c65,
391   /* Fix / */  0x006e, 0x186a,
392   /* Fix 0 */  0x8874, 0x8c73, 0x9072,
393   /* Fix 1 */  0x0878, 0x1878, 0x187c,
394   /* Fix 5 */  0x8895, 0x0c94, 0x0c95,
395   /* Fix 6 */  0x809f, 0x8c9c, 0x109c,
396   /* Fix 7 */  0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
397   /* Fix 9 */  0x08b3, 0x8cb3, 0x98b0,
398   /* Fix : */  0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
399                0x18ba, 0x1cb9,
400   /* Fix ; */  0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
401                0x1cbf,
402   /* Fix < */  0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
403                0x90c6, 0x10c7,
404                0x94c7, 0x14c8, 0x98c8, 0x18c9,
405   /* Fix > */  0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
406                0x90d5, 0x10d6,
407                0x94d4, 0x14d5, 0x98d3, 0x18d4,
408   /* Fix @ */  0x88e3, 0x08e4, 0x8ce4, 0x98e5,
409   /* Fix B */  0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
410                0x14f0,
411   /* Fix D */  0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
412                0x94fd, 0x14fe,
413   /* Fix G */  0x8116, 0x0516, 0x9916,
414   /* Fix J */  0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
415                0x112a, 0x912b,
416                0x152a, 0x952b, 0x992a,
417   /* Fix M */  0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
418   /* Fix Q */  0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
419   /* Fix V */  0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
420   /* Fix [ */  0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
421                0x99a2,
422   /* Fix \ */  0x01a5, 0x19a9,
423   /* Fix ] */  0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
424                0x99b0,
425   /* Fix ^ */  0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
426                0x91b3, 0x91b7,
427   /* Fix _ */  0x9db9, 0x9dbf,
428   0,
429 };
430
431 static void
432 a2_make_font(apple2_sim_t *sim)
433 {
434   /*
435     Generate the font. It used a 5x7 font which looks a lot like the standard X
436     6x10 font, with a few differences. So we render up all the uppercase
437     letters of 6x10, and make a few tweaks (like putting a slash across the
438     zero) according to fixfont.
439    */
440
441   int i;
442   const char *def_font="6x10";
443   XFontStruct *font;
444   Pixmap text_pm;
445   GC gc;
446   XGCValues gcv;
447
448   font = XLoadQueryFont (sim->dpy, def_font);
449   if (!font) {
450     fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
451     abort();
452   }
453
454   text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, sim->dec->xgwa.depth);
455
456   memset(&gcv, 0, sizeof(gcv));
457   gcv.foreground=1;
458   gcv.background=0;
459   gcv.font=font->fid;
460   gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
461
462   XSetForeground(sim->dpy, gc, 0);
463   XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
464   XSetForeground(sim->dpy, gc, 1);
465   for (i=0; i<64; i++) {
466     char c=32+i;
467     int x=7*i+1;
468     int y=7;
469     if (c=='0') {
470       c='O';
471       XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
472     } else {
473       XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
474     }
475   }
476   sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
477   XFreeGC(sim->dpy, gc);
478   XFreePixmap(sim->dpy, text_pm);
479
480   for (i=0; a2_fixfont[i]; i++) {
481     XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
482               (a2_fixfont[i]>>10)&0xf,
483               (a2_fixfont[i]>>15)&1);
484   }
485 }
486
487
488 void
489 apple2(Display *dpy, Window window, int delay,
490        void (*controller)(apple2_sim_t *sim,
491                           int *stepno,
492                           double *next_actiontime))
493 {
494   int i,textrow,row,col,stepno;
495   int c;
496   double next_actiontime;
497   apple2_sim_t *sim;
498
499   sim=(apple2_sim_t *)calloc(1,sizeof(apple2_state_t));
500   sim->dpy = dpy;
501   sim->window = window;
502   sim->delay = delay;
503
504   sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
505   sim->dec = analogtv_allocate(dpy, window);
506   sim->dec->event_handler = screenhack_handle_event;
507   sim->inp = analogtv_input_allocate();
508
509   sim->reception.input = sim->inp;
510   sim->reception.level = 1.0;
511
512   sim->prompt=']';
513
514   if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
515     sim->dec->flutter_tint=1;
516   }
517   else if (random()%3==0) {
518     sim->dec->flutter_horiz_desync=1;
519   }
520   sim->typing_rate = 1.0;
521
522   analogtv_set_defaults(sim->dec, "");
523   sim->dec->squish_control=0.05;
524   analogtv_setup_sync(sim->inp, 1, 0);
525
526
527   a2_make_font(sim);
528
529   stepno=0;
530   a2_goto(sim->st,23,0);
531
532   if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
533   next_actiontime=0.0;
534
535   sim->curtime=0.0;
536   next_actiontime=sim->curtime;
537   (*controller)(sim, &stepno, &next_actiontime);
538
539 # ifdef GETTIMEOFDAY_TWO_ARGS
540   gettimeofday(&sim->basetime_tv, NULL);
541 # else
542   gettimeofday(&sim->basetime_tv);
543 # endif
544
545   while (1) {
546     double blinkphase;
547
548     {
549       struct timeval curtime_tv;
550 # ifdef GETTIMEOFDAY_TWO_ARGS
551       struct timezone tzp;
552       gettimeofday(&curtime_tv, &tzp);
553 # else
554       gettimeofday(&curtime_tv);
555 # endif
556       sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
557         0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
558       if (sim->curtime > sim->dec->powerup)
559         sim->dec->powerup=sim->curtime;
560     }
561
562     if (analogtv_handle_events(sim->dec)) {
563       sim->typing=NULL;
564       sim->printing=NULL;
565       stepno=A2CONTROLLER_FREE;
566       next_actiontime = sim->curtime;
567       (*controller)(sim, &stepno, &next_actiontime);
568       stepno=0;
569       sim->controller_data=NULL;
570       sim->st->gr_mode=0;
571       continue;
572     }
573
574     blinkphase=sim->curtime/0.8;
575
576     /* The blinking rate was controlled by 555 timer with a resistor/capacitor
577        time constant. Because the capacitor was electrolytic, the flash rate
578        varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
579        reasonable. (I soldered a resistor in mine to make it blink faster.) */
580     i=sim->st->blink;
581     sim->st->blink=((int)blinkphase)&1;
582     if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
583       int downcounter=0;
584       /* For every row with blinking text, set the changed flag. This basically
585          works great except with random screen garbage in text mode, when we
586          end up redrawing the whole screen every second */
587       for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
588         for (col=0; col<40; col++) {
589           c=sim->st->textlines[row][col];
590           if ((c & 0xc0) == 0x40) {
591             downcounter=4;
592             break;
593           }
594         }
595         if (downcounter>0) {
596           downcounter--;
597         }
598       }
599     }
600
601     if (sim->printing) {
602       int nlcnt=0;
603       while (*sim->printing) {
604         if (*sim->printing=='\001') { /* pause */
605           sim->printing++;
606           break;
607         }
608         else if (*sim->printing=='\n') {
609           a2_printc(sim->st,*sim->printing);
610           sim->printing++;
611           nlcnt++;
612           if (nlcnt>=2) break;
613         }
614         else {
615           a2_printc(sim->st,*sim->printing);
616           sim->printing++;
617         }
618       }
619       if (!*sim->printing) sim->printing=NULL;
620     }
621     else if (sim->curtime >= next_actiontime) {
622       if (sim->typing) {
623
624         int c;
625         /* If we're in the midst of typing a string, emit a character with
626            random timing. */
627         c =*sim->typing++;
628         if (c==0) {
629           sim->typing=NULL;
630         }
631         else {
632           a2_printc(sim->st, c);
633           if (c=='\r' || c=='\n') {
634             next_actiontime = sim->curtime;
635           }
636           else if (c==010) {
637             next_actiontime = sim->curtime + 0.1;
638           }
639           else {
640             next_actiontime = (sim->curtime +
641                                (((random()%1000)*0.001 + 0.3) *
642                                 sim->typing_rate));
643           }
644         }
645       } else {
646         next_actiontime=sim->curtime;
647
648         (*controller)(sim, &stepno, &next_actiontime);
649         if (stepno==A2CONTROLLER_DONE) goto finished;
650
651       }
652     }
653
654
655     analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
656     analogtv_setup_frame(sim->dec);
657
658     for (textrow=0; textrow<24; textrow++) {
659       for (row=textrow*8; row<textrow*8+8; row++) {
660
661         /* First we generate the pattern that the video circuitry shifts out
662            of memory. It has a 14.something MHz dot clock, equal to 4 times
663            the color burst frequency. So each group of 4 bits defines a color.
664            Each character position, or byte in hires, defines 14 dots, so odd
665            and even bytes have different color spaces. So, pattern[0..600]
666            gets the dots for one scan line. */
667
668         char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
669
670         if ((sim->st->gr_mode&A2_GR_HIRES) &&
671             (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
672
673           /* Emulate the mysterious pink line, due to a bit getting
674              stuck in a shift register between the end of the last
675              row and the beginning of this one. */
676           if ((sim->st->hireslines[row][0] & 0x80) &&
677               (sim->st->hireslines[row][39]&0x40)) {
678             pp[-1]=ANALOGTV_WHITE_LEVEL;
679           }
680
681           for (col=0; col<40; col++) {
682             u_char b=sim->st->hireslines[row][col];
683             int shift=(b&0x80)?0:1;
684
685             /* Each of the low 7 bits in hires mode corresponded to 2 dot
686                clocks, shifted by one if the high bit was set. */
687             for (i=0; i<7; i++) {
688               pp[shift+1] = pp[shift] = (((b>>i)&1)
689                                          ?ANALOGTV_WHITE_LEVEL
690                                          :ANALOGTV_BLACK_LEVEL);
691               pp+=2;
692             }
693           }
694         }
695         else if ((sim->st->gr_mode&A2_GR_LORES) &&
696                  (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
697           for (col=0; col<40; col++) {
698             u_char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
699                         & 0xf);
700             /* The low or high nybble was shifted out one bit at a time. */
701             for (i=0; i<14; i++) {
702               *pp = (((nib>>((col*14+i)&3))&1)
703                      ?ANALOGTV_WHITE_LEVEL
704                      :ANALOGTV_BLACK_LEVEL);
705               pp++;
706             }
707           }
708         }
709         else {
710           for (col=0; col<40; col++) {
711             int rev;
712             c=sim->st->textlines[textrow][col]&0xff;
713             /* hi bits control inverse/blink as follows:
714                0x00: inverse
715                0x40: blink
716                0x80: normal
717                0xc0: normal */
718             rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
719
720             for (i=0; i<7; i++) {
721               unsigned long pix=XGetPixel(sim->text_im,
722                                           ((c&0x3f)^0x20)*7+i,
723                                           row%8);
724               pp[1] = pp[2] = ((pix^rev)
725                                ?ANALOGTV_WHITE_LEVEL
726                                :ANALOGTV_BLACK_LEVEL);
727               pp+=2;
728             }
729           }
730         }
731       }
732     }
733     analogtv_init_signal(sim->dec, 0.02);
734     analogtv_reception_update(&sim->reception);
735     analogtv_add_signal(sim->dec, &sim->reception);
736     analogtv_draw(sim->dec);
737   }
738
739  finished:
740
741   stepno=A2CONTROLLER_FREE;
742   (*controller)(sim, &stepno, &next_actiontime);
743
744   XSync(sim->dpy, False);
745   XClearWindow(sim->dpy, sim->window);
746 }
747
748 void
749 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
750 {
751   int row,col;
752
753   switch(*stepno) {
754   case 0:
755     a2_invalidate(sim->st);
756     /*
757       For testing color rendering. The spec is:
758       red grn blu
759       0  black       0   0   0
760       1  red       227  30  96
761       2  dk blue    96  78 189
762       3  purple    255  68 253
763       4  dk green    0 163  96
764       5  gray      156 156 156
765       6  med blue   20 207 253
766       7  lt blue   208 195 255
767       8  brown      96 114   3
768       9  orange    255 106  60
769       10 grey      156 156 156
770       11 pink      255 160 208
771       12 lt green   20 245  60
772       13 yellow    208 221 141
773       14 aqua      114 255 208
774       15 white     255 255 255
775     */
776     sim->st->gr_mode=A2_GR_LORES;
777     for (row=0; row<24; row++) {
778       for (col=0; col<40; col++) {
779         sim->st->textlines[row][col]=(row&15)*17;
780       }
781     }
782     *next_actiontime+=0.4;
783     *stepno=99;
784     break;
785
786   case 99:
787     if (sim->curtime > 10) *stepno=-1;
788     break;
789   }
790 }