1 /* xscreensaver, Copyright (c) 1998-2014 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 #endif /* HAVE_CONFIG_H */
27 #include "screenhack.h"
29 #include "textclient.h"
33 #define countof(x) (sizeof((x))/sizeof((*x)))
35 #define SCREEN_COLS 40
36 #define SCREEN_ROWS 24
39 /* Given a bitmask, returns the position and width of the field.
42 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
45 for (i = 0; i < 32; i++)
50 for (; i < 32; i++, j++)
51 if (! (mask & (1L << i)))
59 /* Given a value and a field-width, expands the field to fill out 8 bits.
62 spread_bits (unsigned char value, unsigned char width)
67 case 7: return (value << 1) | (value >> 6);
68 case 6: return (value << 2) | (value >> 4);
69 case 5: return (value << 3) | (value >> 2);
70 case 4: return (value << 4) | (value);
71 case 3: return (value << 5) | (value << 2) | (value >> 2);
72 case 2: return (value << 6) | (value << 4) | (value);
73 default: abort(); break;
78 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
79 Scales it (without dithering) to WxH.
82 scale_image (Display *dpy, Window window, XImage *in,
83 int fromx, int fromy, int fromw, int fromh,
84 unsigned int *out, int w, int h)
88 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
89 unsigned int rsiz=0, gsiz=0, bsiz=0;
90 unsigned int rmsk=0, gmsk=0, bmsk=0;
91 unsigned char spread_map[3][256];
92 XWindowAttributes xgwa;
95 if (fromx + fromw > in->width ||
96 fromy + fromh > in->height)
99 XGetWindowAttributes (dpy, window, &xgwa);
101 /* Compute the field offsets for RGB decoding in the XImage,
102 when in TrueColor mode. Otherwise we use the colormap.
104 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
105 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
107 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
108 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
109 for (i = 0; i < ncolors; i++)
111 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
115 rmsk = xgwa.visual->red_mask;
116 gmsk = xgwa.visual->green_mask;
117 bmsk = xgwa.visual->blue_mask;
118 decode_mask (rmsk, &rpos, &rsiz);
119 decode_mask (gmsk, &gpos, &gsiz);
120 decode_mask (bmsk, &bpos, &bsiz);
122 for (i = 0; i < 256; i++)
124 spread_map[0][i] = spread_bits (i, rsiz);
125 spread_map[1][i] = spread_bits (i, gsiz);
126 spread_map[2][i] = spread_bits (i, bsiz);
130 scale = (fromw > fromh
132 : (float) fromh / h);
134 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
136 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
137 for (x = 0; x < w-1; x++)
140 unsigned int r=0, g=0, b=0;
142 int xx1 = x * scale + fromx;
143 int yy1 = y * scale + fromy;
144 int xx2 = (x+1) * scale + fromx;
145 int yy2 = (y+1) * scale + fromy;
147 /* Iterate over the source pixels contributing to this one, and sum. */
148 for (xx = xx1; xx < xx2; xx++)
149 for (yy = yy1; yy < yy2; yy++)
151 unsigned char rr, gg, bb;
152 unsigned long sp = ((xx > in->width || yy > in->height)
153 ? 0 : XGetPixel (in, xx, yy));
156 rr = colors[sp].red & 0xFF;
157 gg = colors[sp].green & 0xFF;
158 bb = colors[sp].blue & 0xFF;
162 rr = (sp & rmsk) >> rpos;
163 gg = (sp & gmsk) >> gpos;
164 bb = (sp & bmsk) >> bpos;
165 rr = spread_map[0][rr];
166 gg = spread_map[1][gg];
167 bb = spread_map[2][bb];
174 /* Scale summed pixel values down to 8/8/8 range */
175 i = (xx2 - xx1) * (yy2 - yy1);
181 out[y * w + x] = (r << 16) | (g << 8) | b;
186 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
187 Picks a random sub-image out of the source image, and scales it to WxH.
190 pick_a2_subimage (Display *dpy, Window window, XImage *in,
191 unsigned int *out, int w, int h)
193 int fromx, fromy, fromw, fromh;
194 if (in->width <= w || in->height <= h)
205 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
208 } while (fromw > in->width ||
211 dw = (in->width - fromw) / 2; /* near the center! */
212 dh = (in->height - fromh) / 2;
214 fromx = (dw <= 0 ? 0 : (random() % dw) + (dw/2));
215 fromy = (dh <= 0 ? 0 : (random() % dh) + (dh/2));
218 scale_image (dpy, window, in,
219 fromx, fromy, fromw, fromh,
224 /* Floyd-Steinberg dither. Derived from ppmquant.c,
225 Copyright (c) 1989, 1991 by Jef Poskanzer.
228 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
231 Apple ][ color map. Each pixel can only be 1 or 0, but what that
232 means depends on whether it's an odd or even pixel, and whether
233 the high bit in the byte is set or not. If it's 0, it's always
236 static const int a2_cmap[2][2][3] = {
239 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
240 {/* even pixels = red */ 0xff, 0x80, 0x00}
244 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
245 {/* odd pixels = green */ 0x40, 0xff, 0x40}
250 unsigned int **pixels;
265 FILE *pipe = popen ("xv -", "w");
266 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
267 for (y = 0; y < h; y++)
268 for (x = 0; x < w; x++)
270 unsigned int p = in[y * w + x];
271 unsigned int r = (p >> 16) & 0xFF;
272 unsigned int g = (p >> 8) & 0xFF;
273 unsigned int b = (p ) & 0xFF;
274 fprintf(pipe, "%c%c%c", r, g, b);
280 /* Initialize Floyd-Steinberg error vectors. */
281 this_rerr = (long *) calloc (w + 2, sizeof(long));
282 next_rerr = (long *) calloc (w + 2, sizeof(long));
283 this_gerr = (long *) calloc (w + 2, sizeof(long));
284 next_gerr = (long *) calloc (w + 2, sizeof(long));
285 this_berr = (long *) calloc (w + 2, sizeof(long));
286 next_berr = (long *) calloc (w + 2, sizeof(long));
289 /* #### do we really need more than one element of "pixels" at once?
291 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
292 for (y = 0; y < h; y++)
293 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
295 for (x = 0; x < w + 2; ++x)
297 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
298 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
299 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
300 /* (random errors in [-1 .. 1]) */
303 for (y = 0; y < h; y++)
304 for (x = 0; x < w; x++)
305 pixels[y][x] = in[y * w + x];
307 for (y = 0; y < h; y++)
313 for (x = 0; x < w + 2; x++)
314 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
316 /* It's too complicated to go back and forth on alternate rows,
317 so we always go left-right here. It doesn't change the result
320 For each group of 7 pixels, we have to try it both with the
321 high bit=0 and =1. For each high bit value, we add up the
322 total error and pick the best one.
324 Because we have to go through each group of bits twice, we
325 don't propagate the error values through this_[rgb]err since
326 it would add them twice. So we keep seperate local_[rgb]err
327 variables for propagating error within the 7-pixel group.
331 for (xbyte=0; xbyte<280; xbyte+=7)
334 int best_error=2000000000;
338 int local_rerr=0, local_gerr=0, local_berr=0;
340 for (hibit=0; hibit<2; hibit++)
345 for (x=xbyte; x<xbyte+7; x++)
349 /* Use Floyd-Steinberg errors to adjust actual color. */
350 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
351 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
352 sb = ((pP[x] ) & 0xFF) * brightness/256;
353 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
354 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
355 sb += (this_berr[x + 1] + local_berr) / fs_scale;
358 else if (sr > maxval) sr = maxval;
360 else if (sg > maxval) sg = maxval;
362 else if (sb > maxval) sb = maxval;
364 /* This is the color we'd get if we set the bit 1. For 0,
366 r2=a2_cmap[hibit][x&1][0];
367 g2=a2_cmap[hibit][x&1][1];
368 b2=a2_cmap[hibit][x&1][2];
371 dist0 and dist1 are the error (Minkowski 2-metric
372 distances in the color space) for choosing 0 and
373 1 respectively. 0 is black, 1 is the color r2,g2,b2.
375 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
376 dist0= sr*sr + sg*sg + sb*sb;
380 byte |= 1 << (x-xbyte);
383 /* Wanted sr but got r2, so propagate sr-r2 */
384 local_rerr = (sr - r2) * fs_scale * 7/16;
385 local_gerr = (sg - g2) * fs_scale * 7/16;
386 local_berr = (sb - b2) * fs_scale * 7/16;
392 /* Wanted sr but got 0, so propagate sr */
393 local_rerr = sr * fs_scale * 7/16;
394 local_gerr = sg * fs_scale * 7/16;
395 local_berr = sb * fs_scale * 7/16;
399 if (tot_error < best_error)
402 best_error = tot_error;
406 /* Avoid alternating 7f and ff in all-white areas, because it makes
407 regular pink vertical lines */
408 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
413 Now that we've chosen values for all 8 bits of the byte, we
414 have to fill in the real pixel values into pP and propagate
415 all the error terms. We end up repeating a lot of the code
419 for (x=xbyte; x<xbyte+7; x++)
421 int bit=(best_byte>>(x-xbyte))&1;
422 hibit=(best_byte>>7)&1;
424 sr = (pP[x] >> 16) & 0xFF;
425 sg = (pP[x] >> 8) & 0xFF;
426 sb = (pP[x] ) & 0xFF;
427 sr += this_rerr[x + 1] / fs_scale;
428 sg += this_gerr[x + 1] / fs_scale;
429 sb += this_berr[x + 1] / fs_scale;
432 else if (sr > maxval) sr = maxval;
434 else if (sg > maxval) sg = maxval;
436 else if (sb > maxval) sb = maxval;
438 r2=a2_cmap[hibit][x&1][0] * bit;
439 g2=a2_cmap[hibit][x&1][1] * bit;
440 b2=a2_cmap[hibit][x&1][2] * bit;
442 pP[x] = (r2<<16) | (g2<<8) | (b2);
444 /* Propagate Floyd-Steinberg error terms. */
445 err = (sr - r2) * fs_scale;
446 this_rerr[x + 2] += (err * 7) / 16;
447 next_rerr[x ] += (err * 3) / 16;
448 next_rerr[x + 1] += (err * 5) / 16;
449 next_rerr[x + 2] += (err ) / 16;
450 err = (sg - g2) * fs_scale;
451 this_gerr[x + 2] += (err * 7) / 16;
452 next_gerr[x ] += (err * 3) / 16;
453 next_gerr[x + 1] += (err * 5) / 16;
454 next_gerr[x + 2] += (err ) / 16;
455 err = (sb - b2) * fs_scale;
456 this_berr[x + 2] += (err * 7) / 16;
457 next_berr[x ] += (err * 3) / 16;
458 next_berr[x + 1] += (err * 5) / 16;
459 next_berr[x + 2] += (err ) / 16;
463 And put the actual byte into out.
466 out[y*(w/7) + xbyte/7] = best_byte;
470 temp_err = this_rerr;
471 this_rerr = next_rerr;
472 next_rerr = temp_err;
473 temp_err = this_gerr;
474 this_gerr = next_gerr;
475 next_gerr = temp_err;
476 temp_err = this_berr;
477 this_berr = next_berr;
478 next_berr = temp_err;
494 /* let's see what we got... */
495 FILE *pipe = popen ("xv -", "w");
496 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
497 for (y = 0; y < h; y++)
498 for (x = 0; x < w; x++)
500 unsigned int r = (pixels[y][x]>>16)&0xff;
501 unsigned int g = (pixels[y][x]>>8)&0xff;
502 unsigned int b = (pixels[y][x]>>0)&0xff;
503 fprintf(pipe, "%c%c%c", r, g, b);
510 typedef struct slideshow_data_s {
512 int render_img_lineno;
513 unsigned char *render_img;
515 Bool image_loading_p;
520 image_loaded_cb (Screen *screen, Window window, Drawable p,
521 const char *name, XRectangle *geometry,
524 Display *dpy = DisplayOfScreen (screen);
525 apple2_sim_t *sim = (apple2_sim_t *) closure;
526 slideshow_data *mine = (slideshow_data *) sim->controller_data;
527 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);
542 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
543 XFreePixmap (dpy, p);
546 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
547 image (regardless of whether it started as TrueColor/PseudoColor.)
549 pick_a2_subimage (dpy, window, image, buf32, w, h);
552 XDestroyImage(image);
554 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
556 a2_dither (buf32, buf8, w, h);
560 mine->image_loading_p = False;
561 mine->img_filename = (name ? strdup (name) : 0);
562 mine->render_img = buf8;
567 static const char *apple2_defaults [] = {
568 ".background: black",
569 ".foreground: white",
572 "*program: xscreensaver-text --cols 40",
573 "*metaSendsESC: True",
580 # endif /* !HAVE_FORKPTY */
586 static XrmOptionDescRec apple2_options [] = {
587 { "-mode", ".mode", XrmoptionSepArg, 0 },
588 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
589 { "-basic", ".mode", XrmoptionNoArg, "basic" },
590 { "-text", ".mode", XrmoptionNoArg, "text" },
591 { "-program", ".program", XrmoptionSepArg, 0 },
592 { "-duration", ".duration", XrmoptionSepArg, 0 },
593 { "-pty", ".usePty", XrmoptionNoArg, "True" },
594 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
595 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
596 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
597 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
598 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
599 { "-fast", ".fast", XrmoptionNoArg, "True" },
605 TODO: this should load 10 images at startup time, then cycle through them
606 to avoid the pause while it loads.
609 static void slideshow_controller(apple2_sim_t *sim, int *stepno,
610 double *next_actiontime)
612 apple2_state_t *st=sim->st;
614 slideshow_data *mine;
616 if (!sim->controller_data)
617 sim->controller_data = calloc (1, sizeof(*mine));
618 mine = (slideshow_data *) sim->controller_data;
626 sim->typing_rate = 0.3;
627 sim->dec->powerup=0.0;
630 a2_prints(st, "APPLE ][");
639 XWindowAttributes xgwa;
641 XGetWindowAttributes (sim->dpy, sim->window, &xgwa);
642 p = XCreatePixmap (sim->dpy, sim->window, xgwa.width, xgwa.height,
644 mine->image_loading_p = True;
645 load_image_async (xgwa.screen, sim->window, p, image_loaded_cb, sim);
647 /* pause with a blank screen for a bit, while the image loads in the
649 *next_actiontime += 2.0;
655 if (! mine->image_loading_p) { /* image is finally loaded */
661 *next_actiontime += 3.0;
676 st->gr_mode=A2_GR_HIRES;
677 if (mine->img_filename) {
678 char *basename, *tmp;
681 basename = tmp = strdup (mine->img_filename);
684 char *slash = strchr(basename, '/');
685 if (!slash || !slash[1]) break;
689 char *dot=strrchr(basename,'.');
692 if (strlen(basename)>20) basename[20]=0;
693 for (s=basename; *s; s++) {
695 if (*s <= ' ') *s = '_';
697 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
698 sim->typing = sim->typing_buf;
702 sim->typing = "BLOAD IMAGE\n";
704 mine->render_img_lineno=0;
710 *next_actiontime += 0.7;
715 if (mine->render_img_lineno>=192) {
717 sim->typing="POKE 49234,0\n";
722 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
723 a2_display_image_loading(st, mine->render_img,
724 mine->render_img_lineno++);
727 /* The disk would have to seek every 13 sectors == 78 lines.
728 (This ain't no newfangled 16-sector operating system) */
729 if ((mine->render_img_lineno%78)==0) {
730 *next_actiontime += 0.5;
732 *next_actiontime += 0.08;
737 st->gr_mode |= A2_GR_FULL;
739 /* Note that sim->delay is sometimes "infinite" in this controller.
740 These images are kinda dull anyway, so don't leave it on too long. */
741 *next_actiontime += 2;
746 sim->typing="POKE 49235,0\n";
752 st->gr_mode &= ~A2_GR_FULL;
753 if (mine->render_img) {
754 free(mine->render_img);
755 mine->render_img=NULL;
757 if (mine->img_filename) {
758 free(mine->img_filename);
759 mine->img_filename=NULL;
765 /* Do nothing, just wait */
766 *next_actiontime += 2.0;
767 *stepno = A2CONTROLLER_FREE;
770 case A2CONTROLLER_FREE:
771 /* It is possible that still image is being loaded,
772 in that case mine cannot be freed, because
773 callback function tries to use it, so wait.
775 if (mine->image_loading_p) {
779 free(mine->render_img);
780 free(mine->img_filename);
790 struct terminal_controller_data {
794 double last_emit_time;
800 int cursor_x, cursor_y;
801 int saved_x, saved_y;
802 int unicruds; char unicrud[7];
805 unsigned int bold : 1;
806 unsigned int blink : 1;
807 unsigned int rev : 1;
816 /* The structure of closure linkage throughout this code is so amazingly
817 baroque that I can't get to the 'struct state' from where I need it. */
818 static const char *global_program;
819 static Bool global_fast_p;
823 terminal_closegen(struct terminal_controller_data *mine)
826 textclient_close (mine->tc);
832 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
834 if (!mine || !mine->tc) {
838 for (i = 0; i < n; i++) {
839 int c = textclient_getc (mine->tc);
851 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
853 struct terminal_controller_data *mine =
854 (struct terminal_controller_data *) data;
856 if (event->xany.type == KeyPress && mine->tc)
857 return textclient_putc (mine->tc, &event->xkey);
863 a2_ascii_printc (apple2_state_t *st, unsigned char c,
864 Bool bold_p, Bool blink_p, Bool rev_p,
867 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
871 else if ((c >= 'A'+128) || /* upcase and blink */
872 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
873 c != '\r' && c != '\n' && c!='\t'))
875 c = (c & 0x1F) | 0x80;
877 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
882 if (bold_p) c |= 0xc0;
883 if (blink_p) c = (c & ~0x40) | 0x80;
884 if (rev_p) c |= 0xc0;
889 a2_printc_noscroll(st, c);
894 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
897 apple2_state_t *st=sim->st;
898 int cols = SCREEN_COLS;
899 int rows = SCREEN_ROWS;
904 /* Mostly duplicated in phosphor.c */
906 switch (state->escstate)
912 /* Dummy case - we don't want the screensaver to beep */
913 /* #### But maybe this should flash the screen? */
916 if (state->cursor_x > 0)
920 if (state->cursor_x < cols - 8)
922 state->cursor_x = (state->cursor_x & ~7) + 8;
927 if (state->cursor_y < rows - 1)
934 # ifndef HAVE_FORKPTY
935 state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */
939 if (state->cursor_y < rows - 1)
949 /* Dummy case - there is one and only one font. */
953 /* Dummy case - these interrupt escape sequences, so
954 they don't do anything in this state */
960 /* Dummy case - this is supposed to be ignored */
964 for(i = 0; i < NPAR; i++)
965 state->csiparam[i] = 0;
970 /* states 102-106 are for UTF-8 decoding */
972 if ((c & 0xE0) == 0xC0) { /* 110xxxxx - 11 bits, 2 bytes */
974 state->unicrud[0] = c;
975 state->escstate = 102;
977 } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx - 16 bits, 3 bytes */
979 state->unicrud[0] = c;
980 state->escstate = 103;
982 } else if ((c & 0xF8) == 0xF0) { /* 11110xxx - 21 bits, 4 bytes */
984 state->unicrud[0] = c;
985 state->escstate = 104;
987 } else if ((c & 0xFC) == 0xF8) { /* 111110xx - 26 bits, 5 bytes */
989 state->unicrud[0] = c;
990 state->escstate = 105;
992 } else if ((c & 0xFE) == 0xFC) { /* 1111110x - 31 bits, 6 bytes */
994 state->unicrud[0] = c;
995 state->escstate = 106;
1001 /* If the cursor is in column 39 and we print a character, then
1002 that character shows up in column 39, and the cursor is no longer
1003 visible on the screen (it's in "column 40".) If another character
1004 is printed, then that character shows up in column 0, and the
1005 cursor moves to column 1.
1007 This is empirically what xterm and gnome-terminal do, so that must
1008 be the right thing. (In xterm, the cursor vanishes, whereas; in
1009 gnome-terminal, the cursor overprints the character in col 39.)
1011 if (state->cursor_x >= cols)
1013 state->cursor_x = 0;
1014 if (state->cursor_y >= rows - 1)
1020 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
1021 a2_ascii_printc (st, c,
1022 state->termattrib.bf.bold,
1023 state->termattrib.bf.blink,
1024 state->termattrib.bf.rev,
1036 state->escstate = 0;
1038 case 'c': /* Reset */
1040 state->escstate = 0;
1042 case 'D': /* Linefeed */
1043 if (state->cursor_y < rows - 1)
1047 state->escstate = 0;
1049 case 'E': /* Newline */
1050 state->cursor_x = 0;
1051 state->escstate = 0;
1053 case 'M': /* Reverse newline */
1054 if (state->cursor_y > 0)
1056 state->escstate = 0;
1058 case '7': /* Save state */
1059 state->saved_x = state->cursor_x;
1060 state->saved_y = state->cursor_y;
1061 state->escstate = 0;
1063 case '8': /* Restore state */
1064 state->cursor_x = state->saved_x;
1065 state->cursor_y = state->saved_y;
1066 state->escstate = 0;
1069 state->escstate = 2;
1070 for(i = 0; i < NPAR; i++)
1071 state->csiparam[i] = 0;
1072 state->curparam = 0;
1074 case '%': /* Select charset */
1075 /* @: Select default (ISO 646 / ISO 8859-1)
1077 8: Select UTF-8 (obsolete)
1079 We can just ignore this and always process UTF-8, I think?
1080 We must still catch the last byte, though.
1084 /* I don't support different fonts either - see above
1086 state->escstate = 3;
1089 /* Escape sequences not supported:
1092 * Z - Terminal identification
1094 * = - Other keypad change
1097 state->escstate = 0;
1106 state->escstate = 0;
1108 case '0': case '1': case '2': case '3': case '4':
1109 case '5': case '6': case '7': case '8': case '9':
1110 if (state->curparam < NPAR)
1111 state->csiparam[state->curparam] =
1112 (state->csiparam[state->curparam] * 10) + (c - '0');
1115 state->csiparam[++state->curparam] = 0;
1118 state->escstate = 3;
1121 for (i = 0; i < state->csiparam[0]; i++)
1123 if(++state->cursor_x > cols)
1125 state->cursor_x = 0;
1126 if (state->cursor_y < rows - 1)
1132 state->escstate = 0;
1135 state->cursor_x = 0;
1137 if (state->csiparam[0] == 0)
1138 state->csiparam[0] = 1;
1139 if ((state->cursor_y -= state->csiparam[0]) < 0)
1140 state->cursor_y = 0;
1141 state->escstate = 0;
1144 state->cursor_x = 0;
1147 if (state->csiparam[0] == 0)
1148 state->csiparam[0] = 1;
1149 if ((state->cursor_y += state->csiparam[0]) >= rows)
1150 state->cursor_y = rows - 1;
1151 state->escstate = 0;
1155 if (state->csiparam[0] == 0)
1156 state->csiparam[0] = 1;
1157 if ((state->cursor_x += state->csiparam[0]) >= cols)
1158 state->cursor_x = cols - 1;
1159 state->escstate = 0;
1162 if (state->csiparam[0] == 0)
1163 state->csiparam[0] = 1;
1164 if ((state->cursor_x -= state->csiparam[0]) < 0)
1165 state->cursor_x = 0;
1166 state->escstate = 0;
1169 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1170 state->cursor_y = rows - 1;
1171 state->escstate = 0;
1175 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1176 state->cursor_x = cols - 1;
1177 state->escstate = 0;
1181 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1182 state->cursor_y = rows - 1;
1183 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1184 state->cursor_x = cols - 1;
1185 if(state->cursor_y < 0)
1186 state->cursor_y = 0;
1187 if(state->cursor_x < 0)
1188 state->cursor_x = 0;
1189 state->escstate = 0;
1194 if (state->csiparam[0] == 0)
1195 start = cols * state->cursor_y + state->cursor_x;
1196 if (state->csiparam[0] == 1)
1197 end = cols * state->cursor_y + state->cursor_x;
1199 a2_goto(st, state->cursor_y, state->cursor_x);
1200 for (i = start; i < end; i++)
1202 a2_ascii_printc(st, ' ', False, False, False, False);
1204 state->escstate = 0;
1209 if (state->csiparam[0] == 0)
1210 start = state->cursor_x;
1211 if (state->csiparam[1] == 1)
1212 end = state->cursor_x;
1214 a2_goto(st, state->cursor_y, state->cursor_x);
1215 for (i = start; i < end; i++)
1217 a2_ascii_printc(st, ' ', False, False, False, False);
1219 state->escstate = 0;
1221 case 'm': /* Set attributes */
1222 for (i = 0; i <= state->curparam; i++)
1224 switch(state->csiparam[i])
1227 state->termattrib.w = 0;
1230 state->termattrib.bf.bold = 1;
1233 state->termattrib.bf.blink = 1;
1236 state->termattrib.bf.rev = 1;
1240 state->termattrib.bf.bold = 0;
1243 state->termattrib.bf.blink = 0;
1246 state->termattrib.bf.rev = 0;
1250 state->escstate = 0;
1252 case 's': /* Save position */
1253 state->saved_x = state->cursor_x;
1254 state->saved_y = state->cursor_y;
1255 state->escstate = 0;
1257 case 'u': /* Restore position */
1258 state->cursor_x = state->saved_x;
1259 state->cursor_y = state->saved_y;
1260 state->escstate = 0;
1262 case '?': /* DEC Private modes */
1263 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1264 state->escstate = 0;
1267 /* Known unsupported CSIs:
1269 * L - Insert blank lines
1270 * M - Delete lines (I don't know what this means...)
1271 * P - Delete characters
1272 * X - Erase characters (difference with P being...?)
1273 * c - Terminal identification
1274 * g - Clear tab stop(s)
1275 * h - Set mode (Mainly due to its complexity and lack of good
1278 * m - Set mode (Phosphor is, per defenition, green on black)
1280 * q - Set keyboard LEDs
1281 * r - Set scrolling region (too exhausting - noone uses this,
1284 state->escstate = 0;
1289 state->escstate = 0;
1298 int total = state->escstate - 100; /* see what I did there */
1299 if (state->unicruds < total) {
1300 /* Buffer more bytes of the UTF-8 sequence */
1301 state->unicrud[state->unicruds++] = c;
1304 if (state->unicruds >= total) {
1305 /* Done! Convert it to ASCII and print that. */
1307 state->unicrud[state->unicruds] = 0;
1308 s = utf8_to_latin1 ((const char *) state->unicrud, True);
1309 state->unicruds = 0;
1310 state->escstate = 0;
1325 a2_goto(st, state->cursor_y, state->cursor_x);
1330 It's fun to put things like "gdb" as the command. For one, it's
1331 amusing how the standard mumble (version, no warranty, it's
1332 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1336 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1338 apple2_state_t *st=sim->st;
1342 struct terminal_controller_data *mine;
1343 if (!sim->controller_data)
1344 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1345 mine=(struct terminal_controller_data *) sim->controller_data;
1346 mine->dpy = sim->dpy;
1348 mine->fast_p = global_fast_p;
1354 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1358 a2_prints(st, "APPLE ][");
1363 mine->tc = textclient_open (mine->dpy);
1364 textclient_reshape (mine->tc,
1365 SCREEN_COLS, SCREEN_ROWS,
1366 SCREEN_COLS, SCREEN_ROWS,
1371 *next_actiontime += 4.0;
1374 mine->last_emit_time = sim->curtime;
1380 Bool first_line_p = (*stepno == 10);
1381 unsigned char buf[1024];
1385 elapsed=sim->curtime - mine->last_emit_time;
1387 nwant = elapsed * 25.0; /* characters per second */
1394 if (nwant > 40) nwant = 40;
1397 nwant = sizeof(buf)-1;
1399 if (nwant <= 0) break;
1401 mine->last_emit_time = sim->curtime;
1403 nr=terminal_read(mine, buf, nwant);
1404 for (i=0; i<nr; i++) {
1408 a2_vt100_printc (sim, mine, c);
1410 a2_ascii_printc (st, c, False, False, False, True);
1415 case A2CONTROLLER_FREE:
1416 terminal_closegen(mine);
1423 struct basic_controller_data {
1426 const char * const * progtext;
1430 double prog_start_time;
1431 char error_buf[256];
1435 Adding more programs is easy. Just add a listing here and to all_programs,
1436 then add the state machine to actually execute it to basic_controller.
1438 static const char * const moire_program[]={
1440 "20 FOR Y = 0 TO 190 STEP 2\n",
1441 "30 HCOLOR=4 : REM BLACK\n",
1442 "40 HPLOT 0,191-Y TO 279,Y\n",
1443 "60 HCOLOR=7 : REM WHITE\n",
1444 "80 HPLOT 0,190-Y TO 279,Y+1\n",
1446 "100 FOR X = 0 TO 278 STEP 3\n",
1448 "120 HPLOT 279-X,0 TO X,191\n",
1450 "150 HPLOT 278-X,0 TO X+1,191\n",
1455 static const char * const sinewave_program[] = {
1458 "30 FOR X = 0 TO 279\n",
1460 "35 HPLOT X,0 TO X,159\n",
1462 "40 Y = 80 + SIN(15*(X-K)/279) * 40\n",
1471 static const char * const dumb_program[]={
1472 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1478 static const char * const random_lores_program[]={
1479 "1 REM APPLE ][ SCREEN SAVER\n",
1481 "100 COLOR= RND(1)*16\n",
1483 "110 X=RND(1)*40\n",
1484 "120 Y1=RND(1)*40\n",
1485 "130 Y2=RND(1)*40\n",
1486 "140 FOR Y = Y1 TO Y2\n",
1490 "210 Y=RND(1)*40\n",
1491 "220 X1=RND(1)*40\n",
1492 "230 X2=RND(1)*40\n",
1493 "240 FOR X = X1 TO X2\n",
1501 static char typo_map[256];
1503 static int make_typo(char *out_buf, const char *orig, char *err_buf)
1563 strcpy(out_buf, orig);
1564 for (i=0; out_buf[i]; i++) {
1565 char *p = out_buf+i;
1567 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1570 if (isalpha(p[0]) &&
1579 sprintf(err_buf,"?SYNTAX ERROR\n");
1583 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(unsigned char)p[0]])) {
1584 int remain=strlen(p);
1585 int past=random()%(remain-2)+1;
1586 memmove(p+past+past, p, remain+1);
1588 for (j=0; j<past; j++) {
1597 static const struct {
1598 const char * const * progtext;
1601 {moire_program, 100},
1602 /*{dumb_program, 200}, */
1603 {sinewave_program, 400},
1604 {random_lores_program, 500},
1608 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1610 apple2_state_t *st=sim->st;
1613 struct basic_controller_data *mine;
1614 if (!sim->controller_data)
1615 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1616 mine=(struct basic_controller_data *) sim->controller_data;
1623 a2_prints(st, "APPLE ][");
1626 sim->typing_rate=0.2;
1628 i=random()%countof(all_programs);
1629 mine->progtext=all_programs[i].progtext;
1630 mine->progstep=all_programs[i].progstep;
1633 *next_actiontime += 1.0;
1638 if (st->cursx==0) a2_printc(st,']');
1639 if (mine->progtext[mine->prog_line]) {
1640 if (random()%4==0) {
1641 int err=make_typo(sim->typing_buf,
1642 mine->progtext[mine->prog_line],
1644 sim->typing=sim->typing_buf;
1651 sim->typing=mine->progtext[mine->prog_line++];
1659 sim->printing=mine->error_buf;
1664 if (st->cursx==0) a2_printc(st,']');
1665 *next_actiontime+=1.0;
1670 sim->typing="RUN\n";
1674 mine->prog_start_time=*next_actiontime;
1675 *stepno=mine->progstep;
1680 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1681 for (i=0; i<24 && mine->y<192; i++)
1683 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1684 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1694 for (i=0; i<24 && mine->x<280; i++)
1696 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1697 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1700 if (mine->x >= 280) *stepno=120;
1704 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1709 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1710 for (i=0; i<30; i++) {
1711 a2_prints(st, mine->rep_str);
1717 i=random()%strlen(mine->rep_str);
1718 while (mine->rep_pos != i) {
1719 a2_printc(st, mine->rep_str[mine->rep_pos]);
1721 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1723 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1726 /* sinewave_program */
1728 st->gr_mode=A2_GR_HIRES;
1733 for (i=0; i<48; i++) {
1734 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1735 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1736 a2_hplot(st, 3, mine->x, y);
1743 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1751 /* random_lores_program */
1753 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1758 for (i=0; i<10; i++) {
1759 int color,x,y,x1,x2,y1,y2;
1765 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1770 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1772 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1779 case A2CONTROLLER_FREE:
1787 static void (* const controllers[]) (apple2_sim_t *sim, int *stepno,
1788 double *next_actiontime) = {
1789 slideshow_controller,
1790 terminal_controller,
1798 void (*controller) (apple2_sim_t *sim, int *stepno, double *next_actiontime);
1803 apple2_init (Display *dpy, Window window)
1805 struct state *st = (struct state *) calloc (1, sizeof(*st));
1808 st->duration = get_integer_resource (dpy, "duration", "Integer");
1811 if (st->duration < 1) st->duration = 1;
1813 s = get_string_resource (dpy, "mode", "Mode");
1814 if (!s || !*s || !strcasecmp(s, "random"))
1815 st->random_p = True;
1816 else if (!strcasecmp(s, "text"))
1817 st->controller = terminal_controller;
1818 else if (!strcasecmp(s, "slideshow"))
1819 st->controller = slideshow_controller;
1820 else if (!strcasecmp(s, "basic"))
1821 st->controller = basic_controller;
1824 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1830 global_program = get_string_resource (dpy, "program", "Program");
1831 global_fast_p = get_boolean_resource (dpy, "fast", "Boolean");
1834 /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
1836 const char *s = getenv ("XSCREENSAVER_STANDALONE");
1837 if (s && *s && strcmp(s, "0"))
1839 st->controller = terminal_controller;
1840 st->random_p = False;
1841 global_program = getenv ("SHELL");
1842 global_fast_p = True;
1847 if (! st->random_p) {
1848 if (st->controller == terminal_controller ||
1849 st->controller == slideshow_controller)
1850 st->duration = 999999; /* these run "forever" */
1856 static unsigned long
1857 apple2_draw (Display *dpy, Window window, void *closure)
1859 struct state *st = (struct state *) closure;
1863 st->controller = controllers[random() % (countof(controllers))];
1864 st->sim = apple2_start (dpy, window, st->duration, st->controller);
1867 if (! apple2_one_frame (st->sim)) {
1879 apple2_reshape (Display *dpy, Window window, void *closure,
1880 unsigned int w, unsigned int h)
1882 struct state *st = (struct state *) closure;
1884 analogtv_reconfigure (st->sim->dec);
1888 apple2_event (Display *dpy, Window window, void *closure, XEvent *event)
1890 struct state *st = (struct state *) closure;
1893 st->controller == terminal_controller &&
1894 event->xany.type == KeyPress) {
1895 terminal_keypress_handler (dpy, event, st->sim->controller_data);
1903 apple2_free (Display *dpy, Window window, void *closure)
1905 struct state *st = (struct state *) closure;
1907 st->sim->stepno = A2CONTROLLER_DONE;
1908 if (apple2_one_frame (st->sim))
1909 abort(); /* should have freed! */
1915 XSCREENSAVER_MODULE ("Apple2", apple2)