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 st->textlines[st->cursy][st->cursx] ^= 0xc0; /* turn off cursor */
75 for (i=0; i<23; i++) {
76 memcpy(st->textlines[i],st->textlines[i+1],40);
78 memset(st->textlines[23],0xe0,40);
79 st->textlines[st->cursy][st->cursx] ^= 0xc0; /* turn cursor back on */
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) {
622 while (*sim->printing) {
623 if (*sim->printing=='\001') { /* pause */
627 else if (*sim->printing=='\n') {
628 a2_printc(sim->st,*sim->printing);
634 a2_printc(sim->st,*sim->printing);
638 if (!*sim->printing) sim->printing=NULL;
640 else if (sim->curtime >= next_actiontime) {
644 /* If we're in the midst of typing a string, emit a character with
651 a2_printc(sim->st, c);
652 if (c=='\r' || c=='\n') {
653 next_actiontime = sim->curtime;
656 next_actiontime = sim->curtime + 0.1;
659 next_actiontime = (sim->curtime +
660 (((random()%1000)*0.001 + 0.3) *
665 next_actiontime=sim->curtime;
667 (*controller)(sim, &stepno, &next_actiontime);
668 if (stepno==A2CONTROLLER_DONE) goto finished;
674 analogtv_setup_sync(sim->inp, sim->st->gr_mode? 1 : 0, 0);
675 analogtv_setup_frame(sim->dec);
677 for (textrow=0; textrow<24; textrow++) {
678 for (row=textrow*8; row<textrow*8+8; row++) {
680 /* First we generate the pattern that the video circuitry shifts out
681 of memory. It has a 14.something MHz dot clock, equal to 4 times
682 the color burst frequency. So each group of 4 bits defines a color.
683 Each character position, or byte in hires, defines 14 dots, so odd
684 and even bytes have different color spaces. So, pattern[0..600]
685 gets the dots for one scan line. */
687 char *pp=&sim->inp->signal[row+ANALOGTV_TOP+4][ANALOGTV_PIC_START+100];
689 if ((sim->st->gr_mode&A2_GR_HIRES) &&
690 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
692 /* Emulate the mysterious pink line, due to a bit getting
693 stuck in a shift register between the end of the last
694 row and the beginning of this one. */
695 if ((sim->st->hireslines[row][0] & 0x80) &&
696 (sim->st->hireslines[row][39]&0x40)) {
697 pp[-1]=ANALOGTV_WHITE_LEVEL;
700 for (col=0; col<40; col++) {
701 u_char b=sim->st->hireslines[row][col];
702 int shift=(b&0x80)?0:1;
704 /* Each of the low 7 bits in hires mode corresponded to 2 dot
705 clocks, shifted by one if the high bit was set. */
706 for (i=0; i<7; i++) {
707 pp[shift+1] = pp[shift] = (((b>>i)&1)
708 ?ANALOGTV_WHITE_LEVEL
709 :ANALOGTV_BLACK_LEVEL);
714 else if ((sim->st->gr_mode&A2_GR_LORES) &&
715 (row<160 || (sim->st->gr_mode&A2_GR_FULL))) {
716 for (col=0; col<40; col++) {
717 u_char nib=((sim->st->textlines[textrow][col] >> (((row/4)&1)*4))
719 /* The low or high nybble was shifted out one bit at a time. */
720 for (i=0; i<14; i++) {
721 *pp = (((nib>>((col*14+i)&3))&1)
722 ?ANALOGTV_WHITE_LEVEL
723 :ANALOGTV_BLACK_LEVEL);
729 for (col=0; col<40; col++) {
731 c=sim->st->textlines[textrow][col]&0xff;
732 /* hi bits control inverse/blink as follows:
737 rev=!(c&0x80) && (!(c&0x40) || sim->st->blink);
739 for (i=0; i<7; i++) {
740 unsigned long pix=XGetPixel(sim->text_im,
743 pp[1] = pp[2] = ((pix^rev)
744 ?ANALOGTV_WHITE_LEVEL
745 :ANALOGTV_BLACK_LEVEL);
752 analogtv_init_signal(sim->dec, 0.02);
753 analogtv_reception_update(&sim->reception);
754 analogtv_add_signal(sim->dec, &sim->reception);
755 analogtv_draw(sim->dec);
760 stepno=A2CONTROLLER_FREE;
761 (*controller)(sim, &stepno, &next_actiontime);
763 XSync(sim->dpy, False);
764 XClearWindow(sim->dpy, sim->window);
768 a2controller_test(apple2_sim_t *sim, int *stepno, double *next_actiontime)
774 a2_invalidate(sim->st);
776 For testing color rendering. The spec is:
784 6 med blue 20 207 253
785 7 lt blue 208 195 255
790 12 lt green 20 245 60
791 13 yellow 208 221 141
795 sim->st->gr_mode=A2_GR_LORES;
796 for (row=0; row<24; row++) {
797 for (col=0; col<40; col++) {
798 sim->st->textlines[row][col]=(row&15)*17;
801 *next_actiontime+=0.4;
806 if (sim->curtime > 10) *stepno=-1;