1 /*
2 * Copyright (c) 2011 Tim van der Molen <tim@kariliq.nl>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <curses.h>
18 #include <errno.h>
19 #include <pthread.h>
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include "siren.h"
26
27 #ifdef HAVE_RESIZETERM
28 #include <sys/ioctl.h>
29 #endif
30
31 #define SCREEN_OBJ_ACTIVE 0
32 #define SCREEN_OBJ_ERROR 1
33 #define SCREEN_OBJ_INFO 2
34 #define SCREEN_OBJ_PLAYER 3
35 #define SCREEN_OBJ_PROMPT 4
36 #define SCREEN_OBJ_SELECTOR 5
37 #define SCREEN_OBJ_STATUS 6
38 #define SCREEN_OBJ_TITLE 7
39 #define SCREEN_OBJ_VIEW 8
40
41 #define SCREEN_PLAYER_NROWS 2
42 #define SCREEN_STATUS_NROWS 1
43 #define SCREEN_TITLE_NROWS 1
44
45 #define SCREEN_TITLE_ROW 0
46 #define SCREEN_VIEW_ROW 1
47
48 static short int screen_get_colour(const char *, int);
49 static void screen_msg_vprintf(int, const char *,
50 va_list);
51 static void screen_print_row(const char *);
52 static void screen_view_print_row(chtype, const char *);
53 static void screen_vprintf(const char *, va_list);
54 #if defined(HAVE_RESIZETERM) && defined(TIOCGWINSZ)
55 static void screen_resize(void);
56 #endif
57
58 static pthread_mutex_t screen_curses_mtx = PTHREAD_MUTEX_INITIALIZER;
59 static int screen_have_colours;
60 static int screen_player_row;
61 static int screen_status_col;
62 static int screen_status_row;
63 static int screen_view_current_row;
64 static int screen_view_selected_row;
65 static int screen_view_nrows;
66
67 static char *screen_row = NULL;
68 static size_t screen_rowsize;
69
70 #ifdef HAVE_USE_DEFAULT_COLORS
71 static int screen_have_default_colours;
72 #endif
73
74 static const struct {
75 const int attrib;
76 const chtype curses_attrib;
77 } screen_attribs[] = {
78 { ATTRIB_BLINK, A_BLINK },
79 { ATTRIB_BOLD, A_BOLD },
80 { ATTRIB_DIM, A_DIM },
81 { ATTRIB_REVERSE, A_REVERSE },
82 { ATTRIB_STANDOUT, A_STANDOUT },
83 { ATTRIB_UNDERLINE, A_UNDERLINE }
84 };
85
86 static const struct {
87 const enum colour colour;
88 const short int curses_colour;
89 } screen_colours[] = {
90 { COLOUR_BLACK, COLOR_BLACK },
91 { COLOUR_BLUE, COLOR_BLUE },
92 { COLOUR_CYAN, COLOR_CYAN },
93 { COLOUR_DEFAULT, -1 },
94 { COLOUR_GREEN, COLOR_GREEN },
95 { COLOUR_MAGENTA, COLOR_MAGENTA },
96 { COLOUR_RED, COLOR_RED },
97 { COLOUR_WHITE, COLOR_WHITE },
98 { COLOUR_YELLOW, COLOR_YELLOW }
99 };
100
101 static const struct {
102 const int key;
103 const int curses_key;
104 } screen_keys[] = {
105 { K_BACKSPACE, '\b' },
106 { K_BACKSPACE, '\177' /* ^? */ },
107 { K_BACKSPACE, KEY_BACKSPACE },
108 { K_BACKTAB, KEY_BTAB },
109 { K_DELETE, KEY_DC },
110 { K_DOWN, KEY_DOWN },
111 { K_END, KEY_END },
112 { K_ENTER, '\n' },
113 { K_ENTER, '\r' },
114 { K_ENTER, KEY_ENTER },
115 { K_ESCAPE, K_CTRL('[') },
116 { K_F1, KEY_F(1) },
117 { K_F2, KEY_F(2) },
118 { K_F3, KEY_F(3) },
119 { K_F4, KEY_F(4) },
120 { K_F5, KEY_F(5) },
121 { K_F6, KEY_F(6) },
122 { K_F7, KEY_F(7) },
123 { K_F8, KEY_F(8) },
124 { K_F9, KEY_F(9) },
125 { K_F10, KEY_F(10) },
126 { K_F11, KEY_F(11) },
127 { K_F12, KEY_F(12) },
128 { K_F13, KEY_F(13) },
129 { K_F14, KEY_F(14) },
130 { K_F15, KEY_F(15) },
131 { K_F16, KEY_F(16) },
132 { K_F17, KEY_F(17) },
133 { K_F18, KEY_F(18) },
134 { K_F19, KEY_F(19) },
135 { K_F20, KEY_F(20) },
136 { K_HOME, KEY_HOME },
137 { K_INSERT, KEY_IC },
138 { K_LEFT, KEY_LEFT },
139 { K_PAGEDOWN, KEY_NPAGE },
140 { K_PAGEUP, KEY_PPAGE },
141 { K_RIGHT, KEY_RIGHT },
142 { K_TAB, '\t' },
143 { K_UP, KEY_UP }
144 };
145
146 static struct {
147 chtype attr;
148 const short int colour_pair;
149 const char *option_attr;
150 const char *option_bg;
151 const char *option_fg;
152 } screen_objects[] = {
153 { A_NORMAL, 1, "active-attr", "active-bg", "active-fg" },
154 { A_NORMAL, 2, "error-attr", "error-bg", "error-fg" },
155 { A_NORMAL, 3, "info-attr", "info-bg", "info-fg" },
156 { A_NORMAL, 4, "player-attr", "player-bg", "player-fg" },
157 { A_NORMAL, 5, "prompt-attr", "prompt-bg", "prompt-fg" },
158 { A_NORMAL, 6, "selection-attr", "selection-bg", "selection-fg" },
159 { A_NORMAL, 7, "status-attr", "status-bg", "status-fg" },
160 { A_NORMAL, 8, "view-title-attr", "view-title-bg", "view-title-fg" },
161 { A_NORMAL, 9, "view-attr", "view-bg", "view-fg" }
162 };
163
164 static void
screen_configure_attribs(void)165 screen_configure_attribs(void)
166 {
167 size_t i, j;
168 int attr;
169 chtype cattr;
170
171 for (i = 0; i < NELEMENTS(screen_objects); i++) {
172 attr = option_get_attrib(screen_objects[i].option_attr);
173 cattr = A_NORMAL;
174
175 for (j = 0; j < NELEMENTS(screen_attribs); j++)
176 if (attr & screen_attribs[j].attrib)
177 cattr |= screen_attribs[j].curses_attrib;
178
179 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
180 screen_objects[i].attr = cattr;
181 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
182 }
183 }
184
185 static void
screen_configure_colours(void)186 screen_configure_colours(void)
187 {
188 size_t i;
189 short int bg, fg;
190
191 if (!screen_have_colours)
192 return;
193
194 for (i = 0; i < NELEMENTS(screen_objects); i++) {
195 bg = screen_get_colour(screen_objects[i].option_bg,
196 COLOUR_BLACK);
197 fg = screen_get_colour(screen_objects[i].option_fg,
198 COLOUR_WHITE);
199
200 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
201 if (init_pair(screen_objects[i].colour_pair, fg, bg) == OK)
202 screen_objects[i].attr |=
203 COLOR_PAIR(screen_objects[i].colour_pair);
204 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
205 }
206 }
207
208 void
screen_configure_cursor(void)209 screen_configure_cursor(void)
210 {
211 int show;
212
213 show = option_get_boolean("show-cursor");
214 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
215 curs_set(show);
216 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
217 }
218
219 void
screen_configure_objects(void)220 screen_configure_objects(void)
221 {
222 screen_configure_attribs();
223 screen_configure_colours();
224 screen_print();
225 }
226
227 static void
screen_configure_rows(void)228 screen_configure_rows(void)
229 {
230 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
231
232 /* Calculate the number of rows available to the view area. */
233 if (LINES < SCREEN_TITLE_NROWS + SCREEN_PLAYER_NROWS +
234 SCREEN_STATUS_NROWS)
235 screen_view_nrows = 0;
236 else
237 screen_view_nrows = LINES - SCREEN_TITLE_NROWS -
238 SCREEN_PLAYER_NROWS - SCREEN_STATUS_NROWS;
239
240 /* Calculate the row offsets of the player and status areas. */
241 screen_player_row = SCREEN_TITLE_NROWS + screen_view_nrows;
242 screen_status_row = screen_player_row + SCREEN_PLAYER_NROWS;
243
244 /* (Re)allocate memory for the row buffer. */
245 if (screen_rowsize != (size_t)COLS + 1) {
246 screen_rowsize = COLS + 1;
247 screen_row = xrealloc(screen_row, screen_rowsize);
248 }
249
250 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
251 }
252
253 void
screen_end(void)254 screen_end(void)
255 {
256 endwin();
257 free(screen_row);
258 }
259
260 static short int
screen_get_colour(const char * option,enum colour default_colour)261 screen_get_colour(const char *option, enum colour default_colour)
262 {
263 size_t i;
264 int colour;
265
266 colour = option_get_colour(option);
267
268 if (colour >= 0 && colour < COLORS)
269 return colour;
270
271 #ifdef HAVE_USE_DEFAULT_COLORS
272 if (colour == COLOUR_DEFAULT && !screen_have_default_colours)
273 #else
274 if (colour == COLOUR_DEFAULT)
275 #endif
276 colour = default_colour;
277
278 for (i = 0; i < NELEMENTS(screen_colours); i++)
279 if (colour == screen_colours[i].colour)
280 return screen_colours[i].curses_colour;
281
282 LOG_FATALX("unknown colour: %d", colour);
283 }
284
285 int
screen_get_key(void)286 screen_get_key(void)
287 {
288 size_t i;
289 int key;
290
291 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
292 while ((key = getch()) == ERR && errno == EINTR);
293 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
294
295 if (key != ERR) {
296 for (i = 0; i < NELEMENTS(screen_keys); i++)
297 if (key == screen_keys[i].curses_key)
298 return screen_keys[i].key;
299
300 /* Only allow ASCII characters. */
301 if (key > -1 && key < 128)
302 return key;
303 }
304
305 return K_NONE;
306 }
307
308 int
screen_get_ncolours(void)309 screen_get_ncolours(void)
310 {
311 return screen_have_colours ? COLORS : 0;
312 }
313
314 unsigned int
screen_get_ncols(void)315 screen_get_ncols(void)
316 {
317 return COLS;
318 }
319
320 void
screen_init(void)321 screen_init(void)
322 {
323 if (initscr() == NULL)
324 LOG_FATALX("cannot initialise screen");
325
326 cbreak();
327 noecho();
328 nonl();
329 keypad(stdscr, TRUE);
330
331 if (has_colors() == TRUE) {
332 if (start_color() == ERR)
333 LOG_ERRX("start_color() failed");
334 else {
335 screen_have_colours = 1;
336 LOG_INFO("terminal supports %d colours", COLORS);
337 #ifdef HAVE_USE_DEFAULT_COLORS
338 if (use_default_colors() == OK) {
339 screen_have_default_colours = 1;
340 LOG_INFO("terminal supports default colours");
341 }
342 #endif
343 }
344 }
345
346 screen_configure_rows();
347 screen_configure_cursor();
348 screen_configure_attribs();
349 screen_configure_colours();
350 }
351
352 void
screen_msg_error_printf(const char * fmt,...)353 screen_msg_error_printf(const char *fmt, ...)
354 {
355 va_list ap;
356
357 va_start(ap, fmt);
358 screen_msg_vprintf(SCREEN_OBJ_ERROR, fmt, ap);
359 va_end(ap);
360 }
361
362 void
screen_msg_error_vprintf(const char * fmt,va_list ap)363 screen_msg_error_vprintf(const char *fmt, va_list ap)
364 {
365 screen_msg_vprintf(SCREEN_OBJ_ERROR, fmt, ap);
366 }
367
368 void
screen_msg_info_vprintf(const char * fmt,va_list ap)369 screen_msg_info_vprintf(const char *fmt, va_list ap)
370 {
371 screen_msg_vprintf(SCREEN_OBJ_INFO, fmt, ap);
372 }
373
374 static void
screen_msg_vprintf(int obj,const char * fmt,va_list ap)375 screen_msg_vprintf(int obj, const char *fmt, va_list ap)
376 {
377 int col, row;
378
379 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
380 getyx(stdscr, row, col);
381 if (move(screen_status_row, 0) == OK) {
382 bkgdset(screen_objects[obj].attr);
383 screen_vprintf(fmt, ap);
384 move(row, col);
385 refresh();
386 }
387 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
388 }
389
390 void
screen_player_status_printf(const struct format * fmt,const struct format_variable * fmtvar,size_t nfmtvars)391 screen_player_status_printf(const struct format *fmt,
392 const struct format_variable *fmtvar, size_t nfmtvars)
393 {
394 int col, row;
395
396 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
397 format_snprintf(screen_row, screen_rowsize, fmt, fmtvar, nfmtvars);
398 getyx(stdscr, row, col);
399 if (move(screen_player_row + 1, 0) == OK) {
400 bkgdset(screen_objects[SCREEN_OBJ_PLAYER].attr);
401 screen_print_row(screen_row);
402 move(row, col);
403 refresh();
404 }
405 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
406 }
407
408 void
screen_player_track_printf(const struct format * fmt,const struct format * altfmt,const struct track * track)409 screen_player_track_printf(const struct format *fmt,
410 const struct format *altfmt, const struct track *track)
411 {
412 int col, row;
413
414 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
415 if (track == NULL)
416 screen_row[0] = '\0';
417 else
418 format_track_snprintf(screen_row, screen_rowsize, fmt, altfmt,
419 track);
420
421 getyx(stdscr, row, col);
422 if (move(screen_player_row, 0) == OK) {
423 bkgdset(screen_objects[SCREEN_OBJ_PLAYER].attr);
424 screen_print_row(screen_row);
425 move(row, col);
426 refresh();
427 }
428 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
429 }
430
431 void
screen_print(void)432 screen_print(void)
433 {
434 view_print();
435 player_print();
436 if (input_get_mode() == INPUT_MODE_PROMPT)
437 prompt_print();
438 else
439 screen_status_clear();
440 }
441
442 /*
443 * The screen_curses_mtx mutex must be locked before calling this function.
444 */
445 static void
screen_print_row(const char * s)446 screen_print_row(const char *s)
447 {
448 int col UNUSED, row;
449
450 addnstr(s, COLS);
451
452 if (strlen(s) < (size_t)COLS)
453 clrtoeol();
454 else {
455 /*
456 * If the length of the printed string is equal to the screen
457 * width, the cursor will advance to the next row. Undo this by
458 * moving the cursor back to the original row.
459 */
460 getyx(stdscr, row, col);
461 move(row - 1, COLS - 1);
462 }
463 }
464
465 void
screen_prompt_begin(void)466 screen_prompt_begin(void)
467 {
468 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
469 curs_set(1);
470 screen_status_col = 0;
471 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
472 }
473
474 void
screen_prompt_end(void)475 screen_prompt_end(void)
476 {
477 int show;
478
479 show = option_get_boolean("show-cursor");
480 screen_status_clear();
481 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
482 if (!show)
483 curs_set(0);
484 move(screen_view_selected_row, 0);
485 refresh();
486 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
487 }
488
489 void
screen_prompt_printf(size_t cursorpos,const char * fmt,...)490 screen_prompt_printf(size_t cursorpos, const char *fmt, ...)
491 {
492 va_list ap;
493
494 va_start(ap, fmt);
495 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
496 if ((int)cursorpos >= COLS && COLS > 0)
497 screen_status_col = COLS - 1;
498 else
499 screen_status_col = cursorpos;
500
501 if (move(screen_status_row, 0) == OK) {
502 bkgdset(screen_objects[SCREEN_OBJ_PROMPT].attr);
503 screen_vprintf(fmt, ap);
504 move(screen_status_row, screen_status_col);
505 refresh();
506 }
507 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
508 va_end(ap);
509 }
510
511 void
screen_refresh(void)512 screen_refresh(void)
513 {
514 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
515 clear();
516 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
517 #if defined(HAVE_RESIZETERM) && defined(TIOCGWINSZ)
518 screen_resize();
519 #endif
520 screen_print();
521 }
522
523 #if defined(HAVE_RESIZETERM) && defined(TIOCGWINSZ)
524 static void
screen_resize(void)525 screen_resize(void)
526 {
527 struct winsize ws;
528
529 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) {
530 LOG_ERR("ioctl");
531 return;
532 }
533
534 /*
535 * resizeterm() will fail if the width or height of the terminal window
536 * is not larger than zero.
537 */
538 if (ws.ws_col == 0 || ws.ws_row == 0)
539 return;
540
541 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
542 if (resizeterm(ws.ws_row, ws.ws_col) == ERR)
543 /*
544 * resizeterm() might have failed to allocate memory, so treat
545 * this as fatal.
546 */
547 LOG_FATALX("resizeterm() failed");
548 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
549
550 screen_configure_rows();
551 }
552 #endif
553
554 void
screen_status_clear(void)555 screen_status_clear(void)
556 {
557 int col, row;
558
559 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
560 getyx(stdscr, row, col);
561 if (move(screen_status_row, 0) == OK) {
562 bkgdset(screen_objects[SCREEN_OBJ_STATUS].attr);
563 clrtoeol();
564 move(row, col);
565 refresh();
566 }
567 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
568 }
569
570 unsigned int
screen_view_get_nrows(void)571 screen_view_get_nrows(void)
572 {
573 return screen_view_nrows;
574 }
575
576 void
screen_view_print(const char * s)577 screen_view_print(const char *s)
578 {
579 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
580 screen_view_print_row(screen_objects[SCREEN_OBJ_VIEW].attr, s);
581 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
582 }
583
584 void
screen_view_print_active(const char * s)585 screen_view_print_active(const char *s)
586 {
587 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
588 screen_view_print_row(screen_objects[SCREEN_OBJ_ACTIVE].attr, s);
589 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
590 }
591
592 void
screen_view_print_begin(void)593 screen_view_print_begin(void)
594 {
595 int i;
596
597 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
598 /* Clear the view area. */
599 bkgdset(screen_objects[SCREEN_OBJ_VIEW].attr);
600 for (i = 0; i < screen_view_nrows; i++) {
601 move(SCREEN_VIEW_ROW + i, 0);
602 clrtoeol();
603 }
604 screen_view_current_row = screen_view_selected_row = SCREEN_VIEW_ROW;
605 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
606 }
607
608 void
screen_view_print_end(void)609 screen_view_print_end(void)
610 {
611 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
612 if (input_get_mode() == INPUT_MODE_PROMPT)
613 move(screen_status_row, screen_status_col);
614 else
615 move(screen_view_selected_row, 0);
616 refresh();
617 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
618 }
619
620 /*
621 * The screen_curses_mtx mutex must be locked before calling this function.
622 */
623 static void
screen_view_print_row(chtype attr,const char * s)624 screen_view_print_row(chtype attr, const char *s)
625 {
626 bkgdset(attr);
627 move(screen_view_current_row++, 0);
628 screen_print_row(s);
629 }
630
631 void
screen_view_print_selected(const char * s)632 screen_view_print_selected(const char *s)
633 {
634 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
635 screen_view_selected_row = screen_view_current_row;
636 screen_view_print_row(screen_objects[SCREEN_OBJ_SELECTOR].attr, s);
637 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
638 }
639
640 void
screen_view_title_printf(const char * fmt,...)641 screen_view_title_printf(const char *fmt, ...)
642 {
643 va_list ap;
644
645 va_start(ap, fmt);
646 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
647 if (move(SCREEN_TITLE_ROW, 0) == OK) {
648 bkgdset(screen_objects[SCREEN_OBJ_TITLE].attr);
649 screen_vprintf(fmt, ap);
650 /* No refresh() yet; screen_view_print_end() will do that. */
651 }
652 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
653 va_end(ap);
654 }
655
656 void
screen_view_title_printf_right(const char * fmt,...)657 screen_view_title_printf_right(const char *fmt, ...)
658 {
659 va_list ap;
660 int len;
661
662 va_start(ap, fmt);
663 XPTHREAD_MUTEX_LOCK(&screen_curses_mtx);
664 bkgdset(screen_objects[SCREEN_OBJ_TITLE].attr);
665 len = xvsnprintf(screen_row, screen_rowsize, fmt, ap);
666 mvaddstr(SCREEN_TITLE_ROW, (len < COLS) ? (COLS - len) : 0, screen_row);
667 /* No refresh() yet; screen_view_print_end() will do that. */
668 XPTHREAD_MUTEX_UNLOCK(&screen_curses_mtx);
669 va_end(ap);
670 }
671
672 /*
673 * The screen_curses_mtx mutex must be locked before calling this function.
674 */
675 PRINTFLIKE(1, 0) static void
screen_vprintf(const char * fmt,va_list ap)676 screen_vprintf(const char *fmt, va_list ap)
677 {
678 int col UNUSED, len, row;
679
680 len = xvsnprintf(screen_row, screen_rowsize, fmt, ap);
681 addnstr(screen_row, COLS);
682
683 if (len < COLS)
684 clrtoeol();
685 else {
686 /*
687 * If the length of the printed string is equal to the screen
688 * width, the cursor will advance to the next row. Undo this by
689 * moving the cursor back to the original row.
690 */
691 getyx(stdscr, row, col);
692 move(row - 1, COLS - 1);
693 }
694 }
695