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