Abe Crabtree complains that flushing the log file as often as we do in 0.56
[u/mdw/putty] / terminal.c
index f070579..54f86f8 100644 (file)
@@ -554,15 +554,39 @@ static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state)
      * store a two-byte value with the top bit clear (indicating
      * just that value), or a four-byte value with the top bit set
      * (indicating the same value with its top bit clear).
+     * 
+     * However, first I permute the bits of the attribute value, so
+     * that the eight bits of colour (four in each of fg and bg)
+     * which are never non-zero unless xterm 256-colour mode is in
+     * use are placed higher up the word than everything else. This
+     * ensures that attribute values remain 16-bit _unless_ the
+     * user uses extended colour.
      */
-    if (c->attr < 0x8000) {
-       add(b, (unsigned char)((c->attr >> 8) & 0xFF));
-       add(b, (unsigned char)(c->attr & 0xFF));
+    unsigned attr, colourbits;
+
+    attr = c->attr;
+
+    assert(ATTR_BGSHIFT > ATTR_FGSHIFT);
+
+    colourbits = (attr >> (ATTR_BGSHIFT + 4)) & 0xF;
+    colourbits <<= 4;
+    colourbits |= (attr >> (ATTR_FGSHIFT + 4)) & 0xF;
+
+    attr = (((attr >> (ATTR_BGSHIFT + 8)) << (ATTR_BGSHIFT + 4)) |
+           (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+    attr = (((attr >> (ATTR_FGSHIFT + 8)) << (ATTR_FGSHIFT + 4)) |
+           (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+
+    attr |= (colourbits << (32-9));
+
+    if (attr < 0x8000) {
+       add(b, (unsigned char)((attr >> 8) & 0xFF));
+       add(b, (unsigned char)(attr & 0xFF));
     } else {
-       add(b, (unsigned char)(((c->attr >> 24) & 0x7F) | 0x80));
-       add(b, (unsigned char)((c->attr >> 16) & 0xFF));
-       add(b, (unsigned char)((c->attr >> 8) & 0xFF));
-       add(b, (unsigned char)(c->attr & 0xFF));
+       add(b, (unsigned char)(((attr >> 24) & 0x7F) | 0x80));
+       add(b, (unsigned char)((attr >> 16) & 0xFF));
+       add(b, (unsigned char)((attr >> 8) & 0xFF));
+       add(b, (unsigned char)(attr & 0xFF));
     }
 }
 static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state)
@@ -758,18 +782,30 @@ static void readliteral_chr(struct buf *b, termchar *c, termline *ldata,
 static void readliteral_attr(struct buf *b, termchar *c, termline *ldata,
                             unsigned long *state)
 {
-    int val;
+    unsigned val, attr, colourbits;
 
     val = get(b) << 8;
     val |= get(b);
 
     if (val >= 0x8000) {
+       val &= ~0x8000;
        val <<= 16;
        val |= get(b) << 8;
        val |= get(b);
     }
 
-    c->attr = val;
+    colourbits = (val >> (32-9)) & 0xFF;
+    attr = (val & ((1<<(32-9))-1));
+
+    attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
+           (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+    attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
+           (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+
+    attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
+    attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
+
+    c->attr = attr;
 }
 static void readliteral_cc(struct buf *b, termchar *c, termline *ldata,
                           unsigned long *state)
@@ -1024,6 +1060,14 @@ static void term_timer(void *ctx, long now)
        term_update(term);
 }
 
+static void term_schedule_update(Terminal *term)
+{
+    if (!term->window_update_pending) {
+       term->window_update_pending = TRUE;
+       term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
+    }
+}
+
 /*
  * Call this whenever the terminal window state changes, to queue
  * an update.
@@ -1031,10 +1075,7 @@ static void term_timer(void *ctx, long now)
 static void seen_disp_event(Terminal *term)
 {
     term->seen_disp_event = TRUE;      /* for scrollback-reset-on-activity */
-    if (!term->window_update_pending) {
-       term->window_update_pending = TRUE;
-       term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
-    }
+    term_schedule_update(term);
 }
 
 /*
@@ -1043,10 +1084,9 @@ static void seen_disp_event(Terminal *term)
  */
 static void term_schedule_tblink(Terminal *term)
 {
-    if (term->tblink_pending)
-       return;                        /* already well in hand */
     if (term->blink_is_real) {
-       term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term);
+       if (!term->tblink_pending)
+           term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term);
        term->tblink_pending = TRUE;
     } else {
        term->tblinker = 1;            /* reset when not in use */
@@ -1059,10 +1099,9 @@ static void term_schedule_tblink(Terminal *term)
  */
 static void term_schedule_cblink(Terminal *term)
 {
-    if (term->cblink_pending)
-       return;                        /* already well in hand */
-    if (term->cfg.blink_cur) {
-       term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term);
+    if (term->cfg.blink_cur && term->has_focus) {
+       if (!term->cblink_pending)
+           term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term);
        term->cblink_pending = TRUE;
     } else {
        term->cblinker = 1;            /* reset when not in use */
@@ -3473,7 +3512,7 @@ static void term_out(Terminal *term)
                                    /* xterm-style bright foreground */
                                    term->curr_attr &= ~ATTR_FGMASK;
                                    term->curr_attr |=
-                                       ((term->esc_args[i] - 90 + 16)
+                                       ((term->esc_args[i] - 90 + 8)
                                          << ATTR_FGSHIFT);
                                    break;
                                  case 39:      /* default-foreground */
@@ -3504,13 +3543,33 @@ static void term_out(Terminal *term)
                                    /* xterm-style bright background */
                                    term->curr_attr &= ~ATTR_BGMASK;
                                    term->curr_attr |=
-                                       ((term->esc_args[i] - 100 + 16)
+                                       ((term->esc_args[i] - 100 + 8)
                                          << ATTR_BGSHIFT);
                                    break;
                                  case 49:      /* default-background */
                                    term->curr_attr &= ~ATTR_BGMASK;
                                    term->curr_attr |= ATTR_DEFBG;
                                    break;
+                                 case 38:   /* xterm 256-colour mode */
+                                   if (i+2 < term->esc_nargs &&
+                                       term->esc_args[i+1] == 5) {
+                                       term->curr_attr &= ~ATTR_FGMASK;
+                                       term->curr_attr |=
+                                           ((term->esc_args[i+2] & 0xFF)
+                                            << ATTR_FGSHIFT);
+                                       i += 2;
+                                   }
+                                   break;
+                                 case 48:   /* xterm 256-colour mode */
+                                   if (i+2 < term->esc_nargs &&
+                                       term->esc_args[i+1] == 5) {
+                                       term->curr_attr &= ~ATTR_BGMASK;
+                                       term->curr_attr |=
+                                           ((term->esc_args[i+2] & 0xFF)
+                                            << ATTR_BGSHIFT);
+                                       i += 2;
+                                   }
+                                   break;
                                }
                            }
                            set_erase_char(term);
@@ -3803,7 +3862,7 @@ static void term_out(Terminal *term)
                        if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
-                                ((term->esc_args[0] & 0x8) << 1)) <<
+                                (term->esc_args[0] & 0x8)) <<
                                ATTR_FGSHIFT;
                            term->curr_attr &= ~ATTR_FGMASK;
                            term->curr_attr |= colour;
@@ -3816,7 +3875,7 @@ static void term_out(Terminal *term)
                        if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
-                                ((term->esc_args[0] & 0x8) << 1)) <<
+                                (term->esc_args[0] & 0x8)) <<
                                ATTR_BGSHIFT;
                            term->curr_attr &= ~ATTR_BGMASK;
                            term->curr_attr |= colour;
@@ -4257,22 +4316,14 @@ static void term_out(Terminal *term)
                term->termstate = TOPLEVEL;
                term->curr_attr &= ~ATTR_FGMASK;
                term->curr_attr &= ~ATTR_BOLD;
-               term->curr_attr |= (c & 0x7) << ATTR_FGSHIFT;
-               if ((c & 0x8) || term->vt52_bold)
-                   term->curr_attr |= ATTR_BOLD;
-
+               term->curr_attr |= (c & 0xF) << ATTR_FGSHIFT;
                set_erase_char(term);
                break;
              case VT52_BG:
                term->termstate = TOPLEVEL;
                term->curr_attr &= ~ATTR_BGMASK;
                term->curr_attr &= ~ATTR_BLINK;
-               term->curr_attr |= (c & 0x7) << ATTR_BGSHIFT;
-
-               /* Note: bold background */
-               if (c & 0x8)
-                   term->curr_attr |= ATTR_BLINK;
-
+               term->curr_attr |= (c & 0xF) << ATTR_BGSHIFT;
                set_erase_char(term);
                break;
 #endif
@@ -4286,7 +4337,8 @@ static void term_out(Terminal *term)
     }
 
     term_print_flush(term);
-    logflush(term->logctx);
+    if (term->cfg.logflush)
+       logflush(term->logctx);
 }
 
 /*
@@ -4319,8 +4371,11 @@ static int term_bidi_cache_hit(Terminal *term, int line,
 }
 
 static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
-                                 termchar *lafter, int width)
+                                 termchar *lafter, bidi_char *wcTo,
+                                 int width, int size)
 {
+    int i;
+
     if (!term->pre_bidi_cache || term->bidi_cache_size <= line) {
        int j = term->bidi_cache_size;
        term->bidi_cache_size = line+1;
@@ -4335,20 +4390,141 @@ static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
                term->post_bidi_cache[j].chars = NULL;
            term->pre_bidi_cache[j].width =
                term->post_bidi_cache[j].width = -1;
+           term->pre_bidi_cache[j].forward =
+               term->post_bidi_cache[j].forward = NULL;
+           term->pre_bidi_cache[j].backward =
+               term->post_bidi_cache[j].backward = NULL;
            j++;
        }
     }
 
     sfree(term->pre_bidi_cache[line].chars);
     sfree(term->post_bidi_cache[line].chars);
+    sfree(term->post_bidi_cache[line].forward);
+    sfree(term->post_bidi_cache[line].backward);
 
     term->pre_bidi_cache[line].width = width;
-    term->pre_bidi_cache[line].chars = snewn(width, termchar);
+    term->pre_bidi_cache[line].chars = snewn(size, termchar);
     term->post_bidi_cache[line].width = width;
-    term->post_bidi_cache[line].chars = snewn(width, termchar);
+    term->post_bidi_cache[line].chars = snewn(size, termchar);
+    term->post_bidi_cache[line].forward = snewn(width, int);
+    term->post_bidi_cache[line].backward = snewn(width, int);
+
+    memcpy(term->pre_bidi_cache[line].chars, lbefore, size * TSIZE);
+    memcpy(term->post_bidi_cache[line].chars, lafter, size * TSIZE);
+    memset(term->post_bidi_cache[line].forward, 0, width * sizeof(int));
+    memset(term->post_bidi_cache[line].backward, 0, width * sizeof(int));
+
+    for (i = 0; i < width; i++) {
+       int p = wcTo[i].index;
+
+       assert(0 <= p && p < width);
+
+       term->post_bidi_cache[line].backward[i] = p;
+       term->post_bidi_cache[line].forward[p] = i;
+    }
+}
+
+/*
+ * Prepare the bidi information for a screen line. Returns the
+ * transformed list of termchars, or NULL if no transformation at
+ * all took place (because bidi is disabled). If return was
+ * non-NULL, auxiliary information such as the forward and reverse
+ * mappings of permutation position are available in
+ * term->post_bidi_cache[scr_y].*.
+ */
+static termchar *term_bidi_line(Terminal *term, struct termline *ldata,
+                               int scr_y)
+{
+    termchar *lchars;
+    int it;
+
+    /* Do Arabic shaping and bidi. */
+    if(!term->cfg.bidi || !term->cfg.arabicshaping) {
+
+       if (!term_bidi_cache_hit(term, scr_y, ldata->chars, term->cols)) {
+
+           if (term->wcFromTo_size < term->cols) {
+               term->wcFromTo_size = term->cols;
+               term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size,
+                                      bidi_char);
+               term->wcTo = sresize(term->wcTo, term->wcFromTo_size,
+                                    bidi_char);
+           }
+
+           for(it=0; it<term->cols ; it++)
+           {
+               unsigned long uc = (ldata->chars[it].chr);
+
+               switch (uc & CSET_MASK) {
+                 case CSET_LINEDRW:
+                   if (!term->cfg.rawcnp) {
+                       uc = term->ucsdata->unitab_xterm[uc & 0xFF];
+                       break;
+                   }
+                 case CSET_ASCII:
+                   uc = term->ucsdata->unitab_line[uc & 0xFF];
+                   break;
+                 case CSET_SCOACS:
+                   uc = term->ucsdata->unitab_scoacs[uc&0xFF];
+                   break;
+               }
+               switch (uc & CSET_MASK) {
+                 case CSET_ACP:
+                   uc = term->ucsdata->unitab_font[uc & 0xFF];
+                   break;
+                 case CSET_OEMCP:
+                   uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
+                   break;
+               }
+
+               term->wcFrom[it].origwc = term->wcFrom[it].wc =
+                   (wchar_t)uc;
+               term->wcFrom[it].index = it;
+           }
+
+           if(!term->cfg.bidi)
+               do_bidi(term->wcFrom, term->cols);
+
+           /* this is saved iff done from inside the shaping */
+           if(!term->cfg.bidi && term->cfg.arabicshaping)
+               for(it=0; it<term->cols; it++)
+                   term->wcTo[it] = term->wcFrom[it];
 
-    memcpy(term->pre_bidi_cache[line].chars, lbefore, width * TSIZE);
-    memcpy(term->post_bidi_cache[line].chars, lafter, width * TSIZE);
+           if(!term->cfg.arabicshaping)
+               do_shape(term->wcFrom, term->wcTo, term->cols);
+
+           if (term->ltemp_size < ldata->size) {
+               term->ltemp_size = ldata->size;
+               term->ltemp = sresize(term->ltemp, term->ltemp_size,
+                                     termchar);
+           }
+
+           memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE);
+
+           for(it=0; it<term->cols ; it++)
+           {
+               term->ltemp[it] = ldata->chars[term->wcTo[it].index];
+               if (term->ltemp[it].cc_next)
+                   term->ltemp[it].cc_next -=
+                   it - term->wcTo[it].index;
+
+               if (term->wcTo[it].origwc != term->wcTo[it].wc)
+                   term->ltemp[it].chr = term->wcTo[it].wc;
+           }
+           term_bidi_cache_store(term, scr_y, ldata->chars,
+                                 term->ltemp, term->wcTo,
+                                  term->cols, ldata->size);
+
+           lchars = term->ltemp;
+       } else {
+           lchars = term->post_bidi_cache[scr_y].chars;
+       }
+    } else {
+       lchars = NULL;
+    }
+
+    return lchars;
 }
 
 /*
@@ -4357,7 +4533,7 @@ static void term_bidi_cache_store(Terminal *term, int line, termchar *lbefore,
  */
 static void do_paint(Terminal *term, Context ctx, int may_optimise)
 {
-    int i, it, j, our_curs_y, our_curs_x;
+    int i, j, our_curs_y, our_curs_x;
     int rv, cursor;
     pos scrpos;
     wchar_t *ch;
@@ -4397,17 +4573,28 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
     our_curs_y = term->curs.y - term->disptop;
     {
        /*
-        * Adjust the cursor position in the case where it's
-        * resting on the right-hand half of a CJK wide character.
-        * xterm's behaviour here, which seems adequate to me, is
-        * to display the cursor covering the _whole_ character,
-        * exactly as if it were one space to the left.
+        * Adjust the cursor position:
+        *  - for bidi
+        *  - in the case where it's resting on the right-hand half
+        *    of a CJK wide character. xterm's behaviour here,
+        *    which seems adequate to me, is to display the cursor
+        *    covering the _whole_ character, exactly as if it were
+        *    one space to the left.
         */
        termline *ldata = lineptr(term->curs.y);
+       termchar *lchars;
+
        our_curs_x = term->curs.x;
+
+       if ( (lchars = term_bidi_line(term, ldata, our_curs_y)) != NULL) {
+           our_curs_x = term->post_bidi_cache[our_curs_y].forward[our_curs_x];
+       } else
+           lchars = ldata->chars;
+
        if (our_curs_x > 0 &&
-           ldata->chars[our_curs_x].chr == UCSWIDE)
+           lchars[our_curs_x].chr == UCSWIDE)
            our_curs_x--;
+
        unlineptr(ldata);
     }
 
@@ -4455,6 +4642,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
        int start = 0;
        int ccount = 0;
        int last_run_dirty = 0;
+       int *backward;
 
        scrpos.y = i + term->disptop;
        ldata = lineptr(scrpos.y);
@@ -4464,93 +4652,19 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
        term->disptext[i]->lattr = ldata->lattr;
 
        /* Do Arabic shaping and bidi. */
-       if(!term->cfg.bidi || !term->cfg.arabicshaping) {
-
-           if (!term_bidi_cache_hit(term, i, ldata->chars, term->cols)) {
-
-               if (term->wcFromTo_size < term->cols) {
-                   term->wcFromTo_size = term->cols;
-                   term->wcFrom = sresize(term->wcFrom, term->wcFromTo_size,
-                                          bidi_char);
-                   term->wcTo = sresize(term->wcTo, term->wcFromTo_size,
-                                        bidi_char);
-               }
-
-               for(it=0; it<term->cols ; it++)
-               {
-                   unsigned long uc = (ldata->chars[it].chr);
-
-                   switch (uc & CSET_MASK) {
-                     case CSET_LINEDRW:
-                       if (!term->cfg.rawcnp) {
-                           uc = term->ucsdata->unitab_xterm[uc & 0xFF];
-                           break;
-                       }
-                     case CSET_ASCII:
-                       uc = term->ucsdata->unitab_line[uc & 0xFF];
-                       break;
-                     case CSET_SCOACS:
-                       uc = term->ucsdata->unitab_scoacs[uc&0xFF];
-                       break;
-                   }
-                   switch (uc & CSET_MASK) {
-                     case CSET_ACP:
-                       uc = term->ucsdata->unitab_font[uc & 0xFF];
-                       break;
-                     case CSET_OEMCP:
-                       uc = term->ucsdata->unitab_oemcp[uc & 0xFF];
-                       break;
-                   }
-
-                   term->wcFrom[it].origwc = term->wcFrom[it].wc =
-                       (wchar_t)uc;
-                   term->wcFrom[it].index = it;
-               }
-
-               if(!term->cfg.bidi)
-                   do_bidi(term->wcFrom, term->cols);
-
-               /* this is saved iff done from inside the shaping */
-               if(!term->cfg.bidi && term->cfg.arabicshaping)
-                   for(it=0; it<term->cols; it++)
-                       term->wcTo[it] = term->wcFrom[it];
-
-               if(!term->cfg.arabicshaping)
-                   do_shape(term->wcFrom, term->wcTo, term->cols);
-
-               if (term->ltemp_size < ldata->size) {
-                   term->ltemp_size = ldata->size;
-                   term->ltemp = sresize(term->ltemp, term->ltemp_size,
-                                         termchar);
-               }
-
-               memcpy(term->ltemp, ldata->chars, ldata->size * TSIZE);
-
-               for(it=0; it<term->cols ; it++)
-               {
-                   term->ltemp[it] = ldata->chars[term->wcTo[it].index];
-                   if (term->ltemp[it].cc_next)
-                       term->ltemp[it].cc_next -=
-                       it - term->wcTo[it].index;
-
-                   if (term->wcTo[it].origwc != term->wcTo[it].wc)
-                       term->ltemp[it].chr = term->wcTo[it].wc;
-               }
-               term_bidi_cache_store(term, i, ldata->chars,
-                                     term->ltemp, ldata->size);
-
-               lchars = term->ltemp;
-           } else {
-               lchars = term->post_bidi_cache[i].chars;
-           }
-       } else
+       lchars = term_bidi_line(term, ldata, i);
+       if (lchars) {
+           backward = term->post_bidi_cache[i].backward;
+       } else {
            lchars = ldata->chars;
+           backward = NULL;
+       }
 
        for (j = 0; j < term->cols; j++) {
            unsigned long tattr, tchar;
            termchar *d = lchars + j;
            int break_run, do_copy;
-           scrpos.x = j;
+           scrpos.x = backward ? backward[j] : j;
 
            tchar = d->chr;
            tattr = d->attr;
@@ -4559,6 +4673,16 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
                 tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | 
                 ATTR_DEFFG | ATTR_DEFBG;
 
+           if (!term->cfg.xterm_256_colour) {
+               int colour;
+               colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT;
+               if (colour >= 16 && colour < 256)
+                   tattr = (tattr &~ ATTR_FGMASK) | ATTR_DEFFG;
+               colour = (tattr & ATTR_BGMASK) >> ATTR_BGSHIFT;
+               if (colour >= 16 && colour < 256)
+                   tattr = (tattr &~ ATTR_BGMASK) | ATTR_DEFBG;
+           }
+
            switch (tchar & CSET_MASK) {
              case CSET_ASCII:
                tchar = term->ucsdata->unitab_line[tchar & 0xFF];
@@ -4796,6 +4920,8 @@ void term_invalidate(Terminal *term)
     for (i = 0; i < term->rows; i++)
        for (j = 0; j < term->cols; j++)
            term->disptext[i]->chars[j].attr = ATTR_INVALID;
+
+    term_schedule_update(term);
 }
 
 /*
@@ -4822,10 +4948,7 @@ void term_paint(Terminal *term, Context ctx,
     if (immediately) {
         do_paint (term, ctx, FALSE);
     } else {
-       if (!term->window_update_pending) {
-           term->window_update_pending = TRUE;
-           term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term);
-       }
+       term_schedule_update(term);
     }
 }
 
@@ -5343,10 +5466,20 @@ void term_mouse(Terminal *term, Mouse_Button braw, Mouse_Button bcooked,
        x = term->cols - 1;
 
     selpoint.y = y + term->disptop;
-    selpoint.x = x;
     ldata = lineptr(selpoint.y);
+
     if ((ldata->lattr & LATTR_MODE) != LATTR_NORM)
-       selpoint.x /= 2;
+       x /= 2;
+
+    /*
+     * Transform x through the bidi algorithm to find the _logical_
+     * click point from the physical one.
+     */
+    if (term_bidi_line(term, ldata, y) != NULL) {
+       x = term->post_bidi_cache[y].backward[x];
+    }
+
+    selpoint.x = x;
     unlineptr(ldata);
 
     if (raw_mouse) {
@@ -6074,3 +6207,9 @@ void term_provide_logctx(Terminal *term, void *logctx)
 {
     term->logctx = logctx;
 }
+
+void term_set_focus(Terminal *term, int has_focus)
+{
+    term->has_focus = has_focus;
+    term_schedule_cblink(term);
+}