Coverage Report

Created: 2026-10-17 06:19

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/status.c
Line
Count
Source
1
/* $OpenBSD: status.c,v 1.275 2026/10/16 07:59:06 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
#include <sys/time.h>
21
22
#include <errno.h>
23
#include <limits.h>
24
#include <stdarg.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <time.h>
28
#include <unistd.h>
29
30
#include "tmux.h"
31
32
static void  status_message_area(struct client *, u_int *, u_int *);
33
static void  status_message_callback(int, short, void *);
34
static void  status_timer_callback(int, short, void *);
35
36
/* Status timer callback. */
37
static void
38
status_timer_callback(__unused int fd, __unused short events, void *arg)
39
0
{
40
0
  struct client *c = arg;
41
0
  struct session  *s = c->session;
42
0
  struct timeval   tv;
43
44
0
  evtimer_del(&c->status.timer);
45
46
0
  if (s == NULL)
47
0
    return;
48
49
0
  if (c->message_string == NULL && c->prompt == NULL)
50
0
    c->flags |= CLIENT_REDRAWSTATUS;
51
52
0
  timerclear(&tv);
53
0
  tv.tv_sec = options_get_number(s->options, "status-interval");
54
55
0
  if (tv.tv_sec != 0)
56
0
    evtimer_add(&c->status.timer, &tv);
57
0
  log_debug("client %p, status interval %d", c, (int)tv.tv_sec);
58
0
}
59
60
/* Start status timer for client. */
61
void
62
status_timer_start(struct client *c)
63
0
{
64
0
  struct session  *s = c->session;
65
66
0
  if (event_initialized(&c->status.timer))
67
0
    evtimer_del(&c->status.timer);
68
0
  else
69
0
    evtimer_set(&c->status.timer, status_timer_callback, c);
70
71
0
  if (s != NULL &&
72
0
      (options_get_number(s->options, "status") ||
73
0
      options_get_number(s->options, "side-status")))
74
0
    status_timer_callback(-1, 0, c);
75
0
}
76
77
/* Start status timer for all clients. */
78
void
79
status_timer_start_all(void)
80
0
{
81
0
  struct client *c;
82
83
0
  TAILQ_FOREACH(c, &clients, entry)
84
0
    status_timer_start(c);
85
0
}
86
87
/* Update status cache. */
88
void
89
status_update_cache(struct session *s)
90
0
{
91
0
  struct options  *oo = s->options;
92
93
0
  s->statuslines = options_get_number(oo, "status");
94
0
  if (s->statuslines == 0)
95
0
    s->statusat = -1;
96
0
  else if (options_get_number(oo, "status-position") == 0)
97
0
    s->statusat = 0;
98
0
  else
99
0
    s->statusat = 1;
100
101
0
  switch (options_get_number(oo, "side-status")) {
102
0
  case 1:
103
0
    s->sidestatusat = 0;
104
0
    break;
105
0
  case 2:
106
0
    s->sidestatusat = 1;
107
0
    break;
108
0
  default:
109
0
    s->sidestatusat = -1;
110
0
    break;
111
0
  }
112
0
  s->sidestatuswidth = options_get_number(oo, "side-status-width");
113
0
}
114
115
/* Get screen line of status line. -1 means off. */
116
int
117
status_at_line(struct client *c)
118
0
{
119
0
  struct session  *s = c->session;
120
121
0
  if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
122
0
    return (-1);
123
0
  if (s->statusat != 1)
124
0
    return (s->statusat);
125
0
  return (c->tty.sy - status_line_size(c));
126
0
}
127
128
/* Get size of status line for client's session. 0 means off. */
129
u_int
130
status_line_size(struct client *c)
131
0
{
132
0
  struct session  *s = c->session;
133
134
0
  if (c->flags & (CLIENT_STATUSOFF|CLIENT_CONTROL))
135
0
    return (0);
136
0
  if (s == NULL)
137
0
    return (options_get_number(global_s_options, "status"));
138
0
  return (s->statuslines);
139
0
}
140
141
/* Get width of side status line for client's session. 0 means off. */
142
u_int
143
status_side_size(struct client *c)
144
0
{
145
0
  struct session  *s = c->session;
146
147
0
  if (c->flags & (CLIENT_SIDESTATUSOFF|CLIENT_CONTROL))
148
0
    return (0);
149
0
  if (s == NULL || s->sidestatusat == -1)
150
0
    return (0);
151
0
  return (s->sidestatuswidth);
152
0
}
153
154
/* Get starting column of side status line. -1 means off. */
155
int
156
status_side_at_column(struct client *c)
157
0
{
158
0
  struct session  *s = c->session;
159
0
  u_int    width = status_side_size(c);
160
161
0
  if (width == 0)
162
0
    return (-1);
163
0
  if (s->sidestatusat == 0)
164
0
    return (0);
165
0
  return (c->tty.sx - width);
166
0
}
167
168
/* Get number of rows of side status line for client. */
169
u_int
170
status_side_rows(struct client *c)
171
0
{
172
0
  u_int lines = status_line_size(c);
173
174
0
  if (status_side_size(c) == 0 || c->tty.sy <= lines)
175
0
    return (0);
176
0
  return (c->tty.sy - lines);
177
0
}
178
179
/* Get the prompt line number for client's session. 1 means at the bottom. */
180
u_int
181
status_prompt_line_at(struct client *c)
182
0
{
183
0
  struct session  *s = c->session;
184
0
  u_int    line, lines;
185
186
0
  lines = status_line_size(c);
187
0
  if (lines == 0)
188
0
    return (0);
189
0
  line = options_get_number(s->options, "message-line");
190
0
  if (line >= lines)
191
0
    return (lines - 1);
192
0
  return (line);
193
0
}
194
195
/* Get window at window list position. */
196
struct style_range *
197
status_get_range(struct client *c, u_int x, u_int y)
198
0
{
199
0
  struct status_line  *sl = &c->status;
200
201
0
  if (y >= nitems(sl->entries))
202
0
    return (NULL);
203
0
  return (style_ranges_get_range(&sl->entries[y].ranges, x));
204
0
}
205
206
/* Save old status line. */
207
static void
208
status_push_screen(struct client *c)
209
0
{
210
0
  struct status_line *sl = &c->status;
211
212
0
  if (sl->active == &sl->screen) {
213
0
    sl->active = xmalloc(sizeof *sl->active);
214
0
    screen_init(sl->active, c->tty.sx, status_line_size(c), 0);
215
0
  }
216
0
  sl->references++;
217
0
}
218
219
/* Restore old status line. */
220
static void
221
status_pop_screen(struct client *c)
222
0
{
223
0
  struct status_line *sl = &c->status;
224
225
0
  if (--sl->references == 0) {
226
0
    screen_free(sl->active);
227
0
    free(sl->active);
228
0
    sl->active = &sl->screen;
229
0
  }
230
0
}
231
232
/* Initialize status line. */
233
void
234
status_init(struct client *c)
235
0
{
236
0
  struct status_line  *sl = &c->status;
237
0
  u_int      i;
238
239
0
  for (i = 0; i < nitems(sl->entries); i++)
240
0
    style_ranges_init(&sl->entries[i].ranges);
241
242
0
  screen_init(&sl->screen, c->tty.sx, 1, 0);
243
0
  sl->active = &sl->screen;
244
0
}
245
246
/* Free status line. */
247
void
248
status_free(struct client *c)
249
0
{
250
0
  struct status_line  *sl = &c->status;
251
0
  u_int      i;
252
253
0
  for (i = 0; i < nitems(sl->entries); i++) {
254
0
    style_ranges_free(&sl->entries[i].ranges);
255
0
    free((void *)sl->entries[i].expanded);
256
0
  }
257
258
0
  if (event_initialized(&sl->timer))
259
0
    evtimer_del(&sl->timer);
260
261
0
  if (sl->active != &sl->screen) {
262
0
    screen_free(sl->active);
263
0
    free(sl->active);
264
0
  }
265
0
  screen_free(&sl->screen);
266
0
}
267
268
/* Draw status line for client. */
269
int
270
status_redraw(struct client *c)
271
0
{
272
0
  struct status_line    *sl = &c->status;
273
0
  struct style_line_entry   *sle;
274
0
  struct session      *s = c->session;
275
0
  struct screen_write_ctx    ctx;
276
0
  struct grid_cell     gc;
277
0
  u_int        lines, i, n, width = c->tty.sx;
278
0
  int        flags, force = 0, changed = 0, fg, bg;
279
0
  struct options_entry    *o;
280
0
  union options_value   *ov;
281
0
  struct format_tree    *ft;
282
0
  char        *expanded;
283
284
0
  log_debug("%s enter", __func__);
285
286
  /* Shouldn't get here if not the active screen. */
287
0
  if (sl->active != &sl->screen)
288
0
    fatalx("not the active screen");
289
290
  /* No status line? */
291
0
  lines = status_line_size(c);
292
0
  if (c->tty.sy == 0 || lines == 0)
293
0
    return (1);
294
295
  /* Create format tree. */
296
0
  flags = FORMAT_STATUS;
297
0
  if (c->flags & CLIENT_STATUSFORCE)
298
0
    flags |= FORMAT_FORCE;
299
0
  ft = format_create(c, NULL, FORMAT_NONE, flags);
300
0
  format_defaults(ft, c, NULL, NULL, NULL);
301
302
  /* Set up default colour. */
303
0
  style_apply(&gc, s->options, "status-style", ft);
304
0
  fg = options_get_number(s->options, "status-fg");
305
0
  if (!COLOUR_DEFAULT(fg))
306
0
    gc.fg = fg;
307
0
  bg = options_get_number(s->options, "status-bg");
308
0
  if (!COLOUR_DEFAULT(bg))
309
0
    gc.bg = bg;
310
0
  if (!grid_cells_equal(&gc, &sl->style)) {
311
0
    force = 1;
312
0
    memcpy(&sl->style, &gc, sizeof sl->style);
313
0
  }
314
315
  /* Resize the target screen. */
316
0
  if (screen_size_x(&sl->screen) != width ||
317
0
      screen_size_y(&sl->screen) != lines) {
318
0
    screen_resize(&sl->screen, width, lines, 0);
319
0
    changed = force = 1;
320
0
  }
321
0
  screen_write_start(&ctx, &sl->screen);
322
323
  /* Write the status lines. */
324
0
  o = options_get(s->options, "status-format");
325
0
  if (o == NULL) {
326
0
    for (n = 0; n < width * lines; n++)
327
0
      screen_write_putc(&ctx, &gc, ' ');
328
0
  } else {
329
0
    for (i = 0; i < lines; i++) {
330
0
      screen_write_cursormove(&ctx, 0, i, 0);
331
332
0
      ov = options_array_getv(o, "%u", i);
333
0
      if (ov == NULL) {
334
0
        for (n = 0; n < width; n++)
335
0
          screen_write_putc(&ctx, &gc, ' ');
336
0
        continue;
337
0
      }
338
0
      sle = &sl->entries[i];
339
340
0
      expanded = format_expand_time(ft, ov->string);
341
0
      if (!force &&
342
0
          sle->expanded != NULL &&
343
0
          strcmp(expanded, sle->expanded) == 0) {
344
0
        free(expanded);
345
0
        continue;
346
0
      }
347
0
      changed = 1;
348
349
0
      for (n = 0; n < width; n++)
350
0
        screen_write_putc(&ctx, &gc, ' ');
351
0
      screen_write_cursormove(&ctx, 0, i, 0);
352
353
0
      style_ranges_free(&sle->ranges);
354
0
      format_draw(&ctx, &gc, width, expanded, &sle->ranges,
355
0
          0);
356
357
0
      free(sle->expanded);
358
0
      sle->expanded = expanded;
359
0
    }
360
0
  }
361
0
  screen_write_stop(&ctx);
362
363
  /* Free the format tree. */
364
0
  format_free(ft);
365
366
  /* Return if the status line has changed. */
367
0
  log_debug("%s exit: force=%d, changed=%d", __func__, force, changed);
368
0
  return (force || changed);
369
0
}
370
371
/* Prepare side status line. */
372
void
373
status_side_init(struct client *c)
374
0
{
375
0
  struct side_status_line *ss = &c->side_status;
376
377
0
  screen_init(&ss->screen, 1, 1, 0);
378
0
  ss->linex = -1;
379
0
  style_ranges_init(&ss->ranges);
380
0
}
381
382
/* Free side status line. */
383
void
384
status_side_free(struct client *c)
385
0
{
386
0
  struct side_status_line *ss = &c->side_status;
387
388
0
  style_ranges_free(&ss->ranges);
389
0
  free(ss->expanded);
390
0
  screen_free(&ss->screen);
391
0
}
392
393
/*
394
 * Get the column of the ACS line separating the side status line from the
395
 * window area, or -1 if there is none. The line takes the edge column
396
 * nearest the window area out of the side status width.
397
 */
398
static int
399
status_side_line_at(struct client *c)
400
0
{
401
0
  struct session  *s = c->session;
402
0
  u_int    width = status_side_size(c);
403
404
0
  if (width < 2)
405
0
    return (-1);
406
0
  if (s->sidestatusat == 0)
407
0
    return (width - 1);
408
0
  return (0);
409
0
}
410
411
/* Get side status range for position. */
412
struct style_range *
413
status_side_get_range(struct client *c, u_int x, u_int y)
414
0
{
415
0
  struct side_status_line *ss = &c->side_status;
416
417
  /* On a right side status line, content starts after the line. */
418
0
  if (status_side_line_at(c) == 0) {
419
0
    if (x == 0)
420
0
      return (NULL);
421
0
    x--;
422
0
  }
423
0
  return (style_ranges_get_range_at(&ss->ranges, x, y));
424
0
}
425
426
/* Draw side status line for client. Returns 1 if changed. */
427
int
428
status_side_redraw(struct client *c)
429
0
{
430
0
  struct side_status_line   *ss = &c->side_status;
431
0
  struct session      *s = c->session;
432
0
  struct screen_write_ctx    ctx;
433
0
  struct grid_cell     gc, lgc;
434
0
  struct format_tree    *ft;
435
0
  char        *expanded;
436
0
  u_int        width, rows, n;
437
0
  int        flags, force = 0, linex;
438
439
0
  log_debug("%s enter", __func__);
440
441
  /* No side status line? */
442
0
  width = status_side_size(c);
443
0
  rows = status_side_rows(c);
444
0
  if (width == 0 || rows == 0)
445
0
    return (0);
446
447
  /* Create format tree. */
448
0
  flags = FORMAT_STATUS;
449
0
  if (c->flags & CLIENT_STATUSFORCE)
450
0
    flags |= FORMAT_FORCE;
451
0
  ft = format_create(c, NULL, FORMAT_NONE, flags);
452
0
  format_defaults(ft, c, NULL, NULL, NULL);
453
454
  /* Set up default colour. */
455
0
  style_apply(&gc, s->options, "side-status-style", ft);
456
0
  if (!grid_cells_equal(&gc, &ss->style)) {
457
0
    force = 1;
458
0
    memcpy(&ss->style, &gc, sizeof ss->style);
459
0
  }
460
461
  /* Resize the target screen. */
462
0
  if (screen_size_x(&ss->screen) != width ||
463
0
      screen_size_y(&ss->screen) != rows) {
464
0
    screen_resize(&ss->screen, width, rows, 0);
465
0
    force = 1;
466
0
  }
467
468
  /* The line moves if the side status line changes side. */
469
0
  linex = status_side_line_at(c);
470
0
  if (linex != ss->linex) {
471
0
    force = 1;
472
0
    ss->linex = linex;
473
0
  }
474
475
  /* Expand the format. */
476
0
  expanded = format_expand_time(ft,
477
0
      options_get_string(s->options, "side-status-format"));
478
0
  format_free(ft);
479
480
  /* Skip the redraw if nothing has changed. */
481
0
  if (!force &&
482
0
      ss->expanded != NULL &&
483
0
      strcmp(expanded, ss->expanded) == 0) {
484
0
    free(expanded);
485
0
    log_debug("%s exit: unchanged", __func__);
486
0
    return (0);
487
0
  }
488
489
  /* Draw the rows over a blank background. */
490
0
  screen_write_start(&ctx, &ss->screen);
491
0
  screen_write_cursormove(&ctx, 0, 0, 0);
492
0
  for (n = 0; n < width * rows; n++)
493
0
    screen_write_putc(&ctx, &gc, ' ');
494
0
  style_ranges_free(&ss->ranges);
495
496
  /* Draw the line on the edge next to the window area. */
497
0
  if (linex != -1) {
498
0
    memcpy(&lgc, &gc, sizeof lgc);
499
0
    lgc.attr |= GRID_ATTR_CHARSET;
500
0
    for (n = 0; n < rows; n++) {
501
0
      screen_write_cursormove(&ctx, linex, n, 0);
502
0
      screen_write_putc(&ctx, &lgc, 'x');
503
0
    }
504
0
    width--;
505
0
  }
506
507
0
  screen_write_cursormove(&ctx, 0, 0, 0);
508
0
  format_draw_lines(&ctx, &gc, linex == 0 ? 1 : 0, width, rows, expanded,
509
0
      &ss->ranges, 0);
510
0
  screen_write_stop(&ctx);
511
512
0
  free(ss->expanded);
513
0
  ss->expanded = expanded;
514
515
0
  log_debug("%s exit: changed", __func__);
516
0
  return (1);
517
0
}
518
519
/* Escape # characters in a string so format_draw treats them as literal. */
520
static char *
521
status_message_escape(const char *s)
522
0
{
523
0
  const char  *cp;
524
0
  char    *out, *p;
525
0
  size_t     n = 0;
526
527
0
  for (cp = s; *cp != '\0'; cp++) {
528
0
    if (*cp == '#')
529
0
      n++;
530
0
  }
531
0
  p = out = xmalloc(strlen(s) + n + 1);
532
0
  for (cp = s; *cp != '\0'; cp++) {
533
0
    if (*cp == '#')
534
0
      *p++ = '#';
535
0
    *p++ = *cp;
536
0
  }
537
0
  *p = '\0';
538
0
  return (out);
539
0
}
540
541
/* Set a status line message. */
542
void
543
status_message_set(struct client *c, int delay, int ignore_styles,
544
    int ignore_keys, int no_freeze, const char *fmt, ...)
545
0
{
546
0
  struct timeval   tv;
547
0
  va_list    ap;
548
0
  char    *s;
549
550
0
  va_start(ap, fmt);
551
0
  xvasprintf(&s, fmt, ap);
552
0
  va_end(ap);
553
554
0
  log_debug("%s: %s", __func__, s);
555
556
0
  if (c == NULL) {
557
0
    server_add_message("message: %s", s);
558
0
    free(s);
559
0
    return;
560
0
  }
561
562
0
  status_message_clear(c);
563
0
  status_push_screen(c);
564
0
  c->message_string = s;
565
0
  server_add_message("%s message: %s", c->name, s);
566
567
  /*
568
   * With delay -1, the display-time option is used; zero means wait for
569
   * key press; more than zero is the actual delay time in milliseconds.
570
   */
571
0
  if (delay == -1)
572
0
    delay = options_get_number(c->session->options, "display-time");
573
0
  if (delay > 0) {
574
0
    tv.tv_sec = delay / 1000;
575
0
    tv.tv_usec = (delay % 1000) * 1000L;
576
577
0
    if (event_initialized(&c->message_timer))
578
0
      evtimer_del(&c->message_timer);
579
0
    evtimer_set(&c->message_timer, status_message_callback, c);
580
581
0
    evtimer_add(&c->message_timer, &tv);
582
0
  }
583
584
0
  if (delay != 0)
585
0
    c->message_ignore_keys = ignore_keys;
586
0
  c->message_ignore_styles = ignore_styles;
587
588
0
  if (!no_freeze)
589
0
    c->tty.flags |= TTY_FREEZE;
590
0
  c->tty.flags |= TTY_NOCURSOR;
591
0
  c->flags |= CLIENT_REDRAWSTATUS;
592
0
}
593
594
/* Clear status line message. */
595
void
596
status_message_clear(struct client *c)
597
0
{
598
0
  if (c->message_string == NULL)
599
0
    return;
600
601
0
  free(c->message_string);
602
0
  c->message_string = NULL;
603
604
0
  if (c->prompt == NULL)
605
0
    c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
606
0
  c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
607
608
0
  status_pop_screen(c);
609
0
}
610
611
/*
612
 * Calculate prompt/message area geometry from the style's width and align
613
 * directives: x offset and available width within the status line.
614
 */
615
static void
616
status_message_area(struct client *c, u_int *area_x, u_int *area_w)
617
0
{
618
0
  struct session    *s = c->session;
619
0
  struct style    *sy;
620
0
  u_int      w;
621
622
  /* Get width from message-style's width directive. */
623
0
  sy = options_string_to_style(s->options, "message-style", NULL);
624
0
  if (sy != NULL && sy->width >= 0) {
625
0
    if (sy->width_percentage)
626
0
      w = (c->tty.sx * (u_int)sy->width) / 100;
627
0
    else
628
0
      w = (u_int)sy->width;
629
0
  } else
630
0
    w = c->tty.sx;
631
0
  if (w == 0 || w > c->tty.sx)
632
0
    w = c->tty.sx;
633
634
  /* Get horizontal position from message-style's align directive. */
635
0
  if (sy != NULL) {
636
0
    switch (sy->align) {
637
0
    case STYLE_ALIGN_CENTRE:
638
0
    case STYLE_ALIGN_ABSOLUTE_CENTRE:
639
0
      *area_x = (c->tty.sx - w) / 2;
640
0
      break;
641
0
    case STYLE_ALIGN_RIGHT:
642
0
      *area_x = c->tty.sx - w;
643
0
      break;
644
0
    default:
645
0
      *area_x = 0;
646
0
      break;
647
0
    }
648
0
  } else
649
0
    *area_x = 0;
650
651
0
  *area_w = w;
652
0
}
653
654
/* Clear status line message after timer expires. */
655
static void
656
status_message_callback(__unused int fd, __unused short event, void *data)
657
0
{
658
0
  struct client *c = data;
659
660
0
  status_message_clear(c);
661
0
}
662
663
/* Draw client message on status line of present else on last line. */
664
int
665
status_message_redraw(struct client *c)
666
0
{
667
0
  struct status_line  *sl = &c->status;
668
0
  struct screen_write_ctx  ctx;
669
0
  struct session    *s = c->session;
670
0
  struct screen    old_screen;
671
0
  u_int      lines, messageline;
672
0
  u_int      ax, aw;
673
0
  struct grid_cell   gc;
674
0
  struct format_tree  *ft;
675
0
  const char    *msgfmt;
676
0
  char      *expanded, *msg;
677
678
0
  if (c->tty.sx == 0 || c->tty.sy == 0)
679
0
    return (0);
680
0
  memcpy(&old_screen, sl->active, sizeof old_screen);
681
682
0
  lines = status_line_size(c);
683
0
  if (lines <= 1)
684
0
    lines = 1;
685
0
  screen_init(sl->active, c->tty.sx, lines, 0);
686
687
0
  messageline = status_prompt_line_at(c);
688
0
  if (messageline > lines - 1)
689
0
    messageline = lines - 1;
690
691
0
  status_message_area(c, &ax, &aw);
692
693
0
  ft = format_create_defaults(NULL, c, NULL, NULL, NULL);
694
0
  style_apply(&gc, s->options, "message-style", ft);
695
696
  /*
697
   * Set #{message} in the format tree. If styles should be ignored in
698
   * the message content, escape # characters so format_draw treats them
699
   * as literal text.
700
   */
701
0
  if (c->message_ignore_styles) {
702
0
    msg = status_message_escape(c->message_string);
703
0
    format_add(ft, "message", "%s", msg);
704
0
    free(msg);
705
0
  } else
706
0
    format_add(ft, "message", "%s", c->message_string);
707
0
  format_add(ft, "command_prompt", "%d", 0);
708
709
0
  msgfmt = options_get_string(s->options, "message-format");
710
0
  expanded = format_expand_time(ft, msgfmt);
711
0
  format_free(ft);
712
713
0
  screen_write_start(&ctx, sl->active);
714
0
  screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
715
0
  screen_write_cursormove(&ctx, ax, messageline, 0);
716
0
  format_draw(&ctx, &gc, aw, expanded, NULL, 0);
717
0
  screen_write_stop(&ctx);
718
719
0
  free(expanded);
720
721
0
  if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
722
0
    screen_free(&old_screen);
723
0
    return (0);
724
0
  }
725
0
  screen_free(&old_screen);
726
0
  return (1);
727
0
}
728
729
730
struct status_prompt_data {
731
  struct client   *c;
732
  status_prompt_input_cb   inputcb;
733
  prompt_free_cb     freecb;
734
  void      *data;
735
};
736
737
static enum prompt_result
738
status_prompt_input_callback(void *data, const char *s,
739
    enum prompt_key_result key)
740
0
{
741
0
  struct status_prompt_data *spd = data;
742
0
  struct client     *c = spd->c;
743
0
  status_prompt_input_cb     inputcb = spd->inputcb;
744
0
  void        *arg = spd->data;
745
746
0
  if (inputcb != NULL)
747
0
    return (inputcb(c, arg, s, key));
748
0
  return (PROMPT_CLOSE);
749
0
}
750
751
static void
752
status_prompt_free_callback(void *data)
753
0
{
754
0
  struct status_prompt_data *spd = data;
755
0
  prompt_free_cb       freecb = spd->freecb;
756
0
  void        *arg = spd->data;
757
758
0
  if (freecb != NULL)
759
0
    freecb(arg);
760
0
  free(spd);
761
0
}
762
763
/* Accept prompt immediately. */
764
static enum cmd_retval
765
status_prompt_accept(__unused struct cmdq_item *item, void *data)
766
0
{
767
0
  struct client *c = data;
768
769
0
  if (c->prompt != NULL)
770
0
    status_prompt_key(c, 'y', NULL);
771
0
  return (CMD_RETURN_NORMAL);
772
0
}
773
774
/* Enable status line prompt. */
775
void
776
status_prompt_set(struct client *c, struct cmd_find_state *fs,
777
    const char *msg, const char *input, status_prompt_input_cb inputcb,
778
    prompt_free_cb freecb, void *data, int flags, enum prompt_type prompt_type)
779
0
{
780
0
  struct prompt_create_data pd;
781
0
  struct status_prompt_data *spd;
782
783
0
  status_message_clear(c);
784
0
  status_prompt_clear(c);
785
0
  status_push_screen(c);
786
787
0
  spd = xcalloc(1, sizeof *spd);
788
0
  spd->c = c;
789
0
  spd->inputcb = inputcb;
790
0
  spd->freecb = freecb;
791
0
  spd->data = data;
792
793
0
  memset(&pd, 0, sizeof pd);
794
0
  prompt_set_options(&pd, c->session);
795
0
  pd.fs = fs;
796
0
  pd.prompt = msg;
797
0
  pd.input = input;
798
0
  pd.type = prompt_type;
799
0
  pd.flags = flags;
800
0
  pd.inputcb = status_prompt_input_callback;
801
0
  pd.freecb = status_prompt_free_callback;
802
0
  pd.data = spd;
803
0
  c->prompt = prompt_create(&pd);
804
805
0
  if ((~flags & PROMPT_INCREMENTAL) && (~flags & PROMPT_NOFREEZE))
806
0
    c->tty.flags |= TTY_FREEZE;
807
0
  c->flags |= CLIENT_REDRAWSTATUS;
808
809
0
  prompt_incremental_start(c->prompt);
810
811
0
  if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT))
812
0
    cmdq_append(c, cmdq_get_callback(status_prompt_accept, c));
813
0
}
814
815
/* Remove status line prompt. */
816
void
817
status_prompt_clear(struct client *c)
818
0
{
819
0
  if (c->prompt == NULL)
820
0
    return;
821
822
0
  prompt_free(c->prompt);
823
0
  c->prompt = NULL;
824
825
0
  c->tty.flags &= ~(TTY_NOCURSOR|TTY_FREEZE);
826
0
  c->flags |= CLIENT_ALLREDRAWFLAGS; /* was frozen and may have changed */
827
828
0
  status_pop_screen(c);
829
0
}
830
831
/* Update status line prompt with a new prompt string. */
832
void
833
status_prompt_update(struct client *c, const char *msg, const char *input)
834
0
{
835
0
  if (c->prompt == NULL)
836
0
    return;
837
0
  prompt_update(c->prompt, msg, input);
838
0
  c->flags |= CLIENT_REDRAWSTATUS;
839
0
}
840
841
/* Get the screen line on which the prompt is drawn. */
842
static u_int
843
status_prompt_screen_line(struct client *c)
844
0
{
845
0
  struct tty  *tty = &c->tty;
846
0
  u_int    n;
847
848
0
  if (options_get_number(c->session->options, "status-position") == 0)
849
0
    return (status_prompt_line_at(c));
850
0
  n = status_line_size(c) - status_prompt_line_at(c);
851
0
  if (n <= tty->sy)
852
0
    return (tty->sy - n);
853
0
  return (tty->sy - 1);
854
0
}
855
856
/* Draw client prompt on status line of present else on last line. */
857
int
858
status_prompt_redraw(struct client *c)
859
0
{
860
0
  struct status_line  *sl = &c->status;
861
0
  struct screen_write_ctx  ctx;
862
0
  struct screen    old_screen;
863
0
  struct prompt_draw_data  pdd;
864
0
  u_int      lines, ax, aw, promptline;
865
866
0
  if (c->tty.sx == 0 || c->tty.sy == 0)
867
0
    return (0);
868
0
  memcpy(&old_screen, sl->active, sizeof old_screen);
869
870
0
  lines = status_line_size(c);
871
0
  if (lines <= 1)
872
0
    lines = 1;
873
0
  screen_init(sl->active, c->tty.sx, lines, 0);
874
875
0
  promptline = status_prompt_line_at(c);
876
0
  if (promptline > lines - 1)
877
0
    promptline = lines - 1;
878
879
0
  status_message_area(c, &ax, &aw);
880
881
0
  screen_write_start(&ctx, sl->active);
882
0
  screen_write_fast_copy(&ctx, &sl->screen, 0, 0, c->tty.sx, lines);
883
884
0
  pdd.ctx = &ctx;
885
0
  pdd.area_x = ax;
886
0
  pdd.area_width = aw;
887
0
  pdd.prompt_line = promptline;
888
0
  pdd.cursor_x = &sl->prompt_cx;
889
0
  prompt_draw(c->prompt, &pdd);
890
891
0
  screen_write_stop(&ctx);
892
893
0
  if (grid_compare(sl->active->grid, old_screen.grid) == 0) {
894
0
    screen_free(&old_screen);
895
0
    return (0);
896
0
  }
897
0
  screen_free(&old_screen);
898
0
  return (1);
899
0
}
900
901
/* Work out the tty cursor position for the prompt. */
902
void
903
status_prompt_cursor(struct client *c, u_int *cx, u_int *cy)
904
0
{
905
0
  *cy = status_prompt_screen_line(c);
906
0
  *cx = c->status.prompt_cx;
907
0
}
908
909
/* Handle keys in prompt. */
910
enum prompt_key_result
911
status_prompt_key(struct client *c, key_code key, struct mouse_event *m)
912
0
{
913
0
  enum prompt_key_result  result;
914
0
  u_int     ax, aw;
915
0
  int     redraw = 0;
916
917
0
  if (KEYC_IS_MOUSE(key)) {
918
0
    if (m == NULL || MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 ||
919
0
        MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) ||
920
0
        m->y != status_prompt_screen_line(c))
921
0
      return (PROMPT_KEY_NOT_HANDLED);
922
0
    status_message_area(c, &ax, &aw);
923
0
    result = prompt_mouse(c->prompt, m->x, ax, aw, &redraw);
924
0
  } else
925
0
    result = prompt_key(c->prompt, key, &redraw);
926
0
  if (redraw && c->prompt != NULL)
927
0
    c->flags |= CLIENT_REDRAWSTATUS;
928
0
  if (c->prompt != NULL && prompt_closed(c->prompt))
929
0
    status_prompt_clear(c);
930
0
  return (result);
931
0
}