1 /* xscreensaver, Copyright (c) 1998-2004 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 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off cursor */
76 for (i=0; i<23; i++) {
77 memcpy(st->textlines[i],st->textlines[i+1],40);
79 memset(st->textlines[23],0xe0,40);
83 a2_printc_1(apple2_state_t *st, char c, int scroll_p)
85 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
87 if (c == '\n') /* ^J == NL */
100 else if (c == 014) /* ^L == CLS, Home */
105 else if (c == '\t') /* ^I == tab */
107 a2_goto(st, st->cursy, (st->cursx+8)&~7);
109 else if (c == 010) /* ^H == backspace */
111 st->textlines[st->cursy][st->cursx]=0xe0;
112 a2_goto(st, st->cursy, st->cursx-1);
114 else if (c == '\r') /* ^M == CR */
120 st->textlines[st->cursy][st->cursx]=c ^ 0xc0;
133 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
137 a2_printc(apple2_state_t *st, char c)
139 a2_printc_1(st, c, 1);
143 a2_printc_noscroll(apple2_state_t *st, char c)
145 a2_printc_1(st, c, 0);
150 a2_prints(apple2_state_t *st, char *s)
152 while (*s) a2_printc(st, *s++);
156 a2_goto(apple2_state_t *st, int r, int c)
160 st->textlines[st->cursy][st->cursx] |= 0xc0; /* turn off blink */
163 st->textlines[st->cursy][st->cursx] &= 0x7f; /* turn on blink */
167 a2_cls(apple2_state_t *st)
170 for (i=0; i<24; i++) {
171 memset(st->textlines[i],0xe0,40);
176 a2_clear_gr(apple2_state_t *st)
179 for (i=0; i<24; i++) {
180 memset(st->textlines[i],0x00,40);
185 a2_clear_hgr(apple2_state_t *st)
188 for (i=0; i<192; i++) {
189 memset(st->hireslines[i],0,40);
194 a2_invalidate(apple2_state_t *st)
199 a2_poke(apple2_state_t *st, int addr, int val)
202 if (addr>=0x400 && addr<0x800) {
204 int row=((addr&0x380)/0x80) + ((addr&0x7f)/0x28)*8;
205 int col=(addr&0x7f)%0x28;
206 if (row<24 && col<40) {
207 st->textlines[row][col]=val;
208 if (!(st->gr_mode&(A2_GR_HIRES)) ||
209 (!(st->gr_mode&(A2_GR_FULL)) && row>=20)) {
213 else if (addr>=0x2000 && addr<0x4000) {
214 int row=(((addr&0x1c00) / 0x400) * 1 +
215 ((addr&0x0380) / 0x80) * 8 +
216 ((addr&0x0078) / 0x28) * 64);
217 int col=((addr&0x07f)%0x28);
218 if (row<192 && col<40) {
219 st->hireslines[row][col]=val;
220 if (st->gr_mode&A2_GR_HIRES) {
227 a2_hplot(apple2_state_t *st, int hcolor, int x, int y)
231 highbit=((hcolor<<5)&0x80) ^ 0x80; /* capture bit 2 into bit 7 */
233 if (y<0 || y>=192 || x<0 || x>=280) return;
235 for (run=0; run<2 && x<280; run++) {
236 u_char *vidbyte = &st->hireslines[y][x/7];
237 u_char whichbit=1<<(x%7);
240 *vidbyte = (*vidbyte & 0x7f) | highbit;
242 /* use either bit 0 or 1 of hcolor for odd or even pixels */
243 masked_bit = (hcolor>>(1-(x&1)))&1;
245 /* Set whichbit to 1 or 0 depending on color */
246 *vidbyte = (*vidbyte & ~whichbit) | (masked_bit ? whichbit : 0);
253 a2_hline(apple2_state_t *st, int hcolor, int x1, int y1, int x2, int y2)
255 int dx,dy,incx,incy,x,y,balance;
257 /* Bresenham's line drawing algorithm */
281 a2_hplot(st, hcolor, x, y);
289 a2_hplot(st, hcolor, x, y);
295 a2_hplot(st, hcolor, x, y);
303 a2_hplot(st, hcolor, x, y);
308 a2_plot(apple2_state_t *st, int color, int x, int y)
313 if (x<0 || x>=40 || y<0 || y>=48) return;
315 byte=st->textlines[textrow][x];
317 byte = (byte&0xf0) | (color&0x0f);
319 byte = (byte&0x0f) | ((color&0x0f)<<4);
321 st->textlines[textrow][x]=byte;
325 a2_display_image_loading(apple2_state_t *st, unsigned char *image,
329 When loading images,it would normally just load the big binary
330 dump into screen memory while you watched. Because of the way
331 screen memory was laid out, it wouldn't load from the top down,
332 but in a funny interleaved way. You should call this with lineno
333 increasing from 0 thru 191 over a period of a few seconds.
336 int row=(((lineno / 24) % 8) * 1 +
337 ((lineno / 3 ) % 8) * 8 +
338 ((lineno / 1 ) % 3) * 64);
340 memcpy (st->hireslines[row], &image[row * 40], 40);
344 Simulate plausible initial memory contents for running a program.
347 a2_init_memory_active(apple2_sim_t *sim)
351 apple2_state_t *st=sim->st;
353 while (addr<0x4000) {
356 switch (random()%4) {
360 for (i=0; i<n && addr<0x4000; i++) {
361 u_char rb=((random()%6==0 ? 0 : random()%16) |
362 ((random()%5==0 ? 0 : random()%16)<<4));
363 a2_poke(st, addr++, rb);
368 /* Simulate shapes stored in memory. We use the font since we have it.
369 Unreadable, since rows of each character are stored in consecutive
370 bytes. It was typical to store each of the 7 possible shifts of
371 bitmaps, for fastest blitting to the screen. */
372 x=random()%(sim->text_im->width);
373 for (i=0; i<100; i++) {
374 for (y=0; y<8; y++) {
376 for (j=0; j<8; j++) {
377 c |= XGetPixel(sim->text_im, (x+j)%sim->text_im->width, y)<<j;
379 a2_poke(st, addr++, c);
381 x=(x+1)%(sim->text_im->width);
388 for (i=0; i<n && addr<0x4000; i++) {
389 a2_poke(st, addr++, 0);
399 /* This table lists fixes for characters that differ from the standard 6x10
400 font. Each encodes a pixel, as (charindex*7 + x) + (y<<10) + (value<<15)
401 where value is 0 for white and 1 for black. */
402 static unsigned short a2_fixfont[] = {
403 /* Fix $ */ 0x8421, 0x941d,
404 /* Fix % */ 0x8024, 0x0028, 0x8425, 0x0426, 0x0825, 0x1027, 0x1426, 0x9427,
406 /* Fix * */ 0x8049, 0x8449, 0x8849, 0x0c47, 0x0c48, 0x0c4a, 0x0c4b, 0x9049,
408 /* Fix , */ 0x9057, 0x1458, 0x9856, 0x1857, 0x1c56,
409 /* Fix . */ 0x1465, 0x1864, 0x1866, 0x1c65,
410 /* Fix / */ 0x006e, 0x186a,
411 /* Fix 0 */ 0x8874, 0x8c73, 0x9072,
412 /* Fix 1 */ 0x0878, 0x1878, 0x187c,
413 /* Fix 5 */ 0x8895, 0x0c94, 0x0c95,
414 /* Fix 6 */ 0x809f, 0x8c9c, 0x109c,
415 /* Fix 7 */ 0x8ca4, 0x0ca5, 0x90a3, 0x10a4,
416 /* Fix 9 */ 0x08b3, 0x8cb3, 0x98b0,
417 /* Fix : */ 0x04b9, 0x08b8, 0x08ba, 0x0cb9, 0x90b9, 0x14b9, 0x18b8, 0x18b9,
419 /* Fix ; */ 0x04c0, 0x08bf, 0x08c1, 0x0cc0, 0x90c0, 0x14c1, 0x98bf, 0x18c0,
421 /* Fix < */ 0x80c8, 0x00c9, 0x84c7, 0x04c8, 0x88c6, 0x08c7, 0x8cc5, 0x0cc6,
423 0x94c7, 0x14c8, 0x98c8, 0x18c9,
424 /* Fix > */ 0x80d3, 0x00d4, 0x84d4, 0x04d5, 0x88d5, 0x08d6, 0x8cd6, 0x0cd7,
426 0x94d4, 0x14d5, 0x98d3, 0x18d4,
427 /* Fix @ */ 0x88e3, 0x08e4, 0x8ce4, 0x98e5,
428 /* Fix B */ 0x84ef, 0x04f0, 0x88ef, 0x08f0, 0x8cef, 0x90ef, 0x10f0, 0x94ef,
430 /* Fix D */ 0x84fd, 0x04fe, 0x88fd, 0x08fe, 0x8cfd, 0x0cfe, 0x90fd, 0x10fe,
432 /* Fix G */ 0x8116, 0x0516, 0x9916,
433 /* Fix J */ 0x0129, 0x012a, 0x052a, 0x852b, 0x092a, 0x892b, 0x0d2a, 0x8d2b,
435 0x152a, 0x952b, 0x992a,
436 /* Fix M */ 0x853d, 0x853f, 0x093d, 0x893e, 0x093f,
437 /* Fix Q */ 0x915a, 0x155a, 0x955b, 0x155c, 0x195b, 0x995c, 0x1d5c,
438 /* Fix V */ 0x8d7b, 0x0d7c, 0x0d7e, 0x8d7f, 0x917b, 0x117c, 0x117e, 0x917f,
439 /* Fix [ */ 0x819e, 0x81a2, 0x859e, 0x899e, 0x8d9e, 0x919e, 0x959e, 0x999e,
441 /* Fix \ */ 0x01a5, 0x19a9,
442 /* Fix ] */ 0x81ac, 0x81b0, 0x85b0, 0x89b0, 0x8db0, 0x91b0, 0x95b0, 0x99ac,
444 /* Fix ^ */ 0x01b5, 0x05b4, 0x05b6, 0x09b3, 0x89b5, 0x09b7, 0x8db4, 0x8db6,
446 /* Fix _ */ 0x9db9, 0x9dbf,
451 a2_make_font(apple2_sim_t *sim)
454 Generate the font. It used a 5x7 font which looks a lot like the standard X
455 6x10 font, with a few differences. So we render up all the uppercase
456 letters of 6x10, and make a few tweaks (like putting a slash across the
457 zero) according to fixfont.
461 const char *def_font="6x10";
467 font = XLoadQueryFont (sim->dpy, def_font);
469 fprintf(stderr, "%s: can't load font %s\n", progname, def_font);
473 text_pm=XCreatePixmap(sim->dpy, sim->window, 64*7, 8, sim->dec->xgwa.depth);
475 memset(&gcv, 0, sizeof(gcv));
479 gc=XCreateGC(sim->dpy, text_pm, GCFont|GCBackground|GCForeground, &gcv);
481 XSetForeground(sim->dpy, gc, 0);
482 XFillRectangle(sim->dpy, text_pm, gc, 0, 0, 64*7, 8);
483 XSetForeground(sim->dpy, gc, 1);
484 for (i=0; i<64; i++) {
490 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
492 XDrawString(sim->dpy, text_pm, gc, x, y, &c, 1);
495 sim->text_im = XGetImage(sim->dpy, text_pm, 0, 0, 64*7, 8, ~0L, ZPixmap);
496 XFreeGC(sim->dpy, gc);
497 XFreePixmap(sim->dpy, text_pm);
499 for (i=0; a2_fixfont[i]; i++) {
500 XPutPixel(sim->text_im, a2_fixfont[i]&0x3ff,
501 (a2_fixfont[i]>>10)&0xf,
502 (a2_fixfont[i]>>15)&1);
508 apple2(Display *dpy, Window window, int delay,
509 void (*controller)(apple2_sim_t *sim,
511 double *next_actiontime))
513 int i,textrow,row,col,stepno;
515 double next_actiontime;
518 sim=(apple2_sim_t *)calloc(1,sizeof(apple2_state_t));
520 sim->window = window;
523 sim->st = (apple2_state_t *)calloc(1,sizeof(apple2_state_t));
524 sim->dec = analogtv_allocate(dpy, window);
525 sim->dec->event_handler = screenhack_handle_event;
526 sim->inp = analogtv_input_allocate();
528 sim->reception.input = sim->inp;
529 sim->reception.level = 1.0;
533 if (random()%4==0 && !sim->dec->use_cmap && sim->dec->use_color && sim->dec->visbits>=8) {
534 sim->dec->flutter_tint=1;
536 else if (random()%3==0) {
537 sim->dec->flutter_horiz_desync=1;
539 sim->typing_rate = 1.0;
541 analogtv_set_defaults(sim->dec, "");
542 sim->dec->squish_control=0.05;
543 analogtv_setup_sync(sim->inp, 1, 0);
549 a2_goto(sim->st,23,0);
551 if (random()%2==0) sim->basetime_tv.tv_sec -= 1; /* random blink phase */
555 next_actiontime=sim->curtime;
556 (*controller)(sim, &stepno, &next_actiontime);
558 # ifdef GETTIMEOFDAY_TWO_ARGS
559 gettimeofday(&sim->basetime_tv, NULL);
561 gettimeofday(&sim->basetime_tv);
568 struct timeval curtime_tv;
569 # ifdef GETTIMEOFDAY_TWO_ARGS
571 gettimeofday(&curtime_tv, &tzp);
573 gettimeofday(&curtime_tv);
575 sim->curtime=(curtime_tv.tv_sec - sim->basetime_tv.tv_sec) +
576 0.000001*(curtime_tv.tv_usec - sim->basetime_tv.tv_usec);
577 if (sim->curtime > sim->dec->powerup)
578 sim->dec->powerup=sim->curtime;
581 if (analogtv_handle_events(sim->dec)) {
584 stepno=A2CONTROLLER_FREE;
585 next_actiontime = sim->curtime;
586 (*controller)(sim, &stepno, &next_actiontime);
588 sim->controller_data=NULL;
593 blinkphase=sim->curtime/0.8;
595 /* The blinking rate was controlled by 555 timer with a resistor/capacitor
596 time constant. Because the capacitor was electrolytic, the flash rate
597 varied somewhat between machines. I'm guessing 1.6 seconds/cycle was
598 reasonable. (I soldered a resistor in mine to make it blink faster.) */
600 sim->st->blink=((int)blinkphase)&1;
601 if (sim->st->blink!=i && !(sim->st->gr_mode&A2_GR_FULL)) {
603 /* For every row with blinking text, set the changed flag. This basically
604 works great except with random screen garbage in text mode, when we
605 end up redrawing the whole screen every second */
606 for (row=(sim->st->gr_mode ? 20 : 0); row<24; row++) {
607 for (col=0; col<40; col++) {
608 c=sim->st->textlines[row][col];
609 if ((c & 0xc0) == 0x40) {
620 if (sim->curtime >= delay)
621 stepno = A2CONTROLLER_DONE;
625 while (*sim->printing) {
626 if (*sim->printing=='\001') { /* pause */
630 else if (*sim->printing=='\n') {
631 a2_printc(sim->st,*sim->printing);
637 a2_printc(sim->st,*sim->printing);
641 if (!*sim->printing) sim->printing=NULL;
643 else if (sim->curtime >= next_actiontime) {
647 /* If we're in the midst of typing a string, emit a character with
654 a2_printc(sim->st, c);
655 if (c=='\r' || c=='\n') {
656 next_actiontime = sim->curtime;
659 next_actiontime = sim->curtime + 0.1;
662 next_actiontime = (sim->curtime +
663 (((random()%1000)*0.001 + 0.3) *
668 next_actiontime=sim->curtime;
670 (*controller)(sim, &stepno, &next_actiontime);
671 if (stepno==A2CONTROLLER_DONE) goto finished;
677 analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
678 analogtv_setup_frame(sim->dec);
680 for (textrow=0; textrow<24; textrow++) {
681 for (row=textrow*8; row<textrow*8+8; row++) {
683 /* First we generate the pattern that the video circuitry shifts out
684 of memory. It has a 14.something MHz dot clock, equal to 4 times
685 the color burst frequency. So each group of 4 bits defines a color.
686 Each character position, or byte in hires, defines 14 dots, so odd
687 and even bytes have different color spaces. So, pattern[0..600]
688 gets the dots for one scan line. */
690 signed char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
692 if ((sim->st->gr_mode&A2_GR_HIRES) &&
693 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
695 /* Emulate the mysterious pink line, due to a bit getting
696 stuck in a shift register between the end of the last
697 row and the beginning of this one. */
698 if ((sim->st->hireslines[row][0] & 0x80) &&
699 (sim->st->hireslines[row][39]&0x40)) {
700 pp[-1]=ANALOGTV_WHITE_LEVEL;
703 for (col=0; col<40; col++) {
704 u_char b=sim->st->hireslines[row][col];
705 int shift=(b&0x80)?0:1;
707 /* Each of the low 7 bits in hires mode corresponded to 2 dot
708 clocks, shifted by one if the high bit was set. */
709 for (i=0; i<7; i++) {
710 pp[shift+1] = pp[shift] = (((b>>i)&1)
711 ?ANALOGTV_WHITE_LEVEL
712 :ANALOGTV_BLACK_LEVEL);
717 else if ((sim->st->gr_mode&A2_GR_LORES) &&
718 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
719 for (col=0; col<40; col++) {
720 u_char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
722 /* The low or high nybble was shifted out one bit at a time. */
723 for (i=0; i<14; i++) {
724 *pp = (((nib>>((col*14+i)&3))&1)
725 ?ANALOGTV_WHITE_LEVEL
726 :ANALOGTV_BLACK_LEVEL);
732 for (col=0; col<40; col++) {
734 c=sim->st->textlines[textrow][col]&0xff;
735 /* hi bits control inverse/blink as follows:
740 rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
742 for (i=0; i<7; i++) {
743 unsigned long pix=XGetPixel(sim->text_im,
746 pp[1] = pp[2] = ((pix^rev)
747 ?ANALOGTV_WHITE_LEVEL
748 :ANALOGTV_BLACK_LEVEL);
755 analogtv_init_signal(sim->dec, 0.02);
756 analogtv_reception_update(&sim->reception);
757 analogtv_add_signal(sim->dec, &sim->reception);
758 analogtv_draw(sim->dec);
763 stepno=A2CONTROLLER_FREE;
764 (*controller)(sim, &stepno, &next_actiontime);
766 XSync(sim->dpy, False);
767 XClearWindow(sim->dpy, sim->window);
771 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
777 a2_invalidate(sim->st);
779 For testing color rendering. The spec is:
787 6 med blue 20 207 253
788 7 lt blue 208 195 255
793 12 lt green 20 245 60
794 13 yellow 208 221 141
798 sim->st->gr_mode=A2_GR_LORES;
799 for (row=0; row<24; row++) {
800 for (col=0; col<40; col++) {
801 sim->st->textlines[row][col]=(row&15)*17;
804 *next_actiontime+=0.4;
809 if (sim->curtime > 10) *stepno=-1;