Thanks to Richard B for pointing out that xterm has its own variants
[u/mdw/putty] / terminal.c
index 8afb98b..2785689 100644 (file)
@@ -1,5 +1,3 @@
-#include <windows.h>
-
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
@@ -59,7 +57,7 @@ static unsigned long *disptext;              /* buffer of text on real screen */
 static unsigned long *dispcurs;               /* location of cursor on real screen */
 static unsigned long curstype;        /* type of cursor on real screen */
 
-#define VBELL_TIMEOUT 100             /* millisecond len of visual bell */
+#define VBELL_TIMEOUT (TICKSPERSEC/10) /* visual bell lasts 1/10 sec */
 
 struct beeptime {
     struct beeptime *next;
@@ -115,6 +113,10 @@ static int vt52_bold;                     /* Force bold on non-bold colours */
 static int utf_state;                 /* Is there a pending UTF-8 character */
 static int utf_char;                  /* and what is it so far. */
 static int utf_size;                  /* The size of the UTF character. */
+static int printing, only_printing;    /* Are we doing ANSI printing? */
+static int print_state;                       /* state of print-end-sequence scan */
+static bufchain printer_buf;          /* buffered data for printer */
+static printer_job *print_job;
 
 static int xterm_mouse;                       /* send mouse messages to app */
 
@@ -203,9 +205,10 @@ static int paste_len, paste_pos, paste_hold;
  */
 static void do_paint(Context, int);
 static void erase_lots(int, int, int);
-static void swap_screen(int);
+static void swap_screen(int, int, int);
 static void update_sbar(void);
 static void deselect(void);
+static void term_print_finish(void);
 
 /*
  * Resize a line to make it `cols' columns wide.
@@ -302,15 +305,16 @@ static void power_on(void)
     blink_is_real = cfg.blinktext;
     erase_char = ERASE_CHAR;
     alt_which = 0;
+    term_print_finish();
     {
        int i;
        for (i = 0; i < 256; i++)
            wordness[i] = cfg.wordness[i];
     }
     if (screen) {
-       swap_screen(1);
+       swap_screen(1, FALSE, FALSE);
        erase_lots(FALSE, TRUE, TRUE);
-       swap_screen(0);
+       swap_screen(0, FALSE, FALSE);
        erase_lots(FALSE, TRUE, TRUE);
     }
 }
@@ -324,10 +328,9 @@ void term_update(void)
     ctx = get_ctx();
     if (ctx) {
        int need_sbar_update = seen_disp_event;
-       if ((seen_key_event && (cfg.scroll_on_key)) ||
-           (seen_disp_event && (cfg.scroll_on_disp))) {
+       if (seen_disp_event && cfg.scroll_on_disp) {
            disptop = 0;               /* return to main screen */
-           seen_disp_event = seen_key_event = 0;
+           seen_disp_event = 0;
            need_sbar_update = TRUE;
        }
        if (need_sbar_update)
@@ -339,6 +342,37 @@ void term_update(void)
 }
 
 /*
+ * Called from front end when a keypress occurs, to trigger
+ * anything magical that needs to happen in that situation.
+ */
+void term_seen_key_event(void)
+{
+    /*
+     * On any keypress, clear the bell overload mechanism
+     * completely, on the grounds that large numbers of
+     * beeps coming from deliberate key action are likely
+     * to be intended (e.g. beeps from filename completion
+     * blocking repeatedly).
+     */
+    beep_overloaded = FALSE;
+    while (beephead) {
+       struct beeptime *tmp = beephead;
+       beephead = tmp->next;
+       sfree(tmp);
+    }
+    beeptail = NULL;
+    nbeeps = 0;
+
+    /*
+     * Reset the scrollback on keypress, if we're doing that.
+     */
+    if (cfg.scroll_on_key) {
+       disptop = 0;                   /* return to main screen */
+       seen_disp_event = 1;
+    }
+}
+
+/*
  * Same as power_on(), but an external function.
  */
 void term_pwron(void)
@@ -352,17 +386,26 @@ void term_pwron(void)
 
 /*
  * When the user reconfigures us, we need to check the forbidden-
- * alternate-screen config option.
+ * alternate-screen config option, disable raw mouse mode if the
+ * user has disabled mouse reporting, and abandon a print job if
+ * the user has disabled printing.
  */
 void term_reconfig(void)
 {
     if (cfg.no_alt_screen)
-       swap_screen(0);
+       swap_screen(0, FALSE, FALSE);
+    if (cfg.no_mouse_rep) {
+       xterm_mouse = 0;
+       set_raw_mouse_mode(0);
+    }
     if (cfg.no_remote_charset) {
        cset_attr[0] = cset_attr[1] = ATTR_ASCII;
        sco_acs = alt_sco_acs = 0;
        utf = 0;
     }
+    if (!*cfg.printer) {
+       term_print_finish();
+    }
 }
 
 /*
@@ -411,7 +454,7 @@ void term_size(int newrows, int newcols, int newsavelines)
        return;                        /* nothing to do */
 
     deselect();
-    swap_screen(0);
+    swap_screen(0, FALSE, FALSE);
 
     alt_t = marg_t = 0;
     alt_b = marg_b = newrows - 1;
@@ -514,7 +557,7 @@ void term_size(int newrows, int newcols, int newsavelines)
     savelines = newsavelines;
     fix_cpos;
 
-    swap_screen(save_alt_which);
+    swap_screen(save_alt_which, FALSE, FALSE);
 
     update_sbar();
     term_update();
@@ -522,56 +565,76 @@ void term_size(int newrows, int newcols, int newsavelines)
 }
 
 /*
- * Swap screens.
+ * Swap screens. If `reset' is TRUE and we have been asked to
+ * switch to the alternate screen, we must bring most of its
+ * configuration from the main screen and erase the contents of the
+ * alternate screen completely. (This is even true if we're already
+ * on it! Blame xterm.)
  */
-static void swap_screen(int which)
+static void swap_screen(int which, int reset, int keep_cur_pos)
 {
     int t;
     tree234 *ttr;
 
-    if (which == alt_which)
-       return;
+    if (!which)
+       reset = FALSE;                 /* do no weird resetting if which==0 */
+
+    if (which != alt_which) {
+       alt_which = which;
+
+       ttr = alt_screen;
+       alt_screen = screen;
+       screen = ttr;
+       t = curs.x;
+       if (!reset && !keep_cur_pos)
+           curs.x = alt_x;
+       alt_x = t;
+       t = curs.y;
+       if (!reset && !keep_cur_pos)
+           curs.y = alt_y;
+       alt_y = t;
+       t = marg_t;
+       if (!reset) marg_t = alt_t;
+       alt_t = t;
+       t = marg_b;
+       if (!reset) marg_b = alt_b;
+       alt_b = t;
+       t = dec_om;
+       if (!reset) dec_om = alt_om;
+       alt_om = t;
+       t = wrap;
+       if (!reset) wrap = alt_wrap;
+       alt_wrap = t;
+       t = wrapnext;
+       if (!reset) wrapnext = alt_wnext;
+       alt_wnext = t;
+       t = insert;
+       if (!reset) insert = alt_ins;
+       alt_ins = t;
+       t = cset;
+       if (!reset) cset = alt_cset;
+       alt_cset = t;
+       t = utf;
+       if (!reset) utf = alt_utf;
+       alt_utf = t;
+       t = sco_acs;
+       if (!reset) sco_acs = alt_sco_acs;
+       alt_sco_acs = t;
+    }
 
-    alt_which = which;
-
-    ttr = alt_screen;
-    alt_screen = screen;
-    screen = ttr;
-    t = curs.x;
-    curs.x = alt_x;
-    alt_x = t;
-    t = curs.y;
-    curs.y = alt_y;
-    alt_y = t;
-    t = marg_t;
-    marg_t = alt_t;
-    alt_t = t;
-    t = marg_b;
-    marg_b = alt_b;
-    alt_b = t;
-    t = dec_om;
-    dec_om = alt_om;
-    alt_om = t;
-    t = wrap;
-    wrap = alt_wrap;
-    alt_wrap = t;
-    t = wrapnext;
-    wrapnext = alt_wnext;
-    alt_wnext = t;
-    t = insert;
-    insert = alt_ins;
-    alt_ins = t;
-    t = cset;
-    cset = alt_cset;
-    alt_cset = t;
-    t = utf;
-    utf = alt_utf;
-    alt_utf = t;
-    t = sco_acs;
-    sco_acs = alt_sco_acs;
-    alt_sco_acs = t;
+    if (reset && screen) {
+       /*
+        * Yes, this _is_ supposed to honour background-colour-erase.
+        */
+       erase_lots(FALSE, TRUE, TRUE);
+    }
 
-    fix_cpos;
+    /*
+     * This might not be possible if we're called during
+     * initialisation.
+     */
+    if (screen)
+       fix_cpos;
 }
 
 /*
@@ -903,7 +966,7 @@ static void toggle_mode(int mode, int query, int state)
             * effective visual bell, so that ESC[?5hESC[?5l will
             * always be an actually _visible_ visual bell.
             */
-           ticks = GetTickCount();
+           ticks = GETTICKCOUNT();
            /* turn off a previous vbell to avoid inconsistencies */
            if (ticks - vbell_startpoint >= VBELL_TIMEOUT)
                in_vbell = FALSE;
@@ -945,7 +1008,7 @@ static void toggle_mode(int mode, int query, int state)
          case 47:                     /* alternate screen */
            compatibility(OTHER);
            deselect();
-           swap_screen(cfg.no_alt_screen ? 0 : state);
+           swap_screen(cfg.no_alt_screen ? 0 : state, FALSE, FALSE);
            disptop = 0;
            break;
          case 1000:                   /* xterm mouse 1 */
@@ -956,6 +1019,27 @@ static void toggle_mode(int mode, int query, int state)
            xterm_mouse = state ? 2 : 0;
            set_raw_mouse_mode(state);
            break;
+         case 1047:                   /* alternate screen */
+           compatibility(OTHER);
+           deselect();
+           swap_screen(cfg.no_alt_screen ? 0 : state, TRUE, TRUE);
+           disptop = 0;
+           break;
+         case 1048:                   /* save/restore cursor */
+           save_cursor(state);
+           if (!state) seen_disp_event = TRUE;
+           break;
+         case 1049:                   /* cursor & alternate screen */
+           if (state)
+               save_cursor(state);
+           if (!state) seen_disp_event = TRUE;
+           compatibility(OTHER);
+           deselect();
+           swap_screen(cfg.no_alt_screen ? 0 : state, TRUE, FALSE);
+           if (!state)
+               save_cursor(state);
+           disptop = 0;
+           break;
     } else
        switch (mode) {
          case 4:                      /* set insert mode */
@@ -1003,6 +1087,53 @@ static void do_osc(void)
 }
 
 /*
+ * ANSI printing routines.
+ */
+static void term_print_setup(void)
+{
+    bufchain_clear(&printer_buf);
+    print_job = printer_start_job(cfg.printer);
+}
+static void term_print_flush(void)
+{
+    void *data;
+    int len;
+    int size;
+    while ((size = bufchain_size(&printer_buf)) > 5) {
+       bufchain_prefix(&printer_buf, &data, &len);
+       if (len > size-5)
+           len = size-5;
+       printer_job_data(print_job, data, len);
+       bufchain_consume(&printer_buf, len);
+    }
+}
+static void term_print_finish(void)
+{
+    void *data;
+    int len, size;
+    char c;
+
+    if (!printing && !only_printing)
+       return;                        /* we need do nothing */
+
+    term_print_flush();
+    while ((size = bufchain_size(&printer_buf)) > 0) {
+       bufchain_prefix(&printer_buf, &data, &len);
+       c = *(char *)data;
+       if (c == '\033' || c == '\233') {
+           bufchain_consume(&printer_buf, size);
+           break;
+       } else {
+           printer_job_data(print_job, &c, 1);
+           bufchain_consume(&printer_buf, 1);
+       }
+    }
+    printer_finish_job(print_job);
+    print_job = NULL;
+    printing = only_printing = FALSE;
+}
+
+/*
  * Remove everything currently in `inbuf' and stick it up on the
  * in-memory display. There's a big state machine in here to
  * process escape sequences...
@@ -1015,6 +1146,7 @@ void term_out(void)
 
     unget = -1;
 
+    chars = NULL;                     /* placate compiler warnings */
     while (nchars > 0 || bufchain_size(&inbuf) > 0) {
        if (unget == -1) {
            if (nchars == 0) {
@@ -1046,6 +1178,38 @@ void term_out(void)
         * of i18n.
         */
 
+       /*
+        * If we're printing, add the character to the printer
+        * buffer.
+        */
+       if (printing) {
+           bufchain_add(&printer_buf, &c, 1);
+
+           /*
+            * If we're in print-only mode, we use a much simpler
+            * state machine designed only to recognise the ESC[4i
+            * termination sequence.
+            */
+           if (only_printing) {
+               if (c == '\033')
+                   print_state = 1;
+               else if (c == (unsigned char)'\233')
+                   print_state = 2;
+               else if (c == '[' && print_state == 1)
+                   print_state = 2;
+               else if (c == '4' && print_state == 2)
+                   print_state = 3;
+               else if (c == 'i' && print_state == 3)
+                   print_state = 4;
+               else
+                   print_state = 0;
+               if (print_state == 4) {
+                   term_print_finish();
+               }
+               continue;
+           }
+       }
+
        /* First see about all those translations. */
        if (termstate == TOPLEVEL) {
            if (in_utf)
@@ -1224,7 +1388,7 @@ void term_out(void)
                        } else
                            *d++ = *s;
                    }
-                   lpage_send(CP_ACP, abuf, d - abuf, 0);
+                   lpage_send(DEFAULT_CODEPAGE, abuf, d - abuf, 0);
                }
                break;
              case '\007':
@@ -1232,7 +1396,7 @@ void term_out(void)
                    struct beeptime *newbeep;
                    unsigned long ticks;
 
-                   ticks = GetTickCount();
+                   ticks = GETTICKCOUNT();
 
                    if (!beep_overloaded) {
                        newbeep = smalloc(sizeof(struct beeptime));
@@ -1283,12 +1447,12 @@ void term_out(void)
                     * Perform an actual beep if we're not overloaded.
                     */
                    if (!cfg.bellovl || !beep_overloaded) {
-                       beep(cfg.beep);
                        if (cfg.beep == BELL_VISUAL) {
                            in_vbell = TRUE;
                            vbell_startpoint = ticks;
                            term_update();
-                       }
+                       } else
+                           beep(cfg.beep);
                    }
                    disptop = 0;
                }
@@ -1810,6 +1974,21 @@ void term_out(void)
                                toggle_mode(esc_args[i], esc_query, TRUE);
                        }
                        break;
+                     case 'i':
+                     case ANSI_QUE('i'):
+                       compatibility(VT100);
+                       {
+                           if (esc_nargs != 1) break;
+                           if (esc_args[0] == 5 && *cfg.printer) {
+                               printing = TRUE;
+                               only_printing = !esc_query;
+                               print_state = 0;
+                               term_print_setup();
+                           } else if (esc_args[0] == 4 && printing) {
+                               term_print_finish();
+                           }
+                       }
+                       break;                  
                      case 'l':       /* toggle modes to low */
                      case ANSI_QUE('l'):
                        compatibility(VT100);
@@ -2652,6 +2831,8 @@ void term_out(void)
            check_selection(curs, cursplus);
        }
     }
+
+    term_print_flush();
 }
 
 #if 0
@@ -2687,7 +2868,7 @@ static void do_paint(Context ctx, int may_optimise)
      * Check the visual bell state.
      */
     if (in_vbell) {
-       ticks = GetTickCount();
+       ticks = GETTICKCOUNT();
        if (ticks - vbell_startpoint >= VBELL_TIMEOUT)
            in_vbell = FALSE; 
    }
@@ -2882,11 +3063,11 @@ void term_blink(int flg)
     static long last_tblink = 0;
     long now, blink_diff;
 
-    now = GetTickCount();
+    now = GETTICKCOUNT();
     blink_diff = now - last_tblink;
 
-    /* Make sure the text blinks no more than 2Hz */
-    if (blink_diff < 0 || blink_diff > 450) {
+    /* Make sure the text blinks no more than 2Hz; we'll use 0.45 s period. */
+    if (blink_diff < 0 || blink_diff > (TICKSPERSEC * 9 / 20)) {
        last_tblink = now;
        tblinker = !tblinker;
     }
@@ -2899,8 +3080,8 @@ void term_blink(int flg)
 
     blink_diff = now - last_blink;
 
-    /* Make sure the cursor blinks no faster than GetCaretBlinkTime() */
-    if (blink_diff >= 0 && blink_diff < (long) GetCaretBlinkTime())
+    /* Make sure the cursor blinks no faster than system blink rate */
+    if (blink_diff >= 0 && blink_diff < (long) CURSORBLINK)
        return;
 
     last_blink = now;
@@ -3067,16 +3248,14 @@ static void clipme(pos top, pos bottom, int rect)
                    unsigned char buf[4];
                    WCHAR wbuf[4];
                    int rv;
-                   if (IsDBCSLeadByteEx(font_codepage, (BYTE) c)) {
+                   if (is_dbcs_leadbyte(font_codepage, (BYTE) c)) {
                        buf[0] = c;
                        buf[1] = (unsigned char) ldata[top.x + 1];
-                       rv = MultiByteToWideChar(font_codepage,
-                                                0, buf, 2, wbuf, 4);
+                       rv = mb_to_wc(font_codepage, 0, buf, 2, wbuf, 4);
                        top.x++;
                    } else {
                        buf[0] = c;
-                       rv = MultiByteToWideChar(font_codepage,
-                                                0, buf, 1, wbuf, 4);
+                       rv = mb_to_wc(font_codepage, 0, buf, 1, wbuf, 4);
                    }
 
                    if (rv > 0) {
@@ -3110,8 +3289,10 @@ static void clipme(pos top, pos bottom, int rect)
        top.y++;
        top.x = rect ? old_top_x : 0;
     }
+#if SELECTION_NUL_TERMINATED
     wblen++;
     *wbptr++ = 0;
+#endif
     write_clip(workbuf, wblen, FALSE); /* transfer to clipboard */
     if (buflen > 0)                   /* indicates we allocated this buffer */
        sfree(workbuf);
@@ -3340,9 +3521,11 @@ void term_do_paste(void)
     int len;
 
     get_clip(&data, &len);
-    if (data) {
+    if (data && len > 0) {
         wchar_t *p, *q;
 
+       term_seen_key_event();         /* pasted data counts */
+
         if (paste_buffer)
             sfree(paste_buffer);
         paste_pos = paste_hold = paste_len = 0;
@@ -3387,7 +3570,9 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
 {
     pos selpoint;
     unsigned long *ldata;
-    int raw_mouse = xterm_mouse && !(cfg.mouse_override && shift);
+    int raw_mouse = (xterm_mouse &&
+                    !cfg.no_mouse_rep &&
+                    !(cfg.mouse_override && shift));
     int default_seltype;
 
     if (y < 0) {
@@ -3573,8 +3758,12 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
        } else
            selstate = NO_SELECTION;
     } else if (b == MBT_PASTE
-              && (a == MA_CLICK || a == MA_2CLK || a == MA_3CLK)) {
-        term_do_paste();
+              && (a == MA_CLICK
+#if MULTICLICK_ONLY_EVENT
+                  || a == MA_2CLK || a == MA_3CLK
+#endif
+                  )) {
+       request_paste();
     }
 
     term_update();
@@ -3589,6 +3778,11 @@ void term_nopaste()
     paste_len = 0;
 }
 
+int term_paste_pending(void)
+{
+    return paste_len != 0;
+}
+
 void term_paste()
 {
     static long last_paste = 0;
@@ -3599,7 +3793,7 @@ void term_paste()
 
     /* Don't wait forever to paste */
     if (paste_hold) {
-       now = GetTickCount();
+       now = GETTICKCOUNT();
        paste_diff = now - last_paste;
        if (paste_diff >= 0 && paste_diff < 450)
            return;