1 /* xscreensaver, Copyright (c) 1998-2005 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>
27 # include <sys/ioctl.h>
34 #endif /* HAVE_FORKPTY */
37 #define countof(x) (sizeof((x))/sizeof((*x)))
39 #define SCREEN_COLS 40
40 #define SCREEN_ROWS 24
44 extern XtAppContext app;
46 Time subproc_relaunch_delay = 3000;
49 /* Given a bitmask, returns the position and width of the field.
52 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
55 for (i = 0; i < 32; i++)
60 for (; i < 32; i++, j++)
61 if (! (mask & (1L << i)))
69 /* Given a value and a field-width, expands the field to fill out 8 bits.
72 spread_bits (unsigned char value, unsigned char width)
77 case 7: return (value << 1) | (value >> 6);
78 case 6: return (value << 2) | (value >> 4);
79 case 5: return (value << 3) | (value >> 2);
80 case 4: return (value << 4) | (value);
81 case 3: return (value << 5) | (value << 2) | (value >> 2);
82 case 2: return (value << 6) | (value << 4) | (value);
83 default: abort(); break;
88 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
89 Scales it (without dithering) to WxH.
92 scale_image (Display *dpy, Window window, XImage *in,
93 int fromx, int fromy, int fromw, int fromh,
94 unsigned int *out, int w, int h)
98 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
99 unsigned int rsiz=0, gsiz=0, bsiz=0;
100 unsigned int rmsk=0, gmsk=0, bmsk=0;
101 unsigned char spread_map[3][256];
102 XWindowAttributes xgwa;
105 if (fromx + fromw > in->width ||
106 fromy + fromh > in->height)
109 XGetWindowAttributes (dpy, window, &xgwa);
111 /* Compute the field offsets for RGB decoding in the XImage,
112 when in TrueColor mode. Otherwise we use the colormap.
114 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
115 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
117 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
118 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
119 for (i = 0; i < ncolors; i++)
121 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
125 rmsk = xgwa.visual->red_mask;
126 gmsk = xgwa.visual->green_mask;
127 bmsk = xgwa.visual->blue_mask;
128 decode_mask (rmsk, &rpos, &rsiz);
129 decode_mask (gmsk, &gpos, &gsiz);
130 decode_mask (bmsk, &bpos, &bsiz);
132 for (i = 0; i < 256; i++)
134 spread_map[0][i] = spread_bits (i, rsiz);
135 spread_map[1][i] = spread_bits (i, gsiz);
136 spread_map[2][i] = spread_bits (i, bsiz);
140 scale = (fromw > fromh
142 : (float) fromh / h);
144 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
146 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
147 for (x = 0; x < w-1; x++)
150 unsigned int r=0, g=0, b=0;
152 int xx1 = x * scale + fromx;
153 int yy1 = y * scale + fromy;
154 int xx2 = (x+1) * scale + fromx;
155 int yy2 = (y+1) * scale + fromy;
157 /* Iterate over the source pixels contributing to this one, and sum. */
158 for (xx = xx1; xx < xx2; xx++)
159 for (yy = yy1; yy < yy2; yy++)
161 unsigned char rr, gg, bb;
162 unsigned long sp = ((xx > in->width || yy > in->height)
163 ? 0 : XGetPixel (in, xx, yy));
166 rr = colors[sp].red & 0xFF;
167 gg = colors[sp].green & 0xFF;
168 bb = colors[sp].blue & 0xFF;
172 rr = (sp & rmsk) >> rpos;
173 gg = (sp & gmsk) >> gpos;
174 bb = (sp & bmsk) >> bpos;
175 rr = spread_map[0][rr];
176 gg = spread_map[1][gg];
177 bb = spread_map[2][bb];
184 /* Scale summed pixel values down to 8/8/8 range */
185 i = (xx2 - xx1) * (yy2 - yy1);
191 out[y * w + x] = (r << 16) | (g << 8) | b;
196 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
197 Picks a random sub-image out of the source image, and scales it to WxH.
200 pick_a2_subimage (Display *dpy, Window window, XImage *in,
201 unsigned int *out, int w, int h)
203 int fromx, fromy, fromw, fromh;
204 if (in->width <= w || in->height <= h)
215 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
218 } while (fromw > in->width ||
221 dw = (in->width - fromw) / 2; /* near the center! */
222 dh = (in->height - fromh) / 2;
224 fromx = (random() % dw) + (dw/2);
225 fromy = (random() % dh) + (dh/2);
228 scale_image (dpy, window, in,
229 fromx, fromy, fromw, fromh,
234 /* Floyd-Steinberg dither. Derived from ppmquant.c,
235 Copyright (c) 1989, 1991 by Jef Poskanzer.
238 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
241 Apple ][ color map. Each pixel can only be 1 or 0, but what that
242 means depends on whether it's an odd or even pixel, and whether
243 the high bit in the byte is set or not. If it's 0, it's always
246 static const int a2_cmap[2][2][3] = {
249 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
250 {/* even pixels = red */ 0xff, 0x80, 0x00}
254 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
255 {/* odd pixels = green */ 0x40, 0xff, 0x40}
260 unsigned int **pixels;
276 FILE *pipe = popen ("xv -", "w");
277 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
278 for (y = 0; y < h; y++)
279 for (x = 0; x < w; x++)
281 unsigned int p = in[y * w + x];
282 unsigned int r = (p >> 16) & 0xFF;
283 unsigned int g = (p >> 8) & 0xFF;
284 unsigned int b = (p ) & 0xFF;
285 fprintf(pipe, "%c%c%c", r, g, b);
291 /* Initialize Floyd-Steinberg error vectors. */
292 this_rerr = (long *) calloc (w + 2, sizeof(long));
293 next_rerr = (long *) calloc (w + 2, sizeof(long));
294 this_gerr = (long *) calloc (w + 2, sizeof(long));
295 next_gerr = (long *) calloc (w + 2, sizeof(long));
296 this_berr = (long *) calloc (w + 2, sizeof(long));
297 next_berr = (long *) calloc (w + 2, sizeof(long));
300 /* #### do we really need more than one element of "pixels" at once?
302 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
303 for (y = 0; y < h; y++)
304 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
306 for (x = 0; x < w + 2; ++x)
308 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
309 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
310 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
311 /* (random errors in [-1 .. 1]) */
315 for (y = 0; y < h; y++)
316 for (x = 0; x < w; x++)
317 pixels[y][x] = in[y * w + x];
319 for (y = 0; y < h; y++)
325 for (x = 0; x < w + 2; x++)
326 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
328 /* It's too complicated to go back and forth on alternate rows,
329 so we always go left-right here. It doesn't change the result
332 For each group of 7 pixels, we have to try it both with the
333 high bit=0 and =1. For each high bit value, we add up the
334 total error and pick the best one.
336 Because we have to go through each group of bits twice, we
337 don't propagate the error values through this_[rgb]err since
338 it would add them twice. So we keep seperate local_[rgb]err
339 variables for propagating error within the 7-pixel group.
343 for (xbyte=0; xbyte<280; xbyte+=7)
346 int best_error=2000000000;
350 int local_rerr=0, local_gerr=0, local_berr=0;
352 for (hibit=0; hibit<2; hibit++)
357 for (x=xbyte; x<xbyte+7; x++)
361 /* Use Floyd-Steinberg errors to adjust actual color. */
362 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
363 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
364 sb = ((pP[x] ) & 0xFF) * brightness/256;
365 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
366 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
367 sb += (this_berr[x + 1] + local_berr) / fs_scale;
370 else if (sr > maxval) sr = maxval;
372 else if (sg > maxval) sg = maxval;
374 else if (sb > maxval) sb = maxval;
376 /* This is the color we'd get if we set the bit 1. For 0,
378 r2=a2_cmap[hibit][x&1][0];
379 g2=a2_cmap[hibit][x&1][1];
380 b2=a2_cmap[hibit][x&1][2];
383 dist0 and dist1 are the error (Minkowski 2-metric
384 distances in the color space) for choosing 0 and
385 1 respectively. 0 is black, 1 is the color r2,g2,b2.
387 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
388 dist0= sr*sr + sg*sg + sb*sb;
392 byte |= 1 << (x-xbyte);
395 /* Wanted sr but got r2, so propagate sr-r2 */
396 local_rerr = (sr - r2) * fs_scale * 7/16;
397 local_gerr = (sg - g2) * fs_scale * 7/16;
398 local_berr = (sb - b2) * fs_scale * 7/16;
404 /* Wanted sr but got 0, so propagate sr */
405 local_rerr = sr * fs_scale * 7/16;
406 local_gerr = sg * fs_scale * 7/16;
407 local_berr = sb * fs_scale * 7/16;
411 if (tot_error < best_error)
414 best_error = tot_error;
418 /* Avoid alternating 7f and ff in all-white areas, because it makes
419 regular pink vertical lines */
420 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
425 Now that we've chosen values for all 8 bits of the byte, we
426 have to fill in the real pixel values into pP and propagate
427 all the error terms. We end up repeating a lot of the code
431 for (x=xbyte; x<xbyte+7; x++)
433 int bit=(best_byte>>(x-xbyte))&1;
434 hibit=(best_byte>>7)&1;
436 sr = (pP[x] >> 16) & 0xFF;
437 sg = (pP[x] >> 8) & 0xFF;
438 sb = (pP[x] ) & 0xFF;
439 sr += this_rerr[x + 1] / fs_scale;
440 sg += this_gerr[x + 1] / fs_scale;
441 sb += this_berr[x + 1] / fs_scale;
444 else if (sr > maxval) sr = maxval;
446 else if (sg > maxval) sg = maxval;
448 else if (sb > maxval) sb = maxval;
450 r2=a2_cmap[hibit][x&1][0] * bit;
451 g2=a2_cmap[hibit][x&1][1] * bit;
452 b2=a2_cmap[hibit][x&1][2] * bit;
454 pP[x] = (r2<<16) | (g2<<8) | (b2);
456 /* Propagate Floyd-Steinberg error terms. */
457 err = (sr - r2) * fs_scale;
458 this_rerr[x + 2] += (err * 7) / 16;
459 next_rerr[x ] += (err * 3) / 16;
460 next_rerr[x + 1] += (err * 5) / 16;
461 next_rerr[x + 2] += (err ) / 16;
462 err = (sg - g2) * fs_scale;
463 this_gerr[x + 2] += (err * 7) / 16;
464 next_gerr[x ] += (err * 3) / 16;
465 next_gerr[x + 1] += (err * 5) / 16;
466 next_gerr[x + 2] += (err ) / 16;
467 err = (sb - b2) * fs_scale;
468 this_berr[x + 2] += (err * 7) / 16;
469 next_berr[x ] += (err * 3) / 16;
470 next_berr[x + 1] += (err * 5) / 16;
471 next_berr[x + 2] += (err ) / 16;
475 And put the actual byte into out.
478 out[y*(w/7) + xbyte/7] = best_byte;
482 temp_err = this_rerr;
483 this_rerr = next_rerr;
484 next_rerr = temp_err;
485 temp_err = this_gerr;
486 this_gerr = next_gerr;
487 next_gerr = temp_err;
488 temp_err = this_berr;
489 this_berr = next_berr;
490 next_berr = temp_err;
506 /* let's see what we got... */
507 FILE *pipe = popen ("xv -", "w");
508 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
509 for (y = 0; y < h; y++)
510 for (x = 0; x < w; x++)
512 unsigned int r = (pixels[y][x]>>16)&0xff;
513 unsigned int g = (pixels[y][x]>>8)&0xff;
514 unsigned int b = (pixels[y][x]>>0)&0xff;
515 fprintf(pipe, "%c%c%c", r, g, b);
523 static unsigned char *
524 load_image (Display *dpy, Window window, char **image_filename_r)
526 XWindowAttributes xgwa;
532 unsigned int *buf32 = (unsigned int *) calloc (w, h * 4);
533 unsigned char *buf8 = (unsigned char *) calloc (w/7, h);
537 fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
541 XGetWindowAttributes (dpy, window, &xgwa);
542 p = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
543 load_random_image (xgwa.screen, window, p, image_filename_r, NULL);
544 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
545 XFreePixmap (dpy, p);
548 /* Make sure the window's background is not set to None, and get the
549 grabbed bits (if any) off it as soon as possible. */
550 XSetWindowBackground (dpy, window,
551 get_pixel_resource ("background", "Background",
552 dpy, xgwa.colormap));
553 XClearWindow (dpy, window);
555 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
556 image (regardless of whether it started as TrueColor/PseudoColor.)
558 pick_a2_subimage (dpy, window, image, buf32, w, h);
560 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
562 a2_dither (buf32, buf8, w, h);
569 char *progclass = "Apple2";
571 char *defaults [] = {
572 ".background: black",
573 ".foreground: white",
576 "*program: xscreensaver-text --cols 40",
577 "*metaSendsESC: True",
584 # endif /* !HAVE_FORKPTY */
590 XrmOptionDescRec options [] = {
591 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
592 { "-basic", ".mode", XrmoptionNoArg, "basic" },
593 { "-text", ".mode", XrmoptionNoArg, "text" },
594 { "-program", ".program", XrmoptionSepArg, 0 },
595 { "-duration", ".duration", XrmoptionSepArg, 0 },
596 { "-pty", ".usePty", XrmoptionNoArg, "True" },
597 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
598 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
599 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
600 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
601 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
602 { "-fast", ".fast", XrmoptionNoArg, "True" },
608 TODO: this should load 10 images at startup time, then cycle through them
609 to avoid the pause while it loads.
612 void slideshow_controller(apple2_sim_t *sim, int *stepno,
613 double *next_actiontime)
615 apple2_state_t *st=sim->st;
619 int render_img_lineno;
624 if (!sim->controller_data)
625 sim->controller_data = calloc(sizeof(struct mydata),1);
626 mine=(struct mydata *) sim->controller_data;
634 sim->typing_rate = 0.3;
635 sim->dec->powerup=0.0;
638 a2_prints(st, "APPLE ][");
642 *next_actiontime += 4.0;
646 mine->render_img = load_image (sim->dpy, sim->window, &mine->img_filename);
652 *next_actiontime += 3.0;
666 st->gr_mode=A2_GR_HIRES;
667 if (mine->img_filename) {
668 char *basename, *tmp;
671 basename = tmp = strdup (mine->img_filename);
674 char *slash = strchr(basename, '/');
675 if (!slash || !slash[1]) break;
679 char *dot=strchr(basename,'.');
682 if (strlen(basename)>20) basename[20]=0;
683 for (s=basename; *s; s++) {
685 if (*s <= ' ') *s = '_';
687 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
688 sim->typing = sim->typing_buf;
692 sim->typing = "BLOAD IMAGE\n";
694 mine->render_img_lineno=0;
700 *next_actiontime += 0.7;
705 if (mine->render_img_lineno>=192) {
707 sim->typing="POKE 49234,0\n";
712 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
713 a2_display_image_loading(st, mine->render_img,
714 mine->render_img_lineno++);
717 /* The disk would have to seek every 13 sectors == 78 lines.
718 (This ain't no newfangled 16-sector operating system) */
719 if ((mine->render_img_lineno%78)==0) {
720 *next_actiontime += 0.5;
722 *next_actiontime += 0.08;
727 st->gr_mode |= A2_GR_FULL;
729 *next_actiontime += sim->delay;
734 sim->typing="POKE 49235,0\n";
740 st->gr_mode &= ~A2_GR_FULL;
741 if (mine->render_img) {
742 free(mine->render_img);
743 mine->render_img=NULL;
745 if (mine->img_filename) {
746 free(mine->img_filename);
747 mine->img_filename=NULL;
752 case A2CONTROLLER_FREE:
753 free(mine->render_img);
754 free(mine->img_filename);
763 struct terminal_controller_data {
767 int input_available_p;
768 XtIntervalId timeout_id;
772 double last_emit_time;
773 XComposeStatus compose;
778 int cursor_x, cursor_y;
779 int saved_x, saved_y;
782 unsigned int bold : 1;
783 unsigned int blink : 1;
784 unsigned int rev : 1;
788 Bool meta_sends_esc_p;
795 subproc_cb (XtPointer closure, int *source, XtInputId *id)
797 struct terminal_controller_data *mine =
798 (struct terminal_controller_data *) closure;
799 mine->input_available_p = True;
803 launch_text_generator (struct terminal_controller_data *mine)
806 char *oprogram = get_string_resource ("program", "Program");
807 char *program = (char *) malloc (strlen (oprogram) + 10);
809 strcpy (program, "( ");
810 strcat (program, oprogram);
811 strcat (program, " ) 2>&1");
813 if (mine->pipe) abort();
816 if (get_boolean_resource ("usePty", "Boolean"))
821 ws.ws_col = SCREEN_COLS;
822 ws.ws_row = SCREEN_ROWS;
823 ws.ws_xpixel = ws.ws_col * 6;
824 ws.ws_ypixel = ws.ws_row * 8;
827 if((mine->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
830 sprintf (buf, "%.100s: forkpty", progname);
835 /* This is the child fork. */
838 if (putenv("TERM=vt100"))
845 sprintf (buf, "%.100s: %.100s", progname, oprogram);
851 /* This is the parent fork. */
852 mine->pipe = fdopen(fd, "r+");
854 XtAppAddInput (app, fileno (mine->pipe),
855 (XtPointer) (XtInputReadMask | XtInputExceptMask),
856 subproc_cb, (XtPointer) mine);
860 # endif /* HAVE_FORKPTY */
862 if ((mine->pipe = popen (program, "r")))
864 if (mine->pipe_id) abort();
866 XtAppAddInput (app, fileno (mine->pipe),
867 (XtPointer) (XtInputReadMask | XtInputExceptMask),
868 subproc_cb, (XtPointer) mine);
872 sprintf (buf, "%.100s: %.100s", progname, program);
878 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
880 struct terminal_controller_data *mine =
881 (struct terminal_controller_data *) closure;
883 launch_text_generator (mine);
887 terminal_closegen(struct terminal_controller_data *mine)
890 XtRemoveInput (mine->pipe_id);
897 if (mine->timeout_id) {
898 XtRemoveTimeOut(mine->timeout_id);
904 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
913 if (!mine->input_available_p) return 0;
915 rc=read (fileno (mine->pipe), (void *) buf, n);
916 if (rc>0) mine->lastc=buf[rc-1];
920 terminal_closegen(mine);
922 if (mine->lastc != '\n') { /* add a newline at eof if there wasn't one */
926 /* Set up a timer to re-launch the subproc in a bit. */
928 XtAppAddTimeOut(app, subproc_relaunch_delay,
929 relaunch_generator_timer,
933 mine->input_available_p = False;
939 /* The interpretation of the ModN modifiers is dependent on what keys
940 are bound to them: Mod1 does not necessarily mean "meta". It only
941 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
942 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
943 aren't necessarily the same thing. Icepicks in my forehead!
946 do_icccm_meta_key_stupidity (Display *dpy)
948 unsigned int modbits = 0;
950 XModifierKeymap *modmap = XGetModifierMapping (dpy);
951 for (i = 3; i < 8; i++)
952 for (j = 0; j < modmap->max_keypermod; j++)
954 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
957 if (code == 0) continue;
958 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
959 for (k = 0; k < nsyms; k++)
960 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
961 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
965 XFreeModifiermap (modmap);
969 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
972 meta_modifier (Display *dpy)
974 static Bool done_once = False;
975 static unsigned int mask = 0;
978 /* Really, we are supposed to recompute this if a KeymapNotify
979 event comes in, but fuck it. */
981 mask = do_icccm_meta_key_stupidity (dpy);
988 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
990 struct terminal_controller_data *mine =
991 (struct terminal_controller_data *) data;
994 XLookupString (&event->xkey, (char *) &c, 1, &keysym, &mine->compose);
995 if (c == 0 || !mine->pipe)
998 if (!mine->swap_bs_del_p) ;
999 else if (c == 127) c = 8;
1000 else if (c == 8) c = 127;
1002 /* If meta was held down, send ESC, or turn on the high bit. */
1003 if (event->xkey.state & meta_modifier (dpy))
1005 if (mine->meta_sends_esc_p)
1006 fputc ('\033', mine->pipe);
1011 fputc (c, mine->pipe);
1012 fflush (mine->pipe);
1014 event->xany.type = 0; /* do not process this event further */
1021 a2_ascii_printc (apple2_state_t *st, unsigned char c,
1022 Bool bold_p, Bool blink_p, Bool rev_p,
1025 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
1029 else if ((c >= 'A'+128) || /* upcase and blink */
1030 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
1031 c != '\r' && c != '\n' && c!='\t'))
1033 c = (c & 0x1F) | 0x80;
1035 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
1040 if (bold_p) c |= 0xc0;
1041 if (blink_p) c = (c & ~0x40) | 0x80;
1042 if (rev_p) c |= 0xc0;
1047 a2_printc_noscroll(st, c);
1052 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
1055 apple2_state_t *st=sim->st;
1056 int cols = SCREEN_COLS;
1057 int rows = SCREEN_ROWS;
1062 switch (state->escstate)
1068 /* Dummy case - we don't want the screensaver to beep */
1069 /* #### But maybe this should flash the screen? */
1072 if (state->cursor_x > 0)
1076 if (state->cursor_x < cols - 8)
1078 state->cursor_x = (state->cursor_x & ~7) + 8;
1082 state->cursor_x = 0;
1083 if (state->cursor_y < rows - 1)
1092 if (state->cursor_y < rows - 1)
1098 state->cursor_x = 0;
1102 /* Dummy case - there is one and only one font. */
1106 /* Dummy case - these interrupt escape sequences, so
1107 they don't do anything in this state */
1110 state->escstate = 1;
1113 /* Dummy case - this is supposed to be ignored */
1116 state->escstate = 2;
1117 for(i = 0; i < NPAR; i++)
1118 state->csiparam[i] = 0;
1119 state->curparam = 0;
1122 /* If the cursor is in column 39 and we print a character, then
1123 that character shows up in column 39, and the cursor is no longer
1124 visible on the screen (it's in "column 40".) If another character
1125 is printed, then that character shows up in column 0, and the
1126 cursor moves to column 1.
1128 This is empirically what xterm and gnome-terminal do, so that must
1129 be the right thing. (In xterm, the cursor vanishes, whereas; in
1130 gnome-terminal, the cursor overprints the character in col 39.)
1132 if (state->cursor_x >= cols)
1134 state->cursor_x = 0;
1135 if (state->cursor_y >= rows - 1)
1141 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
1142 a2_ascii_printc (st, c,
1143 state->termattrib.bf.bold,
1144 state->termattrib.bf.blink,
1145 state->termattrib.bf.rev,
1157 state->escstate = 0;
1159 case 'c': /* Reset */
1161 state->escstate = 0;
1163 case 'D': /* Linefeed */
1164 if (state->cursor_y < rows - 1)
1168 state->escstate = 0;
1170 case 'E': /* Newline */
1171 state->cursor_x = 0;
1172 state->escstate = 0;
1174 case 'M': /* Reverse newline */
1175 if (state->cursor_y > 0)
1177 state->escstate = 0;
1179 case '7': /* Save state */
1180 state->saved_x = state->cursor_x;
1181 state->saved_y = state->cursor_y;
1182 state->escstate = 0;
1184 case '8': /* Restore state */
1185 state->cursor_x = state->saved_x;
1186 state->cursor_y = state->saved_y;
1187 state->escstate = 0;
1190 state->escstate = 2;
1191 for(i = 0; i < NPAR; i++)
1192 state->csiparam[i] = 0;
1193 state->curparam = 0;
1195 case '%': /* Select charset */
1196 /* No, I don't support UTF-8, since the apple2 font
1197 isn't even Unicode anyway. We must still catch the
1198 last byte, though. */
1201 /* I don't support different fonts either - see above
1203 state->escstate = 3;
1206 /* Escape sequences not supported:
1209 * Z - Terminal identification
1211 * = - Other keypad change
1214 state->escstate = 0;
1223 state->escstate = 0;
1225 case '0': case '1': case '2': case '3': case '4':
1226 case '5': case '6': case '7': case '8': case '9':
1227 if (state->curparam < NPAR)
1228 state->csiparam[state->curparam] =
1229 (state->csiparam[state->curparam] * 10) + (c - '0');
1232 state->csiparam[++state->curparam] = 0;
1235 state->escstate = 3;
1238 for (i = 0; i < state->csiparam[0]; i++)
1240 if(++state->cursor_x > cols)
1242 state->cursor_x = 0;
1243 if (state->cursor_y < rows - 1)
1249 state->escstate = 0;
1252 state->cursor_x = 0;
1254 if (state->csiparam[0] == 0)
1255 state->csiparam[0] = 1;
1256 if ((state->cursor_y -= state->csiparam[0]) < 0)
1257 state->cursor_y = 0;
1258 state->escstate = 0;
1261 state->cursor_x = 0;
1264 if (state->csiparam[0] == 0)
1265 state->csiparam[0] = 1;
1266 if ((state->cursor_y += state->csiparam[0]) >= rows)
1267 state->cursor_y = rows - 1;
1268 state->escstate = 0;
1272 if (state->csiparam[0] == 0)
1273 state->csiparam[0] = 1;
1274 if ((state->cursor_x += state->csiparam[0]) >= cols)
1275 state->cursor_x = cols - 1;
1276 state->escstate = 0;
1279 if (state->csiparam[0] == 0)
1280 state->csiparam[0] = 1;
1281 if ((state->cursor_x -= state->csiparam[0]) < 0)
1282 state->cursor_x = 0;
1283 state->escstate = 0;
1286 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1287 state->cursor_y = rows - 1;
1288 state->escstate = 0;
1292 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1293 state->cursor_x = cols - 1;
1294 state->escstate = 0;
1298 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1299 state->cursor_y = rows - 1;
1300 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1301 state->cursor_x = cols - 1;
1302 if(state->cursor_y < 0)
1303 state->cursor_y = 0;
1304 if(state->cursor_x < 0)
1305 state->cursor_x = 0;
1306 state->escstate = 0;
1311 if (state->csiparam[0] == 0)
1312 start = cols * state->cursor_y + state->cursor_x;
1313 if (state->csiparam[0] == 1)
1314 end = cols * state->cursor_y + state->cursor_x;
1316 a2_goto(st, state->cursor_y, state->cursor_x);
1317 for (i = start; i < end; i++)
1319 a2_ascii_printc(st, ' ', False, False, False, False);
1321 state->escstate = 0;
1326 if (state->csiparam[0] == 0)
1327 start = state->cursor_x;
1328 if (state->csiparam[1] == 1)
1329 end = state->cursor_x;
1331 a2_goto(st, state->cursor_y, state->cursor_x);
1332 for (i = start; i < end; i++)
1334 a2_ascii_printc(st, ' ', False, False, False, False);
1336 state->escstate = 0;
1338 case 'm': /* Set attributes */
1339 for (i = 0; i <= state->curparam; i++)
1341 switch(state->csiparam[i])
1344 state->termattrib.w = 0;
1347 state->termattrib.bf.bold = 1;
1350 state->termattrib.bf.blink = 1;
1353 state->termattrib.bf.rev = 1;
1357 state->termattrib.bf.bold = 0;
1360 state->termattrib.bf.blink = 0;
1363 state->termattrib.bf.rev = 0;
1367 state->escstate = 0;
1369 case 's': /* Save position */
1370 state->saved_x = state->cursor_x;
1371 state->saved_y = state->cursor_y;
1372 state->escstate = 0;
1374 case 'u': /* Restore position */
1375 state->cursor_x = state->saved_x;
1376 state->cursor_y = state->saved_y;
1377 state->escstate = 0;
1379 case '?': /* DEC Private modes */
1380 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1381 state->escstate = 0;
1384 /* Known unsupported CSIs:
1386 * L - Insert blank lines
1387 * M - Delete lines (I don't know what this means...)
1388 * P - Delete characters
1389 * X - Erase characters (difference with P being...?)
1390 * c - Terminal identification
1391 * g - Clear tab stop(s)
1392 * h - Set mode (Mainly due to its complexity and lack of good
1395 * m - Set mode (Phosphor is, per defenition, green on black)
1397 * q - Set keyboard LEDs
1398 * r - Set scrolling region (too exhausting - noone uses this,
1401 state->escstate = 0;
1406 state->escstate = 0;
1409 a2_goto(st, state->cursor_y, state->cursor_x);
1414 It's fun to put things like "gdb" as the command. For one, it's
1415 amusing how the standard mumble (version, no warranty, it's
1416 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1420 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1422 apple2_state_t *st=sim->st;
1426 struct terminal_controller_data *mine;
1427 if (!sim->controller_data)
1428 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1429 mine=(struct terminal_controller_data *) sim->controller_data;
1431 mine->meta_sends_esc_p = get_boolean_resource ("metaSendsESC", "Boolean");
1432 mine->swap_bs_del_p = get_boolean_resource ("swapBSDEL", "Boolean");
1433 mine->fast_p = get_boolean_resource ("fast", "Boolean");
1435 sim->dec->key_handler = terminal_keypress_handler;
1436 sim->dec->key_data = mine;
1442 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1446 a2_prints(st, "APPLE ][");
1451 launch_text_generator(mine);
1454 *next_actiontime += 4.0;
1460 unsigned char buf[1024];
1464 elapsed=sim->curtime - mine->last_emit_time;
1465 mine->last_emit_time=sim->curtime;
1467 if (elapsed>1.0) nwant=1;
1468 if (nwant<1) nwant=1;
1469 if (nwant>4) nwant=4;
1472 nwant = sizeof(buf)-1;
1474 nr=terminal_read(mine, buf, nwant);
1475 for (i=0; i<nr; i++) {
1479 a2_vt100_printc (sim, mine, c);
1481 a2_ascii_printc (st, c, False, False, False, True);
1486 case A2CONTROLLER_FREE:
1487 terminal_closegen(mine);
1493 struct basic_controller_data {
1500 double prog_start_time;
1501 char error_buf[256];
1505 Adding more programs is easy. Just add a listing here and to all_programs,
1506 then add the state machine to actually execute it to basic_controller.
1508 static char *moire_program[]={
1510 "20 FOR Y = 0 TO 191 STEP 2\n",
1511 "30 HCOLOR=4 : REM BLACK\n",
1512 "40 HLINE 0,191-Y TO 279,Y\n",
1513 "60 HCOLOR=7 : REM WHITE\n",
1514 "80 HLINE 0,190-Y TO 279,Y+1\n",
1516 "100 FOR X = 0 TO 279 STEP 3\n",
1518 "120 HLINE 279-X,0 TO X,192\n",
1520 "150 HLINE 278-X,0 TO X+1,192\n",
1525 static char *sinewave_program[] = {
1528 "30 FOR X = 0 TO 279\n",
1530 "35 HLINE X,0 TO X,159\n",
1532 "40 Y = 80 + SIN(15*(X-K)/279)\n",
1541 static char *dumb_program[]={
1542 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1548 static char *random_lores_program[]={
1549 "1 REM APPLE ][ SCREEN SAVER\n",
1551 "100 COLOR= RND(1)*16\n",
1553 "110 X=RND(1)*40\n",
1554 "120 Y1=RND(1)*48\n",
1555 "130 Y2=RND(1)*48\n",
1556 "140 FOR Y = Y1 TO Y2\n",
1560 "210 Y=RND(1)*48\n",
1561 "220 X1=RND(1)*40\n",
1562 "230 X2=RND(1)*40\n",
1563 "240 FOR X = X1 TO X2\n",
1571 static char typo_map[256];
1573 int make_typo(char *out_buf, char *orig, char *err_buf)
1633 strcpy(out_buf, orig);
1634 for (i=0; out_buf[i]; i++) {
1635 char *p = out_buf+i;
1637 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1640 if (isalpha(p[0]) &&
1649 sprintf(err_buf,"?SYNTAX ERROR\n");
1653 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(u_char)p[0]])) {
1654 int remain=strlen(p);
1655 int past=random()%(remain-2)+1;
1656 memmove(p+past+past, p, remain+1);
1658 for (j=0; j<past; j++) {
1671 {moire_program, 100},
1672 /*{dumb_program, 200}, */
1673 {sinewave_program, 400},
1674 {random_lores_program, 500},
1678 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1680 apple2_state_t *st=sim->st;
1683 struct basic_controller_data *mine;
1684 if (!sim->controller_data)
1685 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1686 mine=(struct basic_controller_data *) sim->controller_data;
1693 a2_prints(st, "APPLE ][");
1696 sim->typing_rate=0.2;
1698 i=random()%countof(all_programs);
1699 mine->progtext=all_programs[i].progtext;
1700 mine->progstep=all_programs[i].progstep;
1703 *next_actiontime += 1.0;
1708 if (st->cursx==0) a2_printc(st,']');
1709 if (mine->progtext[mine->prog_line]) {
1710 if (random()%4==0) {
1711 int err=make_typo(sim->typing_buf,
1712 mine->progtext[mine->prog_line],
1714 sim->typing=sim->typing_buf;
1721 sim->typing=mine->progtext[mine->prog_line++];
1729 sim->printing=mine->error_buf;
1734 if (st->cursx==0) a2_printc(st,']');
1735 *next_actiontime+=1.0;
1740 sim->typing="RUN\n";
1744 mine->prog_start_time=*next_actiontime;
1745 *stepno=mine->progstep;
1750 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1751 for (i=0; i<24 && mine->y<192; i++)
1753 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1754 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1764 for (i=0; i<24 && mine->x<280; i++)
1766 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1767 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1770 if (mine->x >= 280) *stepno=120;
1774 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1779 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1780 for (i=0; i<30; i++) {
1781 a2_prints(st, mine->rep_str);
1787 i=random()%strlen(mine->rep_str);
1788 while (mine->rep_pos != i) {
1789 a2_printc(st, mine->rep_str[mine->rep_pos]);
1791 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1793 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1796 /* sinewave_program */
1798 st->gr_mode=A2_GR_HIRES;
1803 for (i=0; i<48; i++) {
1804 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1805 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1806 a2_hplot(st, 3, mine->x, y);
1813 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1821 /* random_lores_program */
1823 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1828 for (i=0; i<10; i++) {
1829 int color,x,y,x1,x2,y1,y2;
1835 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1840 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1842 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1849 case A2CONTROLLER_FREE:
1856 void (*controllers[])(apple2_sim_t *sim, int *stepno,
1857 double *next_actiontime) = {
1858 slideshow_controller,
1859 terminal_controller,
1864 screenhack (Display *dpy, Window window)
1866 int duration = get_integer_resource ("duration", "Integer");
1868 void (*controller)(apple2_sim_t *sim, int *stepno, double *next_actiontime);
1869 Bool random_p = False;
1872 if (duration < 1) duration = 1;
1874 if (!get_boolean_resource ("root", "Boolean"))
1876 XWindowAttributes xgwa;
1877 XGetWindowAttributes (dpy, window, &xgwa);
1878 XSelectInput (dpy, window,
1879 xgwa.your_event_mask |
1880 KeyPressMask | ButtonPressMask | ExposureMask);
1883 s = get_string_resource ("mode", "Mode");
1884 if (!s || !*s || !strcasecmp(s, "random"))
1886 else if (!strcasecmp(s, "text"))
1887 controller = terminal_controller;
1888 else if (!strcasecmp(s, "slideshow"))
1889 controller = slideshow_controller;
1890 else if (!strcasecmp(s, "basic"))
1891 controller = basic_controller;
1894 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1900 if (controller == terminal_controller)
1901 duration = 999999; /* this one runs "forever" */
1906 controller = controllers[random() % (countof(controllers))];
1908 apple2 (dpy, window, duration, controller);