Reintroduce Cyrillic Caps Lock mode, which was in 0.51 but got
[u/mdw/putty] / terminal.c
index 9f623e5..ab97e3c 100644 (file)
@@ -94,6 +94,7 @@ static int wrap, wrapnext;           /* wrap flags */
 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 save_utf;                  /* 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 */
@@ -104,6 +105,7 @@ static int tblinker;                       /* When the blinking text is on */
 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 sco_acs, save_sco_acs;      /* CSI 10,11,12m -> OEM charset */
 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. */
@@ -116,7 +118,7 @@ static unsigned long cset_attr[2];
 /*
  * Saved settings on the alternate screen.
  */
-static int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins, alt_cset;
+static int alt_x, alt_y, alt_om, alt_wrap, alt_wnext, alt_ins, alt_cset, alt_sco_acs, alt_utf;
 static int alt_t, alt_b;
 static int alt_which;
 
@@ -199,6 +201,7 @@ static void deselect(void);
 /* log session to file stuff ... */
 static FILE *lgfp = NULL;
 static void logtraffic(unsigned char c, int logmode);
+static void xlatlognam(char *d, char *s, char *hostname, struct tm *tm);
 
 /*
  * Resize a line to make it `cols' columns wide.
@@ -252,6 +255,7 @@ unsigned long *lineptr(int y, int lineno)
     if (newline != line) {
        delpos234(whichtree, treeindex);
        addpos234(whichtree, newline, treeindex);
+        line = newline;
     }
 
     return line + 1;
@@ -278,6 +282,8 @@ static void power_on(void)
     alt_wnext = wrapnext = alt_ins = insert = FALSE;
     alt_wrap = wrap = cfg.wrap_mode;
     alt_cset = cset = 0;
+    alt_utf = utf = 0;
+    alt_sco_acs = sco_acs = 0;
     cset_attr[0] = cset_attr[1] = ATTR_ASCII;
     rvideo = 0;
     in_vbell = FALSE;
@@ -491,6 +497,7 @@ void term_size(int newrows, int newcols, int newsavelines)
 
     update_sbar();
     term_update();
+    back->size();
 }
 
 /*
@@ -536,6 +543,12 @@ static void swap_screen(int which)
     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;
 
     fix_cpos;
 }
@@ -575,7 +588,7 @@ static void check_selection(pos from, pos to)
 static void scroll(int topline, int botline, int lines, int sb)
 {
     unsigned long *line, *line2;
-    int i;
+    int i, seltop;
 
     if (topline != 0 || alt_which != 0)
        sb = FALSE;
@@ -625,6 +638,23 @@ static void scroll(int topline, int botline, int lines, int sb)
                }
                addpos234(scrollback, line, sblen);
                line = line2;
+
+               /*
+                * If the user is currently looking at part of the
+                * scrollback, and they haven't enabled any options
+                * that are going to reset the scrollback as a
+                * result of this movement, then the chances are
+                * they'd like to keep looking at the same line. So
+                * we move their viewpoint at the same rate as the
+                * scroll, at least until their viewpoint hits the
+                * top end of the scrollback buffer, at which point
+                * we don't have the choice any more.
+                * 
+                * Thanks to Jan Holmen Holsten for the idea and
+                * initial implementation.
+                */
+               if (disptop > -savelines && disptop < 0)
+                   disptop--;
            }
             line = resizeline(line, cols);
            for (i = 0; i < cols; i++)
@@ -632,17 +662,26 @@ static void scroll(int topline, int botline, int lines, int sb)
            line[cols + 1] = 0;
            addpos234(screen, line, botline);
 
-           if (selstart.y >= topline && selstart.y <= botline) {
+           /*
+            * If the selection endpoints move into the scrollback,
+            * we keep them moving until they hit the top. However,
+            * of course, if the line _hasn't_ moved into the
+            * scrollback then we don't do this, and cut them off
+            * at the top of the scroll region.
+            */
+           seltop = sb ? -savelines : 0;
+
+           if (selstart.y >= seltop && selstart.y <= botline) {
                selstart.y--;
-               if (selstart.y < topline) {
-                   selstart.y = topline;
+               if (selstart.y < seltop) {
+                   selstart.y = seltop;
                    selstart.x = 0;
                }
            }
-           if (selend.y >= topline && selend.y <= botline) {
+           if (selend.y >= seltop && selend.y <= botline) {
                selend.y--;
-               if (selend.y < topline) {
-                   selend.y = topline;
+               if (selend.y < seltop) {
+                   selend.y = seltop;
                    selend.x = 0;
                }
            }
@@ -689,7 +728,9 @@ static void save_cursor(int save)
        savecurs = curs;
        save_attr = curr_attr;
        save_cset = cset;
+       save_utf = utf;
        save_csattr = cset_attr[cset];
+       save_sco_acs = sco_acs;
     } else {
        curs = savecurs;
        /* Make sure the window hasn't shrunk since the save */
@@ -700,10 +741,13 @@ static void save_cursor(int save)
 
        curr_attr = save_attr;
        cset = save_cset;
+       utf = save_utf;
        cset_attr[cset] = save_csattr;
+       sco_acs = save_sco_acs;
        fix_cpos;
        if (use_bce)
-           erase_char = (' ' | (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
+           erase_char = (' ' | ATTR_ASCII |
+                        (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
     }
 }
 
@@ -808,7 +852,7 @@ static void toggle_mode(int mode, int query, int state)
            break;
          case 3:                      /* 80/132 columns */
            deselect();
-           request_resize(state ? 132 : 80, rows, 1);
+           request_resize(state ? 132 : 80, rows);
            reset_132 = state;
            break;
          case 5:                      /* reverse video */
@@ -918,15 +962,18 @@ void term_out(void)
 {
     int c, inbuf_reap;
 
+    /*
+     * Optionally log the session traffic to a file. Useful for
+     * debugging and possibly also useful for actual logging.
+     */
+    if (cfg.logtype == LGTYP_DEBUG)
+       for (inbuf_reap = 0; inbuf_reap < inbuf_head; inbuf_reap++) {
+           logtraffic((unsigned char) inbuf[inbuf_reap], LGTYP_DEBUG);
+       }
+
     for (inbuf_reap = 0; inbuf_reap < inbuf_head; inbuf_reap++) {
        c = inbuf[inbuf_reap];
 
-       /*
-        * Optionally log the session traffic to a file. Useful for
-        * debugging and possibly also useful for actual logging.
-        */
-       logtraffic((unsigned char) c, LGTYP_DEBUG);
-
        /* Note only VT220+ are 8-bit VT102 is seven bit, it shouldn't even
         * be able to display 8-bit characters, but I'll let that go 'cause
         * of i18n.
@@ -934,18 +981,15 @@ void term_out(void)
 
        /* First see about all those translations. */
        if (termstate == TOPLEVEL) {
-           if (utf)
+           if (in_utf)
                switch (utf_state) {
                  case 0:
                    if (c < 0x80) {
-                       /* I know; gotos are evil. This one is really bad!
-                        * But before you try removing it follow the path of the
-                        * sequence "0x5F 0xC0 0x71" with UTF and VTGraphics on.
-                        */
-                       /*
-                          if (cfg.no_vt_graph_with_utf8) break;
-                        */
-                       goto evil_jump;
+                       /* UTF-8 must be stateless so we ignore iso2022. */
+                       if (unitab_ctrl[c] != 0xFF) 
+                            c = unitab_ctrl[c];
+                       else c = ((unsigned char)c) | ATTR_ASCII;
+                       break;
                    } else if ((c & 0xe0) == 0xc0) {
                        utf_size = utf_state = 1;
                        utf_char = (c & 0x1f);
@@ -972,9 +1016,9 @@ void term_out(void)
                  case 4:
                  case 5:
                    if ((c & 0xC0) != 0x80) {
-                       inbuf_reap--;  /* This causes the faulting character */
-                       c = UCSERR;    /* to be logged twice - not really a */
-                       utf_state = 0; /* serious problem. */
+                       inbuf_reap--;
+                       c = UCSERR;
+                       utf_state = 0;
                        break;
                    }
                    utf_char = (utf_char << 6) | (c & 0x3f);
@@ -1024,8 +1068,14 @@ void term_out(void)
                    if (c >= 0x10000)
                        c = 0xFFFD;
                    break;
+           }
+           /* Are we in the nasty ACS mode? Note: no sco in utf mode. */
+           else if(sco_acs && 
+                   (c!='\033' && c!='\n' && c!='\r' && c!='\b'))
+           {
+              if (sco_acs == 2) c ^= 0x80;
+              c |= ATTR_SCOACS;
            } else {
-             evil_jump:;
                switch (cset_attr[cset]) {
                    /* 
                     * Linedraw characters are different from 'ESC ( B'
@@ -1052,6 +1102,9 @@ void term_out(void)
                    else
                        c = ((unsigned char) c) | ATTR_ASCII;
                    break;
+               case ATTR_SCOACS:
+                   if (c>=' ') c = ((unsigned char)c) | ATTR_SCOACS;
+                   break;
                }
            }
        }
@@ -1287,12 +1340,18 @@ void term_out(void)
                        width = wcwidth((wchar_t) c);
                    switch (width) {
                      case 2:
-                       if (curs.x + 1 != cols) {
-                           *cpos++ = c | ATTR_WIDE | curr_attr;
-                           *cpos++ = UCSWIDE | curr_attr;
-                           curs.x++;
-                           break;
+                       *cpos++ = c | curr_attr;
+                       if (++curs.x == cols) {
+                           *cpos |= LATTR_WRAPPED;
+                           if (curs.y == marg_b)
+                               scroll(marg_t, marg_b, 1, TRUE);
+                           else if (curs.y < rows - 1)
+                               curs.y++;
+                           curs.x = 0;
+                           fix_cpos;
                        }
+                       *cpos++ = UCSWIDE | curr_attr;
+                       break;
                      case 1:
                        *cpos++ = c | curr_attr;
                        break;
@@ -1409,7 +1468,7 @@ void term_out(void)
                    compatibility(VT100);
                    power_on();
                    if (reset_132) {
-                       request_resize(80, rows, 1);
+                       request_resize(80, rows);
                        reset_132 = 0;
                    }
                    fix_cpos;
@@ -1483,6 +1542,10 @@ void term_out(void)
                    compatibility(VT100);
                    cset_attr[0] = ATTR_LINEDRW;
                    break;
+                 case ANSI('U', '('): 
+                   compatibility(OTHER);
+                   cset_attr[0] = ATTR_SCOACS; 
+                   break;
 
                  case ANSI('A', ')'):
                    compatibility(VT100);
@@ -1496,6 +1559,10 @@ void term_out(void)
                    compatibility(VT100);
                    cset_attr[1] = ATTR_LINEDRW;
                    break;
+                 case ANSI('U', ')'): 
+                   compatibility(OTHER);
+                   cset_attr[1] = ATTR_SCOACS; 
+                   break;
 
                  case ANSI('8', '%'):  /* Old Linux code */
                  case ANSI('G', '%'):
@@ -1504,8 +1571,7 @@ void term_out(void)
                    break;
                  case ANSI('@', '%'):
                    compatibility(OTHER);
-                   if (line_codepage != CP_UTF8)
-                       utf = 0;
+                   utf = 0;
                    break;
                }
                break;
@@ -1763,6 +1829,15 @@ void term_out(void)
                                  case 7:       /* enable reverse video */
                                    curr_attr |= ATTR_REVERSE;
                                    break;
+                                 case 10:      /* SCO acs off */
+                                   compatibility(SCOANSI);
+                                   sco_acs = 0; break;
+                                 case 11:      /* SCO acs on */
+                                   compatibility(SCOANSI);
+                                   sco_acs = 1; break;
+                                 case 12:      /* SCO acs on flipped */
+                                   compatibility(SCOANSI);
+                                   sco_acs = 2; break;
                                  case 22:      /* disable bold */
                                    compatibility2(OTHER, VT220);
                                    curr_attr &= ~ATTR_BOLD;
@@ -1816,11 +1891,9 @@ void term_out(void)
                                }
                            }
                            if (use_bce)
-                               erase_char =
-                                   (' ' |
-                                    (curr_attr &
-                                     (ATTR_FGMASK | ATTR_BGMASK |
-                                      ATTR_BLINK)));
+                               erase_char = (' ' | ATTR_ASCII |
+                                            (curr_attr & 
+                                             (ATTR_FGMASK | ATTR_BGMASK)));
                        }
                        break;
                      case 's':       /* save cursor */
@@ -1840,7 +1913,7 @@ void term_out(void)
                        compatibility(VT340TEXT);
                        if (esc_nargs <= 1
                            && (esc_args[0] < 1 || esc_args[0] >= 24)) {
-                           request_resize(cols, def(esc_args[0], 24), 0);
+                           request_resize(cols, def(esc_args[0], 24));
                            deselect();
                        }
                        break;
@@ -1866,9 +1939,7 @@ void term_out(void)
                         */
                        compatibility(VT420);
                        if (esc_nargs == 1 && esc_args[0] > 0) {
-                           request_resize(cols,
-                                          def(esc_args[0], cfg.height),
-                                          0);
+                           request_resize(cols, def(esc_args[0], cfg.height));
                            deselect();
                        }
                        break;
@@ -1879,8 +1950,7 @@ void term_out(void)
                         */
                        compatibility(VT340TEXT);
                        if (esc_nargs <= 1) {
-                           request_resize(def(esc_args[0], cfg.width),
-                                          rows, 0);
+                           request_resize(def(esc_args[0], cfg.width), rows);
                            deselect();
                        }
                        break;
@@ -1913,15 +1983,29 @@ void term_out(void)
                            }
                        }
                        break;
+                     case 'Z':         /* BackTab for xterm */
+                       compatibility(OTHER);
+                       {
+                           int i = def(esc_args[0], 1);
+                           pos old_curs = curs;
+
+                           for(;i>0 && curs.x>0; i--) {
+                               do {
+                                   curs.x--;
+                               } while (curs.x >0 && !tabs[curs.x]);
+                           }
+                           fix_cpos;
+                           check_selection(old_curs, curs);
+                       }
+                       break;
                      case ANSI('L', '='):
                        compatibility(OTHER);
                        use_bce = (esc_args[0] <= 0);
                        erase_char = ERASE_CHAR;
                        if (use_bce)
-                           erase_char =
-                               (' ' |
-                                (curr_attr &
-                                 (ATTR_FGMASK | ATTR_BGMASK)));
+                           erase_char = (' ' | ATTR_ASCII |
+                                        (curr_attr & 
+                                         (ATTR_FGMASK | ATTR_BGMASK)));
                        break;
                      case ANSI('E', '='):
                        compatibility(OTHER);
@@ -1994,9 +2078,9 @@ void term_out(void)
                         */
                        if (!has_compat(VT420) && has_compat(VT100)) {
                            if (reset_132)
-                               request_resize(132, 24, 1);
+                               request_resize(132, 24);
                            else
-                               request_resize(80, 24, 1);
+                               request_resize(80, 24);
                        }
 #endif
                        break;
@@ -2313,10 +2397,9 @@ void term_out(void)
                    vt52_bold = FALSE;
                    curr_attr = ATTR_DEFAULT;
                    if (use_bce)
-                       erase_char = (' ' |
-                                     (curr_attr &
-                                      (ATTR_FGMASK | ATTR_BGMASK |
-                                       ATTR_BLINK)));
+                       erase_char = (' ' | ATTR_ASCII |
+                                    (curr_attr & 
+                                     (ATTR_FGMASK | ATTR_BGMASK)));
                    break;
                  case 'S':
                    /* compatibility(VI50) */
@@ -2358,10 +2441,8 @@ void term_out(void)
                    curr_attr |= ATTR_BOLD;
 
                if (use_bce)
-                   erase_char = (' ' |
-                                 (curr_attr &
-                                  (ATTR_FGMASK | ATTR_BGMASK |
-                                   ATTR_BLINK)));
+                   erase_char = (' ' | ATTR_ASCII |
+                                (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
                break;
              case VT52_BG:
                termstate = TOPLEVEL;
@@ -2374,10 +2455,8 @@ void term_out(void)
                    curr_attr |= ATTR_BLINK;
 
                if (use_bce)
-                   erase_char = (' ' |
-                                 (curr_attr &
-                                  (ATTR_FGMASK | ATTR_BGMASK |
-                                   ATTR_BLINK)));
+                   erase_char = (' ' | ATTR_ASCII |
+                                (curr_attr & (ATTR_FGMASK | ATTR_BGMASK)));
                break;
 #endif
              default: break;          /* placate gcc warning about enum use */
@@ -2456,9 +2535,10 @@ static void do_paint(Context ctx, int may_optimise)
     if (dispcurs && (curstype != cursor ||
                     dispcurs !=
                     disptext + our_curs_y * (cols + 1) + curs.x)) {
-       if (dispcurs > disptext && (dispcurs[-1] & ATTR_WIDE))
+       if (dispcurs > disptext && 
+               (*dispcurs & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
            dispcurs[-1] |= ATTR_INVALID;
-       if ((*dispcurs & ATTR_WIDE))
+       if ( (dispcurs[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
            dispcurs[1] |= ATTR_INVALID;
        *dispcurs |= ATTR_INVALID;
        curstype = 0;
@@ -2499,9 +2579,14 @@ static void do_paint(Context ctx, int may_optimise)
              case ATTR_LINEDRW:
                tchar = unitab_xterm[tchar & 0xFF];
                break;
+             case ATTR_SCOACS:  
+               tchar = unitab_scoacs[tchar&0xFF]; 
+               break;
            }
            tattr |= (tchar & CSET_MASK);
            tchar &= CHAR_MASK;
+           if ((d[1] & (CHAR_MASK | CSET_MASK)) == UCSWIDE)
+                   tattr |= ATTR_WIDE;
 
            /* Video reversing things */
            tattr = (tattr ^ rv
@@ -2518,6 +2603,17 @@ static void do_paint(Context ctx, int may_optimise)
                tattr &= ~ATTR_BLINK;
            }
 
+           /*
+            * Check the font we'll _probably_ be using to see if 
+            * the character is wide when we don't want it to be.
+            */
+           if ((tchar | tattr) != (disptext[idx]& ~ATTR_NARROW)) {
+               if ((tattr & ATTR_WIDE) == 0 && 
+                   CharWidth(ctx, (tchar | tattr) & 0xFFFF) == 2)
+                   tattr |= ATTR_NARROW;
+           } else if (disptext[idx]&ATTR_NARROW)
+               tattr |= ATTR_NARROW;
+
            /* Cursor here ? Save the 'background' */
            if (i == our_curs_y && j == curs.x) {
                cursor_background = tattr | tchar;
@@ -2638,14 +2734,14 @@ void term_invalidate(void)
 /*
  * Paint the window in response to a WM_PAINT message.
  */
-void term_paint(Context ctx, int l, int t, int r, int b)
+void term_paint(Context ctx, int left, int top, int right, int bottom)
 {
-    int i, j, left, top, right, bottom;
+    int i, j;
+    if (left < 0) left = 0;
+    if (top < 0) top = 0;
+    if (right >= cols) right = cols-1;
+    if (bottom >= rows) bottom = rows-1;
 
-    left = l / font_width;
-    right = (r - 1) / font_width;
-    top = t / font_height;
-    bottom = (b - 1) / font_height;
     for (i = top; i <= bottom && i < rows; i++) {
        if ((disptext[i * (cols + 1) + cols] & LATTR_MODE) == LATTR_NORM)
            for (j = left; j <= right && j < cols; j++)
@@ -2658,8 +2754,9 @@ void term_paint(Context ctx, int l, int t, int r, int b)
     /* This should happen soon enough, also for some reason it sometimes 
      * fails to actually do anything when re-sizing ... painting the wrong
      * window perhaps ?
-     do_paint (ctx, FALSE);
      */
+    if (alt_pressed)
+        do_paint (ctx, FALSE);
 }
 
 /*
@@ -2733,6 +2830,9 @@ static void clipme(pos top, pos bottom)
              case ATTR_ASCII:
                uc = unitab_line[uc & 0xFF];
                break;
+             case ATTR_SCOACS:  
+               uc = unitab_scoacs[uc&0xFF]; 
+               break;
            }
            switch (uc & CSET_MASK) {
              case ATTR_ACP:
@@ -2893,6 +2993,9 @@ static int wordtype(int uc)
       case ATTR_ASCII:
        uc = unitab_line[uc & 0xFF];
        break;
+      case ATTR_SCOACS:  
+       uc = unitab_scoacs[uc&0xFF]; 
+       break;
     }
     switch (uc & CSET_MASK) {
       case ATTR_ACP:
@@ -2903,6 +3006,12 @@ static int wordtype(int uc)
        break;
     }
 
+    /* For DBCS font's I can't do anything usefull. Even this will sometimes
+     * fail as there's such a thing as a double width space. :-(
+     */
+    if (dbcs_screenfont && font_codepage == line_codepage)
+       return (uc != ' ');
+
     if (uc < 0x80)
        return wordness[uc];
 
@@ -3025,11 +3134,18 @@ 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);
 
-    if (y < 0)
+    if (y < 0) {
        y = 0;
-    if (y >= rows)
+       if (a == MA_DRAG && !raw_mouse)
+           term_scroll(0, -1);
+    }
+    if (y >= rows) {
        y = rows - 1;
+       if (a == MA_DRAG && !raw_mouse)
+           term_scroll(0, +1);
+    }
     if (x < 0) {
        if (y > 0) {
            x = cols - 1;
@@ -3046,7 +3162,7 @@ void term_mouse(Mouse_Button b, Mouse_Action a, int x, int y,
     if ((ldata[cols] & LATTR_MODE) != LATTR_NORM)
        selpoint.x /= 2;
 
-    if (xterm_mouse) {
+    if (raw_mouse) {
        int encstate = 0, r, c;
        char abuf[16];
        static int is_down = 0;
@@ -3227,13 +3343,33 @@ int term_ldisc(int option)
 /*
  * from_backend(), to get data from the backend for the terminal.
  */
-void from_backend(int is_stderr, char *data, int len)
+int from_backend(int is_stderr, char *data, int len)
 {
     while (len--) {
        if (inbuf_head >= INBUF_SIZE)
            term_out();
        inbuf[inbuf_head++] = *data++;
     }
+
+    /*
+     * We process all stdout/stderr data immediately we receive it,
+     * and don't return until it's all gone. Therefore, there's no
+     * reason at all to return anything other than zero from this
+     * function.
+     * 
+     * This is a slightly suboptimal way to deal with SSH2 - in
+     * principle, the window mechanism would allow us to continue
+     * to accept data on forwarded ports and X connections even
+     * while the terminal processing was going slowly - but we
+     * can't do the 100% right thing without moving the terminal
+     * processing into a separate thread, and that might hurt
+     * portability. So we manage stdout buffering the old SSH1 way:
+     * if the terminal processing goes slowly, the whole SSH
+     * connection stops accepting data until it's ready.
+     * 
+     * In practice, I can't imagine this causing serious trouble.
+     */
+    return 0;
 }
 
 /*
@@ -3252,22 +3388,35 @@ void logtraffic(unsigned char c, int logmode)
     }
 }
 
+void settimstr(char *ta, int no_sec);
+char *subslfcode(char *dest, char *src, char *dstrt);
+char *stpncpy(char *dst, const char *src, size_t maxlen);
+char timdatbuf[20];
+char currlogfilename[FILENAME_MAX];
+
 /* open log file append/overwrite mode */
 void logfopen(void)
 {
     char buf[256];
     time_t t;
-    struct tm *tm;
+    struct tm tm;
     char writemod[4];
 
     if (!cfg.logtype)
        return;
     sprintf(writemod, "wb");          /* default to rewrite */
-    lgfp = fopen(cfg.logfilename, "r");        /* file already present? */
+
+    time(&t);
+    tm = *localtime(&t);
+
+    /* substitute special codes in file name */
+    xlatlognam(currlogfilename,cfg.logfilename,cfg.host, &tm);
+
+    lgfp = fopen(currlogfilename, "r");        /* file already present? */
     if (lgfp) {
        int i;
        fclose(lgfp);
-       i = askappend(cfg.logfilename);
+       i = askappend(currlogfilename);
        if (i == 1)
            writemod[0] = 'a';         /* set append mode */
        else if (i == 0) {             /* cancelled */
@@ -3277,22 +3426,20 @@ void logfopen(void)
        }
     }
 
-    lgfp = fopen(cfg.logfilename, writemod);
+    lgfp = fopen(currlogfilename, writemod);
     if (lgfp) {                               /* enter into event log */
        sprintf(buf, "%s session log (%s mode) to file : ",
                (writemod[0] == 'a') ? "Appending" : "Writing new",
                (cfg.logtype == LGTYP_ASCII ? "ASCII" :
                 cfg.logtype == LGTYP_DEBUG ? "raw" : "<ukwn>"));
        /* Make sure we do not exceed the output buffer size */
-       strncat(buf, cfg.logfilename, 128);
+       strncat(buf, currlogfilename, 128);
        buf[strlen(buf)] = '\0';
        logevent(buf);
 
-       /* --- write header line iinto log file */
+       /* --- write header line into log file */
        fputs("=~=~=~=~=~=~=~=~=~=~=~= PuTTY log ", lgfp);
-       time(&t);
-       tm = localtime(&t);
-       strftime(buf, 24, "%Y.%m.%d %H:%M:%S", tm);
+       strftime(buf, 24, "%Y.%m.%d %H:%M:%S", &tm);
        fputs(buf, lgfp);
        fputs(" =~=~=~=~=~=~=~=~=~=~=~=\r\n", lgfp);
     }
@@ -3305,3 +3452,57 @@ void logfclose(void)
        lgfp = NULL;
     }
 }
+
+/*
+ * translate format codes into time/date strings
+ * and insert them into log file name
+ *
+ * "&Y":YYYY   "&m":MM   "&d":DD   "&T":hhmm   "&h":<hostname>   "&&":&
+ */
+static void xlatlognam(char *d, char *s, char *hostname, struct tm *tm) {
+    char buf[10], *bufp;
+    int size;
+    char *ds = d; /* save start pos. */
+    int len = FILENAME_MAX-1;
+
+    while (*s) {
+       /* Let (bufp, len) be the string to append. */
+       bufp = buf;                    /* don't usually override this */
+       if (*s == '&') {
+           char c;
+           s++;
+           if (*s) switch (c = *s++, tolower(c)) {
+             case 'y':
+               size = strftime(buf, sizeof(buf), "%Y", tm);
+               break;
+             case 'm':
+               size = strftime(buf, sizeof(buf), "%m", tm);
+               break;
+             case 'd':
+               size = strftime(buf, sizeof(buf), "%d", tm);
+               break;
+             case 't':
+               size = strftime(buf, sizeof(buf), "%H%M%S", tm);
+               break;
+             case 'h':
+               bufp = hostname;
+               size = strlen(bufp);
+               break;
+             default:
+               buf[0] = '&';
+               size = 1;
+               if (c != '&')
+                   buf[size++] = c;
+           }
+       } else {
+           buf[0] = *s++;
+           size = 1;
+       }
+       if (size > len)
+           size = len;
+       memcpy(d, bufp, size);
+       d += size;
+       len -= size;
+    }
+    *d = '\0';
+}