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>
13 * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
18 #include "screenhack.h"
20 #include <X11/Xutil.h>
21 #include <X11/Intrinsic.h>
24 #include <X11/keysymdef.h>
33 #endif /* HAVE_FORKPTY */
36 #define countof(x) (sizeof((x))/sizeof((*x)))
38 #define SCREEN_COLS 40
39 #define SCREEN_ROWS 24
43 extern XtAppContext app;
45 Time subproc_relaunch_delay = 3000;
48 /* Given a bitmask, returns the position and width of the field.
51 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
54 for (i = 0; i < 32; i++)
59 for (; i < 32; i++, j++)
60 if (! (mask & (1L << i)))
68 /* Given a value and a field-width, expands the field to fill out 8 bits.
71 spread_bits (unsigned char value, unsigned char width)
76 case 7: return (value << 1) | (value >> 6);
77 case 6: return (value << 2) | (value >> 4);
78 case 5: return (value << 3) | (value >> 2);
79 case 4: return (value << 4) | (value);
80 case 3: return (value << 5) | (value << 2) | (value >> 2);
81 case 2: return (value << 6) | (value << 4) | (value);
82 default: abort(); break;
87 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
88 Scales it (without dithering) to WxH.
91 scale_image (Display *dpy, Window window, XImage *in,
92 int fromx, int fromy, int fromw, int fromh,
93 unsigned int *out, int w, int h)
97 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
98 unsigned int rsiz=0, gsiz=0, bsiz=0;
99 unsigned int rmsk=0, gmsk=0, bmsk=0;
100 unsigned char spread_map[3][256];
101 XWindowAttributes xgwa;
104 if (fromx + fromw > in->width ||
105 fromy + fromh > in->height)
108 XGetWindowAttributes (dpy, window, &xgwa);
110 /* Compute the field offsets for RGB decoding in the XImage,
111 when in TrueColor mode. Otherwise we use the colormap.
113 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
114 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
116 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
117 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
118 for (i = 0; i < ncolors; i++)
120 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
124 rmsk = xgwa.visual->red_mask;
125 gmsk = xgwa.visual->green_mask;
126 bmsk = xgwa.visual->blue_mask;
127 decode_mask (rmsk, &rpos, &rsiz);
128 decode_mask (gmsk, &gpos, &gsiz);
129 decode_mask (bmsk, &bpos, &bsiz);
131 for (i = 0; i < 256; i++)
133 spread_map[0][i] = spread_bits (i, rsiz);
134 spread_map[1][i] = spread_bits (i, gsiz);
135 spread_map[2][i] = spread_bits (i, bsiz);
139 scale = (fromw > fromh
141 : (float) fromh / h);
143 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
145 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
146 for (x = 0; x < w-1; x++)
149 unsigned int r=0, g=0, b=0;
151 int xx1 = x * scale + fromx;
152 int yy1 = y * scale + fromy;
153 int xx2 = (x+1) * scale + fromx;
154 int yy2 = (y+1) * scale + fromy;
156 /* Iterate over the source pixels contributing to this one, and sum. */
157 for (xx = xx1; xx < xx2; xx++)
158 for (yy = yy1; yy < yy2; yy++)
160 unsigned char rr, gg, bb;
161 unsigned long sp = ((xx > in->width || yy > in->height)
162 ? 0 : XGetPixel (in, xx, yy));
165 rr = colors[sp].red & 0xFF;
166 gg = colors[sp].green & 0xFF;
167 bb = colors[sp].blue & 0xFF;
171 rr = (sp & rmsk) >> rpos;
172 gg = (sp & gmsk) >> gpos;
173 bb = (sp & bmsk) >> bpos;
174 rr = spread_map[0][rr];
175 gg = spread_map[1][gg];
176 bb = spread_map[2][bb];
183 /* Scale summed pixel values down to 8/8/8 range */
184 i = (xx2 - xx1) * (yy2 - yy1);
190 out[y * w + x] = (r << 16) | (g << 8) | b;
195 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
196 Picks a random sub-image out of the source image, and scales it to WxH.
199 pick_a2_subimage (Display *dpy, Window window, XImage *in,
200 unsigned int *out, int w, int h)
202 int fromx, fromy, fromw, fromh;
203 if (in->width <= w || in->height <= h)
214 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
217 } while (fromw > in->width ||
220 dw = (in->width - fromw) / 2; /* near the center! */
221 dh = (in->height - fromh) / 2;
223 fromx = (random() % dw) + (dw/2);
224 fromy = (random() % dh) + (dh/2);
227 scale_image (dpy, window, in,
228 fromx, fromy, fromw, fromh,
233 /* Floyd-Steinberg dither. Derived from ppmquant.c,
234 Copyright (c) 1989, 1991 by Jef Poskanzer.
237 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
240 Apple ][ color map. Each pixel can only be 1 or 0, but what that
241 means depends on whether it's an odd or even pixel, and whether
242 the high bit in the byte is set or not. If it's 0, it's always
245 static const int a2_cmap[2][2][3] = {
248 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
249 {/* even pixels = red */ 0xff, 0x80, 0x00}
253 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
254 {/* odd pixels = green */ 0x40, 0xff, 0x40}
259 unsigned int **pixels;
275 FILE *pipe = popen ("xv -", "w");
276 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
277 for (y = 0; y < h; y++)
278 for (x = 0; x < w; x++)
280 unsigned int p = in[y * w + x];
281 unsigned int r = (p >> 16) & 0xFF;
282 unsigned int g = (p >> 8) & 0xFF;
283 unsigned int b = (p ) & 0xFF;
284 fprintf(pipe, "%c%c%c", r, g, b);
290 /* Initialize Floyd-Steinberg error vectors. */
291 this_rerr = (long *) calloc (w + 2, sizeof(long));
292 next_rerr = (long *) calloc (w + 2, sizeof(long));
293 this_gerr = (long *) calloc (w + 2, sizeof(long));
294 next_gerr = (long *) calloc (w + 2, sizeof(long));
295 this_berr = (long *) calloc (w + 2, sizeof(long));
296 next_berr = (long *) calloc (w + 2, sizeof(long));
299 /* #### do we really need more than one element of "pixels" at once?
301 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
302 for (y = 0; y < h; y++)
303 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
305 for (x = 0; x < w + 2; ++x)
307 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
308 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
309 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
310 /* (random errors in [-1 .. 1]) */
314 for (y = 0; y < h; y++)
315 for (x = 0; x < w; x++)
316 pixels[y][x] = in[y * w + x];
318 for (y = 0; y < h; y++)
324 for (x = 0; x < w + 2; x++)
325 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
327 /* It's too complicated to go back and forth on alternate rows,
328 so we always go left-right here. It doesn't change the result
331 For each group of 7 pixels, we have to try it both with the
332 high bit=0 and =1. For each high bit value, we add up the
333 total error and pick the best one.
335 Because we have to go through each group of bits twice, we
336 don't propagate the error values through this_[rgb]err since
337 it would add them twice. So we keep seperate local_[rgb]err
338 variables for propagating error within the 7-pixel group.
342 for (xbyte=0; xbyte<280; xbyte+=7)
345 int best_error=2000000000;
349 int local_rerr=0, local_gerr=0, local_berr=0;
351 for (hibit=0; hibit<2; hibit++)
356 for (x=xbyte; x<xbyte+7; x++)
360 /* Use Floyd-Steinberg errors to adjust actual color. */
361 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
362 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
363 sb = ((pP[x] ) & 0xFF) * brightness/256;
364 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
365 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
366 sb += (this_berr[x + 1] + local_berr) / fs_scale;
369 else if (sr > maxval) sr = maxval;
371 else if (sg > maxval) sg = maxval;
373 else if (sb > maxval) sb = maxval;
375 /* This is the color we'd get if we set the bit 1. For 0,
377 r2=a2_cmap[hibit][x&1][0];
378 g2=a2_cmap[hibit][x&1][1];
379 b2=a2_cmap[hibit][x&1][2];
382 dist0 and dist1 are the error (Minkowski 2-metric
383 distances in the color space) for choosing 0 and
384 1 respectively. 0 is black, 1 is the color r2,g2,b2.
386 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
387 dist0= sr*sr + sg*sg + sb*sb;
391 byte |= 1 << (x-xbyte);
394 /* Wanted sr but got r2, so propagate sr-r2 */
395 local_rerr = (sr - r2) * fs_scale * 7/16;
396 local_gerr = (sg - g2) * fs_scale * 7/16;
397 local_berr = (sb - b2) * fs_scale * 7/16;
403 /* Wanted sr but got 0, so propagate sr */
404 local_rerr = sr * fs_scale * 7/16;
405 local_gerr = sg * fs_scale * 7/16;
406 local_berr = sb * fs_scale * 7/16;
410 if (tot_error < best_error)
413 best_error = tot_error;
417 /* Avoid alternating 7f and ff in all-white areas, because it makes
418 regular pink vertical lines */
419 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
424 Now that we've chosen values for all 8 bits of the byte, we
425 have to fill in the real pixel values into pP and propagate
426 all the error terms. We end up repeating a lot of the code
430 for (x=xbyte; x<xbyte+7; x++)
432 int bit=(best_byte>>(x-xbyte))&1;
433 hibit=(best_byte>>7)&1;
435 sr = (pP[x] >> 16) & 0xFF;
436 sg = (pP[x] >> 8) & 0xFF;
437 sb = (pP[x] ) & 0xFF;
438 sr += this_rerr[x + 1] / fs_scale;
439 sg += this_gerr[x + 1] / fs_scale;
440 sb += this_berr[x + 1] / fs_scale;
443 else if (sr > maxval) sr = maxval;
445 else if (sg > maxval) sg = maxval;
447 else if (sb > maxval) sb = maxval;
449 r2=a2_cmap[hibit][x&1][0] * bit;
450 g2=a2_cmap[hibit][x&1][1] * bit;
451 b2=a2_cmap[hibit][x&1][2] * bit;
453 pP[x] = (r2<<16) | (g2<<8) | (b2);
455 /* Propagate Floyd-Steinberg error terms. */
456 err = (sr - r2) * fs_scale;
457 this_rerr[x + 2] += (err * 7) / 16;
458 next_rerr[x ] += (err * 3) / 16;
459 next_rerr[x + 1] += (err * 5) / 16;
460 next_rerr[x + 2] += (err ) / 16;
461 err = (sg - g2) * fs_scale;
462 this_gerr[x + 2] += (err * 7) / 16;
463 next_gerr[x ] += (err * 3) / 16;
464 next_gerr[x + 1] += (err * 5) / 16;
465 next_gerr[x + 2] += (err ) / 16;
466 err = (sb - b2) * fs_scale;
467 this_berr[x + 2] += (err * 7) / 16;
468 next_berr[x ] += (err * 3) / 16;
469 next_berr[x + 1] += (err * 5) / 16;
470 next_berr[x + 2] += (err ) / 16;
474 And put the actual byte into out.
477 out[y*(w/7) + xbyte/7] = best_byte;
481 temp_err = this_rerr;
482 this_rerr = next_rerr;
483 next_rerr = temp_err;
484 temp_err = this_gerr;
485 this_gerr = next_gerr;
486 next_gerr = temp_err;
487 temp_err = this_berr;
488 this_berr = next_berr;
489 next_berr = temp_err;
505 /* let's see what we got... */
506 FILE *pipe = popen ("xv -", "w");
507 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
508 for (y = 0; y < h; y++)
509 for (x = 0; x < w; x++)
511 unsigned int r = (pixels[y][x]>>16)&0xff;
512 unsigned int g = (pixels[y][x]>>8)&0xff;
513 unsigned int b = (pixels[y][x]>>0)&0xff;
514 fprintf(pipe, "%c%c%c", r, g, b);
522 static unsigned char *
523 load_image (Display *dpy, Window window, char **image_filename_r)
525 XWindowAttributes xgwa;
531 unsigned int *buf32 = (unsigned int *) calloc (w, h * 4);
532 unsigned char *buf8 = (unsigned char *) calloc (w/7, h);
536 fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
540 XGetWindowAttributes (dpy, window, &xgwa);
541 p = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
542 load_random_image (xgwa.screen, window, p, image_filename_r);
543 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
544 XFreePixmap (dpy, p);
547 /* Make sure the window's background is not set to None, and get the
548 grabbed bits (if any) off it as soon as possible. */
549 XSetWindowBackground (dpy, window,
550 get_pixel_resource ("background", "Background",
551 dpy, xgwa.colormap));
552 XClearWindow (dpy, window);
554 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
555 image (regardless of whether it started as TrueColor/PseudoColor.)
557 pick_a2_subimage (dpy, window, image, buf32, w, h);
559 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
561 a2_dither (buf32, buf8, w, h);
568 char *progclass = "Apple2";
570 char *defaults [] = {
571 ".background: black",
572 ".foreground: white",
575 "*metaSendsESC: True",
582 # endif /* !HAVE_FORKPTY */
588 XrmOptionDescRec options [] = {
589 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
590 { "-basic", ".mode", XrmoptionNoArg, "basic" },
591 { "-text", ".mode", XrmoptionNoArg, "text" },
592 { "-program", ".program", XrmoptionSepArg, 0 },
593 { "-duration", ".duration", XrmoptionSepArg, 0 },
594 { "-pty", ".usePty", XrmoptionNoArg, "True" },
595 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
596 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
597 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
598 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
599 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
600 { "-fast", ".fast", XrmoptionNoArg, "True" },
606 TODO: this should load 10 images at startup time, then cycle through them
607 to avoid the pause while it loads.
610 void slideshow_controller(apple2_sim_t *sim, int *stepno,
611 double *next_actiontime)
613 apple2_state_t *st=sim->st;
617 int render_img_lineno;
622 if (!sim->controller_data)
623 sim->controller_data = calloc(sizeof(struct mydata),1);
624 mine=(struct mydata *) sim->controller_data;
632 sim->typing_rate = 0.3;
633 sim->dec->powerup=0.0;
636 a2_prints(st, "APPLE ][");
640 *next_actiontime += 4.0;
644 mine->render_img = load_image (sim->dpy, sim->window, &mine->img_filename);
650 *next_actiontime += 3.0;
664 st->gr_mode=A2_GR_HIRES;
665 if (mine->img_filename) {
666 char *basename, *tmp;
669 basename = tmp = strdup (mine->img_filename);
672 char *slash = strchr(basename, '/');
673 if (!slash || !slash[1]) break;
677 char *dot=strchr(basename,'.');
680 if (strlen(basename)>20) basename[20]=0;
681 for (s=basename; *s; s++) {
683 if (*s <= ' ') *s = '_';
685 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
686 sim->typing = sim->typing_buf;
690 sim->typing = "BLOAD IMAGE\n";
692 mine->render_img_lineno=0;
698 *next_actiontime += 0.7;
703 if (mine->render_img_lineno>=192) {
705 sim->typing="POKE 49234,0\n";
710 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
711 a2_display_image_loading(st, mine->render_img,
712 mine->render_img_lineno++);
715 /* The disk would have to seek every 13 sectors == 78 lines.
716 (This ain't no newfangled 16-sector operating system) */
717 if ((mine->render_img_lineno%78)==0) {
718 *next_actiontime += 0.5;
720 *next_actiontime += 0.08;
725 st->gr_mode |= A2_GR_FULL;
727 *next_actiontime += sim->delay;
732 sim->typing="POKE 49235,0\n";
738 st->gr_mode &= ~A2_GR_FULL;
739 if (mine->render_img) {
740 free(mine->render_img);
741 mine->render_img=NULL;
743 if (mine->img_filename) {
744 free(mine->img_filename);
745 mine->img_filename=NULL;
750 case A2CONTROLLER_FREE:
751 free(mine->render_img);
752 free(mine->img_filename);
761 struct terminal_controller_data {
765 int input_available_p;
766 XtIntervalId timeout_id;
770 double last_emit_time;
771 XComposeStatus compose;
776 int cursor_x, cursor_y;
777 int saved_x, saved_y;
780 unsigned int bold : 1;
781 unsigned int blink : 1;
782 unsigned int rev : 1;
786 Bool meta_sends_esc_p;
793 subproc_cb (XtPointer closure, int *source, XtInputId *id)
795 struct terminal_controller_data *mine =
796 (struct terminal_controller_data *) closure;
797 mine->input_available_p = True;
801 launch_text_generator (struct terminal_controller_data *mine)
804 char *oprogram = get_string_resource ("program", "Program");
807 if (!oprogram || !*oprogram)
808 oprogram = FORTUNE_PROGRAM;
810 program = (char *) malloc (strlen (oprogram) + 10);
812 strcpy (program, "( ");
813 strcat (program, oprogram);
814 strcat (program, " ) 2>&1");
816 if (mine->pipe) abort();
819 if (get_boolean_resource ("usePty", "Boolean"))
824 ws.ws_col = SCREEN_COLS;
825 ws.ws_row = SCREEN_ROWS;
826 ws.ws_xpixel = ws.ws_col * 6;
827 ws.ws_ypixel = ws.ws_row * 8;
830 if((mine->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
833 sprintf (buf, "%.100s: forkpty", progname);
838 /* This is the child fork. */
839 if (putenv("TERM=vt100"))
841 execl("/bin/sh", "/bin/sh", "-c", oprogram, NULL);
842 sprintf (buf, "%.100s: %.100s", progname, oprogram);
848 /* This is the parent fork. */
849 mine->pipe = fdopen(fd, "r+");
851 XtAppAddInput (app, fileno (mine->pipe),
852 (XtPointer) (XtInputReadMask | XtInputExceptMask),
853 subproc_cb, (XtPointer) mine);
857 # endif /* HAVE_FORKPTY */
859 if ((mine->pipe = popen (program, "r")))
861 if (mine->pipe_id) abort();
863 XtAppAddInput (app, fileno (mine->pipe),
864 (XtPointer) (XtInputReadMask | XtInputExceptMask),
865 subproc_cb, (XtPointer) mine);
869 sprintf (buf, "%.100s: %.100s", progname, program);
875 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
877 struct terminal_controller_data *mine =
878 (struct terminal_controller_data *) closure;
880 launch_text_generator (mine);
884 terminal_closegen(struct terminal_controller_data *mine)
887 XtRemoveInput (mine->pipe_id);
894 if (mine->timeout_id) {
895 XtRemoveTimeOut(mine->timeout_id);
901 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
910 if (!mine->input_available_p) return 0;
912 rc=read (fileno (mine->pipe), (void *) buf, n);
913 if (rc>0) mine->lastc=buf[rc-1];
917 terminal_closegen(mine);
919 if (mine->lastc != '\n') { /* add a newline at eof if there wasn't one */
923 /* Set up a timer to re-launch the subproc in a bit. */
925 XtAppAddTimeOut(app, subproc_relaunch_delay,
926 relaunch_generator_timer,
930 mine->input_available_p = False;
936 /* The interpretation of the ModN modifiers is dependent on what keys
937 are bound to them: Mod1 does not necessarily mean "meta". It only
938 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
939 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
940 aren't necessarily the same thing. Icepicks in my forehead!
943 do_icccm_meta_key_stupidity (Display *dpy)
945 unsigned int modbits = 0;
947 XModifierKeymap *modmap = XGetModifierMapping (dpy);
948 for (i = 3; i < 8; i++)
949 for (j = 0; j < modmap->max_keypermod; j++)
951 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
954 if (code == 0) continue;
955 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
956 for (k = 0; k < nsyms; k++)
957 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
958 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
962 XFreeModifiermap (modmap);
966 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
969 meta_modifier (Display *dpy)
971 static Bool done_once = False;
972 static unsigned int mask = 0;
975 /* Really, we are supposed to recompute this if a KeymapNotify
976 event comes in, but fuck it. */
978 mask = do_icccm_meta_key_stupidity (dpy);
985 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
987 struct terminal_controller_data *mine =
988 (struct terminal_controller_data *) data;
991 XLookupString (&event->xkey, (char *) &c, 1, &keysym, &mine->compose);
992 if (c == 0 || !mine->pipe)
995 if (!mine->swap_bs_del_p) ;
996 else if (c == 127) c = 8;
997 else if (c == 8) c = 127;
999 /* If meta was held down, send ESC, or turn on the high bit. */
1000 if (event->xkey.state & meta_modifier (dpy))
1002 if (mine->meta_sends_esc_p)
1003 fputc ('\033', mine->pipe);
1008 fputc (c, mine->pipe);
1009 fflush (mine->pipe);
1011 event->xany.type = 0; /* do not process this event further */
1018 a2_ascii_printc (apple2_state_t *st, unsigned char c,
1019 Bool bold_p, Bool blink_p, Bool rev_p,
1022 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
1026 else if ((c >= 'A'+128) || /* upcase and blink */
1027 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
1028 c != '\r' && c != '\n' && c!='\t'))
1030 c = (c & 0x1F) | 0x80;
1032 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
1037 if (bold_p) c |= 0xc0;
1038 if (blink_p) c = (c & ~0x40) | 0x80;
1039 if (rev_p) c |= 0xc0;
1044 a2_printc_noscroll(st, c);
1049 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
1052 apple2_state_t *st=sim->st;
1053 int cols = SCREEN_COLS;
1054 int rows = SCREEN_ROWS;
1059 switch (state->escstate)
1065 /* Dummy case - we don't want the screensaver to beep */
1066 /* #### But maybe this should flash the screen? */
1069 if (state->cursor_x > 0)
1073 if (state->cursor_x < cols - 8)
1075 state->cursor_x = (state->cursor_x & ~7) + 8;
1079 state->cursor_x = 0;
1080 if (state->cursor_y < rows - 1)
1089 if (state->cursor_y < rows - 1)
1095 state->cursor_x = 0;
1099 /* Dummy case - there is one and only one font. */
1103 /* Dummy case - these interrupt escape sequences, so
1104 they don't do anything in this state */
1107 state->escstate = 1;
1110 /* Dummy case - this is supposed to be ignored */
1113 state->escstate = 2;
1114 for(i = 0; i < NPAR; i++)
1115 state->csiparam[i] = 0;
1116 state->curparam = 0;
1119 /* If the cursor is in column 39 and we print a character, then
1120 that character shows up in column 39, and the cursor is no longer
1121 visible on the screen (it's in "column 40".) If another character
1122 is printed, then that character shows up in column 0, and the
1123 cursor moves to column 1.
1125 This is empirically what xterm and gnome-terminal do, so that must
1126 be the right thing. (In xterm, the cursor vanishes, whereas; in
1127 gnome-terminal, the cursor overprints the character in col 39.)
1129 if (state->cursor_x >= cols)
1131 state->cursor_x = 0;
1132 if (state->cursor_y >= rows - 1)
1138 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
1139 a2_ascii_printc (st, c,
1140 state->termattrib.bf.bold,
1141 state->termattrib.bf.blink,
1142 state->termattrib.bf.rev,
1154 state->escstate = 0;
1156 case 'c': /* Reset */
1158 state->escstate = 0;
1160 case 'D': /* Linefeed */
1161 if (state->cursor_y < rows - 1)
1165 state->escstate = 0;
1167 case 'E': /* Newline */
1168 state->cursor_x = 0;
1169 state->escstate = 0;
1171 case 'M': /* Reverse newline */
1172 if (state->cursor_y > 0)
1174 state->escstate = 0;
1176 case '7': /* Save state */
1177 state->saved_x = state->cursor_x;
1178 state->saved_y = state->cursor_y;
1179 state->escstate = 0;
1181 case '8': /* Restore state */
1182 state->cursor_x = state->saved_x;
1183 state->cursor_y = state->saved_y;
1184 state->escstate = 0;
1187 state->escstate = 2;
1188 for(i = 0; i < NPAR; i++)
1189 state->csiparam[i] = 0;
1190 state->curparam = 0;
1192 case '%': /* Select charset */
1193 /* No, I don't support UTF-8, since the apple2 font
1194 isn't even Unicode anyway. We must still catch the
1195 last byte, though. */
1198 /* I don't support different fonts either - see above
1200 state->escstate = 3;
1203 /* Escape sequences not supported:
1206 * Z - Terminal identification
1208 * = - Other keypad change
1211 state->escstate = 0;
1220 state->escstate = 0;
1222 case '0': case '1': case '2': case '3': case '4':
1223 case '5': case '6': case '7': case '8': case '9':
1224 if (state->curparam < NPAR)
1225 state->csiparam[state->curparam] =
1226 (state->csiparam[state->curparam] * 10) + (c - '0');
1229 state->csiparam[++state->curparam] = 0;
1232 state->escstate = 3;
1235 for (i = 0; i < state->csiparam[0]; i++)
1237 if(++state->cursor_x > cols)
1239 state->cursor_x = 0;
1240 if (state->cursor_y < rows - 1)
1246 state->escstate = 0;
1249 state->cursor_x = 0;
1251 if (state->csiparam[0] == 0)
1252 state->csiparam[0] = 1;
1253 if ((state->cursor_y -= state->csiparam[0]) < 0)
1254 state->cursor_y = 0;
1255 state->escstate = 0;
1258 state->cursor_x = 0;
1261 if (state->csiparam[0] == 0)
1262 state->csiparam[0] = 1;
1263 if ((state->cursor_y += state->csiparam[0]) >= rows)
1264 state->cursor_y = rows - 1;
1265 state->escstate = 0;
1269 if (state->csiparam[0] == 0)
1270 state->csiparam[0] = 1;
1271 if ((state->cursor_x += state->csiparam[0]) >= cols)
1272 state->cursor_x = cols - 1;
1273 state->escstate = 0;
1276 if (state->csiparam[0] == 0)
1277 state->csiparam[0] = 1;
1278 if ((state->cursor_x -= state->csiparam[0]) < 0)
1279 state->cursor_x = 0;
1280 state->escstate = 0;
1283 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1284 state->cursor_y = rows - 1;
1285 state->escstate = 0;
1289 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1290 state->cursor_x = cols - 1;
1291 state->escstate = 0;
1295 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1296 state->cursor_y = rows - 1;
1297 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1298 state->cursor_x = cols - 1;
1299 if(state->cursor_y < 0)
1300 state->cursor_y = 0;
1301 if(state->cursor_x < 0)
1302 state->cursor_x = 0;
1303 state->escstate = 0;
1308 if (state->csiparam[0] == 0)
1309 start = cols * state->cursor_y + state->cursor_x;
1310 if (state->csiparam[0] == 1)
1311 end = cols * state->cursor_y + state->cursor_x;
1313 a2_goto(st, state->cursor_y, state->cursor_x);
1314 for (i = start; i < end; i++)
1316 a2_ascii_printc(st, ' ', False, False, False, False);
1318 state->escstate = 0;
1323 if (state->csiparam[0] == 0)
1324 start = state->cursor_x;
1325 if (state->csiparam[1] == 1)
1326 end = state->cursor_x;
1328 a2_goto(st, state->cursor_y, state->cursor_x);
1329 for (i = start; i < end; i++)
1331 a2_ascii_printc(st, ' ', False, False, False, False);
1333 state->escstate = 0;
1335 case 'm': /* Set attributes */
1336 for (i = 0; i <= state->curparam; i++)
1338 switch(state->csiparam[i])
1341 state->termattrib.w = 0;
1344 state->termattrib.bf.bold = 1;
1347 state->termattrib.bf.blink = 1;
1350 state->termattrib.bf.rev = 1;
1354 state->termattrib.bf.bold = 0;
1357 state->termattrib.bf.blink = 0;
1360 state->termattrib.bf.rev = 0;
1364 state->escstate = 0;
1366 case 's': /* Save position */
1367 state->saved_x = state->cursor_x;
1368 state->saved_y = state->cursor_y;
1369 state->escstate = 0;
1371 case 'u': /* Restore position */
1372 state->cursor_x = state->saved_x;
1373 state->cursor_y = state->saved_y;
1374 state->escstate = 0;
1376 case '?': /* DEC Private modes */
1377 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1378 state->escstate = 0;
1381 /* Known unsupported CSIs:
1383 * L - Insert blank lines
1384 * M - Delete lines (I don't know what this means...)
1385 * P - Delete characters
1386 * X - Erase characters (difference with P being...?)
1387 * c - Terminal identification
1388 * g - Clear tab stop(s)
1389 * h - Set mode (Mainly due to its complexity and lack of good
1392 * m - Set mode (Phosphor is, per defenition, green on black)
1394 * q - Set keyboard LEDs
1395 * r - Set scrolling region (too exhausting - noone uses this,
1398 state->escstate = 0;
1403 state->escstate = 0;
1406 a2_goto(st, state->cursor_y, state->cursor_x);
1411 It's fun to put things like "gdb" as the command. For one, it's
1412 amusing how the standard mumble (version, no warranty, it's
1413 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1417 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1419 apple2_state_t *st=sim->st;
1423 struct terminal_controller_data *mine;
1424 if (!sim->controller_data)
1425 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1426 mine=(struct terminal_controller_data *) sim->controller_data;
1428 mine->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
1429 mine->swap_bs_del_p = get_boolean_resource ("swapBSDEL", "Boolean");
1430 mine->fast_p = get_boolean_resource ("fast", "Boolean");
1432 sim->dec->key_handler = terminal_keypress_handler;
1433 sim->dec->key_data = mine;
1439 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1443 a2_prints(st, "APPLE ][");
1448 launch_text_generator(mine);
1451 *next_actiontime += 4.0;
1457 unsigned char buf[1024];
1461 elapsed=sim->curtime - mine->last_emit_time;
1462 mine->last_emit_time=sim->curtime;
1464 if (elapsed>1.0) nwant=1;
1465 if (nwant<1) nwant=1;
1466 if (nwant>4) nwant=4;
1469 nwant = sizeof(buf)-1;
1471 nr=terminal_read(mine, buf, nwant);
1472 for (i=0; i<nr; i++) {
1476 a2_vt100_printc (sim, mine, c);
1478 a2_ascii_printc (st, c, False, False, False, True);
1483 case A2CONTROLLER_FREE:
1484 terminal_closegen(mine);
1490 struct basic_controller_data {
1497 double prog_start_time;
1498 char error_buf[256];
1502 Adding more programs is easy. Just add a listing here and to all_programs,
1503 then add the state machine to actually execute it to basic_controller.
1505 static char *moire_program[]={
1507 "20 FOR Y = 0 TO 191 STEP 2\n",
1508 "30 HCOLOR=4 : REM BLACK\n",
1509 "40 HLINE 0,191-Y TO 279,Y\n",
1510 "60 HCOLOR=7 : REM WHITE\n",
1511 "80 HLINE 0,190-Y TO 279,Y+1\n",
1513 "100 FOR X = 0 TO 279 STEP 3\n",
1515 "120 HLINE 279-X,0 TO X,192\n",
1517 "150 HLINE 278-X,0 TO X+1,192\n",
1522 static char *sinewave_program[] = {
1525 "30 FOR X = 0 TO 279\n",
1527 "35 HLINE X,0 TO X,159\n",
1529 "40 Y = 80 + SIN(15*(X-K)/279)\n",
1538 static char *dumb_program[]={
1539 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1545 static char *random_lores_program[]={
1546 "1 REM APPLE ][ SCREEN SAVER\n",
1548 "100 COLOR= RND(1)*16\n",
1550 "110 X=RND(1)*40\n",
1551 "120 Y1=RND(1)*48\n",
1552 "130 Y2=RND(1)*48\n",
1553 "140 FOR Y = Y1 TO Y2\n",
1557 "210 Y=RND(1)*48\n",
1558 "220 X1=RND(1)*40\n",
1559 "230 X2=RND(1)*40\n",
1560 "240 FOR X = X1 TO X2\n",
1568 static char typo_map[256];
1570 int make_typo(char *out_buf, char *orig, char *err_buf)
1630 strcpy(out_buf, orig);
1631 for (i=0; out_buf[i]; i++) {
1632 char *p = out_buf+i;
1634 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1637 if (isalpha(p[0]) &&
1646 sprintf(err_buf,"?SYNTAX ERROR\n");
1650 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(u_char)p[0]])) {
1651 int remain=strlen(p);
1652 int past=random()%(remain-2)+1;
1653 memmove(p+past+past, p, remain+1);
1655 for (j=0; j<past; j++) {
1668 {moire_program, 100},
1669 /*{dumb_program, 200}, */
1670 {sinewave_program, 400},
1671 {random_lores_program, 500},
1675 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1677 apple2_state_t *st=sim->st;
1680 struct basic_controller_data *mine;
1681 if (!sim->controller_data)
1682 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1683 mine=(struct basic_controller_data *) sim->controller_data;
1690 a2_prints(st, "APPLE ][");
1693 sim->typing_rate=0.2;
1695 i=random()%countof(all_programs);
1696 mine->progtext=all_programs[i].progtext;
1697 mine->progstep=all_programs[i].progstep;
1700 *next_actiontime += 1.0;
1705 if (st->cursx==0) a2_printc(st,']');
1706 if (mine->progtext[mine->prog_line]) {
1707 if (random()%4==0) {
1708 int err=make_typo(sim->typing_buf,
1709 mine->progtext[mine->prog_line],
1711 sim->typing=sim->typing_buf;
1718 sim->typing=mine->progtext[mine->prog_line++];
1726 sim->printing=mine->error_buf;
1731 if (st->cursx==0) a2_printc(st,']');
1732 *next_actiontime+=1.0;
1737 sim->typing="RUN\n";
1741 mine->prog_start_time=*next_actiontime;
1742 *stepno=mine->progstep;
1747 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1748 for (i=0; i<24 && mine->y<192; i++)
1750 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1751 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1761 for (i=0; i<24 && mine->x<280; i++)
1763 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1764 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1767 if (mine->x >= 280) *stepno=120;
1771 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1776 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1777 for (i=0; i<30; i++) {
1778 a2_prints(st, mine->rep_str);
1784 i=random()%strlen(mine->rep_str);
1785 while (mine->rep_pos != i) {
1786 a2_printc(st, mine->rep_str[mine->rep_pos]);
1788 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1790 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1793 /* sinewave_program */
1795 st->gr_mode=A2_GR_HIRES;
1800 for (i=0; i<48; i++) {
1801 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1802 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1803 a2_hplot(st, 3, mine->x, y);
1810 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1818 /* random_lores_program */
1820 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1825 for (i=0; i<10; i++) {
1826 int color,x,y,x1,x2,y1,y2;
1832 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1837 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1839 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1846 case A2CONTROLLER_FREE:
1853 void (*controllers[])(apple2_sim_t *sim, int *stepno,
1854 double *next_actiontime) = {
1855 slideshow_controller,
1856 terminal_controller,
1861 screenhack (Display *dpy, Window window)
1863 int duration = get_integer_resource ("duration", "Integer");
1865 void (*controller)(apple2_sim_t *sim, int *stepno, double *next_actiontime);
1867 if (duration < 1) duration = 1;
1869 s = get_string_resource ("mode", "Mode");
1870 if (!s || !*s || !strcasecmp(s, "random"))
1871 controller = controllers[random() % (countof(controllers))];
1872 else if (!strcasecmp(s, "text"))
1873 controller = terminal_controller;
1874 else if (!strcasecmp(s, "slideshow"))
1875 controller = slideshow_controller;
1876 else if (!strcasecmp(s, "basic"))
1877 controller = basic_controller;
1880 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1885 if (!get_boolean_resource ("root", "Boolean"))
1887 XWindowAttributes xgwa;
1888 XGetWindowAttributes (dpy, window, &xgwa);
1889 XSelectInput (dpy, window,
1890 xgwa.your_event_mask |
1891 KeyPressMask | ButtonPressMask | ExposureMask);
1894 apple2 (dpy, window, duration, controller);