Wez Furlong's patch to add xterm mouse reporting and proper mouse
[u/mdw/putty] / terminal.c
index 657aff7..c3a4e2c 100644 (file)
@@ -5,6 +5,7 @@
 #include <ctype.h>
 
 #include <time.h>
+#include <assert.h>
 #include "putty.h"
 #include "tree234.h"
 
@@ -48,14 +49,14 @@ static int compatibility_level = TM_PUTTY;
 static tree234 *scrollback;           /* lines scrolled off top of screen */
 static tree234 *screen;                       /* lines on primary screen */
 static tree234 *alt_screen;           /* lines on alternate screen */
-static int disptop;                   /* distance scrolled back (0 or negative) */
+static int disptop;                   /* distance scrolled back (0 or -ve) */
 
 static unsigned long *cpos;           /* cursor position (convenience) */
 
 static unsigned long *disptext;               /* buffer of text on real screen */
 static unsigned long *wanttext;               /* buffer of text we want on screen */
 
-#define VBELL_TIMEOUT 100             /* millisecond duration of visual bell */
+#define VBELL_TIMEOUT 100             /* millisecond len of visual bell */
 
 struct beeptime {
     struct beeptime *next;
@@ -69,10 +70,7 @@ long lastbeep;
 static unsigned char *selspace;               /* buffer for building selections in */
 
 #define TSIZE (sizeof(unsigned long))
-#define lineptr(x) ( (unsigned long *) \
-                       ((x) >= 0 ? index234(screen, x) : \
-                            index234(scrollback, (x)+count234(scrollback)) ) )
-#define fix_cpos  do { cpos = lineptr(curs.y) + curs.x; } while(0)
+#define fix_cpos do { cpos = lineptr(curs.y) + curs.x; } while(0)
 
 static unsigned long curr_attr, save_attr;
 static unsigned long erase_char = ERASE_CHAR;
@@ -96,6 +94,7 @@ static int insert;                   /* insert-mode flag */
 static int cset;                      /* 0 or 1: which char set */
 static int save_cset, save_csattr;     /* saved with cursor position */
 static int rvideo;                    /* global reverse video flag */
+static int rvbell_timeout;            /* for ESC[?5hESC[?5l vbell */
 static int cursor_on;                 /* cursor enabled flag */
 static int reset_132;                 /* Flag ESC c resets to 80 cols */
 static int use_bce;                   /* Use Background coloured erase */
@@ -105,6 +104,8 @@ static int blink_is_real;          /* Actually blink blinking text */
 static int term_echoing;               /* Does terminal want local echo? */
 static int term_editing;               /* Does terminal want local edit? */
 
+static int xterm_mouse;                /* send mouse messages to app */
+
 static unsigned long cset_attr[2];
 
 /*
@@ -187,6 +188,47 @@ static FILE *lgfp = NULL;
 static void logtraffic(unsigned char c, int logmode);
 
 /*
+ * Retrieve a line of the screen or of the scrollback, according to
+ * whether the y coordinate is non-negative or negative
+ * (respectively).
+ */
+unsigned long *lineptr(int y, int lineno) {
+    unsigned long *line, lineattrs;
+    tree234 *whichtree;
+    int i, treeindex, oldlen;
+
+    if (y >= 0) {
+       whichtree = screen;
+       treeindex = y;
+    } else {
+       whichtree = scrollback;
+       treeindex = y + count234(scrollback);
+    }
+    line = index234(whichtree, treeindex);
+
+    /* We assume that we don't screw up and retrieve something out of range. */
+    assert(line != NULL);
+
+    if (line[0] != cols) {
+       /*
+        * This line is the wrong length, which probably means it
+        * hasn't been accessed since a resize. Resize it now.
+        */
+       oldlen = line[0];
+       lineattrs = line[oldlen+1];
+       delpos234(whichtree, treeindex);
+       line = srealloc(line, TSIZE * (2+cols));
+       line[0] = cols;
+       for (i = oldlen; i < cols; i++)
+           line[i+1] = ERASE_CHAR;
+       line[cols+1] = lineattrs & LATTR_MODE;
+       addpos234(whichtree, line, treeindex);
+    }
+
+    return line+1;
+}
+#define lineptr(x) lineptr(x,__LINE__)
+/*
  * Set up power-on settings for the terminal.
  */
 static void power_on(void) {
@@ -298,7 +340,7 @@ void term_size(int newrows, int newcols, int newsavelines) {
     tree234 *newsb, *newscreen, *newalt;
     unsigned long *newdisp, *newwant, *oldline, *line;
     int i, j, ccols;
-    int posn, oldposn, furthest_back, oldsbsize;
+    int sblen;
     int save_alt_which = alt_which;
 
     if (newrows == rows && newcols == cols && newsavelines == savelines)
@@ -310,48 +352,58 @@ void term_size(int newrows, int newcols, int newsavelines) {
     alt_t = marg_t = 0;
     alt_b = marg_b = newrows - 1;
 
-    newsb = newtree234(NULL);
-    newscreen = newtree234(NULL);
-    ccols = (cols < newcols ? cols : newcols);
-    oldsbsize = scrollback ? count234(scrollback) : 0;
-    furthest_back = newrows - rows - oldsbsize;
-    if (furthest_back > 0)
-       furthest_back = 0;
-    if (furthest_back < -newsavelines)
-       furthest_back = -newsavelines;
-    for (posn = newrows; posn-- > furthest_back ;) {
-       oldposn = posn - newrows + rows;
-       if (rows == -1 || oldposn < -oldsbsize) {
-           line = smalloc(TSIZE * (newcols + 1));
-           for (j = 0; j < newcols; j++)
-               line[j] = erase_char;
-           line[newcols] = 0;
-       } else {
-           oldline = (oldposn >= 0 ?
-                      delpos234(screen, count234(screen)-1) :
-                      delpos234(scrollback, count234(scrollback)-1));
-           if (newcols != cols) {
-               line = smalloc(TSIZE * (newcols + 1));
-               for (j = 0; j < ccols; j++)
-                   line[j] = oldline[j];
-               for (j = ccols; j < newcols; j++)
-                   line[j] = erase_char;
-               line[newcols] = oldline[cols] & LATTR_MODE;
-               sfree(oldline);
-           } else {
-               line = oldline;
-           }
-       }
-       if (posn >= 0)
-           addpos234(newscreen, line, 0);
-       else
-           addpos234(newsb, line, 0);
+    if (rows == -1) {
+       scrollback = newtree234(NULL);
+       screen = newtree234(NULL);
+       rows = 0;
     }
+
+    /*
+     * Resize the screen and scrollback. We only need to shift
+     * lines around within our data structures, because lineptr()
+     * will take care of resizing each individual line if
+     * necessary. So:
+     * 
+     *  - If the new screen and the old screen differ in length, we
+     *    must shunt some lines in from the scrollback or out to
+     *    the scrollback.
+     * 
+     *  - If doing that fails to provide us with enough material to
+     *    fill the new screen (i.e. the number of rows needed in
+     *    the new screen exceeds the total number in the previous
+     *    screen+scrollback), we must invent some blank lines to
+     *    cover the gap.
+     * 
+     *  - Then, if the new scrollback length is less than the
+     *    amount of scrollback we actually have, we must throw some
+     *    away.
+     */
+    sblen = count234(scrollback);
+    /* Do this loop to expand the screen if newrows > rows */
+    for (i = rows; i < newrows; i++) {
+        if (sblen > 0) {
+            line = delpos234(scrollback, --sblen);
+        } else {
+            line = smalloc(TSIZE * (newcols+2));
+            line[0] = newcols;
+            for (j = 0; j <= newcols; j++)
+                line[j+1] = ERASE_CHAR;
+        }
+        addpos234(screen, line, 0);
+    }
+    /* Do this loop to shrink the screen if newrows < rows */
+    for (i = newrows; i < rows; i++) {
+        line = delpos234(screen, 0);
+        addpos234(scrollback, line, sblen++);
+    }
+    assert(count234(screen) == newrows);
+    while (sblen > newsavelines) {
+       line = delpos234(scrollback, 0);
+       sfree(line);
+       sblen--;
+    }
+    assert(count234(scrollback) <= newsavelines);
     disptop = 0;
-    if (scrollback) freetree234(scrollback);
-    if (screen) freetree234(screen);
-    scrollback = newsb;
-    screen = newscreen;
 
     newdisp = smalloc (newrows*(newcols+1)*TSIZE);
     for (i=0; i<newrows*(newcols+1); i++)
@@ -367,9 +419,10 @@ void term_size(int newrows, int newcols, int newsavelines) {
 
     newalt = newtree234(NULL);
     for (i=0; i<newrows; i++) {
-       line = smalloc(TSIZE * (newcols+1));
+       line = smalloc(TSIZE * (newcols+2));
+       line[0] = newcols;
        for (j = 0; j <= newcols; j++)
-           line[j] = erase_char;
+           line[j+1] = erase_char;
        addpos234(newalt, line, i);
     }
     if (alt_screen) {
@@ -478,8 +531,8 @@ static void scroll (int topline, int botline, int lines, int sb) {
        while (lines < 0) {
            line = delpos234(screen, botline);
            for (i = 0; i < cols; i++)
-               line[i] = erase_char;
-           line[cols] = 0;
+               line[i+1] = erase_char;
+           line[cols+1] = 0;
            addpos234(screen, line, topline);
 
            if (selstart.y >= topline && selstart.y <= botline) {
@@ -502,7 +555,7 @@ static void scroll (int topline, int botline, int lines, int sb) {
     } else {
        while (lines > 0) {
            line = delpos234(screen, topline);
-           if (sb) {
+           if (sb && savelines > 0) {
                int sblen = count234(scrollback);
                /*
                 * We must add this line to the scrollback. We'll
@@ -510,16 +563,18 @@ static void scroll (int topline, int botline, int lines, int sb) {
                 * replace it, or allocate a new one if the
                 * scrollback isn't full.
                 */
-               if (sblen == savelines)
+               if (sblen == savelines) {
                    sblen--, line2 = delpos234(scrollback, 0);
-               else
-                   line2 = smalloc(TSIZE * (cols+1));
+               } else {
+                   line2 = smalloc(TSIZE * (cols+2));
+                   line2[0] = cols;
+               }
                addpos234(scrollback, line, sblen);
                line = line2;
            }
            for (i = 0; i < cols; i++)
-               line[i] = erase_char;
-           line[cols] = 0;
+               line[i+1] = erase_char;
+           line[cols+1] = 0;
            addpos234(screen, line, botline);
 
            if (selstart.y >= topline && selstart.y <= botline) {
@@ -632,7 +687,7 @@ static void erase_lots (int line_only, int from_begin, int to_end) {
            ldata[start.x] &= ~ATTR_WRAPPED;
        else
            ldata[start.x] = erase_char;
-       if (incpos(start))
+       if (incpos(start) && start.y < rows)
            ldata = lineptr(start.y);
     }
 }
@@ -671,6 +726,8 @@ static void insch (int n) {
  * whether the mode is a DEC private one or a normal one.)
  */
 static void toggle_mode (int mode, int query, int state) {
+    long ticks;
+
     if (query) switch (mode) {
       case 1:                         /* application cursor keys */
        app_cursor_keys = state;
@@ -684,6 +741,22 @@ static void toggle_mode (int mode, int query, int state) {
        reset_132 = state;
        break;
       case 5:                         /* reverse video */
+       /*
+        * Toggle reverse video. If we receive an OFF within the
+        * visual bell timeout period after an ON, we trigger an
+        * effective visual bell, so that ESC[?5hESC[?5l will
+        * always be an actually _visible_ visual bell.
+        */
+       ticks = GetTickCount();
+       if (rvideo && !state &&        /* we're turning it off */
+           ticks < rvbell_timeout) {  /* and it's not long since it was turned on */
+           in_vbell = TRUE;           /* we may clear rvideo but we set in_vbell */
+           if (vbell_timeout < rvbell_timeout)   /* don't move vbell end forward */
+               vbell_timeout = rvbell_timeout;   /* vbell end is at least then */
+       } else if (!rvideo && state) {
+           /* This is an ON, so we notice the time and save it. */
+           rvbell_timeout = ticks + VBELL_TIMEOUT;
+       }
        rvideo = state;
        seen_disp_event = TRUE;
        if (state) term_update();
@@ -712,6 +785,14 @@ static void toggle_mode (int mode, int query, int state) {
        swap_screen (state);
        disptop = 0;
        break;
+      case 1000:                      /* xterm mouse 1 */
+       xterm_mouse = state ? 1 : 0;
+       set_raw_mouse_mode(state);
+       break;
+      case 1002:                      /* xterm mouse 2 */
+       xterm_mouse = state ? 2: 0;
+       set_raw_mouse_mode(state);
+       break;
     } else switch (mode) {
       case 4:                         /* set insert mode */
        compatibility(VT102);
@@ -835,7 +916,7 @@ void term_out(void) {
                     * t seconds ago.
                     */
                    while (beephead &&
-                          beephead->ticks < ticks - cfg.bellovl_t*1000) {
+                          beephead->ticks < ticks - cfg.bellovl_t) {
                        struct beeptime *tmp = beephead;
                        beephead = tmp->next;
                        sfree(tmp);
@@ -845,7 +926,7 @@ void term_out(void) {
                    }
 
                    if (cfg.bellovl && beep_overloaded &&
-                       ticks-lastbeep >= cfg.bellovl_s * 1000) {
+                       ticks-lastbeep >= cfg.bellovl_s) {
                        /*
                         * If we're currently overloaded and the
                         * last beep was more than s seconds ago,
@@ -1189,6 +1270,12 @@ void term_out(void) {
                break;
              case 'a':      /* move right N cols */
                compatibility(ANSI);
+             case ANSI('c', '>'):      /* report xterm version */
+               compatibility(OTHER);
+               /* this reports xterm version 136 so that VIM can
+                use the drag messages from the mouse reporting */
+               ldisc_send("\033[>0;136;0c", 11);
+               break;
              case 'C':
                move (curs.x + def(esc_args[0], 1), curs.y, 1);
                seen_disp_event = TRUE;
@@ -2168,7 +2255,8 @@ static void sel_spread (void) {
     incpos(selend);
 }
 
-void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
+void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y,
+                int shift, int ctrl) {
     pos selpoint;
     unsigned long *ldata;
     
@@ -2189,12 +2277,64 @@ void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
     if ((ldata[cols]&LATTR_MODE) != LATTR_NORM)
        selpoint.x /= 2;
 
-    if (b == MB_SELECT && a == MA_CLICK) {
+    if (xterm_mouse) {
+       int encstate = 0, r, c;
+       char abuf[16];
+       static int is_down = 0;
+
+       switch(b) {
+         case MBT_LEFT:
+           encstate = 0x20;    /* left button down */
+           break;
+         case MBT_MIDDLE:
+           encstate = 0x21;
+           break;
+         case MBT_RIGHT:
+           encstate = 0x22;
+           break;
+         case MBT_WHEEL_UP:
+           encstate = 0x60;
+           break;
+         case MBT_WHEEL_DOWN:
+           encstate = 0x61;
+           break;
+       }
+       switch(a) {
+         case MA_DRAG:
+           if (xterm_mouse == 1)
+               return;
+           encstate += 0x20;
+           break;
+         case MA_RELEASE:
+           encstate = 0x23;
+           is_down = 0;
+           break;
+         case MA_CLICK:
+           if (is_down == b)
+               return;
+           is_down = b;
+           break;
+       }
+       if (shift)
+           encstate += 0x04;
+       if (ctrl)
+           encstate += 0x10;
+       r = y + 33;
+       c = x + 33;
+
+       sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
+       ldisc_send(abuf, 6);
+       return;
+    }
+
+    b = translate_button(b);
+
+    if (b == MBT_SELECT && a == MA_CLICK) {
        deselect();
        selstate = ABOUT_TO;
        selanchor = selpoint;
        selmode = SM_CHAR;
-    } else if (b == MB_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
+    } else if (b == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
        deselect();
        selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
        selstate = DRAGGING;
@@ -2202,11 +2342,11 @@ void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
        selend = selstart;
        incpos(selend);
        sel_spread();
-    } else if ((b == MB_SELECT && a == MA_DRAG) ||
-              (b == MB_EXTEND && a != MA_RELEASE)) {
+    } else if ((b == MBT_SELECT && a == MA_DRAG) ||
+              (b == MBT_EXTEND && a != MA_RELEASE)) {
        if (selstate == ABOUT_TO && poseq(selanchor, selpoint))
            return;
-       if (b == MB_EXTEND && a != MA_DRAG && selstate == SELECTED) {
+       if (b == MBT_EXTEND && a != MA_DRAG && selstate == SELECTED) {
            if (posdiff(selpoint,selstart) < posdiff(selend,selstart)/2) {
                selanchor = selend;
                decpos(selanchor);
@@ -2228,7 +2368,7 @@ void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
            incpos(selend);
        }
        sel_spread();
-    } else if ((b == MB_SELECT || b == MB_EXTEND) && a == MA_RELEASE) {
+    } else if ((b == MBT_SELECT || b == MBT_EXTEND) && a == MA_RELEASE) {
        if (selstate == DRAGGING) {
            /*
             * We've completed a selection. We now transfer the
@@ -2238,7 +2378,7 @@ void term_mouse (Mouse_Button b, Mouse_Action a, int x, int y) {
            selstate = SELECTED;
        } else
            selstate = NO_SELECTION;
-    } else if (b == MB_PASTE && (a==MA_CLICK || a==MA_2CLK || a==MA_3CLK)) {
+    } else if (b == MBT_PASTE && (a==MA_CLICK || a==MA_2CLK || a==MA_3CLK)) {
        char *data;
        int len;