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>
28 #endif /* HAVE_FORKPTY */
31 #define countof(x) (sizeof((x))/sizeof((*x)))
33 #define SCREEN_COLS 40
34 #define SCREEN_ROWS 24
38 extern XtAppContext app;
40 Time subproc_relaunch_delay = 3000;
43 /* Given a bitmask, returns the position and width of the field.
46 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
49 for (i = 0; i < 32; i++)
54 for (; i < 32; i++, j++)
55 if (! (mask & (1L << i)))
63 /* Given a value and a field-width, expands the field to fill out 8 bits.
66 spread_bits (unsigned char value, unsigned char width)
71 case 7: return (value << 1) | (value >> 6);
72 case 6: return (value << 2) | (value >> 4);
73 case 5: return (value << 3) | (value >> 2);
74 case 4: return (value << 4) | (value);
75 case 3: return (value << 5) | (value << 2) | (value >> 2);
76 case 2: return (value << 6) | (value << 4) | (value);
77 default: abort(); break;
82 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
83 Scales it (without dithering) to WxH.
86 scale_image (Display *dpy, Window window, XImage *in,
87 int fromx, int fromy, int fromw, int fromh,
88 unsigned int *out, int w, int h)
92 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
93 unsigned int rsiz=0, gsiz=0, bsiz=0;
94 unsigned int rmsk=0, gmsk=0, bmsk=0;
95 unsigned char spread_map[3][256];
96 XWindowAttributes xgwa;
99 if (fromx + fromw > in->width ||
100 fromy + fromh > in->height)
103 XGetWindowAttributes (dpy, window, &xgwa);
105 /* Compute the field offsets for RGB decoding in the XImage,
106 when in TrueColor mode. Otherwise we use the colormap.
108 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
109 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
111 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
112 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
113 for (i = 0; i < ncolors; i++)
115 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
119 rmsk = xgwa.visual->red_mask;
120 gmsk = xgwa.visual->green_mask;
121 bmsk = xgwa.visual->blue_mask;
122 decode_mask (rmsk, &rpos, &rsiz);
123 decode_mask (gmsk, &gpos, &gsiz);
124 decode_mask (bmsk, &bpos, &bsiz);
126 for (i = 0; i < 256; i++)
128 spread_map[0][i] = spread_bits (i, rsiz);
129 spread_map[1][i] = spread_bits (i, gsiz);
130 spread_map[2][i] = spread_bits (i, bsiz);
134 scale = (fromw > fromh
136 : (float) fromh / h);
138 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
140 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
141 for (x = 0; x < w-1; x++)
144 unsigned int r=0, g=0, b=0;
146 int xx1 = x * scale + fromx;
147 int yy1 = y * scale + fromy;
148 int xx2 = (x+1) * scale + fromx;
149 int yy2 = (y+1) * scale + fromy;
151 /* Iterate over the source pixels contributing to this one, and sum. */
152 for (xx = xx1; xx < xx2; xx++)
153 for (yy = yy1; yy < yy2; yy++)
155 unsigned char rr, gg, bb;
156 unsigned long sp = ((xx > in->width || yy > in->height)
157 ? 0 : XGetPixel (in, xx, yy));
160 rr = colors[sp].red & 0xFF;
161 gg = colors[sp].green & 0xFF;
162 bb = colors[sp].blue & 0xFF;
166 rr = (sp & rmsk) >> rpos;
167 gg = (sp & gmsk) >> gpos;
168 bb = (sp & bmsk) >> bpos;
169 rr = spread_map[0][rr];
170 gg = spread_map[1][gg];
171 bb = spread_map[2][bb];
178 /* Scale summed pixel values down to 8/8/8 range */
179 i = (xx2 - xx1) * (yy2 - yy1);
185 out[y * w + x] = (r << 16) | (g << 8) | b;
190 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
191 Picks a random sub-image out of the source image, and scales it to WxH.
194 pick_a2_subimage (Display *dpy, Window window, XImage *in,
195 unsigned int *out, int w, int h)
197 int fromx, fromy, fromw, fromh;
198 if (in->width <= w || in->height <= h)
209 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
212 } while (fromw > in->width ||
215 dw = (in->width - fromw) / 2; /* near the center! */
216 dh = (in->height - fromh) / 2;
218 fromx = (random() % dw) + (dw/2);
219 fromy = (random() % dh) + (dh/2);
222 scale_image (dpy, window, in,
223 fromx, fromy, fromw, fromh,
228 /* Floyd-Steinberg dither. Derived from ppmquant.c,
229 Copyright (c) 1989, 1991 by Jef Poskanzer.
232 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
235 Apple ][ color map. Each pixel can only be 1 or 0, but what that
236 means depends on whether it's an odd or even pixel, and whether
237 the high bit in the byte is set or not. If it's 0, it's always
240 static const int a2_cmap[2][2][3] = {
243 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
244 {/* even pixels = red */ 0xff, 0x80, 0x00}
248 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
249 {/* odd pixels = green */ 0x40, 0xff, 0x40}
254 unsigned int **pixels;
270 FILE *pipe = popen ("xv -", "w");
271 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
272 for (y = 0; y < h; y++)
273 for (x = 0; x < w; x++)
275 unsigned int p = in[y * w + x];
276 unsigned int r = (p >> 16) & 0xFF;
277 unsigned int g = (p >> 8) & 0xFF;
278 unsigned int b = (p ) & 0xFF;
279 fprintf(pipe, "%c%c%c", r, g, b);
285 /* Initialize Floyd-Steinberg error vectors. */
286 this_rerr = (long *) calloc (w + 2, sizeof(long));
287 next_rerr = (long *) calloc (w + 2, sizeof(long));
288 this_gerr = (long *) calloc (w + 2, sizeof(long));
289 next_gerr = (long *) calloc (w + 2, sizeof(long));
290 this_berr = (long *) calloc (w + 2, sizeof(long));
291 next_berr = (long *) calloc (w + 2, sizeof(long));
294 /* #### do we really need more than one element of "pixels" at once?
296 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
297 for (y = 0; y < h; y++)
298 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
300 for (x = 0; x < w + 2; ++x)
302 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
303 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
304 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
305 /* (random errors in [-1 .. 1]) */
309 for (y = 0; y < h; y++)
310 for (x = 0; x < w; x++)
311 pixels[y][x] = in[y * w + x];
313 for (y = 0; y < h; y++)
319 for (x = 0; x < w + 2; x++)
320 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
322 /* It's too complicated to go back and forth on alternate rows,
323 so we always go left-right here. It doesn't change the result
326 For each group of 7 pixels, we have to try it both with the
327 high bit=0 and =1. For each high bit value, we add up the
328 total error and pick the best one.
330 Because we have to go through each group of bits twice, we
331 don't propagate the error values through this_[rgb]err since
332 it would add them twice. So we keep seperate local_[rgb]err
333 variables for propagating error within the 7-pixel group.
337 for (xbyte=0; xbyte<280; xbyte+=7)
340 int best_error=2000000000;
344 int local_rerr=0, local_gerr=0, local_berr=0;
346 for (hibit=0; hibit<2; hibit++)
351 for (x=xbyte; x<xbyte+7; x++)
355 /* Use Floyd-Steinberg errors to adjust actual color. */
356 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
357 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
358 sb = ((pP[x] ) & 0xFF) * brightness/256;
359 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
360 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
361 sb += (this_berr[x + 1] + local_berr) / fs_scale;
364 else if (sr > maxval) sr = maxval;
366 else if (sg > maxval) sg = maxval;
368 else if (sb > maxval) sb = maxval;
370 /* This is the color we'd get if we set the bit 1. For 0,
372 r2=a2_cmap[hibit][x&1][0];
373 g2=a2_cmap[hibit][x&1][1];
374 b2=a2_cmap[hibit][x&1][2];
377 dist0 and dist1 are the error (Minkowski 2-metric
378 distances in the color space) for choosing 0 and
379 1 respectively. 0 is black, 1 is the color r2,g2,b2.
381 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
382 dist0= sr*sr + sg*sg + sb*sb;
386 byte |= 1 << (x-xbyte);
389 /* Wanted sr but got r2, so propagate sr-r2 */
390 local_rerr = (sr - r2) * fs_scale * 7/16;
391 local_gerr = (sg - g2) * fs_scale * 7/16;
392 local_berr = (sb - b2) * fs_scale * 7/16;
398 /* Wanted sr but got 0, so propagate sr */
399 local_rerr = sr * fs_scale * 7/16;
400 local_gerr = sg * fs_scale * 7/16;
401 local_berr = sb * fs_scale * 7/16;
405 if (tot_error < best_error)
408 best_error = tot_error;
412 /* Avoid alternating 7f and ff in all-white areas, because it makes
413 regular pink vertical lines */
414 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
419 Now that we've chosen values for all 8 bits of the byte, we
420 have to fill in the real pixel values into pP and propagate
421 all the error terms. We end up repeating a lot of the code
425 for (x=xbyte; x<xbyte+7; x++)
427 int bit=(best_byte>>(x-xbyte))&1;
428 hibit=(best_byte>>7)&1;
430 sr = (pP[x] >> 16) & 0xFF;
431 sg = (pP[x] >> 8) & 0xFF;
432 sb = (pP[x] ) & 0xFF;
433 sr += this_rerr[x + 1] / fs_scale;
434 sg += this_gerr[x + 1] / fs_scale;
435 sb += this_berr[x + 1] / fs_scale;
438 else if (sr > maxval) sr = maxval;
440 else if (sg > maxval) sg = maxval;
442 else if (sb > maxval) sb = maxval;
444 r2=a2_cmap[hibit][x&1][0] * bit;
445 g2=a2_cmap[hibit][x&1][1] * bit;
446 b2=a2_cmap[hibit][x&1][2] * bit;
448 pP[x] = (r2<<16) | (g2<<8) | (b2);
450 /* Propagate Floyd-Steinberg error terms. */
451 err = (sr - r2) * fs_scale;
452 this_rerr[x + 2] += (err * 7) / 16;
453 next_rerr[x ] += (err * 3) / 16;
454 next_rerr[x + 1] += (err * 5) / 16;
455 next_rerr[x + 2] += (err ) / 16;
456 err = (sg - g2) * fs_scale;
457 this_gerr[x + 2] += (err * 7) / 16;
458 next_gerr[x ] += (err * 3) / 16;
459 next_gerr[x + 1] += (err * 5) / 16;
460 next_gerr[x + 2] += (err ) / 16;
461 err = (sb - b2) * fs_scale;
462 this_berr[x + 2] += (err * 7) / 16;
463 next_berr[x ] += (err * 3) / 16;
464 next_berr[x + 1] += (err * 5) / 16;
465 next_berr[x + 2] += (err ) / 16;
469 And put the actual byte into out.
472 out[y*(w/7) + xbyte/7] = best_byte;
476 temp_err = this_rerr;
477 this_rerr = next_rerr;
478 next_rerr = temp_err;
479 temp_err = this_gerr;
480 this_gerr = next_gerr;
481 next_gerr = temp_err;
482 temp_err = this_berr;
483 this_berr = next_berr;
484 next_berr = temp_err;
500 /* let's see what we got... */
501 FILE *pipe = popen ("xv -", "w");
502 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
503 for (y = 0; y < h; y++)
504 for (x = 0; x < w; x++)
506 unsigned int r = (pixels[y][x]>>16)&0xff;
507 unsigned int g = (pixels[y][x]>>8)&0xff;
508 unsigned int b = (pixels[y][x]>>0)&0xff;
509 fprintf(pipe, "%c%c%c", r, g, b);
517 static unsigned char *
518 load_image (Display *dpy, Window window, char **image_filename_r)
520 XWindowAttributes xgwa;
526 unsigned int *buf32 = (unsigned int *) calloc (w, h * 4);
527 unsigned char *buf8 = (unsigned char *) calloc (w/7, h);
531 fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
535 XGetWindowAttributes (dpy, window, &xgwa);
536 p = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
537 load_random_image (xgwa.screen, window, p, image_filename_r);
538 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
539 XFreePixmap (dpy, p);
542 /* Make sure the window's background is not set to None, and get the
543 grabbed bits (if any) off it as soon as possible. */
544 XSetWindowBackground (dpy, window,
545 get_pixel_resource ("background", "Background",
546 dpy, xgwa.colormap));
547 XClearWindow (dpy, window);
549 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
550 image (regardless of whether it started as TrueColor/PseudoColor.)
552 pick_a2_subimage (dpy, window, image, buf32, w, h);
554 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
556 a2_dither (buf32, buf8, w, h);
563 char *progclass = "Apple2";
565 char *defaults [] = {
566 ".background: black",
567 ".foreground: white",
570 "*metaSendsESC: True",
577 # endif /* !HAVE_FORKPTY */
583 XrmOptionDescRec options [] = {
584 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
585 { "-basic", ".mode", XrmoptionNoArg, "basic" },
586 { "-text", ".mode", XrmoptionNoArg, "text" },
587 { "-program", ".program", XrmoptionSepArg, 0 },
588 { "-duration", ".duration", XrmoptionSepArg, 0 },
589 { "-pty", ".usePty", XrmoptionNoArg, "True" },
590 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
591 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
592 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
593 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
594 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
595 { "-fast", ".fast", XrmoptionNoArg, "True" },
601 TODO: this should load 10 images at startup time, then cycle through them
602 to avoid the pause while it loads.
605 void slideshow_controller(apple2_sim_t *sim, int *stepno,
606 double *next_actiontime)
608 apple2_state_t *st=sim->st;
612 int render_img_lineno;
617 if (!sim->controller_data)
618 sim->controller_data = calloc(sizeof(struct mydata),1);
619 mine=(struct mydata *) sim->controller_data;
627 sim->typing_rate = 0.3;
628 sim->dec->powerup=0.0;
631 a2_prints(st, "APPLE ][");
635 *next_actiontime += 4.0;
639 mine->render_img = load_image (sim->dpy, sim->window, &mine->img_filename);
645 *next_actiontime += 3.0;
659 st->gr_mode=A2_GR_HIRES;
660 if (mine->img_filename) {
661 char *basename, *tmp;
664 basename = tmp = strdup (mine->img_filename);
667 char *slash = strchr(basename, '/');
668 if (!slash || !slash[1]) break;
672 char *dot=strchr(basename,'.');
675 if (strlen(basename)>20) basename[20]=0;
676 for (s=basename; *s; s++) *s = toupper (*s);
677 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
678 sim->typing = sim->typing_buf;
682 sim->typing = "BLOAD IMAGE\n";
684 mine->render_img_lineno=0;
690 *next_actiontime += 0.7;
695 if (mine->render_img_lineno>=192) {
697 sim->typing="POKE 49234,0\n";
702 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
703 a2_display_image_loading(st, mine->render_img,
704 mine->render_img_lineno++);
707 /* The disk would have to seek every 13 sectors == 78 lines.
708 (This ain't no newfangled 16-sector operating system) */
709 if ((mine->render_img_lineno%78)==0) {
710 *next_actiontime += 0.5;
712 *next_actiontime += 0.08;
717 st->gr_mode |= A2_GR_FULL;
719 *next_actiontime += sim->delay;
724 sim->typing="POKE 49235,0\n";
730 st->gr_mode &= ~A2_GR_FULL;
731 if (mine->render_img) {
732 free(mine->render_img);
733 mine->render_img=NULL;
735 if (mine->img_filename) {
736 free(mine->img_filename);
737 mine->img_filename=NULL;
742 case A2CONTROLLER_FREE:
743 free(mine->render_img);
744 free(mine->img_filename);
753 struct terminal_controller_data {
757 int input_available_p;
758 XtIntervalId timeout_id;
762 double last_emit_time;
763 XComposeStatus compose;
768 int cursor_x, cursor_y;
769 int saved_x, saved_y;
772 unsigned int bold : 1;
773 unsigned int blink : 1;
774 unsigned int rev : 1;
778 Bool meta_sends_esc_p;
785 subproc_cb (XtPointer closure, int *source, XtInputId *id)
787 struct terminal_controller_data *mine =
788 (struct terminal_controller_data *) closure;
789 mine->input_available_p = True;
793 launch_text_generator (struct terminal_controller_data *mine)
796 char *oprogram = get_string_resource ("program", "Program");
799 if (!oprogram || !*oprogram)
800 oprogram = FORTUNE_PROGRAM;
802 program = (char *) malloc (strlen (oprogram) + 10);
804 strcpy (program, "( ");
805 strcat (program, oprogram);
806 strcat (program, " ) 2>&1");
808 if (mine->pipe) abort();
811 if (get_boolean_resource ("usePty", "Boolean"))
816 ws.ws_col = SCREEN_COLS;
817 ws.ws_row = SCREEN_ROWS;
818 ws.ws_xpixel = ws.ws_col * 6;
819 ws.ws_ypixel = ws.ws_row * 8;
822 if((mine->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
825 sprintf (buf, "%.100s: forkpty", progname);
830 /* This is the child fork. */
831 if (putenv("TERM=vt100"))
833 execl("/bin/sh", "/bin/sh", "-c", oprogram, NULL);
834 sprintf (buf, "%.100s: %.100s", progname, oprogram);
840 /* This is the parent fork. */
841 mine->pipe = fdopen(fd, "r+");
843 XtAppAddInput (app, fileno (mine->pipe),
844 (XtPointer) (XtInputReadMask | XtInputExceptMask),
845 subproc_cb, (XtPointer) mine);
849 # endif /* HAVE_FORKPTY */
851 if ((mine->pipe = popen (program, "r")))
853 if (mine->pipe_id) abort();
855 XtAppAddInput (app, fileno (mine->pipe),
856 (XtPointer) (XtInputReadMask | XtInputExceptMask),
857 subproc_cb, (XtPointer) mine);
861 sprintf (buf, "%.100s: %.100s", progname, program);
867 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
869 struct terminal_controller_data *mine =
870 (struct terminal_controller_data *) closure;
872 launch_text_generator (mine);
876 terminal_closegen(struct terminal_controller_data *mine)
879 XtRemoveInput (mine->pipe_id);
886 if (mine->timeout_id) {
887 XtRemoveTimeOut(mine->timeout_id);
893 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
902 if (!mine->input_available_p) return 0;
904 rc=read (fileno (mine->pipe), (void *) buf, n);
905 if (rc>0) mine->lastc=buf[rc-1];
909 terminal_closegen(mine);
911 if (mine->lastc != '\n') { /* add a newline at eof if there wasn't one */
915 /* Set up a timer to re-launch the subproc in a bit. */
917 XtAppAddTimeOut(app, subproc_relaunch_delay,
918 relaunch_generator_timer,
922 mine->input_available_p = False;
928 /* The interpretation of the ModN modifiers is dependent on what keys
929 are bound to them: Mod1 does not necessarily mean "meta". It only
930 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
931 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
932 aren't necessarily the same thing. Icepicks in my forehead!
935 do_icccm_meta_key_stupidity (Display *dpy)
937 unsigned int modbits = 0;
939 XModifierKeymap *modmap = XGetModifierMapping (dpy);
940 for (i = 3; i < 8; i++)
941 for (j = 0; j < modmap->max_keypermod; j++)
943 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
946 if (code == 0) continue;
947 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
948 for (k = 0; k < nsyms; k++)
949 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
950 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
954 XFreeModifiermap (modmap);
958 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
961 meta_modifier (Display *dpy)
963 static Bool done_once = False;
964 static unsigned int mask = 0;
967 /* Really, we are supposed to recompute this if a KeymapNotify
968 event comes in, but fuck it. */
970 mask = do_icccm_meta_key_stupidity (dpy);
977 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
979 struct terminal_controller_data *mine =
980 (struct terminal_controller_data *) data;
983 XLookupString (&event->xkey, (char *) &c, 1, &keysym, &mine->compose);
984 if (c == 0 || !mine->pipe)
987 if (!mine->swap_bs_del_p) ;
988 else if (c == 127) c = 8;
989 else if (c == 8) c = 127;
991 /* If meta was held down, send ESC, or turn on the high bit. */
992 if (event->xkey.state & meta_modifier (dpy))
994 if (mine->meta_sends_esc_p)
995 fputc ('\033', mine->pipe);
1000 fputc (c, mine->pipe);
1001 fflush (mine->pipe);
1003 event->xany.type = 0; /* do not process this event further */
1010 a2_ascii_printc (apple2_state_t *st, unsigned char c,
1011 Bool bold_p, Bool blink_p, Bool rev_p,
1014 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
1018 else if ((c >= 'A'+128) || /* upcase and blink */
1019 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
1020 c != '\r' && c != '\n' && c!='\t'))
1022 c = (c & 0x1F) | 0x80;
1024 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
1029 if (bold_p) c |= 0xc0;
1030 if (blink_p) c = (c & ~0x40) | 0x80;
1031 if (rev_p) c |= 0xc0;
1036 a2_printc_noscroll(st, c);
1041 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
1044 apple2_state_t *st=sim->st;
1045 int cols = SCREEN_COLS;
1046 int rows = SCREEN_ROWS;
1051 switch (state->escstate)
1057 /* Dummy case - we don't want the screensaver to beep */
1058 /* #### But maybe this should flash the screen? */
1061 if (state->cursor_x > 0)
1065 if (state->cursor_x < cols - 8)
1067 state->cursor_x = (state->cursor_x & ~7) + 8;
1071 state->cursor_x = 0;
1072 if (state->cursor_y < rows - 1)
1081 if (state->cursor_y < rows - 1)
1087 state->cursor_x = 0;
1091 /* Dummy case - there is one and only one font. */
1095 /* Dummy case - these interrupt escape sequences, so
1096 they don't do anything in this state */
1099 state->escstate = 1;
1102 /* Dummy case - this is supposed to be ignored */
1105 state->escstate = 2;
1106 for(i = 0; i < NPAR; i++)
1107 state->csiparam[i] = 0;
1108 state->curparam = 0;
1111 /* If the cursor is in column 39 and we print a character, then
1112 that character shows up in column 39, and the cursor is no longer
1113 visible on the screen (it's in "column 40".) If another character
1114 is printed, then that character shows up in column 0, and the
1115 cursor moves to column 1.
1117 This is empirically what xterm and gnome-terminal do, so that must
1118 be the right thing. (In xterm, the cursor vanishes, whereas; in
1119 gnome-terminal, the cursor overprints the character in col 39.)
1121 if (state->cursor_x >= cols)
1123 state->cursor_x = 0;
1124 if (state->cursor_y >= rows - 1)
1130 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
1131 a2_ascii_printc (st, c,
1132 state->termattrib.bf.bold,
1133 state->termattrib.bf.blink,
1134 state->termattrib.bf.rev,
1146 state->escstate = 0;
1148 case 'c': /* Reset */
1150 state->escstate = 0;
1152 case 'D': /* Linefeed */
1153 if (state->cursor_y < rows - 1)
1157 state->escstate = 0;
1159 case 'E': /* Newline */
1160 state->cursor_x = 0;
1161 state->escstate = 0;
1163 case 'M': /* Reverse newline */
1164 if (state->cursor_y > 0)
1166 state->escstate = 0;
1168 case '7': /* Save state */
1169 state->saved_x = state->cursor_x;
1170 state->saved_y = state->cursor_y;
1171 state->escstate = 0;
1173 case '8': /* Restore state */
1174 state->cursor_x = state->saved_x;
1175 state->cursor_y = state->saved_y;
1176 state->escstate = 0;
1179 state->escstate = 2;
1180 for(i = 0; i < NPAR; i++)
1181 state->csiparam[i] = 0;
1182 state->curparam = 0;
1184 case '%': /* Select charset */
1185 /* No, I don't support UTF-8, since the apple2 font
1186 isn't even Unicode anyway. We must still catch the
1187 last byte, though. */
1190 /* I don't support different fonts either - see above
1192 state->escstate = 3;
1195 /* Escape sequences not supported:
1198 * Z - Terminal identification
1200 * = - Other keypad change
1203 state->escstate = 0;
1212 state->escstate = 0;
1214 case '0': case '1': case '2': case '3': case '4':
1215 case '5': case '6': case '7': case '8': case '9':
1216 if (state->curparam < NPAR)
1217 state->csiparam[state->curparam] =
1218 (state->csiparam[state->curparam] * 10) + (c - '0');
1221 state->csiparam[++state->curparam] = 0;
1224 state->escstate = 3;
1227 for (i = 0; i < state->csiparam[0]; i++)
1229 if(++state->cursor_x > cols)
1231 state->cursor_x = 0;
1232 if (state->cursor_y < rows - 1)
1238 state->escstate = 0;
1241 state->cursor_x = 0;
1243 if (state->csiparam[0] == 0)
1244 state->csiparam[0] = 1;
1245 if ((state->cursor_y -= state->csiparam[0]) < 0)
1246 state->cursor_y = 0;
1247 state->escstate = 0;
1250 state->cursor_x = 0;
1253 if (state->csiparam[0] == 0)
1254 state->csiparam[0] = 1;
1255 if ((state->cursor_y += state->csiparam[0]) >= rows)
1256 state->cursor_y = rows - 1;
1257 state->escstate = 0;
1261 if (state->csiparam[0] == 0)
1262 state->csiparam[0] = 1;
1263 if ((state->cursor_x += state->csiparam[0]) >= cols)
1264 state->cursor_x = cols - 1;
1265 state->escstate = 0;
1268 if (state->csiparam[0] == 0)
1269 state->csiparam[0] = 1;
1270 if ((state->cursor_x -= state->csiparam[0]) < 0)
1271 state->cursor_x = 0;
1272 state->escstate = 0;
1275 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1276 state->cursor_y = rows - 1;
1277 state->escstate = 0;
1281 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1282 state->cursor_x = cols - 1;
1283 state->escstate = 0;
1287 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1288 state->cursor_y = rows - 1;
1289 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1290 state->cursor_x = cols - 1;
1291 if(state->cursor_y < 0)
1292 state->cursor_y = 0;
1293 if(state->cursor_x < 0)
1294 state->cursor_x = 0;
1295 state->escstate = 0;
1300 if (state->csiparam[0] == 0)
1301 start = cols * state->cursor_y + state->cursor_x;
1302 if (state->csiparam[0] == 1)
1303 end = cols * state->cursor_y + state->cursor_x;
1305 a2_goto(st, state->cursor_y, state->cursor_x);
1306 for (i = start; i < end; i++)
1308 a2_ascii_printc(st, ' ', False, False, False, False);
1310 state->escstate = 0;
1315 if (state->csiparam[0] == 0)
1316 start = state->cursor_x;
1317 if (state->csiparam[1] == 1)
1318 end = state->cursor_x;
1320 a2_goto(st, state->cursor_y, state->cursor_x);
1321 for (i = start; i < end; i++)
1323 a2_ascii_printc(st, ' ', False, False, False, False);
1325 state->escstate = 0;
1327 case 'm': /* Set attributes */
1328 for (i = 0; i <= state->curparam; i++)
1330 switch(state->csiparam[i])
1333 state->termattrib.w = 0;
1336 state->termattrib.bf.bold = 1;
1339 state->termattrib.bf.blink = 1;
1342 state->termattrib.bf.rev = 1;
1346 state->termattrib.bf.bold = 0;
1349 state->termattrib.bf.blink = 0;
1352 state->termattrib.bf.rev = 0;
1356 state->escstate = 0;
1358 case 's': /* Save position */
1359 state->saved_x = state->cursor_x;
1360 state->saved_y = state->cursor_y;
1361 state->escstate = 0;
1363 case 'u': /* Restore position */
1364 state->cursor_x = state->saved_x;
1365 state->cursor_y = state->saved_y;
1366 state->escstate = 0;
1368 case '?': /* DEC Private modes */
1369 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1370 state->escstate = 0;
1373 /* Known unsupported CSIs:
1375 * L - Insert blank lines
1376 * M - Delete lines (I don't know what this means...)
1377 * P - Delete characters
1378 * X - Erase characters (difference with P being...?)
1379 * c - Terminal identification
1380 * g - Clear tab stop(s)
1381 * h - Set mode (Mainly due to its complexity and lack of good
1384 * m - Set mode (Phosphor is, per defenition, green on black)
1386 * q - Set keyboard LEDs
1387 * r - Set scrolling region (too exhausting - noone uses this,
1390 state->escstate = 0;
1395 state->escstate = 0;
1398 a2_goto(st, state->cursor_y, state->cursor_x);
1403 It's fun to put things like "gdb" as the command. For one, it's
1404 amusing how the standard mumble (version, no warranty, it's
1405 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1409 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1411 apple2_state_t *st=sim->st;
1415 struct terminal_controller_data *mine;
1416 if (!sim->controller_data)
1417 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1418 mine=(struct terminal_controller_data *) sim->controller_data;
1420 mine->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
1421 mine->swap_bs_del_p = get_boolean_resource ("swapBSDEL", "Boolean");
1422 mine->fast_p = get_boolean_resource ("fast", "Boolean");
1424 sim->dec->key_handler = terminal_keypress_handler;
1425 sim->dec->key_data = mine;
1431 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1435 a2_prints(st, "APPLE ][");
1440 launch_text_generator(mine);
1443 *next_actiontime += 4.0;
1449 unsigned char buf[1024];
1453 elapsed=sim->curtime - mine->last_emit_time;
1454 mine->last_emit_time=sim->curtime;
1456 if (elapsed>1.0) nwant=1;
1457 if (nwant<1) nwant=1;
1458 if (nwant>4) nwant=4;
1461 nwant = sizeof(buf)-1;
1463 nr=terminal_read(mine, buf, nwant);
1464 for (i=0; i<nr; i++) {
1468 a2_vt100_printc (sim, mine, c);
1470 a2_ascii_printc (st, c, False, False, False, True);
1475 case A2CONTROLLER_FREE:
1476 terminal_closegen(mine);
1482 struct basic_controller_data {
1489 double prog_start_time;
1490 char error_buf[256];
1494 Adding more programs is easy. Just add a listing here and to all_programs,
1495 then add the state machine to actually execute it to basic_controller.
1497 static char *moire_program[]={
1499 "20 FOR Y = 0 TO 191 STEP 2\n",
1500 "30 HCOLOR=4 : REM BLACK\n",
1501 "40 HLINE 0,191-Y TO 279,Y\n",
1502 "60 HCOLOR=7 : REM WHITE\n",
1503 "80 HLINE 0,190-Y TO 279,Y+1\n",
1505 "100 FOR X = 0 TO 279 STEP 3\n",
1507 "120 HLINE 279-X,0 TO X,192\n",
1509 "150 HLINE 278-X,0 TO X+1,192\n",
1514 static char *sinewave_program[] = {
1517 "30 FOR X = 0 TO 279\n",
1519 "35 HLINE X,0 TO X,159\n",
1521 "40 Y = 80 + SIN(15*(X-K)/279)\n",
1530 static char *dumb_program[]={
1531 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1537 static char *random_lores_program[]={
1538 "1 REM APPLE ][ SCREEN SAVER\n",
1540 "100 COLOR= RND(1)*16\n",
1542 "110 X=RND(1)*40\n",
1543 "120 Y1=RND(1)*48\n",
1544 "130 Y2=RND(1)*48\n",
1545 "140 FOR Y = Y1 TO Y2\n",
1549 "210 Y=RND(1)*48\n",
1550 "220 X1=RND(1)*40\n",
1551 "230 X2=RND(1)*40\n",
1552 "240 FOR X = X1 TO X2\n",
1560 static char typo_map[256];
1562 int make_typo(char *out_buf, char *orig, char *err_buf)
1622 strcpy(out_buf, orig);
1623 for (i=0; out_buf[i]; i++) {
1624 char *p = out_buf+i;
1626 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1629 if (isalpha(p[0]) &&
1638 sprintf(err_buf,"?SYNTAX ERROR\n");
1642 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(u_char)p[0]])) {
1643 int remain=strlen(p);
1644 int past=random()%(remain-2)+1;
1645 memmove(p+past+past, p, remain+1);
1647 for (j=0; j<past; j++) {
1660 {moire_program, 100},
1661 /*{dumb_program, 200}, */
1662 {sinewave_program, 400},
1663 {random_lores_program, 500},
1667 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1669 apple2_state_t *st=sim->st;
1672 struct basic_controller_data *mine;
1673 if (!sim->controller_data)
1674 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1675 mine=(struct basic_controller_data *) sim->controller_data;
1682 a2_prints(st, "APPLE ][");
1685 sim->typing_rate=0.2;
1687 i=random()%countof(all_programs);
1688 mine->progtext=all_programs[i].progtext;
1689 mine->progstep=all_programs[i].progstep;
1692 *next_actiontime += 1.0;
1697 if (st->cursx==0) a2_printc(st,']');
1698 if (mine->progtext[mine->prog_line]) {
1699 if (random()%4==0) {
1700 int err=make_typo(sim->typing_buf,
1701 mine->progtext[mine->prog_line],
1703 sim->typing=sim->typing_buf;
1710 sim->typing=mine->progtext[mine->prog_line++];
1718 sim->printing=mine->error_buf;
1723 if (st->cursx==0) a2_printc(st,']');
1724 *next_actiontime+=1.0;
1729 sim->typing="RUN\n";
1733 mine->prog_start_time=*next_actiontime;
1734 *stepno=mine->progstep;
1739 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1740 for (i=0; i<24 && mine->y<192; i++)
1742 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1743 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1753 for (i=0; i<24 && mine->x<280; i++)
1755 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1756 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1759 if (mine->x >= 280) *stepno=120;
1763 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1768 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1769 for (i=0; i<30; i++) {
1770 a2_prints(st, mine->rep_str);
1776 i=random()%strlen(mine->rep_str);
1777 while (mine->rep_pos != i) {
1778 a2_printc(st, mine->rep_str[mine->rep_pos]);
1780 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1782 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1785 /* sinewave_program */
1787 st->gr_mode=A2_GR_HIRES;
1792 for (i=0; i<48; i++) {
1793 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1794 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1795 a2_hplot(st, 3, mine->x, y);
1802 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1810 /* random_lores_program */
1812 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1817 for (i=0; i<10; i++) {
1818 int color,x,y,x1,x2,y1,y2;
1824 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1829 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1831 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1838 case A2CONTROLLER_FREE:
1845 void (*controllers[])(apple2_sim_t *sim, int *stepno,
1846 double *next_actiontime) = {
1847 slideshow_controller,
1848 terminal_controller,
1853 screenhack (Display *dpy, Window window)
1855 int duration = get_integer_resource ("duration", "Integer");
1857 void (*controller)(apple2_sim_t *sim, int *stepno, double *next_actiontime);
1859 if (duration < 1) duration = 1;
1861 s = get_string_resource ("mode", "Mode");
1862 if (!s || !*s || !strcasecmp(s, "random"))
1863 controller = controllers[random() % (countof(controllers))];
1864 else if (!strcasecmp(s, "text"))
1865 controller = terminal_controller;
1866 else if (!strcasecmp(s, "slideshow"))
1867 controller = slideshow_controller;
1868 else if (!strcasecmp(s, "basic"))
1869 controller = basic_controller;
1872 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1877 if (!get_boolean_resource ("root", "Boolean"))
1879 XWindowAttributes xgwa;
1880 XGetWindowAttributes (dpy, window, &xgwa);
1881 XSelectInput (dpy, window,
1882 xgwa.your_event_mask |
1883 KeyPressMask | ButtonPressMask | ExposureMask);
1886 apple2 (dpy, window, duration, controller);