/* ** vt.c - a replacement dev:console driver for the Keynote musical expression ** language. ** ** Provides a cursor, constant-width characters, and (someday) all the escape ** sequences for vt100 emulation. The driver (handler) is installed at ** run-time (for MPW 1.0) by doing the following call (before doing any I/O, ** since that would auto-initialize the default system driver). ** ** _addDevHandler(1, 'CONS', vt_faccess, vt_close, vt_read, vt_write, vt_ioctl); ** ** WARNING: this code assumes the MPW 1.0 development system. It hooks itself ** in as a device driver using the undocumented _addDevHandler call, which ** may change in future releases of MPW. It expects the system driver to be ** in slot 1 - this also may change. ** ** If you are not using MPW, some or all of this driver may be unnecessary. ** The vt_getch and vt_peekch functions probably ARE necessary and probably ** work correctly under any development system. They mainly provide the ** ability to stat the console to do non-blocking input. They completely ** avoid MPW's stdio (and all other libraries for that matter), although ** the vt_read call provides an stdio interface if desired (blocking input ** only, of course). They DO assume that the mac has been initialized and ** the event queue has been set up (InitGraf et.al.). ** ** The problem is printf. If your development system has an acceptable ** implementation (MPW *does NOT*), then just comment out the definition ** of MPW in Keynote source file machdep.h and give 'er a try. If, however, ** you want to use my vt_putch or vt_write routines, you will have to figure ** out how to hook them into your system. The only thing to beware of is to ** call vt_open before using vt_putch. This is done automatically via the ** vt_faccess call for MPW 1.0. ** ** Steven A. Falco 4/30/87 moss!saf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vt.h" #include #define MAXROW 24 #define MAXCOL 80 #define TABVAL 8 #define ENONE 0 #define EFIRST 1 #define EBRACKET 2 /* note - the pixel location is based on the character baseline. */ #define XL(x) ((x) * vt_font.widMax + 4) /* x offset from chars to pixels */ #define YL(y) (((y) + 1) * vt_line - 2) /* y offset from chars to pixels */ #define HOP MoveTo(XL(vt_col), YL(vt_row)); GetPen(&vt_pen) /* BLANK(1,1) scrubs one cell. (1,2) does a cell and the cell below it, * while (2,1) does a cell and the cell to the right of it. etc. */ #define BLANK(i, j) SetRect(&vt_one_char, vt_pen.h, vt_pen.v - vt_above, \ vt_pen.h + (i) * vt_font.widMax, \ vt_pen.v + vt_below + ((j) - 1) * vt_line); \ EraseRect(&vt_one_char) #define CURSOR SetRect(&vt_one_char, vt_pen.h, vt_pen.v - vt_above, \ vt_pen.h + vt_font.widMax, vt_pen.v + vt_below); \ PenMode(patXor); \ PaintRect(&vt_one_char); \ PenMode(patCopy) #define SAVE vt_saverow = vt_row; vt_savecol = vt_col #define RESTORE vt_row = vt_saverow; vt_col = vt_savecol WindowPtr vt; FontInfo vt_font; int vt_line, vt_above, vt_below; int vt_col, vt_savecol; int vt_row, vt_saverow; Rect vt_one_char; Rect vt_rect; Rect screenRect; Point vt_pen; int vt_rawf; int vt_esc; int vt_inprog; /* number in progress */ int vt_x, vt_y; /* for motion escape code */ static int firsttime = 0; static int linesize = 0; static char *lineptr; static char linebuf[256]; /* initialize the world. We call this based on the "firsttime" flag - not an * every faccess call! - we only want to do it once... */ vt_open() { char *vt_title; int i; InitGraf(&qd.thePort); InitFonts(); FlushEvents(everyEvent, 0); InitWindows(); InitMenus(); TEInit(); InitDialogs(nil); InitCursor(); vt_title = ""; screenRect = qd.screenBits.bounds; SetRect(&vt_rect, 4, 20 + 4, screenRect.right - 4, screenRect.bottom - 4); vt = NewWindow(nil, &vt_rect, vt_title, true, plainDBox, -1, false, 0); SetPort(vt); vt_rect.left -= 4; /* make vt_rect local - only used for scrolls hereafter */ vt_rect.right -= 4; vt_rect.top -= (20 + 4); vt_rect.bottom -= (20 + 4); TextFont(monaco); /* this driver only supports constant-width */ TextSize(9); GetFontInfo(&vt_font); PenMode(patCopy); vt_line = vt_font.ascent + vt_font.descent + vt_font.leading; vt_above = vt_font.ascent; /* dereference structure for efficiency */ vt_below = vt_font.descent; vt_rawf = 0; /* start out cooked */ vt_esc = ENONE; /* no escape sequence yet */ vt_row = vt_col = 0; /* start out at home */ HOP; /* actually move there */ CURSOR; /* put up a cursor (XOR) */ return; } /* scroll the whole screen up one line-height */ vt_scroll() { RgnHandle scroll_rgn; scroll_rgn = NewRgn(); ScrollRect(&vt_rect, 0, -vt_line, scroll_rgn); DisposeRgn(scroll_rgn); return; } /* put a character on the screen. Set state flags so we can process sequences * of characters (to do escape sequences.) * Assume we are sitting on the cursor and vt_pen is valid */ vt_putch(c) int c; { if(vt_esc > ENONE) { /* we are in the middle of an escape sequence */ escape_code(c); } else if(c >= ' ' && c <= '~') { /* it is printable */ printable_code(c); } else { /* it is control */ control_code(c); } return; } /* it is a simple character */ printable_code(c) int c; { BLANK(1, 1); /* take away the cursor */ DrawChar(c); /* paint in the character */ if(++vt_col >= MAXCOL) { /* time to auto-linefeed */ vt_col = 0; if(++vt_row >= MAXROW) {/* scroll it */ vt_row = MAXROW - 1; /* not so far please... */ vt_scroll(); } } HOP; /* move to the cell */ CURSOR; /* paint in the cursor */ return; } /* process a control code - all are exactly 1 character in length */ control_code(c) int c; { int i, j; switch(c) { case '\007': /* bell */ SysBeep(20); /* beep for 20/60 seconds */ break; case '\010': /* backspace */ if(vt_col > 0) { /* can't backspace past left margin */ CURSOR; /* exor it again to remove */ vt_col--; HOP; /* jump back one */ CURSOR; /* and a new cursor */ } else { SysBeep(20); /* be a pain */ } break; case '\011': /* tab */ j = vt_col; /* stack this for re-entrancy */ for(i = 0; i < (TABVAL - (j % TABVAL)); i++) { vt_putch(' '); /* just do some spaces */ } break; case '\012': /* line feed */ CURSOR; /* kill the old cursor */ if(!vt_rawf) { /* do both cr and lf */ vt_col = 0; /* the cr part is easy */ } if(++vt_row >= MAXROW) {/* scroll it */ vt_row = MAXROW - 1; /* not so far please... */ vt_scroll(); } HOP; CURSOR; break; case '\015': /* carriage return */ CURSOR; /* kill the old cursor */ vt_col = 0; /* the cr part is easy */ if(!vt_rawf) { /* do both cr and lf */ if(++vt_row >= MAXROW) {/* scroll it */ vt_row = MAXROW - 1; /* not so far please... */ vt_scroll(); } } HOP; CURSOR; break; case '\033': /* escape */ vt_esc = EFIRST; /* ok - start an escape sequence */ break; /* the ones we don't handle come next */ case '\013': /* vertical tab */ case '\014': /* form feed */ default: break; } /* end of switch */ return; } escape_code(c) int c; { switch(vt_esc) { case EFIRST: switch(c) { case '[': vt_esc = EBRACKET; /* remember we saw it */ vt_inprog = vt_x = vt_y = 0; /* clear these */ break; default: vt_esc = ENONE; /* blew it */ break; } break; case EBRACKET: switch(c) { case 'H': /* home (or motion) */ vt_x = vt_inprog; /* column number */ vt_inprog = 0; /* adjust for brain damaged notion of screen starting at (1,1) */ if(--vt_x < 0) { vt_x = 0; } if(--vt_y < 0) { vt_y = 0; } CURSOR; /* take it away */ vt_row = vt_y; vt_col = vt_x; HOP; CURSOR; vt_esc = ENONE; /* code is complete */ break; case 'J': /* clear to end of screen */ if(vt_row + 1 < MAXROW) { /* something below us */ SAVE; vt_row++; /* drop down a row */ vt_col = 0; /* and over to the beginning */ HOP; /* actually move there */ BLANK(MAXCOL, MAXROW - vt_row); RESTORE; HOP; } /* fall through to clear the fractional part */ case 'K': /* clear to end of line */ BLANK(MAXCOL - vt_col, 1); /* erase all on the row */ CURSOR; /* replace the cursor */ vt_esc = ENONE; break; case '0': /* a row or column number */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': vt_inprog *= 10; /* make room */ vt_inprog += c - '0'; /* add in the new digit */ break; case ';': /* end of row number */ vt_y = vt_inprog; vt_inprog = 0; break; default: vt_esc = ENONE; /* blew it */ break; } break; default: vt_esc = ENONE; /* blew it */ break; } return; } /* low level reader - call this directly if you also want to use peek - that * way you get raw-mode and avoid all character buffers. But note - this code * does NOT initialize the world first. You must call vt_open() manually! */ vt_getch() { EventRecord x; int rc; SystemTask(); /* GetNextEvent returns a boolean enum - make it an integer */ while(GetNextEvent(autoKeyMask | keyDownMask, &x) == false) { SystemTask(); /* wait for it */ } rc = x.message & 0xff; if(x.modifiers & cmdKey) { /* it is a control character */ rc &= 0x1f; /* so fold it */ } if(!vt_rawf && rc == '\004') { /* cooked mode and a ^D spells EOF */ return(EOF); } return(rc); } /* return a character but don't pull it off the queue! Don't block if nothing is * available - just return a null. */ vt_peekch() { EventRecord x; int rc; SystemTask(); /* EventAvail returns a boolean enum - make it an integer */ if(EventAvail(autoKeyMask | keyDownMask, &x) == true) { /* something */ rc = x.message & 0xff; if(x.modifiers & cmdKey) { /* it is a control character */ rc &= 0x1f; /* so fold it */ } if(!vt_rawf && rc == '\004') { /* cooked mode and a ^D spells EOF */ return(EOF); } else { /* normal character */ return(rc); } /*NOTREACHED*/ } else { return(0); /* nothing - call it a null */ } /*NOTREACHED*/ } /* handle system requests for open, rename, and delete. Only open is legal. */ vt_faccess(fname, mode, perm) char *fname; int mode; int perm; { /* if we are not the correct driver, return and let the system try * another driver. */ if(EqualString(fname, "dev:console", false, true) == false) { return(-1); } /* this driver only handles opens */ if(mode == F_OPEN) { if(firsttime == 0) { /* only do it once */ vt_open(); firsttime++; } return(0); /* but always claim success */ } /* tell them the request is bogus */ return(0x40000016); } /* not much to do */ vt_close() { return(0); } /* return as many characters as asked for, up to a carriage return. Someday * we need to add raw mode here. */ vt_read(ap) IOSTR *ap; { /* fill the buffer if it is empty */ if(linesize <= 0) { linesize = vt_readline(linebuf, 256); lineptr = linebuf; } /* copy till either user is satisfied or we hit the end of the line. * We must leave the count field set in the ap structure to show how * much more is to be done. */ for( ; (ap->count > 0) && (linesize > 0); ap->count--, linesize--) { *(ap->buffer)++ = *lineptr++; } return(0); } /* read until a carriage return (or we fill the buffer). Return how much we * got. */ vt_readline(bp, size) char *bp; int size; { int i; for(i = size; i; bp++, i--) { *bp = vt_getch(); if(*bp == '\010' && i < size) { bp -= 2; i += 2; } if(*bp == '\004' || *bp == '\n') { i--; break; } } return(size - i); } /* loop characters to the screen */ vt_write(ap) IOSTR *ap; { for( ; ap->count; ap->count--) { /* must leave count at 0 on exit */ vt_putch(*(ap->buffer)++); } return(0); } /* this routine turns out to be essential because the system intends to ask us * for the optimum buffer size. We return -1 to tell it to choose for us. */ vt_ioctl(fd, op, arg) int fd; int op; int *arg; { switch(op) { case FIOINTERACTIVE: return(0); case TIOFLUSH: return(0); /* I don't trust this! We would have to clear screen and reset * point sizes for it to be safe. */ case TIOSPORT: /* vt = (WindowPtr) *arg; */ return(0); case TIOGPORT: *arg = (int) vt; return(0); default: return(-1); } /*NOTREACHED*/ } /* this stuff should be via ioctl but why make it so hard? */ vt_raw() { vt_rawf = 1; return; } vt_cooked() { vt_rawf = 0; return; } /* this code is a reverse compile of the cruntime.o module. */ /* write(fd, buf, cnt) int fd; char *buf; int cnt; { IOSTR *ind; int foo; if(fd < 0) { _uerror(0x16, 0); return(-1); } ind = _getIOPort(&fd); if(!ind) { return(-1); } if(!(ind->flags & 2)) { _uerror(0x09, 0); return(-1); } ind->count = cnt; ind->buffer = buf; foo = (*(ind->handler->l_write))(ind); if(foo) { _uerror(foo, ind->errcode); return(-1); } else { return(cnt - (ind->count)); } } */ /* this bypasses printf and is useful for debugging to avoid recursion. * Prints a string. */ /* lpr(s) char *s; { while(*s != 0) { vt_putch(*s++); } return; } */ /* this one prints an integer in hex with leading zeros. */ /* lpx(x) unsigned int x; { int i, j; for(i = 0; i < 8; i++) { j = (x >> (28 - (i * 4))) & 0xf; if(j <= 9) { vt_putch(j + '0'); } else { vt_putch(j - 10 + 'a'); } } return; } */ /* Start of code for da support. We use these for a gp text editor among * other things. Note - there is no way to pass the file name in. Oh * well. Most of this code is stolen from the sample application which * Apple distributes with MPW and hence is copyright Apple Computer Corp. */ /* * Resource ID constants. */ # define appleID 128 # define fileID 129 # define editID 130 # define appleMenu 0 # define fileMenu 1 # define quitCommand 1 # define editMenu 2 # define undoCommand 1 # define cutCommand 3 # define copyCommand 4 # define pasteCommand 5 # define clearCommand 6 # define menuCount 3 /* * HIWORD and LOWORD macros, for readability. */ # define HIWORD(aLong) (((aLong) >> 16) & 0xFFFF) # define LOWORD(aLong) ((aLong) & 0xFFFF) /* * Global Data objects, used by routines external to main(). */ MenuHandle MyMenus[menuCount]; /* The menu handles */ int Doneflag; /* Becomes TRUE when File/Quit chosen */ da_mode(fname) char *fname; { EventRecord myEvent; WindowPtr theActiveWindow, whichWindow; GrafPtr savePort; char forcedDA[256]; setupMenus(); for (Doneflag = 0; !Doneflag; ) { /* * Main Event tasks: */ SystemTask(); /* if a name was passed in, assume it is a DA - we fake an open * of it. DA's are funny in that the name contains a leading * NULL character. So we stick one in 'cause they are hard to * type. This results in a bizzare C string but the Desk Manager * is happy... */ if(fname[0] != '\0') { forcedDA[0] = '\0'; /* DA names (usually) have this */ strcpy(forcedDA + 1, fname); /* don't step on the null */ fname[0] = '\0'; /* open it only once */ GetPort(&savePort); (void) OpenDeskAcc(forcedDA); SetPort(savePort); continue; } theActiveWindow = FrontWindow(); /* Used often, avoid repeated calls */ /* * Handle the next event. */ if ( ! GetNextEvent(everyEvent, &myEvent)) { continue; /* not for us */ } /* * In the unlikely case that the active desk accessory does not * handle mouseDown, keyDown, or other events, GetNextEvent() will * give them to us! So before we perform actions on some events, * we check to see that the affected window in question is really * our window. */ switch (myEvent.what) { case mouseDown: switch (FindWindow(&myEvent.where, &whichWindow)) { case inSysWindow: SystemClick(&myEvent, whichWindow); break; case inMenuBar: doCommand(MenuSelect(&myEvent.where)); break; case inDrag: case inGrow: /* No such - Fall through */ case inContent: if (whichWindow != theActiveWindow) { SelectWindow(whichWindow); } break; default: break; }/*endsw FindWindow*/ break; case keyDown: case autoKey: if (vt == theActiveWindow) { if (myEvent.modifiers & cmdKey) { doCommand(MenuKey(myEvent.message & charCodeMask)); } } break; case activateEvt: if ((WindowPtr) myEvent.message == vt) { if (myEvent.modifiers & activeFlag) { DisableItem(MyMenus[editMenu], 0); } else { EnableItem(MyMenus[editMenu], 0); } DrawMenuBar(); } break; default: break; }/*endsw myEvent.what*/ }/*endfor Main Event loop*/ trashMenus(); return; } setupMenus() { register MenuHandle *pMenu; /* * Set up the desk accessories menu. * We install the desk accessory names from the 'DRVR' resources. */ MyMenus[appleMenu] = GetMenu(appleID); AddResMenu(MyMenus[appleMenu], (ResType) 'DRVR'); /* * Now the File and Edit menus. */ MyMenus[fileMenu] = GetMenu(fileID); MyMenus[editMenu] = GetMenu(editID); /* * Now insert all of the application menus in the menu bar. * * "Real" C programmers never use array indexes * unless they're constants :-) */ for (pMenu = &MyMenus[0]; pMenu < &MyMenus[menuCount]; ++pMenu) { InsertMenu(*pMenu, 0); } DisableItem(MyMenus[editMenu], 0); DrawMenuBar(); return; } trashMenus() { ClearMenuBar(); /* remove menus */ DrawMenuBar(); /* show it */ ReleaseResource(MyMenus[appleMenu]); ReleaseResource(MyMenus[fileMenu]); ReleaseResource(MyMenus[editMenu]); return; } /* * Process mouse clicks in menu bar */ doCommand(mResult) long mResult; { int theMenu, theItem; char daName[256]; GrafPtr savePort; theItem = LOWORD(mResult); theMenu = HIWORD(mResult); /* This is the resource ID */ switch (theMenu) { case appleID: GetItem(MyMenus[appleMenu], theItem, daName); GetPort(&savePort); (void) OpenDeskAcc(daName); SetPort(savePort); break; case fileID: switch (theItem) { case quitCommand: Doneflag++; /* Request exit */ break; default: break; } break; case editID: /* * If this is for a 'standard' edit item, * run it through SystemEdit. */ if (theItem <= clearCommand) { SystemEdit(theItem-1); } break; default: break; }/*endsw theMenu*/ HiliteMenu(0); return; }