/* * screen.c * * Written By Matthew Green, based on window.c by Michael Sandrof * Copyright(c) 1993 Matthew Green * Significant modifications by Jeremy Nelson * Copyright 1997 EPIC Software Labs, * Significant additions by J. Kean Johnston * Copyright 1998 J. Kean Johnston, used with permission * Modifications Copyright Colten Edwards 1997-1998 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT */ #include "irc.h" static char cvsrevision[] = "$Id: screen.c,v 1.1.1.3 2003/07/31 07:01:08 root Exp $"; CVS_REVISION(screen_c) #include "struct.h" #include "commands.h" #include "screen.h" #include "status.h" #include "window.h" #include "output.h" #include "vars.h" #include "server.h" #include "list.h" #include "ircterm.h" #include "names.h" #include "ircaux.h" #include "input.h" #include "log.h" #include "hook.h" #include "dcc.h" #include "exec.h" #include "newio.h" #include "misc.h" #include "cset.h" #include "tcl_bx.h" #include "gui.h" #define MAIN_SOURCE #include "modval.h" #ifdef TRANSLATE #include "translat.h" #endif #ifdef WANT_HEBREW #include "hebrew.h" #endif Screen *main_screen = NULL; Screen *last_input_screen = NULL; Screen *output_screen = NULL; Window *debugging_window = NULL; int extended_handled = 0; extern int in_add_to_tcl; extern int foreground; /* Our list of screens */ Screen *screen_list = NULL; int strip_ansi_never_xlate = 0; /* * * * * * * * * OUTPUT CHAIN * * * * * * * * * * * * * * * * * * * * * * * Entry is to say(), output(), yell(), put_it(), etc. * ------- They call add_to_screen() * ------- Which calls add_to_window() * ------- Which calls split_up_line() * ------- And then rite() * ------- Which calls scroll_window() * ------- And output_line() * ------- Which calls term_putchar(). * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ u_char **BX_prepare_display (const u_char *, int, int *, int); static int add_to_display_list (Window *, const unsigned char *); Display *new_display_line (Display *); void BX_add_to_window (Window *, const unsigned char *); static char display_standout (int flag); static char display_bold (int flag); static char display_blink (int flag); static char display_underline (int flag); static int display_color (long color1, long color2); static void display_normal (void); static char display_altcharset (int flag); static char *replace_color (int, int); const u_char *BX_skip_ctl_c_seq (const u_char *start, int *lhs, int *rhs, int proper); unsigned char *BX_skip_incoming_mirc(unsigned char *text) { int i, x; for (i=0;i= cnum) text[0][i] = text[0][i+1]; text[0][i] = 0; } /* * add_to_screen: This adds the given null terminated buffer to the screen. * That is, it determines which window the information should go to, which * lastlog the information should be added to, which log the information * should be sent to, etc */ void BX_add_to_screen(unsigned char *buffer) { char *out = NULL; if (!get_int_var(MIRCS_VAR)) buffer = skip_incoming_mirc(buffer); out = buffer; if (in_window_command) { in_window_command = 0; update_all_windows(); in_window_command = 1; } if (!current_window) { #ifdef GUI gui_puts(out); #else puts(out); #endif return; } #ifdef WANT_TCL if (in_add_to_tcl) { add_to_tcl(current_window, out); return; } #endif if (dumb_mode) { add_to_lastlog(current_window, out); if (do_hook(WINDOW_LIST, "%u %s", current_window->refnum, out)) puts(out); fflush(stdout); #ifdef GUI gui_flush(); #endif return; } if (target_window) { target_window->output_func(target_window, out); #ifdef GUI gui_activity(COLOR_ACTIVE); #endif } else current_window->output_func(current_window, out); } /* * add_to_window: adds the given string to the display. No kidding. This * routine handles the whole ball of wax. It keeps track of what's on the * screen, does the scrolling, everything... well, not quite everything... * The CONTINUED_LINE idea thanks to Jan L. Peterson (jlp@hamblin.byu.edu) * * At least it used to. Now most of this is done by split_up_line, and this * function just dumps it onto the screen. This is because the scrollback * functions need to be able to figure out how to split things up too. */ void BX_add_to_window(Window *window, const unsigned char *str) { if (window->server >= 0 && get_server_redirect(window->server)) redirect_text(window->server, get_server_redirect(window->server), str, NULL, 0, 0); if (do_hook(WINDOW_LIST, "%u %s", window->refnum, str)) { unsigned char **lines; int cols; add_to_log(window->log_fp, 0, str, window->mangler); add_to_lastlog(window, str); display_standout(OFF); display_bold(OFF); if (window->screen) cols = window->screen->co; else cols = window->columns; for (lines = split_up_line(str, cols/* + 1*/); *lines; lines++) { if (add_to_display_list(window, *lines)) rite(window, *lines); } term_flush(); /* * This used to be in rite(), but since rite() is a general * purpose function, and this stuff really is only intended * to hook on "new" output, it really makes more sense to do * this here. This also avoids the terrible problem of * recursive calls to split_up_line, which are bad. */ if (!window->visible) { /* * This is for archon -- he wanted a way to have * a hidden window always beep, even if BEEP is off. */ if (window->beep_always && strchr(str, '\007')) { Window *old_target_window = target_window; target_window = current_window; term_beep(); say("Beep in window %d", window->refnum); target_window = old_target_window; } /* * Tell some visible window about our problems * if the user wants us to. */ if (!(window->miscflags & WINDOW_NOTIFIED) && who_level & window->notify_level) { window->miscflags |= WINDOW_NOTIFIED; if (window->miscflags & WINDOW_NOTIFY) { Window *old_target_window = target_window; int lastlog_level; lastlog_level = set_lastlog_msg_level(LOG_CRAP); target_window = current_window; say("Activity in window %d", window->refnum); target_window = old_target_window; set_lastlog_msg_level(lastlog_level); } update_all_status(current_window, NULL, 0); } } cursor_in_display(window); } } u_char **BX_split_up_line (const unsigned char *str, int max_cols) { int nl = 0; return prepare_display(str, max_cols, &nl, 0); } /* * Given an input string, prepare it for display. This involves several * phases. We parse through the original string and prepare data in * a list of strings, which are returned as this functions return value. * During the parsing of the string, we adhere to the maximum width of * the string, and to the user variables CONTINUED_LINE and INDENT. * When calculating the width of the string, we do not take into account * "invisible" characters, such as color and attribute setting characters. * * If we encounter an ANSI escape sequence starting, we decide how to * handle it. If COLOR_MODE is 0, we silently drop all ANSI characters. * if it is 1, we convert ANSI escape sequences which set color to the * sequences required by the current terminal. If mode is 2, we leave these * escape sequences alone and pass them through. * * Next, if we encounter an upper ASCII character, or a lower ASCII character * that is not a format-specific character, we check the value of the user * variable DISPLAY_PC_CHARACTERS. This variable is used as follows: * 0 - ignore the character completely. * 1 - if the terminal has a specific escape sequence for displaying PC * ROM characters, use that sequence for displaying the character. * Otherwise, display the character in inverse. If the character is * >= 128, display it in bold inverse. * 2 - if the terminal has a specific escape sequence for displaying * PC ROM characters, display the character with that sequence, otherwise * strip the character from the output string completely. * 3 - insert the character exactly as is, and reply on the users terminal * displaying it correctly as a graphics character (pass-through mode). * * If we detect the beginning of a Ctrl-C color sequence, we pick up the * color values, and then examine the user variable COLOR_CONTROL_C. * If this value is 0, we ignore the sequences completely, stripping them * from the output string. If it is 1, and our terminal supports color, * replace the color sequence with the terminal's color escape sequence. * If the terminal does not support color, strip them from the output * string. If the value is 2, convert these escape sequences explicitly * into ANSI escape sequences. * * While we are preparing the output strings, we take special care not to * extend the maximum column width of the output display. We will return * as many strings as we need to in order to display the string, and we * will wrap as intelligently as we know how. * * This function can also be run in several ways, and the mode parameter * controls this. In the default mode (mode 0) we preserve ^B, ^C, ^F, * ^I, ^V and ^_ characters in the string. This assumes that some other * function will be doing the output interpretation of the strings. * In this mode, depending on the conversion behaviour defined above, * we will always convert ANSI color escape sequences to ^C sequences, * and we will introduce a new sequence, ^R, for displaying PC ROM * characters. ^R is always followed by 3 decimal digits, ALWAYS. * Of course, if pass-through is in effect for graphics characters, * the raw characters are displayed. To support all of this lot, we * provide another function, display_final, which will actually insert * all of the right codes for the colors, highlights etc. * * In mode 1, we prepare a string which is capable of being sent directly * to the display, with all formatting in place. Thus the string can be * sent with a single fputs(), which is much quicker than the looping * one which itteratively does a term_putchar(). * * It may be a good idea to allow users to control this behaviour, possibly * by introducing a new variable, DISPLAY_MODE. 0 is the traditional way * and 1 is the quick way. Anyway, we dont care at this level, as we are * simply preparing the strings here. * * Errm ... oh yes, and since this caught me by surprise and caused many a * minutes worth of hair pulling, dont forget we need to keep count of * PRINTED Characters not buffer positions :-) */ #define SPLIT_EXTENT 40 unsigned char **BX_prepare_display(const unsigned char *orig_str, int max_cols, int *lused, int flags) { int gchar_mode; static int recursion = 0, output_size = 0; int pos = 0, /* Current position in "buffer" */ col = 0, /* Current column in display */ word_break = 0, /* Last end of word */ indent = 0, /* Start of second word */ firstwb = 0, beep_cnt = 0, /* Number of beeps */ beep_max, /* Maximum number of beeps */ tab_cnt = 0, /* TAB counter */ tab_max, /* Maximum number of tabs */ nds_count = 0, nds_max, line = 0, /* Current pos in "output" */ len, i, /* Used for counting tabs */ do_indent, /* Use indent or continued line? */ in_rev = 0, /* Are we in reverse mode? */ newline = 0; /* Number of newlines */ static u_char **output = NULL; const u_char *ptr = NULL; u_char buffer[BIG_BUFFER_SIZE + 1], *cont_ptr = NULL, *cont = empty_string, c, *words = NULL, *str = NULL; if (recursion) ircpanic("prepare_display() called recursively"); recursion++; gchar_mode = get_int_var(DISPLAY_PC_CHARACTERS_VAR); beep_max = get_int_var(BEEP_VAR)? get_int_var(BEEP_MAX_VAR) : -1; tab_max = get_int_var(TAB_VAR) ? get_int_var(TAB_MAX_VAR) : -1; nds_max = get_int_var(ND_SPACE_MAX_VAR); do_indent = get_int_var(INDENT_VAR); words = (char *)get_string_var(WORD_BREAK_VAR); if (!words) words = ", "; if (!(cont_ptr = (char *)get_string_var(CONTINUED_LINE_VAR))) cont_ptr = (char *)empty_string; buffer[0] = 0; /* Handle blank or non-existant lines */ if (!orig_str || !orig_str[0]) orig_str = space; if (!output_size) { int new_i = SPLIT_EXTENT; RESIZE(output, char *, new_i); while (output_size < new_i) output[output_size++] = 0; } /* * Before we do anything else, we convert the string to a normalized * string. Until such time as I can do this all properly, this is * the best effort we can make. The ultimate goal is to have the * ANSI stripper / converter split everything into multiple lines, * which will decrease the amounT of time spent calculating split * lines. For now though, we simply "normalize" the line before we * start working with it. This will convert ANSI escape sequences * into easy-to-work-with Ctrl-C sequences, or IRCI-II formatting * characters. * The stripper is a separate function for right now to make * debugging easier. This code will be improved, this is proof-of-concept * code only, right now. */ str = strip_ansi (orig_str); #ifdef WANT_HEBREW /* * crisk: Hebrew processing the line. * */ if (get_int_var(HEBREW_TOGGLE_VAR)) hebrew_process(str); #endif /* * Start walking through the entire string. */ for (ptr = str; *ptr && (pos < BIG_BUFFER_SIZE - 8); ptr++) { switch (*ptr) { case '\007': /* bell */ { beep_cnt++; if ((beep_max == -1) || (beep_cnt > beep_max)) { if (!in_rev) buffer[pos++] = REV_TOG; buffer[pos++] = (*ptr & 127) | 64; if (!in_rev) buffer[pos++] = REV_TOG; col++; } else buffer[pos++] = *ptr; break; /* case '\a' */ } case '\011': /* TAB */ { tab_cnt++; if ((tab_max > 0) && (tab_cnt > tab_max)) { if (!in_rev) buffer[pos++] = REV_TOG; buffer[pos++] = (*ptr & 0x7f) | 64; if (!in_rev) buffer[pos++] = REV_TOG; col++; } else { if (indent == 0) indent = -1; word_break = pos; /* Only go as far as the edge of the screen */ len = 8 - (col % 8); for (i = 0; i < len; i++) { buffer[pos++] = ' '; if (col++ >= max_cols) break; } } break; /* case '\011' */ } case ND_SPACE: { nds_count++; /* * Just swallop up any ND's over the max */ if ((nds_max > 0) && (nds_count > nds_max)) ; else buffer[pos++] = ND_SPACE; break; } case '\n': /* Forced newline */ { newline = 1; if (indent == 0) indent = -1; word_break = pos; break; /* case '\n' */ } case '\003': { int lhs = 0, rhs = 0; const u_char *end = skip_ctl_c_seq(ptr, &lhs, &rhs, 0); while (ptr < end) buffer[pos++] = *ptr++; ptr = end - 1; break; /* case '\003' */ } case ROM_CHAR: { /* * Copy the \r and the first three digits that * are after it. Careful! There may not be * three digits after it! If there arent 3 * chars, we fake it with zeros. This is the * wrong thing, but its better than crashing. */ buffer[pos++] = *ptr++; /* Copy the \R ... */ if (*ptr) buffer[pos++] = *ptr++; else buffer[pos++] = '0'; if (*ptr) buffer[pos++] = *ptr++; else buffer[pos++] = '0'; if (*ptr) buffer[pos++] = *ptr; else buffer[pos++] = '0'; col++; /* This is a printable */ break; /* case ROM_CHAR */ } case UND_TOG: case ALL_OFF: case REV_TOG: case BOLD_TOG: case BLINK_TOG: case ALT_TOG: { buffer[pos++] = *ptr; if (*ptr == ALL_OFF) in_rev = 0; else if (*ptr == REV_TOG) in_rev = !in_rev; break; } default: { if (*ptr == ' ' || strchr(words, *ptr)) { if (indent == 0) { indent = -1; firstwb = pos; } if (*ptr == ' ') word_break = pos; if (*ptr != ' ' && ptr[1] && (col + 1 < max_cols)) word_break = pos + 1; buffer[pos++] = *ptr; } else { if (indent == -1) indent = col; buffer[pos++] = *ptr; } col++; break; } } /* End of switch (*ptr) */ /* * Must check for cols >= maxcols+1 becuase we can have a * character on the extreme screen edge, and we would still * want to treat this exactly as 1 line, and cols has already * been incremented. */ if ((col >= max_cols) || newline) { unsigned char *pos_copy; if (!word_break || (flags & PREPARE_NOWRAP)) word_break = max_cols /*pos - 1*/; else if (col > max_cols) word_break = pos - 1; /* * XXXX Massive hackwork here. * * Due to some ... interesting design considerations, * if you have /set indent on and your first line has * exactly one word seperation in it, then obviously * there is a really long "word" to the right of the * first word. Normally, we would just break the * line after the first word and then plop the really * big word down to the second line. Only problem is * that the (now) second line needs to be broken right * there, and we chew up (and lose) a character going * through the parsing loop before we notice this. * Not good. It seems that in this very rare case, * people would rather not have the really long word * be sent to the second line, but rather included on * the first line (it does look better this way), * and so we can detect this condition here, without * losing a char but this really is just a hack when * it all comes down to it. Good thing its cheap. ;-) */ if (!*cont && (firstwb == word_break) && do_indent) word_break = pos; /* * If we are approaching the number of lines that * we have space for, then resize the master line * buffer so we dont run out. */ if (line >= output_size - 3) { int new_i = output_size + SPLIT_EXTENT; RESIZE(output, char *, new_i); while (output_size < new_i) output[output_size++] = 0; } c = buffer[word_break]; buffer[word_break] = 0; malloc_strcpy((char **)&(output[line++]), buffer); buffer[word_break] = c; if (!*cont && do_indent && (indent < (max_cols / 3)) && (strlen(cont_ptr) < indent)) { cont = alloca(indent+10); sprintf(cont, "%-*s", indent, cont_ptr); } else if (!*cont && *cont_ptr) cont = cont_ptr; while (word_break < pos && buffer[word_break] == ' ') word_break++; buffer[pos] = 0; pos_copy = alloca(strlen(buffer) + strlen(cont) + 20); strcpy(pos_copy, buffer+word_break); strcpy (buffer, cont); strcat (buffer, pos_copy); col = pos = strlen(buffer); word_break = 0; newline = 0; /* Only allow N lines... */ if (*lused && line >= *lused) { *buffer = 0; break; } } } /* End of (ptr = str; *ptr && (pos < BIG_BUFFER_SIZE - 8); ptr++) */ buffer[pos++] = ALL_OFF; buffer[pos] = 0; if (*buffer) malloc_strcpy((char **)&(output[line++]),buffer); recursion--; new_free(&output[line]); *lused = line-1; new_free(&str); return output; } /* * This puts the given string into a scratch window. It ALWAYS suppresses * any further action (by returning a FAIL, so rite() is not called). */ static int add_to_scratch_window_display_list (Window *window, const unsigned char *str) { Display *my_line, *my_line_prev; int cnt; if (window->scratch_line == -1) ircpanic("This is not a scratch window."); if (window->scratch_line > window->display_size) ircpanic("scratch_line is too big."); my_line = window->top_of_display; my_line_prev = NULL; for (cnt = 0; cnt < window->scratch_line; cnt++) { if (my_line == window->display_ip) { if (my_line_prev) { my_line_prev->next = new_display_line(my_line_prev); my_line = my_line_prev->next; my_line->next = window->display_ip; window->display_ip->prev = my_line; window->display_buffer_size++; } else { my_line = new_display_line(NULL); window->top_of_display = my_line; window->top_of_scrollback = my_line; my_line->next = window->display_ip; window->display_ip->prev = my_line; } } my_line_prev = my_line; my_line = my_line->next; } /* XXXX DO this right!!!! XXXX */ if (my_line == window->display_ip) { my_line = new_display_line(window->display_ip->prev); if (window->display_ip->prev) window->display_ip->prev->next = my_line; my_line->next = window->display_ip; window->display_ip->prev = my_line; window->display_buffer_size++; } malloc_strcpy(&my_line->line, str); window->cursor = window->scratch_line; window->scratch_line++; if (window->scratch_line >= window->display_size) window->scratch_line = 0; /* Just go back to top */ if (window->noscroll) { if (window->scratch_line == 0) my_line = window->top_of_display; else if (my_line->next != window->display_ip) my_line = my_line->next; else my_line = NULL; if (my_line) malloc_strcpy(&my_line->line, empty_string); } /* * Make sure the cursor ends up in the right spot */ if (window->visible) { window->screen->cursor_window = window; term_move_cursor(0, window->top + window->cursor); term_clear_to_eol(); output_line(str); display_standout(OFF); if (window->noscroll) { term_move_cursor(0, window->top + window->scratch_line); term_clear_to_eol(); } } window->cursor = window->scratch_line; return 0; /* Express a failure */ } /* * This function adds an item to the window's display list. If the item * should be displayed on the screen, then 1 is returned. If the item is * not to be displayed, then 0 is returned. This function handles all * the hold_mode stuff. */ static int add_to_display_list (Window *window, const unsigned char *str) { if (window->scratch_line != -1) return add_to_scratch_window_display_list(window, str); /* Add to the display list */ window->display_ip->next = new_display_line(window->display_ip); malloc_strcpy(&window->display_ip->line, str); window->display_ip = window->display_ip->next; window->display_buffer_size++; window->distance_from_display++; /* * Only output the line if hold mode isnt activated */ if (((window->distance_from_display > window->display_size) && window->scrollback_point) || (window->hold_mode && (++window->held_displayed > window->display_size)) || (window->holding_something && window->lines_held)) { if (!window->holding_something) window->holding_something = 1; window->lines_held++; if (window->lines_held >= window->last_lines_held + 10) { update_window_status(window, 0); window->last_lines_held = window->lines_held; } return 0; } if (window->in_more && window->hold_mode && window->last_lines_held && !window->lines_held && !window->holding_something && window->held_displayed) { /* if we get here, it should mean that no more lastlog more * is possible, reset the values. */ reset_hold_mode(window); } /* * Before outputting the line, we snip back the top of the * display buffer. (The fact that we do this only when we get * to here is what keeps whats currently on the window always * active -- once we get out of hold mode or scrollback mode, then * we truncate the display buffer at that point.) */ while (window->display_buffer_size > window->display_buffer_max) { Display *next = window->top_of_scrollback->next; delete_display_line(window->top_of_scrollback); window->top_of_scrollback = next; window->display_buffer_size--; } /* * And tell them its ok to output this line. */ return 1; } /* * rite: this routine displays a line to the screen adding bold facing when * specified by ^Bs, etc. It also does handle scrolling and paging, if * SCROLL is on, and HOLD_MODE is on, etc. This routine assumes that str * already fits on one screen line. If show is true, str is displayed * regardless of the hold mode state. If redraw is true, it is assumed we a * redrawing the screen from the display_ip list, and we should not add what * we are displaying back to the display_ip list again. * * Note that rite sets display_highlight() to what it was at then end of the * last rite(). Also, before returning, it sets display_highlight() to OFF. * This way, between susequent rites(), you can be assured that the state of * bold face will remain the same and the it won't interfere with anything * else (i.e. status line, input line). */ int rite(Window *window, const unsigned char *str) { static int high = OFF; static int bold = OFF; static int undl = OFF; static int blink = OFF; static int altc = OFF; #ifdef GUI gui_flush(); #endif output_screen = window->screen; scroll_window(window); if (window->visible && foreground && window->display_size) { #if 1 display_standout(high); display_bold(bold); display_blink(blink); display_underline(undl); display_altcharset(altc); tputs_x(replace_color(-2, -2)); /* Reinstate color */ output_line(str); high = display_standout(OFF); bold = display_bold(OFF); undl = display_underline(OFF); blink = display_blink(OFF); altc = display_altcharset(OFF); #else tputs_x(replace_color(-2,-2)); output_line(str); #endif term_newline(); term_cr(); } window->cursor++; return 0; } /* * A temporary wrapper function for backwards compatibility. */ int BX_output_line(const unsigned char *str) { output_with_count(str,1, 1); return 0; } /* * NOTE: When we output colors, we explicitly turn off bold and reverse, * as they can affect the display of the colors. We turn them back on * afterwards, though. We dont need to worry about blinking or underline * as they dont affect the colors. But reverse and bold do, so we need to * make sure that the color sequence has preference, rather than the previous * IRC-II formatting colors. * * OKAY ... just how pedantic do you wanna get? We have a problem with * colorization, and its mostly to do with how ANSI is interpreted by * different systems. Both Linux and SCO consoles do the right thing, * but termcap lacks the ability to turn off blinking and underline * as individual sequences, so the "normal" sequence is sent. This causes * things such as reverse video, bold, underline, blinking etc to be turned * off. So, we have to keep track of our current set of attributes always. * Yes, this adds a little bit of processing to the loop, but this has already * been optimized to be faster than the previous mechanism, and the cycles * are so few, it hardly matters. I think it is more important to get the * correct rendering of colors than to get just one more nanosecond of * display speed. */ int BX_output_with_count(const unsigned char *str, int clreol, int output) { const u_char *ptr = str; int beep = 0, out = 0; int val1, val2; char old_bold = 0, old_rev = 0, old_blink = 0, old_undl = 0, old_altc = 0, in_color = 0; if (output) { old_bold = display_bold(999); old_rev = display_standout(999); old_blink = display_blink(999); old_undl = display_underline(999); old_altc = display_altcharset(999); in_color = display_color(999,999); } while (ptr && *ptr) { switch (*ptr) { case REV_TOG: { if (output) { display_standout(TOGGLE); if (!in_color) { if (!(old_rev = !old_rev)) { if (old_bold) term_bold_on(); if (old_blink) term_blink_on(); if (old_undl) term_underline_on(); if (old_altc) term_altcharset_on(); } } } break; } case UND_TOG: { if (output) { display_underline(TOGGLE); if (!in_color) { if (!(old_undl = !old_undl)) { if (old_bold) term_bold_on(); if (old_blink) term_blink_on(); if (old_rev) term_standout_on(); if (old_altc) term_altcharset_on(); } } } break; } case BOLD_TOG: { if (output) { display_bold(TOGGLE); if (!in_color) { if (!(old_bold = !old_bold)) { if (old_undl) term_underline_on(); if (old_blink) term_blink_on(); if (old_rev) term_standout_on(); if (old_altc) term_altcharset_on(); } } } break; } case BLINK_TOG: { if (output) { display_blink(TOGGLE); if (!in_color) { if (!(old_blink = !old_blink)) { if (old_undl) term_underline_on(); if (old_bold) term_bold_on(); if (old_rev) term_standout_on(); if (old_altc) term_altcharset_on(); } } } break; } case ALT_TOG: { if (output) { display_altcharset(TOGGLE); if (!in_color) { if (!(old_altc = !old_altc)) { if (old_undl) term_underline_on(); if (old_bold) term_bold_on(); if (old_rev) term_standout_on(); if (old_blink) term_blink_on(); } } } break; } case ALL_OFF: { if (output) { display_underline(OFF); display_bold(OFF); display_standout(OFF); display_blink(OFF); display_altcharset(OFF); display_color(-1,-1); in_color = 0; old_bold = old_rev = old_blink = old_undl = 0; old_altc = 0; if (!ptr[1]) display_normal(); } break; } case '\007': { beep++; break; } /* Dont ask */ case '\f': { if (output) { display_standout(TOGGLE); putchar_x('f'); display_standout(TOGGLE); } out++; break; } case ROM_CHAR: { /* Sanity... */ if (!ptr[1] || !ptr[2] || !ptr[3]) break; val1 = ((ptr[1] -'0') * 100) + ((ptr[2] -'0') * 10) + (ptr[3] - '0'); if (output) term_putgchar(val1); ptr += 3; out++; break; } case '\003': { /* * By the time we get here, we know we need to * display these as colors, and they have been * conveniently "rationalized" for us to provide * for easy color insertion. */ val1 = val2 = -1; ptr = skip_ctl_c_seq(ptr, &val1, &val2, 0); if (output) { if ((val1 > -1) || (val2 > -1)) { in_color = 1; /* BAH. this screws up ansi term_standout_off();*/ } if (display_bold(999) && (val1 >= 30 && val1 <= 37)) val1 += 20; if (display_blink(999) && (val2 >= 40 && val2 <= 47)) val2 += 10; display_color (val1, val2); if ((val1 == -1) && (val2 == -1)) { display_normal(); if (old_bold == ON) term_bold_on(); if (old_rev == ON) term_standout_on(); if (old_blink == ON) term_blink_on(); if (old_undl == ON) term_underline_on(); if (old_altc == ON) term_altcharset_on(); in_color = 0; } } ptr--; break; } case ND_SPACE: { term_right(1); out++; break; } default: { /* * JKJ TESTME: here I believe it is safe to use putchar_x, * which is MUCH faster than all the weird processing in * term_putchar. The reason for this is we can safely * assume that the stripper has handled control sequences * correctly before we ever get to this point. * term_putchar() can still be left there, for other places * that may want to call it, but at this level, I do not * see any benefit of using it. using putchar_x makes things * a LOT faster, as we have already done the processing once * to make output "terminal friendly". */ if (output) putchar_x(*ptr); out++; } } ptr++; } if (output) { if (beep) term_beep(); if (clreol) term_clear_to_eol(); } return out; } /* * scroll_window: Given a pointer to a window, this determines if that window * should be scrolled, or the cursor moved to the top of the screen again, or * if it should be left alone. */ void BX_scroll_window(Window *window) { if (dumb_mode) return; if (window->cursor == window->display_size) { int scroll, i; if (window->holding_something || window->scrollback_point) ircpanic("Cant scroll this window!"); if ((scroll = get_int_var(SCROLL_LINES_VAR)) <= 0) scroll = 1; for (i = 0; i < scroll; i++) { window->top_of_display = window->top_of_display->next; window->distance_from_display--; } if (window->visible && foreground && window->display_size) { if (window->status_split) term_scroll(window->top+window->status_lines, window->top + window->status_lines + window->cursor-1, scroll); else term_scroll(window->top, window->top - window->status_lines + window->cursor, scroll); } window->cursor -= scroll; } if (window->visible && window->display_size) { window->screen->cursor_window = window; if (window->status_split) term_move_cursor(0, window->top + window->status_lines + window->cursor); else term_move_cursor(0, window->top + window->cursor); term_clear_to_eol(); } } #define ATTRIBUTE_HANDLER(x, y) \ static char display_ ## x (int flag) \ { \ static int x = OFF; \ \ if (flag == 999) \ return (x); \ if (flag == (x)) \ return flag; \ \ switch (flag) \ { \ case ON: \ { \ x = ON; \ term_ ## x ## _on(); \ return OFF; \ } \ case OFF: \ { \ x = OFF; \ term_ ## x ## _off(); \ return ON; \ } \ case TOGGLE: \ { \ if (x == ON) \ { \ x = OFF; \ term_ ## x ## _off(); \ return ON; \ } \ else \ { \ x = ON; \ term_ ## x ## _on(); \ return OFF; \ } \ } \ } \ return OFF; \ } ATTRIBUTE_HANDLER(underline, UNDERLINE_VIDEO_VAR) ATTRIBUTE_HANDLER(bold, BOLD_VIDEO_VAR) ATTRIBUTE_HANDLER(blink, BLINK_VIDEO_VAR) ATTRIBUTE_HANDLER(standout, INVERSE_VIDEO_VAR) ATTRIBUTE_HANDLER(altcharset, ALT_CHARSET_VAR) static void display_normal (void) { tputs_x(current_term->TI_sgrstrs[TERM_SGR_NORMAL-1]); } static int display_color (long color1, long color2) { static int doing_color = 0; if ((color1 == 999) && (color2 == 999)) return doing_color; if ((color1 == -1) && (color2 == -1)) doing_color = 0; else doing_color = 1; tputs_x(replace_color(color1, color2)); return doing_color; } /* * cursor_not_in_display: This forces the cursor out of the display by * setting the cursor window to null. This doesn't actually change the * physical position of the cursor, but it will force rite() to reset the * cursor upon its next call */ void BX_cursor_not_in_display (Screen *s) { if (!s) s = output_screen; if (s->cursor_window) s->cursor_window = NULL; } /* * cursor_in_display: this forces the cursor_window to be the * output_screen->current_window. * It is actually only used in hold.c to trick the system into thinking the * cursor is in a window, thus letting the input updating routines move the * cursor down to the input line. Dumb dumb dumb */ void BX_cursor_in_display (Window *w) { if (!w) w = current_window; if (w->screen) w->screen->cursor_window = w; } /* * is_cursor_in_display: returns true if the cursor is in one of the windows * (cursor_window is not null), false otherwise */ int BX_is_cursor_in_display(Screen *screen) { if (!screen && current_window->screen) screen = current_window->screen; return (screen->cursor_window ? 1 : 0); } /* * * * * * * SCREEN UDPATING AND RESIZING * * * * * * * * */ /* * repaint_window: redraw the "m"th through the "n"th part of the window. * If end_line is -1, then that means clear the display if any of it appears * after the end of the scrollback buffer. */ void BX_repaint_window (Window *w, int start_line, int end_line) { Window *window = (Window *)w; Display *curr_line; int count; int clean_display = 0; if (dumb_mode || !window->visible) return; if (!window) window = current_window; if (end_line == -1) { end_line = window->display_size; clean_display = 1; } curr_line = window->top_of_display; for (count = 0; count < start_line; count++) { curr_line = curr_line->next; if(!curr_line) return; } window->cursor = start_line; for (count = start_line; count < end_line; count++) { if (!curr_line) break; rite(window, curr_line->line); if (curr_line == window->display_ip) break; curr_line = curr_line->next; } /* * If "end_line" is -1, then we're supposed to clear off the rest * of the display, if possible. Do that here. */ if (clean_display) { for (; count < end_line - 1; count++) { term_clear_to_eol(); term_newline(); } /* update_window_status(window, 1);*/ } recalculate_window_cursor(window); /* XXXX */ } /* * create_new_screen creates a new screen structure. with the help of * this structure we maintain ircII windows that cross screen window * boundaries. */ Screen * BX_create_new_screen(void) { Screen *new = NULL, *list; static int refnumber = 0; for (list = screen_list; list; list = list->next) { if (!list->alive) { new = list; break; } if (!list->next) break; } if (!new) { new = (Screen *) new_malloc(sizeof(Screen)); new->screennum = ++refnumber; new->next = NULL; if (list) list->next = new; else screen_list = new; } new->last_window_refnum = 1; #ifdef WINNT new->hStdin = GetStdHandle(STD_INPUT_HANDLE); new->hStdout = GetStdHandle(STD_OUTPUT_HANDLE); #endif /* new->fdout = 1;*/ new->fpout = stdout; new->fdout = fileno(stdout); if (use_input) new_open(0); /* new->fdin = 0;*/ new->fpin = stdin; new->fdin = fileno(stdin); new->alive = 1; new->li = current_term->TI_lines; new->co = current_term->TI_cols; new->old_li = 0; new->old_co = 0; new->buffer_pos = new->buffer_min_pos = 0; new->input_buffer[0] = '\0'; new->input_cursor = 0; new->input_visible = 0; new->input_start_zone = 0; new->input_end_zone = 0; new->input_prompt = NULL; new->input_prompt_len = 0; new->input_prompt_malloc = 0; new->input_line = 23; new->redirect_server = -1; #ifdef GUI /* I don't know why I need these but until I figure it out... */ new->current_window=NULL; new->visible_windows=0; #endif last_input_screen = new; if (!main_screen) #ifdef GUI gui_screen(new); gui_resize(new); #else main_screen = new; #endif init_input(); return new; } #if defined(WINDOW_CREATE) && !defined(WINNT) #ifndef GUI extern Window *BX_create_additional_screen (void) { Window *win; Screen *oldscreen, *new; char *displayvar, *termvar; int screen_type = ST_NOTHING; struct sockaddr_in NewSock; int NsZ; int s; fd_set fd_read; struct timeval timeout; pid_t child; unsigned short port; char buffer[IO_BUFFER_SIZE + 1]; if (!use_input) return NULL; /* Check for X first. */ if ((displayvar = getenv("DISPLAY"))) { if (!(termvar = getenv("TERM"))) say("I don't know how to create new windows for this terminal"); else screen_type = ST_XTERM; } /* * Environment variable STY has to be set for screen to work.. so it is * the best way to check screen.. regardless of what TERM is, the * execpl() for screen won't just open a new window if STY isn't set, * it will open a new screen process, and run the wserv in its first * window, not what we want... -phone */ if (screen_type == ST_NOTHING && getenv("STY")) screen_type = ST_SCREEN; if (screen_type == ST_NOTHING) { say("I don't know how to create new windows for this terminal"); return NULL; } say("Opening new %s...", screen_type == ST_XTERM ? "window" : screen_type == ST_SCREEN ? "screen" : "wound" ); port = 0; s = connect_by_number(NULL, &port, SERVICE_SERVER, PROTOCOL_TCP, 0); if (s < 0) { yell("Couldnt establish server side -- error [%d]", s); return NULL; } oldscreen = current_window->screen; new = create_new_screen(); switch ((child = fork())) { case -1: { kill_screen(new); say("I couldnt fire up a new wserv process"); break; } case 0: { char *opts; char *xterm; static char *args[64], **args_ptr = args; static char geom[32]; int i; setuid(getuid()); setgid(getgid()); setsid(); /* * Make sure that no inhereted file descriptors * are left over past the exec. xterm will reopen * any fd's that it is interested in. */ for (i = 3; i < 256; i++) close(i); /* * Try to restore some sanity to the signal * handlers, since theyre not really appropriate here */ my_signal(SIGINT, SIG_IGN, 0); my_signal(SIGSEGV, SIG_DFL, 0); my_signal(SIGBUS, SIG_DFL, 0); my_signal(SIGABRT, SIG_DFL, 0); if (screen_type == ST_SCREEN) { opts = LOCAL_COPY(get_string_var(SCREEN_OPTIONS_VAR)); *args_ptr++ = "screen"; while (opts && *opts) *args_ptr++ = new_next_arg(opts, &opts); } else if (screen_type == ST_XTERM) { snprintf(geom, 31, "%dx%d", current_term->TI_cols, current_term->TI_lines); opts = LOCAL_COPY(get_string_var(XTERM_OPTIONS_VAR)); if (!(xterm = getenv("XTERM"))) if (!(xterm = get_string_var(XTERM_VAR))) xterm = "xterm"; *args_ptr++ = xterm; *args_ptr++ = "-geometry"; *args_ptr++ = geom; while (opts && *opts) *args_ptr++ = new_next_arg(opts, &opts); if (get_string_var(DEFAULT_FONT_VAR)) { opts = LOCAL_COPY(get_string_var(DEFAULT_FONT_VAR)); while (opts && *opts) *args_ptr++ = new_next_arg(opts, &opts); } *args_ptr++ = "-e"; } *args_ptr++ = WSERV_PATH; *args_ptr++ = "localhost"; *args_ptr++ = ltoa((long)port); *args_ptr++ = NULL; s = execvp(args[0], args); _exit(0); } } /* All the rest of this is the parent.... */ NsZ = sizeof(NewSock); FD_ZERO(&fd_read); FD_SET(s, &fd_read); timeout.tv_sec = (time_t) 10; timeout.tv_usec = 0; /* using say(), yell() can be bad in this next section of code. */ for (;;) switch (select(NFDBITS , &fd_read, NULL, NULL, &timeout)) { case -1: if ((errno == EINTR) || (errno == EAGAIN)) continue; case 0: { int old_errno = errno; int errnod = get_child_exit (child); close(s); kill_screen(new); kill(child, SIGKILL); yell("child %s with %d", (errnod < 1) ? "signaled" : "exited", (errnod < 1) ? -errnod : errnod); yell("Errno is %d", old_errno); return NULL; } default: { new->fdin = new->fdout = my_accept(s, (struct sockaddr *) &NewSock, &NsZ); if (new->fdin < 0) { yell("Couldn't accept the new connection!"); kill_screen(new); return NULL; } new_open(new->fdin); new->fpin = new->fpout = fdopen(new->fdin, "r+"); close(s); FD_ZERO(&fd_read); FD_SET(new->fdin, &fd_read); select(NFDBITS, &fd_read, NULL, NULL, &timeout); if ((s = dgets(buffer, new->fdin, 0, IO_BUFFER_SIZE, NULL)) < 1) { yell("Read [%s] from wserv, but dgets() returned [%d]",buffer, s); new->fdin = new_close(new->fdin); kill_screen(new); kill(child, SIGKILL); return NULL; } else malloc_strcpy(&new->tty_name, buffer); win = new_window(new); refresh_screen(0, NULL); return win; } } return NULL; } #else extern Window *BX_create_additional_screen (void) { Window *win; Screen *oldscreen, *new; oldscreen = current_window->screen; new = create_new_screen(); win = new_window(new); gui_new_window(new, win); output_screen = new; set_input_prompt(win, get_string_var(INPUT_PROMPT_VAR), 0); recalculate_windows(new); return win; } #endif void BX_kill_screen(Screen *screen) { Window *window; ChannelList *chan; Screen *list; if (main_screen == screen) { for (list = screen_list; list; list = list->next) { if (list->alive != 0 && list != main_screen) { main_screen = list; break; } if (!list->next) irc_exit(1, "BitchX: No windows left!", NULL); } } #ifndef GUI if (screen->fdin) { if (use_input) screen->fdin = new_close(screen->fdin); close(screen->fdout); close(screen->fdin); } #endif while ((window = screen->window_list)) { screen->window_list = window->next; if (get_int_var(WINDOW_DESTROY_PART_VAR)) { for (chan = get_server_channels(window->server); chan;chan = chan->next) { if(window == chan->window) my_send_to_server(window->server, "PART %s", window->current_channel); } delete_window(window); } else add_to_invisible_list(window); } if (last_input_screen == screen) last_input_screen = main_screen; #ifndef GUI say("The screen is now dead."); #endif screen->alive = 0; make_window_current(NULL); #ifdef GUI gui_kill_window(screen); #endif } #endif /* WINDOW_CREATE */ void set_screens (fd_set *rd, fd_set *wd) { Screen *screen; if (use_input) for (screen = screen_list; screen; screen = screen->next) if (screen->alive) FD_SET(screen->fdin, rd); } void do_screens (fd_set *rd) { Screen *screen; unsigned char buffer[IO_BUFFER_SIZE + 1]; if (!use_input) return; for (screen = screen_list; screen; screen = screen->next) { if (!screen->alive) continue; #ifdef GUI if (gui_isset(screen, rd, 0)) #else if (FD_ISSET(screen->fdin, rd)) #endif { /* * This section of code handles all in put from the terminal(s). * connected to ircII. Perhaps the idle time *shouldn't* be * reset unless its not a screen-fd that was closed.. */ time(&idle_time); if (cpu_saver) { cpu_saver = 0; update_all_status(current_window, NULL, 0); } if (dumb_mode) { if (dgets(buffer, screen->fdin, 0, IO_BUFFER_SIZE, NULL)) { *(buffer + strlen(buffer) - 1) = '\0'; if (get_int_var(INPUT_ALIASES_VAR)) parse_line(NULL, buffer, empty_string, 1, 0, 1); else parse_line(NULL, buffer, NULL, 1, 0, 1); } else { bitchsay("BitchX exiting on EOF from stdin"); irc_exit(1, "BitchX - EOF from stdin", NULL); } } else { int server; unsigned char loc_buffer[BIG_BUFFER_SIZE + 1]; int n, i; server = from_server; last_input_screen = screen; output_screen = screen; make_window_current(screen->current_window); from_server = current_window->server; #ifdef GUI if ((n = gui_read(screen, loc_buffer, BIG_BUFFER_SIZE)) > 0) #else if ((n = read(screen->fdin, loc_buffer, BIG_BUFFER_SIZE)) > 0) #endif { extended_handled = 0; for (i = 0; i < n; i++) { #ifdef __EMXPM__ if (loc_buffer[i] == '\0') loc_buffer[i] = '\e'; #endif if (!extended_handled) edit_char(loc_buffer[i]); } } #ifndef GUI #ifdef WINDOW_CREATE else if (screen != main_screen) kill_screen(screen); #endif /* WINDOW_CREATE */ else irc_exit(1, "My damn controlling terminal disappeared!", NULL); #endif from_server = server; } } } } /* * Change xterm's title to reflect current connection status. */ void BX_xterm_settitle(void) { char titlestring[BIG_BUFFER_SIZE+1]; char *c; if (!(c = get_current_channel_by_refnum(0))) c = "(none)"; #if defined(GUI) || defined(WINNT) { Screen *tmp; ChannelList *tmpchanlist; int wincount = 0; char *s, *o = NULL, *tmptopic = NULL, *tmpwindowchan = NULL; s = fget_string_var(FORMAT_XTERM_TITLE_FSET); for (tmp = screen_list; tmp; tmp = tmp->next) { wincount++; if(tmp->alive) { if (s) { tmpwindowchan = tmptopic = NULL; if (tmp->current_window->current_channel && (tmpchanlist = lookup_channel(tmp->current_window->current_channel, tmp->current_window->server, 0))) tmptopic = tmpchanlist->topic; if(!tmp->current_window->current_channel && tmp->current_window->query_nick) tmpwindowchan=tmp->current_window->query_nick; else tmpwindowchan=tmp->current_window->current_channel; if ((o = convert_output_format(s, "%d %s %s %s %s %s", wincount, is_server_connected(tmp->current_window->server) ?get_server_nickname(tmp->current_window->server):nickname, tmpwindowchan ? tmpwindowchan : "", irc_version, is_server_connected(from_server) ?get_server_itsname(from_server):"", tmptopic ? tmptopic : empty_string))) chop(o, 4); snprintf(titlestring, BIG_BUFFER_SIZE, "%s", o); } else snprintf(titlestring, BIG_BUFFER_SIZE, "[%d] %s: %s - %s on %s", wincount, irc_version, tmp->current_window->current_channel ? tmp->current_window->current_channel : "", is_server_connected(tmp->current_window->server) ?get_server_nickname(tmp->current_window->server):nickname, is_server_connected(tmp->current_window->server) ?get_server_itsname(tmp->current_window->server):""); #ifdef GUI gui_settitle(stripansicodes(titlestring), tmp); #else if(current_window && current_window->screen == tmp) SetConsoleTitle(titlestring); #endif } } } #else if (get_int_var(XTERM_TITLE_VAR) && (getenv("STY") || getenv("DISPLAY"))) { extern char attach_ttyname[]; char *s, *o = NULL, *t; s = fget_string_var(FORMAT_XTERM_TITLE_FSET); if (s) { if ((t = strrchr(attach_ttyname, '/'))) t++; else t = attach_ttyname; if ((o = convert_output_format(s, "%s %s %s", t, nickname, c ? c : empty_string))) chop(o, 4); snprintf(titlestring, BIG_BUFFER_SIZE, "\033]2;%s: %s", irc_version, o); } else snprintf(titlestring, BIG_BUFFER_SIZE, "\033]2;%s: %s on %s:%s", irc_version, is_server_connected(current_window->server) ?get_server_nickname(current_window->server):nickname, is_server_connected(current_window->server) ?get_server_itsname(current_window->server):"", c); tputs_x(titlestring); term_flush(); } #endif } /* * add_wait_prompt: Given a prompt string, a function to call when * the prompt is entered.. some other data to pass to the function, * and the type of prompt.. either for a line, or a key, we add * this to the prompt_list for the current screen.. and set the * input prompt accordingly. */ void BX_add_wait_prompt(char *prompt, void (*func)(char *, char *), char *data, int type, int echo) { WaitPrompt **AddLoc, *New; New = (WaitPrompt *) new_malloc(sizeof(WaitPrompt)); New->prompt = m_strdup(prompt); New->data = m_strdup(data); New->type = type; New->func = func; New->next = NULL; New->echo = echo; for (AddLoc = ¤t_window->screen->promptlist; *AddLoc; AddLoc = &(*AddLoc)->next); *AddLoc = New; if (AddLoc == ¤t_window->screen->promptlist) change_input_prompt(1); } /* * * * * * * * * * * * * * * * * COLOR SUPPORT * * * * * * * * * * */ /* * This parses out a ^C control sequence. Note that it is not acceptable * to simply slurp up all digits after a ^C sequence (either by calling * strtol(), or while (isdigit())), because people put ^C sequences right * before legit output with numbers (like the time in your status bar.) * Se we have to actually slurp up only those digits that comprise a legal * ^C code. */ const u_char *BX_skip_ctl_c_seq (const u_char *start, int *lhs, int *rhs, int proper) { const u_char *after = start; u_char c1, c2; int * val; int lv1, rv1; /* * For our sanity, just use a placeholder if the caller doesnt * care where the end of the ^C code is. */ if (!lhs) lhs = &lv1; if (!rhs) rhs = &rv1; *lhs = *rhs = -1; /* * If we're passed a non ^C code, dont do anything. */ if (*after != '\003') return after; /* * This is a one-or-two-time-through loop. We find the maximum * span that can compose a legit ^C sequence, then if the first * nonvalid character is a comma, we grab the rhs of the code. */ val = lhs; for (;;) { /* * If its just a lonely old ^C, then its probably a terminator. * Just skip over it and go on. */ after++; if (*after == 0) return after; /* * Check for the very special case of a definite terminator. * If the argument to ^C is -1, then we absolutely know that * this ends the code without starting a new one */ if (after[0] == '-' && after[1] == '1') return after + 2; /* * Further checks against a lonely old naked ^C. */ if (!isdigit(after[0]) && after[0] != ',') return after; /* * Code certainly cant have moRe than two chars in it */ c1 = after[0]; c2 = after[1]; /* * Our action depends on the char immediately after the ^C. */ switch (c1) { /* * 0X -> 0X for all numeric X */ case '0': after++; *val = c1 - '0'; if (c2 >= '0' && c2 <= '9') { after++; *val = *val * 10 + (c2 - '0'); } break; /* * 1X -> 1 X if X >= '6' * 1X -> 1X if X < '6' */ case '1': after++; *val = c1 - '0'; if (c2 >= '0' && c2 < '7') { after++; *val = *val * 10 + (c2 - '0'); } else if (proper && c2 > '5' && c2 <= '9') { after++; *val = *val * 10 + (c2 - '0'); *val = *val - 16; } break; /* * 3X -> 3 X if X >= '8' * 3X -> 3X if X < '8' * (Same for 4X and 5X) */ case '3': case '4': after++; *val = c1 - '0'; if (c2 >= '0' && c2 < '8') { after++; *val = *val * 10 + (c2 - '0'); } break; case '5': after++; *val = c1 - '0'; if (c2 >= '0' && c2 < '9') { after++; *val = *val * 10 + (c2 - '0'); } break; /* * Y -> Y for any other numeric Y. */ case '2': case '6': case '7': case '8': case '9': *val = (c1 - '0'); after++; break; /* * Y -> Y for any other nonnumeric Y */ default: break; } if (val == lhs) { val = rhs; if (*after == ',') continue; } break; } return after; } /* * Used as a translation table when we cant display graphics characters * or we have been told to do translation. A no-brainer, with little attempt * at being smart. * (JKJ: perhaps we should allow a user to /set this?) */ static u_char gcxlate[256] = { '*', '*', '*', '*', '*', '*', '*', '*', '#', '*', '#', '*', '*', '*', '*', '*', '>', '<', '|', '!', '|', '$', '_', '|', '^', 'v', '>', '<', '*', '=', '^', 'v', ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '_', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'Z', 'Y', 'X', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '?', 'C', 'u', 'e', 'a', 'a', 'a', 'a', 'c', 'e', 'e', 'e', 'i', 'i', 'i', 'A', 'A', 'e', 'e', 'e', 'o', 'o', 'o', 'u', 'u', 'y', 'O', 'U', 'C', '#', 'Y', 'P', 'f', 'a', 'i', 'o', 'u', 'n', 'N', '^', '^', '?', '<', '>', '2', '4', '!', '<', '>', '#', '#', '#', '|', '|', '|', '|', '+', '+', '+', '+', '|', '+', '+', '+', '+', '+', '+', '+', '+', '-', '+', '+', '+', '+', '+', '+', '+', '+', '=', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '#', '-', '|', '|', '-', 'a', 'b', 'P', 'p', 'Z', 'o', 'u', 't', '#', 'O', '0', 'O', '-', 'o', 'e', 'U', '*', '+', '>', '<', '|', '|', '/', '=', '*', '*', '*', '*', 'n', '2', '*', '*' }; /* * State 0 is "normal, printable character" * State 1 is "nonprintable character" * State 2 is "Escape character" (033) * State 3 is "Color code" (003) * State 4 is "Special highlight character" * State 5 is "ROM character" (022) * State 6 is a character that should never be printed */ static u_char ansi_state[256] = { /* ^@ ^A ^B ^C ^D ^E ^F ^G */ 6, 6, 4, 3, 6, 4, 4, 4, /* 000 */ /* ^H ^I ^J ^K ^D ^M ^N ^O */ 6, 4, 4, 6, 0, 6, 6, 4, /* 010 */ /* ^P ^Q ^R ^S ^T ^U ^V ^W */ 6, 6, 5, 4, 4, 6, 4, 6, /* 020 */ /* ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */ 6, 6, 6, 2, 6, 6, 6, 4, /* 030 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 040 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 050 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 060 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 070 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 100 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 110 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 120 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 130 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 140 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 150 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 160 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 170 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 200 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 210 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 220 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 230 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 240 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 250 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 260 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 270 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 300 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 310 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 320 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 330 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 340 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 350 */ 1, 1, 1, 1, 1, 1, 1, 1, /* 360 */ 1, 1, 1, 1, 1, 1, 1, 1 /* 370 */ }; /* * This started off as a general ansi parser, and it worked for stuff that * was going out to the display, but it couldnt deal properly with ansi codes, * and so when i tried to use it for the status bar, it just all fell to * pieces. After working it over, i came up with this. What this does is, * (believe it or not) walk through and strip oUt all the ansi codes in the * target string. Any codes that we recognize as being safe (pretty much * just ^[[m), are converted back into their logical characters * (eg, ^B, ^R, ^_, etc), and everything else is completely blown away. */ /* * These macros help keep 8 bit chars from sneaking into the output stream * where they might be stripped out. */ #if 1 #define this_char() (eightbit ? *str : (*str) & 0x7f) #define next_char() (eightbit ? *str++ : (*str++) & 0x7f) #define put_back() (str--) #else #define this_char() (*str) #define next_char() (*str++) #define put_back() (str--) #endif unsigned char *BX_strip_ansi (const unsigned char *str) { u_char *output; u_char chr, chr1; int pos, maxpos; int args[10], nargs, i; int bold = 0; int underline = 0; int reverse = 0; int blink = 0; int alt_mode = 0; int fg_color = 0; int bg_color = 1; int ansi = get_int_var(DISPLAY_ANSI_VAR); int gcmode = get_int_var(DISPLAY_PC_CHARACTERS_VAR); int n; int eightbit = term_eight_bit(); /* * The output string has a few extra chars on the end just * in case you need to tack something else onto it. */ maxpos = strlen(str); output = (u_char *)new_malloc(maxpos + 64); pos = 0; while ((chr = next_char())) { chr1 = *(str - 1); if (pos > maxpos) { maxpos += 64; /* Extend 64 chars at a time */ RESIZE(output, unsigned char, maxpos + 64); } switch (ansi_state[chr1]) { /* * State 0 is a normal, printable ascii character */ case 0: output[pos++] = chr; break; /* * State 1 is an unprintable character that may need * to be translated first. */ case 1: case 6: { int my_gcmode = gcmode; /* * This is a very paranoid check to make sure that * the 8-bit escape code doesnt elude us. */ if (chr == 27 + 128) { output[pos++] = REV_TOG; output[pos++] = '['; output[pos++] = REV_TOG; continue; } if (ansi_state[chr] == 6) my_gcmode = 1; if (strip_ansi_never_xlate) my_gcmode = 4; switch (my_gcmode) { /* * In gcmode 5, translate all characters */ case 5: { output[pos++] = gcxlate[chr]; break; } /* * In gcmode 4, accept all characters */ case 4: { output[pos++] = chr; break; } /* * In gcmode 3, accept or translate chars */ case 3: { if (termfeatures & TERM_CAN_GCHAR) output[pos++] = chr; else output[pos++] = gcxlate[chr]; break; } /* * In gcmode 2, accept or highlight xlate */ case 2: { if (termfeatures & TERM_CAN_GCHAR) output[pos++] = chr; else { output[pos++] = REV_TOG; output[pos++] = gcxlate[chr]; output[pos++] = REV_TOG; } break; } /* * gcmode 1 is "accept or reverse mangle" * If youre doing 8-bit, it accepts eight * bit characters. If youre not doing 8 bit * then it converts the char into something * printable and then reverses it. */ case 1: { if (termfeatures & TERM_CAN_GCHAR) output[pos++] = chr; else if ((chr & 0x80) && eightbit) output[pos++] = chr; else { output[pos++] = REV_TOG; output[pos++] = (chr | 0x40) & 0x7f; output[pos++] = REV_TOG; } break; } /* * gcmode 0 is "always strip out" */ case 0: break; } break; } /* * State 2 is the escape character */ case 2: { /* * The next thing we do is dependant on what the character * is after the escape. Be very conservative in what we * allow. In general, escape sequences shouldnt be very * complex at this point. */ if (!ansi && this_char() == 0) { #if 0 output[pos++] = REV_TOG; output[pos++] = '['; output[pos++] = REV_TOG; #endif continue; } switch ((chr = next_char())) { /* * All these codes we just skip over. We're not * interested in them. */ /* * These are two-character commands. The second * char is the argument. */ case ('#') : case ('(') : case (')') : case ('*') : case ('+') : case ('$') : case ('@') : { next_char(); /* Skip the argument */ break; } /* * These are just single-character commands. */ case ('7') : case ('8') : case ('=') : case ('>') : case ('D') : case ('E') : case ('F') : case ('H') : case ('M') : case ('N') : case ('O') : case ('Z') : case ('l') : case ('m') : case ('n') : /* { */ case ('o') : case ('|') : case ('}') : case ('~') : case ('c') : { break; /* Dont do anything */ } /* * Swallow up graphics sequences... */ case ('G'): { while (((chr = next_char()) != 0) && chr != ':') ; if (chr == 0) put_back(); break; } /* * Not sure what this is, its unimplemented in * rxvt, but its supposed to end with an ESCape. */ case ('P') : { while (((chr = next_char()) != 0) && chr != 033) ; if (chr == 0) put_back(); break; } /* * Anything else, we just munch the escape and * leave it at that. */ default: put_back(); break; /* * Strip out Xterm sequences */ case (']') : { while (((chr = next_char()) != 0) && chr != 7) ; if (chr == 0) put_back(); break; } /* * Now these we're interested in.... * (CSI sequences) */ case ('[') : { /* * Set up the arguments list */ nargs = 0; args[0] = args[1] = args[2] = args[3] = 0; args[4] = args[5] = args[6] = args[7] = 0; args[8] = args[9] = 0; /* * This stuff was taken/modified/inspired by * rxvt. We do it this way in order to trap * an esc sequence that is embedded in another * (blah). We're being really really really * paranoid doing this, but it is for the best. */ /* * Check to see if the stuff after the * command is a "private" modifier. If it is, * then we definitely arent interested. * '<' , '=' , '>' , '?' */ chr = this_char(); if (chr >= '<' && chr <= '?') next_char(); /* skip it */ /* * Now pull the arguments off one at a time. * Keep pulling them off until we find a * character that is not a number or a semi- * colon. Skip everything else. */ for (nargs = 0; nargs < 10; str++) { n = 0; for (n = 0; isdigit(this_char()); next_char()) { n = n * 10 + (this_char() - '0'); } args[nargs++] = n; /* * If we run out of code here, * then we're totaly confused. * just back out with whatever * we have... */ if (!this_char()) { output[pos] = output[pos+1] = 0; return output; } if (this_char() != ';') break; } /* * If we find a new ansi char, start all * over from the top and strip it out too */ if (this_char() == 033) continue; /* * Support "spaces" (cursor right) code */ if (this_char() == 'a' || this_char() == 'C') { next_char(); if (nargs >= 1) { /* * Keep this within reality. */ if (args[0] > 256) args[0] = 256; /* This is just sanity */ if (pos + args[0] > maxpos) { maxpos += args[0]; RESIZE(output, u_char, maxpos + 64); } while (args[0]-- > 0) output[pos++] = ND_SPACE; } break; } /* * The 'm' command is the only one that * we honor. All others are dumped. */ if (next_char() != 'm') break; if (!ansi) break; /* * Walk all of the numeric arguments, * plonking the appropriate commands into * the output... */ for (i = 0; i < nargs; i++) { if (args[i] == 0) { output[pos++] = ALL_OFF; bold = reverse = blink = 0; underline = 0; fg_color = bg_color = 0; } else if (args[i] == 1 && !bold) { output[pos++] = BOLD_TOG; if (fg_color >= 30 && fg_color <= 37) { fg_color += 20; output[pos++] = 3; output[pos++] = '5'; output[pos++] = '0' + fg_color % 10; } bold = 1; } else if (args[i] == 4 && !underline) { output[pos++] = UND_TOG; underline = 1; } else if ((args[i] == 5 || args[i] == 26 ) && !blink) { output[pos++] = BLINK_TOG; if (bg_color >= 40 && bg_color <= 47) { bg_color += 10; output[pos++] = 3; output[pos++] = ','; output[pos++] = '5'; output[pos++] = '0' + bg_color % 10; } blink = 1; } else if ((args[i] == 6 || args[i] == 25) && blink) { output[pos++] = BLINK_TOG; if (bg_color >= 50 && bg_color <= 57) { bg_color -= 10; output[pos++] = 3; output[pos++] = ','; output[pos++] = '4'; output[pos++] = '0' + bg_color % 10; } blink = 0; } else if (args[i] == 7 && !reverse) { output[pos++] = REV_TOG; reverse = 1; } else if (args[i] == 21 && bold) { output[pos++] = BOLD_TOG; if (fg_color >= 50 && fg_color <= 57) { fg_color -= 20; output[pos++] = 3; output[pos++] = '3'; output[pos++] = '0' + fg_color % 10; } bold = 0; } else if (args[i] == 24 && underline) { output[pos++] = UND_TOG; underline = 0; } else if (args[i] == 27 && reverse) { output[pos++] = REV_TOG; reverse = 0; } else if (args[i] >= 30 && args[i] <= 37) { output[pos++] = 003; if (bold) output[pos++] = '5'; else output[pos++] = '3'; output[pos++] = args[i] - 30 + '0'; if (blink) { output[pos++] = ','; output[pos++] = '5'; output[pos++] = '8'; } fg_color = args[i]; } else if (args[i] >= 40 && args[i] <= 47) { output[pos++] = 003; output[pos++] = ','; if (blink) output[pos++] = '5'; else output[pos++] = '4'; output[pos++] = args[i] - 40 + '0'; bg_color = args[i]; } } /* End of for (handling esc-[...m) */ } /* End of escape-[ code handling */ } /* End of ESC handling */ break; } /* End of case 27 handling */ /* * Skip over ^C codes, theyre already normalized * well, thats not totaly true. We do some mangling * in order to make it work better */ case 3: { const u_char *end; int lhs, rhs; put_back(); end = skip_ctl_c_seq (str, &lhs, &rhs, 0); /* * XXX - This is a short-term hack to prevent an * infinite loop. I need to come back and fix * this the right way in the future. * * The infinite loop can happen when a character * 131 is encountered when eight bit chars is OFF. * We see a character 3 (131 with the 8th bit off) * and so we ask skip_ctl_c_seq where the end of * that sequence is. But since it isnt a ^c sequence * it just shrugs its shoulders and returns the * pointer as-is. So we sit asking it where the end * is and it says "its right here". So there is a * need to check the retval of skip_ctl_c_seq to * actually see if there is a sequence here. If there * is not, then we just mangle this character. For * the record, char 131 is a reverse block, so that * seems the most appropriate thing to put here. */ if (end == str) { /* Turn on reverse if neccesary */ if (reverse == 0) output[pos++] = REV_TOG; output[pos++] = ' '; if (reverse == 0) output[pos++] = REV_TOG; next_char(); /* Munch it */ break; } /* * If the user specifies an absolute value ^C * code (eg, not an mirc order one), then we turn * off bold or blink so that their color is rendered * exactly like they specify. */ /* * Turn off BOLD if the user specifies a lhs */ if (lhs != -1 && bold) { output[pos++] = BOLD_TOG; bold = 0; } /* * Ditto with BLINK for rhs */ if (rhs != -1 && blink) { output[pos++] = BLINK_TOG; blink = 0; } while (str < end) output[pos++] = next_char(); fg_color = lhs; bg_color = rhs; break; } /* * State 4 is for the special highlight characters */ case 4: { put_back(); switch (this_char()) { case REV_TOG: reverse = !reverse; break; case BOLD_TOG: bold = !bold; break; case BLINK_TOG: blink = !blink; break; case UND_TOG: underline = !underline; break; case ALT_TOG: alt_mode = !alt_mode; break; case ALL_OFF: { reverse = bold = blink = underline = 0; alt_mode = 0; break; } default: break; } output[pos++] = next_char(); break; } case 5: { put_back(); if (str[0] && str[1] && str[2] && str[3]) { output[pos++] = next_char(); output[pos++] = next_char(); output[pos++] = next_char(); output[pos++] = next_char(); } else output[pos++] = next_char(); break; } default: { ircpanic("Unknown strip_ansi mode"); break; } } } output[pos] = output[pos + 1] = 0; return output; } /* * Meant as an argument to strip_ansi(). This version will replace ANSI * color changes with terminal specific color changes. */ char *replace_color(int fore, int back) { static char retbuf[512]; char *ptr = retbuf; static int last_fore = -2, last_back = -2; static int fore_conv[] = { 15, 0, 4, 2, 1, 3, 5, 9, /* 0-7 */ 11, 10, 6, 14, 12, 13, 8, 7, /* 8-15 */ 15, 0, 0, 0, 0, 0, 0, 0, /* 16-23 */ 0, 0, 0, 0, 0, 0, 0, 1, /* 24-31 */ 2, 3, 4, 5, 6, 7, 0, 0, /* 32-39 */ 0, 0, 0, 0, 0, 0, 0, 0, /* 40-47 */ 0, 0, 8, 9, 10, 11, 12, 13, /* 48-55 */ 14, 15 /* 56-57 */ }; static int back_conv[] = { 7, 0, 4, 2, 1, 3, 5, 1, 3, 2, 6, 6, 4, 5, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 8, 9, 10, 11, 12, 13, 14, 15 }; *ptr = '\0'; if (!get_int_var(COLOR_VAR)) return retbuf; if (fore == -2 && back == -2) { back = fore = -1; } else if (fore == -1 && back == -1) { last_fore = last_back = -2; return (current_term->TI_sgrstrs[TERM_SGR_NORMAL-1]); } if (fore == -1) fore = last_fore; if (back == -1) back = last_back; if (back == 58) strcat(retbuf, current_term->TI_sgrstrs[TERM_SGR_BLINK_ON - 1]); if (fore > -1) strcat(retbuf, current_term->TI_forecolors[fore_conv[fore]]); if (back > -1 && back < 58) strcat(retbuf, current_term->TI_backcolors[back_conv[back]]); last_fore = fore; last_back = back; return retbuf; }