/* * VMS terminal driver * * Uses SMG to lookup capabilities of the terminal (see SYS$SYSTEM:SMGTERMS.TXT * and SYS$SYSTEM:TERMTABLE.TXT) * * Originally written by Curtis Smith in 1987, this module has been rewritten * by Tom Dickey and Clark Morgan to: * * -- simplify it, * -- extend it to support a wider variety of terminal types, * -- add VT220 keyboard mappings, * -- support syntax highlighting, * -- support wide and narrow screen resolutions, * -- support visual bells. * * $Header: /usr/build/vile/vile/RCS/vmsvt.c,v 1.57 2004/10/29 21:16:10 tom Exp $ * */ #include "estruct.h" /* Emacs' structures */ #include "edef.h" /* Emacs' definitions */ #if DISP_VMSVT #include /* Descriptor definitions */ #include /* to get IO$_SENSEMODE */ #include /* to get TT$_UNKNOWN */ #include /* to get $VMS_STATUS_SUCCESS */ #include #include /* to get SMG$K_??? definitions */ #include #include /* function prototypes needed for the dispatch table */ static void vmsscrollregion(int top, int bot); static void vmsscroll_reg(int from, int to, int n); static void vmsscroll_delins(int from, int to, int n); /* SMG stuff (just like termcap) */ static int initialized; static int termtype; static char *tc_SO; /* begin standout (reverse) */ static char *tc_SE; /* end standout (reverse) */ static char *erase_to_end_line; static char *erase_whole_display; static char *delete_line; static char *insert_line; static char *scroll_forw; static char *scroll_back; static char *scroll_regn; static char *set_narrow; static char *set_wide; static long wide_cols; static long narrow_cols; #if OPT_VIDEO_ATTRS static char *tc_MD; /* begin bold */ static char *tc_ME; /* end bold */ static char *tc_US; /* begin underscore */ static char *tc_UE; /* end underscore */ #endif #if OPT_FLASH static char *dark_on; static char *dark_off; #endif /* *INDENT-OFF* */ struct vmskeyseqs { char *seq; int code; }; struct vmskeyseqs vt100seqs[] = { { "\033[A", KEY_Up }, { "\033[B", KEY_Down }, { "\033[C", KEY_Right }, { "\033[D", KEY_Left }, { "\033OP", KEY_KP_F1 }, { "\033OQ", KEY_KP_F2 }, { "\033OR", KEY_KP_F3 }, { "\033OS", KEY_KP_F4 }, /* these are transmitted by xterm and similar programs */ { "\033[11~", KEY_F1 }, { "\033[12~", KEY_F2 }, { "\033[13~", KEY_F3 }, { "\033[14~", KEY_F4 }, { "\033[15~", KEY_F5 }, /* vt220 keys (F1-F5 do not transmit control sequences) include vt100 */ { "\033[17~", KEY_F6 }, { "\033[18~", KEY_F7 }, { "\033[19~", KEY_F8 }, { "\033[20~", KEY_F9 }, { "\033[21~", KEY_F10 }, { "\033[23~", KEY_F11 }, { "\033[24~", KEY_F12 }, { "\033[25~", KEY_F13 }, { "\033[26~", KEY_F14 }, { "\033[28~", KEY_F15 }, { "\033[29~", KEY_F16 }, { "\033[31~", KEY_F17 }, { "\033[32~", KEY_F18 }, { "\033[33~", KEY_F19 }, { "\033[34~", KEY_F20 }, { "\033[3~", KEY_Delete }, /* aka "Remove" */ { "\033[6~", KEY_Next }, { "\033[5~", KEY_Prior }, /* aka "Prev" */ { "\033[1~", KEY_Find }, { "\033[2~", KEY_Insert }, { "\033[4~", KEY_Select }, /* 8-bit codes are the same, substituting the first two characters */ { "\233A", KEY_Up }, { "\233B", KEY_Down }, { "\233C", KEY_Right }, { "\233D", KEY_Left }, { "\217P", KEY_KP_F1 }, { "\217Q", KEY_KP_F2 }, { "\217R", KEY_KP_F3 }, { "\217S", KEY_KP_F4 }, /* these are transmitted by xterm */ { "\233[11~", KEY_F1 }, { "\233[12~", KEY_F2 }, { "\233[13~", KEY_F3 }, { "\233[14~", KEY_F4 }, { "\233[15~", KEY_F5 }, /* vt220 keys (F1-F5 do not transmit control sequences) include vt100 */ { "\23317~", KEY_F6 }, { "\23318~", KEY_F7 }, { "\23319~", KEY_F8 }, { "\23320~", KEY_F9 }, { "\23321~", KEY_F10 }, { "\23323~", KEY_F11 }, { "\23324~", KEY_F12 }, { "\23325~", KEY_F13 }, { "\23326~", KEY_F14 }, { "\23328~", KEY_F15 }, { "\23329~", KEY_F16 }, { "\23331~", KEY_F17 }, { "\23332~", KEY_F18 }, { "\23333~", KEY_F19 }, { "\23334~", KEY_F20 }, { "\2333~", KEY_Delete }, /* aka "Remove" */ { "\2336~", KEY_Next }, { "\2335~", KEY_Prior }, /* aka "Prev" */ { "\2331~", KEY_Find }, { "\2332~", KEY_Insert }, { "\2334~", KEY_Select }, }; struct vmskeyseqs vt52seqs[] = { { "\33A", KEY_Up }, { "\33B", KEY_Down }, { "\33C", KEY_Right }, { "\33D", KEY_Left }, { "\33P", KEY_KP_F1 }, { "\33Q", KEY_KP_F2 }, { "\33R", KEY_KP_F3 }, { "\33S", KEY_KP_F4 }, }; /* *INDENT-ON* */ /*** * ttputs - Send a string to ttputc * * Nothing returned ***/ static void ttputs(char *string) /* String to write */ { if (string) while (*string != EOS) ttputc(*string++); } /*** * vmsvt_tgoto - 2-argument request, e.g., for cursor movement. * * Nothing returned ***/ static void vmsvt_tgoto(request_code, parm1, parm2) { char buffer[32]; int ret_length; static int max_buffer_length = sizeof(buffer); static int arg_list[3] = {2}; register int i; if (!initialized) { printf("\n"); return; } /* Set the arguments into the arg_list array */ arg_list[1] = parm1; arg_list[2] = parm2; if (!$VMS_STATUS_SUCCESS(smg$get_term_data(&termtype, &request_code, &max_buffer_length, &ret_length, buffer, arg_list))) { ttputs("OOPS"); return; } else if (ret_length > 0) { char *cp = buffer; while (ret_length-- > 0) ttputc(*cp++); } } static void vmsmove(int row, int col) { vmsvt_tgoto(SMG$K_SET_CURSOR_ABS, row + 1, col + 1); } #if OPT_VIDEO_ATTRS static void vmsattr(UINT attr) { #define VA_SGR (VASEL|VAREV|VAUL|VAITAL|VABOLD) /* *INDENT-OFF* */ static const struct { char **start; char **end; UINT mask; } tbl[] = { { &tc_SO, &tc_SE, VASEL|VAREV }, { &tc_US, &tc_UE, VAUL }, { &tc_US, &tc_UE, VAITAL }, { &tc_MD, &tc_ME, VABOLD }, }; /* *INDENT-ON* */ static UINT last; attr = VATTRIB(attr); if (attr & VASPCOL) { attr = VCOLORATTR((VCOLORNUM(attr) & (NCOLORS - 1))); } else { attr &= ~(VAML | VAMLFOC); } if (attr != last) { register size_t n; register char *s; UINT diff = attr ^ last; int ends = FALSE; /* turn OFF old attributes */ for (n = 0; n < TABLESIZE(tbl); n++) { if ((tbl[n].mask & diff) != 0 && (tbl[n].mask & attr) == 0 && (s = *(tbl[n].end)) != 0) { ttputs(s); #if OPT_COLOR if (!ends) /* do this once */ reinitialize_colors(); #endif ends = TRUE; diff &= ~(tbl[n].mask); } } /* turn ON new attributes */ for (n = 0; n < TABLESIZE(tbl); n++) { if ((tbl[n].mask & diff) != 0 && (tbl[n].mask & attr) != 0 && (s = *(tbl[n].start)) != 0) { ttputs(s); diff &= ~(tbl[n].mask); } } if (tc_SO != 0 && tc_SE != 0) { if (ends && (attr & (VAREV | VASEL))) { ttputs(tc_SO); } else if (diff & VA_SGR) { /* we didn't find it */ ttputs(tc_SE); } } #if OPT_COLOR if (attr & VACOLOR) tcapfcol(VCOLORNUM(attr)); else if (given_fcolor != gfcolor) tcapfcol(gfcolor); #endif last = attr; } } #else /* highlighting is a minimum attribute */ /* * Change reverse video status. * FALSE = normal video, TRUE = reverse video */ static void tcaprev(UINT state) { static int revstate = -1; if (state == revstate) return; revstate = state; if (state) ttputs(tc_SO); else ttputs(tc_SE); } #endif /* OPT_VIDEO_ATTRS */ static void vmseeol(void) { ttputs(erase_to_end_line); } static void vmseeop(void) { ttputs(erase_whole_display); } static void cache_capabilities(void) { /* *INDENT-OFF* */ static struct caps_struct { long code; char **cap_string; } tbl[] = { { SMG$K_BEGIN_REVERSE, &tc_SO }, { SMG$K_END_REVERSE, &tc_SE }, #if OPT_VIDEO_ATTRS { SMG$K_BEGIN_BOLD, &tc_MD }, { SMG$K_END_BOLD, &tc_ME }, { SMG$K_BEGIN_UNDERSCORE, &tc_US }, { SMG$K_END_UNDERSCORE, &tc_UE }, #endif #if OPT_FLASH { SMG$K_DARK_SCREEN, &dark_on }, { SMG$K_LIGHT_SCREEN, &dark_off }, #endif { SMG$K_ERASE_TO_END_LINE, &erase_to_end_line }, { SMG$K_ERASE_WHOLE_DISPLAY, &erase_whole_display }, { SMG$K_INSERT_LINE, &insert_line }, /* al */ { SMG$K_DELETE_LINE, &delete_line }, /* dl */ { SMG$K_SCROLL_FORWARD, &scroll_forw }, /* SF */ { SMG$K_SCROLL_REVERSE, &scroll_back }, /* SR */ { SMG$K_SET_SCROLL_REGION, &scroll_regn }, /* CS */ { SMG$K_WIDTH_NARROW, &set_narrow }, /* 80 columns */ { SMG$K_WIDTH_WIDE, &set_wide }, /* 132 columns */ }; /* *INDENT-ON* */ char *buf; struct caps_struct *csp; int i, buf_len, ret_len; long rqst_code; static char storage[1024]; for (i = 0, buf = storage, csp = tbl; i < TABLESIZE(tbl); i++, csp++) { buf_len = (storage + sizeof(storage)) - buf; if (!$VMS_STATUS_SUCCESS(smg$get_term_data(&termtype, &csp->code, &buf_len, &ret_len, buf)) || ret_len == 0) { *(csp->cap_string) = NULL; } else { buf[ret_len] = 0; *(csp->cap_string) = buf; buf += ret_len + 1; } } rqst_code = SMG$K_COLUMNS; if (!$VMS_STATUS_SUCCESS(smg$get_numeric_data(&termtype, &rqst_code, &narrow_cols))) { narrow_cols = 80; } rqst_code = SMG$K_WIDE_SCREEN_COLUMNS; if (!$VMS_STATUS_SUCCESS(smg$get_numeric_data(&termtype, &rqst_code, &wide_cols))) { wide_cols = 132; } term.maxcols = wide_cols; } /** I/O information block definitions **/ struct iosb { /* I/O status block */ short i_cond; /* Condition value */ short i_xfer; /* Transfer count */ long i_info; /* Device information */ }; struct termchar { /* Terminal characteristics */ char t_class; /* Terminal class */ char t_type; /* Terminal type */ short t_width; /* Terminal width in characters */ long t_mandl; /* Terminal's mode and length */ long t_extend; /* Extended terminal characteristics */ }; static struct termchar tc; /* Terminal characteristics */ static void get_terminal_type(void) { short fd; int status, deassign_status; struct iosb iostatus; $DESCRIPTOR(devnam, "SYS$COMMAND"); /* Assign input to a channel */ if (!$VMS_STATUS_SUCCESS(status = sys$assign(&devnam, &fd, 0, 0))) tidy_exit(status); /* Get terminal characteristics */ status = sys$qiow(0, /* Wait on event flag zero */ fd, /* Channel to input terminal */ IO$_SENSEMODE, /* Get current characteristic */ &iostatus, /* Status after operation */ 0, 0, /* No AST service */ &tc, /* Terminal characteristics buf */ sizeof(tc), /* Size of the buffer */ 0, 0, 0, 0); /* P3-P6 unused */ /* De-assign the input device */ if (!$VMS_STATUS_SUCCESS(deassign_status = sys$dassgn(fd))) tidy_exit(deassign_status); /* Jump out if bad qiow status */ if (!$VMS_STATUS_SUCCESS(status)) tidy_exit(status); if ((iostatus.i_cond & 1) == 0) tidy_exit(iostatus.i_cond); } static void vmsopen(void) { int i, keyseq_tablesize; struct vmskeyseqs *keyseqs; get_terminal_type(); if (tc.t_type == TT$_UNKNOWN) { printf("Terminal type is unknown!\n"); printf("Set your terminal type using $ SET TERMINAL/INQUIRE\n"); tidy_exit(3); } if (tc.t_type != TT$_VT52) { keyseqs = vt100seqs; keyseq_tablesize = TABLESIZE(vt100seqs); } else { keyseqs = vt52seqs; keyseq_tablesize = TABLESIZE(vt52seqs); } /* Access the system terminal definition table for the */ /* information of the terminal type returned by IO$_SENSEMODE */ if (!$VMS_STATUS_SUCCESS(smg$init_term_table_by_type(&tc.t_type, &termtype))) return; /* Set sizes */ term.rows = ((UINT) tc.t_mandl >> 24); term.cols = tc.t_width; if (term.maxrows < term.rows) term.maxrows = term.rows; if (term.maxcols < term.cols) term.maxcols = term.cols; cache_capabilities(); revexist = (tc_SO != NULL && tc_SE != NULL); eolexist = erase_whole_display != NULL; /* * I tried 'vmsgetstr()' for a VT100 terminal and it had no codes * for insert_line or for delete_line. (Have to work up a test for * that) - TD */ if (scroll_regn && scroll_back) { if (scroll_forw == NULL) /* assume '\n' scrolls forward */ scroll_forw = "\n"; term.scroll = vmsscroll_reg; } else if (delete_line && insert_line) term.scroll = vmsscroll_delins; else term.scroll = nullterm_scroll; /* Set resolution */ (void) strcpy(screen_desc, (term.cols == narrow_cols) ? "NORMAL" : "WIDE"); /* Open terminal I/O drivers */ ttopen(); /* Set predefined keys */ for (i = keyseq_tablesize; i--;) addtosysmap(keyseqs[i].seq, strlen(keyseqs[i].seq), keyseqs[i].code); initialized = TRUE; } /* copied/adapted from 'tcap.c' 19-apr-1993 dickey@software.org */ /* move howmany lines starting at from to to */ static void vmsscroll_reg(int from, int to, int n) { int i; if (to == from) return; if (to < from) { vmsscrollregion(to, from + n - 1); vmsmove(from + n - 1, 0); for (i = from - to; i > 0; i--) ttputs(scroll_forw); } else { /* from < to */ vmsscrollregion(from, to + n - 1); vmsmove(from, 0); for (i = to - from; i > 0; i--) ttputs(scroll_back); } vmsscrollregion(0, term.rows - 1); } /* OPT_PRETTIER_SCROLL is prettier but slower -- it scrolls a line at a time instead of all at once. */ /* move howmany lines starting at from to to */ static void vmsscroll_delins(int from, int to, int n) { int i; if (to == from) return; /* patch: should make this more like 'tcap.c', or merge logic somehow */ #if OPT_PRETTIER_SCROLL if (absol(from - to) > 1) { vmsscroll_delins(from, (from < to) ? to - 1 : to + 1, n); if (from < to) from = to - 1; else from = to + 1; } #endif if (to < from) { vmsmove(to, 0); for (i = from - to; i > 0; i--) ttputs(delete_line); vmsmove(to + n, 0); for (i = from - to; i > 0; i--) ttputs(insert_line); } else { vmsmove(from + n, 0); for (i = to - from; i > 0; i--) ttputs(delete_line); vmsmove(from, 0); for (i = to - from; i > 0; i--) ttputs(insert_line); } } /* cs is set up just like cm, so we use tgoto... */ static void vmsscrollregion(int top, int bot) { vmsvt_tgoto(SMG$K_SET_SCROLL_REGION, top + 1, bot + 1); } /*** * vmscres - Change screen resolution * * support these values: WIDE -> 132 columns, NORMAL -> 80 columns * * T -> if resolution successfully changed, F otherwise. ***/ static int vmscres(const char *res) { char buf[NLINE]; int rc = FALSE; if (tc.t_type == TT$_VT52) { mlforce("[sres not supported for VT52-style terminals]"); return (rc); } strcpy(buf, res); mkupper(buf); if (strcmp(buf, "WIDE") == 0 && set_wide != 0) { ttputs(set_wide); term.cols = wide_cols; rc = TRUE; } else if (strcmp(buf, "NORMAL") == 0 && set_narrow != 0) { ttputs(set_narrow); term.cols = narrow_cols; rc = TRUE; } else mlforce("[invalid sres value (use NORMAL or WIDE)]"); if (rc) newwidth(1, term.cols); return (rc); } static void vmsclose(void) { if (tc.t_type != TT$_VT52) { /* * Restore terminal width to previous state (if necessary) and then * cleanup as usual. */ if (tc.t_width != term.cols) vmscres((tc.t_width == narrow_cols) ? "NORMAL" : "WIDE"); } ttclose(); } /*** * vmsbeep - Ring the bell * * Nothing returned ***/ static void vmsbeep(void) { #if OPT_FLASH int hit = 0; if (global_g_val(GMDFLASH) && dark_off != NULL && dark_on != NULL) { hit = 1; ttputs(dark_off); term.flush(); catnap(200, FALSE); ttputs(dark_on); } if (!hit && tc.t_type != TT$_VT52) { /* *INDENT-OFF* */ static char *seq[][2] = { { NULL, NULL }, /* vtflash = off */ { VTFLASH_NORMSEQ, VTFLASH_REVSEQ }, /* reverse */ { VTFLASH_REVSEQ, VTFLASH_NORMSEQ }, /* normal */ }; /* *INDENT-ON* */ char *str1, *str2; int val; val = global_g_val(GVAL_VTFLASH); str1 = seq[val][0]; if (str1) { str2 = seq[val][1]; ttputs(str1); term.flush(); catnap(200, FALSE); ttputs(str2); hit = 1; } } if (!hit) #endif ttputc(BEL); } /* Dispatch table. All hard fields just point into the terminal I/O code. */ TERM term = { 24, /* Max number of rows allowable */ /* Filled in */ 0, /* Current number of rows used */ 132, /* Max number of columns */ /* Filled in */ 0, /* Current number of columns */ 100, /* # times thru update to pause */ vmsopen, /* Open terminal at the start */ vmsclose, /* Close terminal at end */ nullterm_kopen, /* Open keyboard */ nullterm_kclose, /* Close keyboard */ ttgetc, /* Get character from keyboard */ ttputc, /* Put character to display */ tttypahead, /* char ready for reading */ ttflush, /* Flush output buffers */ vmsmove, /* Move cursor, origin 0 */ vmseeol, /* Erase to end of line */ vmseeop, /* Erase to end of page */ vmsbeep, /* Beep */ #if OPT_VIDEO_ATTRS vmsattr, /* Set attribute video state */ #else vmsrev, /* Set reverse video state */ #endif vmscres, /* Change screen resolution */ nullterm_setfore, /* N/A: Set foreground color */ nullterm_setback, /* N/A: Set background color */ nullterm_setpal, /* N/A: Set palette colors */ nullterm_setccol, nullterm_scroll, /* set at init-time */ nullterm_pflush, nullterm_icursor, nullterm_settitle, nullterm_watchfd, nullterm_unwatchfd, nullterm_cursorvis, }; #endif