1 /* xscreensaver, Copyright (c) 1998-2003 Jamie Zawinski <jwz@jwz.org>
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
11 * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12 * with additional work by Jamie Zawinski <jwz@jwz.org>
16 #include "screenhack.h"
20 #include <X11/Intrinsic.h>
22 #ifdef HAVE_XSHM_EXTENSION
28 extern XtAppContext app;
31 * Implementation notes
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.
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.
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.
47 * The text font is based on X's standard 6x10 font, with a few tweaks like
48 * putting a slash across the zero.
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.
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
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
71 a2_scroll(apple2_state_t *st)
74 for (i=0; i<23; i++) {
75 memcpy(st->textlines[i],st->textlines[i+1],40);
77 memset(st->textlines[23],0xe0,40);
81 a2_printc(apple2_state_t *st, char c)
83 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
85 if (c == '\n') /* ^J == NL */
97 else if (c == 014) /* ^L == CLS, Home */
102 else if (c == '\t') /* ^I == tab */
104 a2_goto(st, st->cursy, (st->cursx+8)&~7);
106 else if (c == 010) /* ^H == backspace */
108 st->textlines[st->cursy][st->cursx]=0xe0;
109 a2_goto(st, st->cursy, st->cursx-1);
111 else if (c == '\r') /* ^M == CR */
117 st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
129 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
133 a2_prints(apple2_state_t *st, char *s)
135 while (*s) a2_printc(st, *s++);
139 a2_goto(apple2_state_t *st, int r, int c)
141 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
144 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
148 a2_cls(apple2_state_t *st)
151 for (i=0; i<24; i++) {
152 memset(st->textlines[i],0xe0,40);
157 a2_clear_gr(apple2_state_t *st)
160 for (i=0; i<24; i++) {
161 memset(st->textlines[i],0x00,40);
166 a2_clear_hgr(apple2_state_t *st)
169 for (i=0; i<192; i++) {
170 memset(st->hireslines[i],0,40);
175 a2_invalidate(apple2_state_t *st)
180 a2_poke(apple2_state_t *st, int addr, int val)
183 if (addr>=0x400 && addr<0x800) {
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)) {
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) {
208 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
212 highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
214 if (y<0 || y>=192 || x<0 || x>=280) return;
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);
221 *vidbyte = (*vidbyte & 0x7f) | highbit;
223 /* use either bit 0 or 1 of hcolor for odd or even pixels */
224 masked_bit = (hcolor>>(1-(x&1)))&1;
226 /* Set whichbit to 1 or 0 depending on color */
227 *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
234 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
236 int dx,dy,incx,incy,x,y,balance;
238 /* Bresenham's line drawing algorithm */
262 a2_hplot(st, hcolor, x, y);
270 a2_hplot(st, hcolor, x, y);
276 a2_hplot(st, hcolor, x, y);
284 a2_hplot(st, hcolor, x, y);
289 a2_plot(apple2_state_t *st, int color, int x, int y)
294 if (x<0 || x>=40 || y<0 || y>=48) return;
296 byte=st->textlines[textrow][x];
298 byte = (byte&0xf0) | (color&0x0f);
300 byte = (byte&0x0f) | ((color&0x0f)<<4);
302 st->textlines[textrow][x]=byte;
306 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
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.
317 int row=(((lineno / 24) % 8) * 1 +
318 ((lineno / 3 ) % 8) * 8 +
319 ((lineno / 1 ) % 3) * 64);
321 memcpy (st->hireslines[row], &image[row * 40], 40);
325 Simulate plausible initial memory contents for running a program.
328 a2_init_memory_active(apple2_sim_t *sim)
332 apple2_state_t *st=sim->st;
334 while (addr<0x4000) {
337 switch (random()%4) {
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);
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++) {
357 for (j=0; j<8; j++) {
358 c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
360 a2_poke(st, addr++, c);
362 x=(x+1)%(sim->text_im->width);
369 for (i=0; i<n && addr<0x4000; i++) {
370 a2_poke(st, addr++, 0);
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,
387 /* Fix * */ 0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
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,
400 /* Fix ; */ 0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
402 /* Fix < */ 0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
404 0x94c7, 0x14c8, 0x98c8, 0x18c9,
405 /* Fix > */ 0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
407 0x94d4, 0x14d5, 0x98d3, 0x18d4,
408 /* Fix @ */ 0x88e3, 0x08e4, 0x8ce4, 0x98e5,
409 /* Fix B */ 0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
411 /* Fix D */ 0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
413 /* Fix G */ 0x8116, 0x0516, 0x9916,
414 /* Fix J */ 0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
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,
422 /* Fix \ */ 0x01a5, 0x19a9,
423 /* Fix ] */ 0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
425 /* Fix ^ */ 0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
427 /* Fix _ */ 0x9db9, 0x9dbf,
432 a2_make_font(apple2_sim_t *sim)
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.
442 const char *def_font="6x10";
448 font = XLoadQueryFont (sim->dpy, def_font);
450 fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
454 text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, sim->dec->xgwa.depth);
456 memset(&gcv, 0, sizeof(gcv));
460 gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
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++) {
471 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
473 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
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);
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);
489 apple2(Display *dpy, Window window, int delay,
490 void (*controller)(apple2_sim_t *sim,
492 double *next_actiontime))
494 int i,textrow,row,col,stepno;
496 double next_actiontime;
499 sim=(apple2_sim_t *)calloc(1,sizeof(apple2_state_t));
501 sim->window = window;
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();
509 sim->reception.input = sim->inp;
510 sim->reception.level = 1.0;
514 if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
515 sim->dec->flutter_tint=1;
517 else if (random()%3==0) {
518 sim->dec->flutter_horiz_desync=1;
520 sim->typing_rate = 1.0;
522 analogtv_set_defaults(sim->dec, "");
523 sim->dec->squish_control=0.05;
524 analogtv_setup_sync(sim->inp, 1, 0);
530 a2_goto(sim->st,23,0);
532 if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
536 next_actiontime=sim->curtime;
537 (*controller)(sim, &stepno, &next_actiontime);
539 # ifdef GETTIMEOFDAY_TWO_ARGS
540 gettimeofday(&sim->basetime_tv, NULL);
542 gettimeofday(&sim->basetime_tv);
549 struct timeval curtime_tv;
550 # ifdef GETTIMEOFDAY_TWO_ARGS
552 gettimeofday(&curtime_tv, &tzp);
554 gettimeofday(&curtime_tv);
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;
562 if (analogtv_handle_events(sim->dec)) {
565 stepno=A2CONTROLLER_FREE;
566 next_actiontime = sim->curtime;
567 (*controller)(sim, &stepno, &next_actiontime);
569 sim->controller_data=NULL;
574 blinkphase=sim->curtime/0.8;
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.) */
581 sim->st->blink=((int)blinkphase)&1;
582 if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
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) {
603 while (*sim->printing) {
604 if (*sim->printing=='\001') { /* pause */
608 else if (*sim->printing=='\n') {
609 a2_printc(sim->st,*sim->printing);
615 a2_printc(sim->st,*sim->printing);
619 if (!*sim->printing) sim->printing=NULL;
621 else if (sim->curtime >= next_actiontime) {
625 /* If we're in the midst of typing a string, emit a character with
632 a2_printc(sim->st, c);
633 if (c=='\r' || c=='\n') {
634 next_actiontime = sim->curtime;
637 next_actiontime = sim->curtime + 0.1;
640 next_actiontime = (sim->curtime +
641 (((random()%1000)*0.001 + 0.3) *
646 next_actiontime=sim->curtime;
648 (*controller)(sim, &stepno, &next_actiontime);
649 if (stepno==A2CONTROLLER_DONE) goto finished;
655 analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
656 analogtv_setup_frame(sim->dec);
658 for (textrow=0; textrow<24; textrow++) {
659 for (row=textrow*8; row<textrow*8+8; row++) {
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. */
668 char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
670 if ((sim->st->gr_mode&A2_GR_HIRES) &&
671 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
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;
681 for (col=0; col<40; col++) {
682 u_char b=sim->st->hireslines[row][col];
683 int shift=(b&0x80)?0:1;
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);
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))
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);
710 for (col=0; col<40; col++) {
712 c=sim->st->textlines[textrow][col]&0xff;
713 /* hi bits control inverse/blink as follows:
718 rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
720 for (i=0; i<7; i++) {
721 unsigned long pix=XGetPixel(sim->text_im,
724 pp[1] = pp[2] = ((pix^rev)
725 ?ANALOGTV_WHITE_LEVEL
726 :ANALOGTV_BLACK_LEVEL);
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);
741 stepno=A2CONTROLLER_FREE;
742 (*controller)(sim, &stepno, &next_actiontime);
744 XSync(sim->dpy, False);
745 XClearWindow(sim->dpy, sim->window);
749 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
755 a2_invalidate(sim->st);
757 For testing color rendering. The spec is:
765 6 med blue 20 207 253
766 7 lt blue 208 195 255
771 12 lt green 20 245 60
772 13 yellow 208 221 141
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;
782 *next_actiontime+=0.4;
787 if (sim->curtime > 10) *stepno=-1;