1 /*
2 ** Copyright 2002-2011, Double Precision Inc.
3 **
4 ** See COPYING for distribution information.
5 */
6 
7 #include "curses_config.h"
8 #include <signal.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <iomanip>
12 #include <iostream>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <cstring>
16 #include <cerrno>
17 
18 #if HAVE_SYS_WAIT_H
19 #include <sys/wait.h>
20 #endif
21 #ifndef WEXITSTATUS
22 #define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
23 #endif
24 #ifndef WIFEXITED
25 #define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
26 #endif
27 
28 #include "cursesscreen.H"
29 #include "cursesfield.H"
30 
31 static unsigned char termStopKey= 'Z' & 31;
32 
bye(int dummy)33 static RETSIGTYPE bye(int dummy)
34 {
35 	endwin();
36 	kill( getpid(), SIGKILL);
37 	_exit(0);
38 }
39 
getColorCount()40 int Curses::getColorCount()
41 {
42 	if (has_colors())
43 		return COLORS;
44 	return 0;
45 }
46 
47 
KeyReader()48 CursesScreen::KeyReader::KeyReader()
49 	: h(iconv_open(unicode_u_ucs4_native, unicode_default_chset())),
50 	  winput_cnt(0)
51 {
52 	if (h == (iconv_t)-1)
53 	{
54 		std::cerr << "Unable to initiale "
55 			  << unicode_default_chset()
56 			  << " mapping to UCS-4" << std::endl;
57 		exit(1);
58 	}
59 }
60 
~KeyReader()61 CursesScreen::KeyReader::~KeyReader()
62 {
63 	if (h != (iconv_t)-1)
64 		iconv_close(h);
65 }
66 
67 void CursesScreen::KeyReader::operator<<(char ch)
68 {
69 	input_buf.push_back(ch);
70 
71 	winput_buf.resize(winput_cnt+128);
72 
73 	while (input_buf.size() > 0)
74 	{
75 
76 		char *inbuf=&input_buf[0], *outbuf=&winput_buf[winput_cnt];
77 		size_t inbytesleft=input_buf.size(),
78 			outbytesleft=winput_buf.size()-winput_cnt;
79 
80 		size_t res=iconv(h, &inbuf, &inbytesleft, &outbuf,
81 				 &outbytesleft);
82 
83 		if (inbuf != &input_buf[0])
84 			input_buf.erase(input_buf.begin(),
85 					input_buf.begin() +
86 					(inbuf - &input_buf[0]));
87 
88 		winput_cnt=outbuf - &winput_buf[0];
89 
90 		if (res == (size_t)-1 && errno == EILSEQ)
91 		{
92 			if (input_buf.size() > 0)
93 				input_buf.erase(input_buf.begin(),
94 						input_buf.begin()+1);
95 			continue;
96 		}
97 
98 		if (res == (size_t)-1 && errno == E2BIG)
99 			winput_buf.resize(winput_buf.size()+128);
100 
101 		if (res == (size_t)-1 && errno == EINVAL)
102 			break;
103 	}
104 }
105 
106 bool CursesScreen::KeyReader::operator>>(char32_t &ch)
107 {
108 	if (winput_cnt < sizeof(ch))
109 		return false;
110 
111 	memcpy(&ch, &winput_buf[0], sizeof(ch));
112 
113 	winput_buf.erase(winput_buf.begin(),
114 			 winput_buf.begin()+sizeof(ch));
115 	winput_cnt -= sizeof(ch);
116 	return true;
117 }
118 
CursesScreen()119 CursesScreen::CursesScreen() : inputcounter(0)
120 {
121 	initscr();
122 	start_color();
123 
124 #if HAS_USE_DEFAULT_COLORS
125 	use_default_colors();
126 #endif
127 
128 	// Assign meaningful colors
129 
130 	if (has_colors())
131 	{
132 		short f;
133 		short b;
134 
135 		int i;
136 
137 		pair_content(0, &f, &b);
138 
139 		int colorcount=getColorCount();
140 
141 		if (colorcount > 0)
142 			--colorcount;
143 
144 		for (i=1; i<COLOR_PAIRS; i++)
145 		{
146 			if (colorcount <= 1)
147 				f=b=0;
148 			else
149 			{
150 				f=(i / COLORS) % COLORS;
151 				b=(i % COLORS);
152 			}
153 
154 #if HAS_USE_DEFAULT_COLORS
155 			if (b == 0)
156 				b= -1;
157 #endif
158 			init_pair(i, f, b);
159 		}
160 	}
161 
162 	raw();
163 	noecho();
164 	nodelay(stdscr, true);
165 	timeout(0);
166 	nonl();
167 	intrflush(stdscr, FALSE);
168 	keypad(stdscr, TRUE);
169 
170 	//	fcntl(0, F_SETFL, O_NONBLOCK);
171 
172 	signal(SIGHUP, bye);
173 	signal(SIGTERM, bye);
174 
175 	save_w=COLS;
176 	save_h=LINES;
177 	shiftmode=false;
178 
179 	const char *underline_hack_env=getenv("UNDERLINE_HACK");
180 
181 	underline_hack=underline_hack_env && atoi(underline_hack_env);
182 }
183 
~CursesScreen()184 CursesScreen::~CursesScreen()
185 {
186 	endwin();
187 }
188 
getWidth()189 int CursesScreen::getWidth() const
190 {
191 	return COLS;
192 }
193 
getHeight()194 int CursesScreen::getHeight() const
195 {
196 	return LINES;
197 }
198 
draw()199 void CursesScreen::draw()
200 {
201 	attroff(A_STANDOUT | A_BOLD | A_UNDERLINE | A_REVERSE);
202 	clear();
203 	CursesContainer::draw();
204 }
205 
resized()206 void CursesScreen::resized()
207 {
208 	CursesContainer::resized();
209 	draw();
210 }
211 
writeText(const char * ctext,int row,int col,const Curses::CursesAttr & attr)212 bool CursesScreen::writeText(const char *ctext, int row, int col,
213 			     const Curses::CursesAttr &attr) const
214 {
215 	std::u32string ubuf;
216 
217 	unicode::iconvert::convert(ctext, unicode_default_chset(), ubuf);
218 
219 	return writeText(ubuf, row, col, attr);
220 }
221 
222 //
223 // Eliminate time-consuming expantabs() call in the hot writeText() codepath
224 // by loading widecharbuf from an iterator that replaces tabs with a single
225 // space character. writeText() should not get a string with tabs anyway.
226 //
227 // As a bonus, any NUL character gets also replaced with a space.
228 //
229 
230 class CursesScreen::repltabs_spaces :
231 	public std::iterator<std::random_access_iterator_tag,
232 			     const char32_t> {
233 
234 	const char32_t *p;
235 
236 	char32_t tmp;
237 
238 public:
repltabs_spaces()239 	repltabs_spaces() : p(0), tmp(0) {}
240 
repltabs_spaces(const char32_t * pVal)241 	repltabs_spaces(const char32_t *pVal) : p(pVal), tmp(0) {}
242 
243 	bool operator==(const repltabs_spaces &v)
244 	{
245 		return p == v.p;
246 	}
247 
248 	bool operator!=(const repltabs_spaces &v)
249 	{
250 		return p != v.p;
251 	}
252 
253 	bool operator<(const repltabs_spaces &v)
254 	{
255 		return p < v.p;
256 	}
257 
258 	bool operator<=(const repltabs_spaces &v)
259 	{
260 		return p <= v.p;
261 	}
262 
263 	bool operator>(const repltabs_spaces &v)
264 	{
265 		return p < v.p;
266 	}
267 
268 	bool operator>=(const repltabs_spaces &v)
269 	{
270 		return p <= v.p;
271 	}
272 
273 	char32_t operator*() const { return operator[](0); }
274 
275 	repltabs_spaces &operator++() { ++p; return *this; }
276 
277 	const char32_t *operator++(int) { tmp=operator*(); ++p; return &tmp; }
278 
279 	repltabs_spaces &operator--() { --p; return *this; }
280 
281 	const char32_t *operator--(int) { tmp=operator*(); --p; return &tmp; }
282 
283 	repltabs_spaces &operator+=(difference_type diff) { p += diff; return *this; }
284 
285 	repltabs_spaces &operator-=(difference_type diff) { p -= diff; return *this; }
286 
287 	repltabs_spaces operator+(difference_type diff) const { repltabs_spaces tmp=*this; tmp += diff; return tmp; }
288 	repltabs_spaces operator-(difference_type diff) const { repltabs_spaces tmp=*this; tmp -= diff; return tmp; }
289 
290 	difference_type operator-(const repltabs_spaces b) const { return p-b.p; }
291 
292 	const char32_t operator[](difference_type diff) const { return p[diff] == 0 || p[diff] == '\t' ? ' ':p[diff]; }
293 };
294 
295 
296 //
297 // When writing EM and EN dashes, use the EM dash character, and write it out
298 // multiple times. Corresponds to logic in widecharbuf::charwidth().
299 //
300 
301 class CursesScreen::writetext_iter_helper
302 	: public std::iterator<std::input_iterator_tag,
303 			       char, void, void, void> {
304 
305 	const char32_t *uptr;
306 	size_t multcnt;
307 
308 public:
writetext_iter_helper(const char32_t * uptrArg)309 	writetext_iter_helper(const char32_t *uptrArg)
310 		: uptr(uptrArg), multcnt(0) {}
311 
312 	char32_t operator*()
313 	{
314 		char32_t c=*uptr;
315 
316 		if (c == 0x2014)
317 			c=0x2013;
318 		return c;
319 	}
320 
321 	writetext_iter_helper &operator++()
322 	{
323 		if (multcnt == 0)
324 			switch (*uptr) {
325 			case 0x2013:
326 				multcnt=2;
327 				break;
328 			case 0x2014:
329 				multcnt=3;
330 				break;
331 			default:
332 				multcnt=1;
333 			}
334 
335 		if (--multcnt == 0)
336 			++uptr;
337 		return *this;
338 	}
339 
340 	writetext_iter_helper operator++(int)
341 	{
342 		writetext_iter_helper helper=*this;
343 
344 		operator++();
345 		return helper;
346 	}
347 
348 	bool operator==(const writetext_iter_helper &o) const
349 	{
350 		return uptr == o.uptr;
351 	}
352 
353 	bool operator!=(const writetext_iter_helper &o) const
354 	{
355 		return !operator==(o);
356 	}
357 };
358 
359 
360 
writeText(const std::u32string & utext,int row,int col,const Curses::CursesAttr & attr)361 bool CursesScreen::writeText(const std::u32string &utext,
362 			     int row, int col,
363 			     const Curses::CursesAttr &attr) const
364 {
365 	// Truncate text to fit within the display
366 
367 	if (row < 0 || row >= getHeight() || col >= getWidth())
368 		return false;
369 
370 	widecharbuf wc;
371 
372 	{
373 		repltabs_spaces ptr(utext.size() ? &*utext.begin():NULL);
374 
375 		wc.init_unicode(ptr, ptr+utext.size());
376 	}
377 
378 	// Advance past any part of the script beyond the left margin
379 
380 	bool left_margin_crossed=false;
381 
382 	std::vector<widecharbuf::grapheme_t>::const_iterator
383 		b(wc.graphemes.begin()), e(wc.graphemes.end());
384 
385 	if (b != e)
386 	{
387 		while (col < 0)
388 		{
389 			if (b == e)
390 				return false;
391 
392 			col += b->wcwidth(col);
393 			++b;
394 			left_margin_crossed=true;
395 		}
396 
397 		if (col < 0 || b == e)
398 			return false;
399 	}
400 
401 	if (left_margin_crossed)
402 	{
403 		// Clear any cells that contain a partial graphemes that
404 		// extends from beyond the left margin
405 		move(row, 0);
406 
407 		for (int i=0; i<col; i++)
408 			addstr(" ");
409 	}
410 	else
411 		move(row, col);
412 
413 	int w=getWidth();
414 
415 	if (col >= w)
416 		return false;
417 
418 	if (b == e)
419 		return true;
420 
421 	size_t cells=w-col;
422 
423 	const char32_t *uptr=b->uptr;
424 	size_t ucnt=0;
425 
426 	bool right_margin_crossed=false;
427 
428 	while (b != e)
429 	{
430 		size_t grapheme_width=b->wcwidth(col);
431 
432 		if (grapheme_width > cells)
433 		{
434 			if (cells)
435 				right_margin_crossed=true;
436 			break;
437 		}
438 
439 		ucnt += b->cnt;
440 		cells -= grapheme_width;
441 		col += grapheme_width;
442 		++b;
443 	}
444 
445 	std::vector<wchar_t> text_buf;
446 
447 	{
448 		std::string s;
449 		bool ignore;
450 
451 		unicode::iconvert::fromu::convert(writetext_iter_helper(uptr),
452 					       writetext_iter_helper(uptr+ucnt),
453 						  unicode_default_chset(), s,
454 						  ignore);
455 
456 		towidechar(s.begin(), s.end(), text_buf);
457 	}
458 	text_buf.push_back(0);
459 
460 	wchar_t *text=&text_buf[0];
461 
462 	int c=0;
463 
464 	// If color was requested, then the colors will wrap if the requested
465 	// color number exceeds the # of colors reported by curses.
466 
467 	int ncolors=getColorCount();
468 
469 	if (ncolors > 2 && COLOR_PAIRS > 0)
470 	{
471 		int bg=attr.getBgColor();
472 		int fg=attr.getFgColor();
473 
474 		if (fg || bg)
475 		{
476 			if (bg > 0)
477 				bg= ((bg - 1) % (ncolors-1)) + 1;
478 
479 			fg %= ncolors;
480 
481 			if (fg == bg)
482 				fg = (fg + ncolors/2) % ncolors;
483 		}
484 
485 		c=bg + ncolors * fg;
486 
487 		c %= COLOR_PAIRS;
488 	}
489 
490 	if (c > 0)
491 		attron(COLOR_PAIR(c));
492 
493 	if (attr.getHighlight())
494 		attron(A_BOLD);
495 	if (attr.getReverse())
496 		attron(A_REVERSE);
497 
498 	if (attr.getUnderline())
499 	{
500 		if (termattrs() & A_UNDERLINE)
501 		{
502 			if (underline_hack)
503 			{
504 				size_t i=0;
505 
506 				for (i=0; text[i]; i++)
507 					if (text[i] == ' ')
508 						text[i]=0xA0;
509 			}
510 			attron(A_UNDERLINE);
511 		}
512 		else
513 		{
514 			size_t i;
515 
516 			for (i=0; text[i]; i++)
517 				if (text[i] == ' ')
518 					text[i]='_';
519 		}
520 	}
521 
522 	addwstr(text);
523 
524 	if (c)
525 		attroff(COLOR_PAIR(c));
526 	attroff(A_STANDOUT | A_BOLD | A_UNDERLINE | A_REVERSE);
527 
528 	if (right_margin_crossed)
529 		clrtoeol();
530 
531 	return true;
532 }
533 
flush()534 void CursesScreen::flush()
535 {
536 	refresh();
537 }
538 
539 void (*Curses::suspendedHook)(void)=&Curses::suspendedStub;
540 
suspendedStub(void)541 void Curses::suspendedStub(void)
542 {
543 }
544 
setSuspendHook(void (* func)(void))545 void Curses::setSuspendHook(void (*func)(void))
546 {
547 	suspendedHook=func;
548 }
549 
getKey()550 Curses::Key CursesScreen::getKey()
551 {
552 	for (;;)
553 	{
554 		Key k=doGetKey();
555 
556 		if (k.plain())
557 		{
558 			if (k.plain() && k.ukey == termStopKey)
559 			{
560 				if (suspendCommand.size() > 0)
561 				{
562 					std::vector<const char *> argv;
563 
564 					const char *p=getenv("SHELL");
565 
566 					if (!p || !*p)
567 						p="/bin/sh";
568 
569 					argv.push_back(p);
570 					argv.push_back("-c");
571 					argv.push_back(suspendCommand.c_str());
572 					argv.push_back(NULL);
573 					Curses::runCommand(argv, -1, "");
574 					draw();
575 					(*suspendedHook)();
576 					continue;
577 				}
578 
579 				endwin();
580 				kill( getpid(), SIGSTOP );
581 				draw();
582 				refresh();
583 				(*suspendedHook)();
584 				continue;
585 			}
586 		}
587 		else if (k == Key::RESIZE)
588 		{
589 			save_w=COLS;
590 			save_h=LINES;
591 
592 			::erase(); // old window
593 			resized();
594 			touchwin(stdscr);
595 			flush();
596 			draw();
597 			flush();
598 		}
599 		return k;
600 	}
601 }
602 
runCommand(std::vector<const char * > & argv,int stdinpipe,std::string continuePrompt)603 int Curses::runCommand(std::vector<const char *> &argv,
604 		       int stdinpipe,
605 		       std::string continuePrompt)
606 {
607 	endwin();
608 	if (continuePrompt.size() > 0)
609 	{
610 		std::cout << std::endl << std::endl;
611 		std::cout.flush();
612 	}
613 
614 	pid_t editor_pid, p2;
615 
616 #ifdef WIFSTOPPED
617 
618 	RETSIGTYPE (*save_tstp)(int)=signal(SIGTSTP, SIG_IGN);
619 	RETSIGTYPE (*save_cont)(int)=signal(SIGCONT, SIG_IGN);
620 #endif
621 
622 	editor_pid=fork();
623 	int waitstat;
624 
625 	signal(SIGCHLD, SIG_DFL);
626 
627 	if (editor_pid < 0)
628 	{
629 #ifdef WIFSTOPPED
630 		signal(SIGTSTP, save_tstp);
631 		signal(SIGCONT, save_cont);
632 #endif
633 		beep();
634 		return -1;
635 	}
636 
637 	if (editor_pid == 0)
638 	{
639 		signal(SIGTSTP, SIG_DFL);
640 		signal(SIGCONT, SIG_DFL);
641 		if (continuePrompt.size() > 0)
642 		{
643 			dup2(1, 2);
644 		}
645 		signal(SIGINT, SIG_DFL);
646 		if (stdinpipe >= 0)
647 		{
648 			dup2(stdinpipe, 0);
649 			close(stdinpipe);
650 		}
651 		execvp(argv[0], (char **)&argv[0]);
652 		kill(getpid(), SIGKILL);
653 		_exit(1);
654 	}
655 
656 	if (stdinpipe >= 0)
657 		close(stdinpipe);
658 
659 	signal(SIGINT, SIG_IGN);
660 
661 	for (;;)
662 	{
663 
664 #ifdef WIFSTOPPED
665 
666 		p2=waitpid(editor_pid, &waitstat, WUNTRACED);
667 
668 		if (p2 != editor_pid)
669 			continue;
670 
671 		if (WIFSTOPPED(waitstat))
672 		{
673 			kill(getpid(), SIGSTOP);
674 			kill(editor_pid, SIGCONT);
675 			continue;
676 		}
677 #else
678 
679 		p2=wait(&waitstat);
680 
681 		if (p2 != editor_pid)
682 			continue;
683 #endif
684 		break;
685 	}
686 
687 	signal(SIGINT, SIG_DFL);
688 
689 #ifdef WIFSTOPPED
690 	signal(SIGTSTP, save_tstp);
691 	signal(SIGCONT, save_cont);
692 #endif
693 
694 	if (continuePrompt.size() > 0)
695 	{
696 		char buffer[80];
697 
698 		std::cout << std::endl << continuePrompt;
699 		std::cout.flush();
700 		std::cin.getline(buffer, sizeof(buffer));
701 	}
702 
703 	refresh();
704 
705 	return WIFEXITED(waitstat) ? WEXITSTATUS(waitstat):-1;
706 }
707 
doGetKey()708 Curses::Key CursesScreen::doGetKey()
709 {
710 	// If, for some reason, a unicode key is available, take it
711 
712 	{
713 		char32_t uk;
714 
715 		if (keyreader >> uk)
716 			return doGetPlainKey(uk);
717 	}
718 
719 	// Position cursor first.
720 
721 	int row=getHeight();
722 	int col=getWidth();
723 
724 	if (row > 0) --row;
725 	if (col > 0) --col;
726 
727 	if (currentFocus != NULL)
728 	{
729 		int new_row=currentFocus->getHeight();
730 		if (new_row > 0) --new_row;
731 
732 		int new_col=currentFocus->getWidth();
733 		if (new_col > 0) --new_col;
734 		curs_set(currentFocus->getCursorPosition(new_row, new_col));
735 
736 		if (new_row < row)
737 			row=new_row;
738 
739 		if (new_col < col)
740 			col=new_col;
741 	}
742 	else
743 		curs_set(0);
744 
745 	move(row, col);
746 
747 	// As long as the bytes from the keyboard keep getting read,
748 	// accumulate them.
749 
750 	while (1)
751 	{
752 		int k=getch();
753 
754 		if (k == ERR)
755 		{
756 			flush();
757 
758 			if (++inputcounter >= 100)
759 			{
760 				// Detect closed xterm without SIGHUP
761 
762 				if (write(1, "", 1) < 0)
763 					bye(0);
764 				inputcounter=0;
765 			}
766 
767 			if (LINES != save_h || COLS != save_w)
768 				return (Key(Key::RESIZE));
769 
770 			return Key((char32_t)0);
771 		}
772 		inputcounter=0;
773 
774 #ifdef KEY_SUSPEND
775 
776 		if (k == KEY_SUSPEND)
777 			k=termStopKey;
778 #endif
779 
780 		static const struct {
781 			int keyval;
782 			const char *keycode;
783 		} keys[]={
784 
785 #define K(v,c) { v, Curses::Key::c }
786 
787 			K(KEY_LEFT, LEFT),
788 			K(KEY_RIGHT, RIGHT),
789 			K(KEY_SLEFT, SHIFTLEFT),
790 			K(KEY_SRIGHT, SHIFTRIGHT),
791 			K(KEY_UP, UP),
792 			K(KEY_DOWN, DOWN),
793 			K(KEY_ENTER, ENTER),
794 			K(KEY_PPAGE, PGUP),
795 			K(KEY_NPAGE, PGDN),
796 			K(KEY_HOME, HOME),
797 			K(KEY_END, END),
798 			K(KEY_SHOME, SHIFTHOME),
799 			K(KEY_SEND, SHIFTEND),
800 			K(KEY_RESIZE, RESIZE),
801 			K(KEY_BACKSPACE, BACKSPACE),
802 			K(KEY_DC, DEL),
803 			K(KEY_EOL, CLREOL),
804 #undef K
805 		};
806 
807 		if (shiftmode)
808 		{
809 			switch (k) {
810 			case KEY_PPAGE:
811 				return (Key(Key::SHIFTPGUP));
812 			case KEY_NPAGE:
813 				return (Key(Key::SHIFTPGDN));
814 			case KEY_UP:
815 				return (Key(Key::SHIFTUP));
816 			case KEY_DOWN:
817 				return (Key(Key::SHIFTDOWN));
818 			case KEY_LEFT:
819 				k=KEY_SLEFT;
820 				break;
821 			case KEY_RIGHT:
822 				k=KEY_SRIGHT;
823 				break;
824 			case KEY_HOME:
825 				k=KEY_SHOME;
826 				break;
827 			case KEY_END:
828 				k=KEY_SEND;
829 				break;
830 			default:
831 				shiftmode=0;
832 			}
833 		}
834 
835 		size_t i;
836 
837 		for (i=0; i<sizeof(keys)/sizeof(keys[0]); i++)
838 			if (keys[i].keyval == k)
839 				return Key(keys[i].keycode);
840 
841 #if HAS_FUNCTIONKEYS
842 		if (k >= KEY_F(0) && k <= KEY_F(63))
843 		{
844 			Key kk("FKEY");
845 			kk.ukey=k-KEY_F(0);
846 			return kk;
847 		}
848 #endif
849 
850 		if ( (unsigned char)k == k)
851 		{
852 			// Plain key
853 
854 			keyreader << k;
855 
856 			char32_t uk;
857 
858 			if (keyreader >> uk)
859 				return doGetPlainKey(uk);
860 		}
861 	}
862 }
863 
doGetPlainKey(char32_t k)864 Curses::Key CursesScreen::doGetPlainKey(char32_t k)
865 {
866 	if (k == 0)
867 	{
868 		shiftmode= !shiftmode;
869 
870 		return Key(Key::SHIFT);
871 	}
872 
873 	if (k == (char32_t)CursesField::clrEolKey)
874 		return Key(Key::CLREOL);
875 
876 	if (k == '\r')
877 		return Key(Key::ENTER);
878 
879 	shiftmode=false;
880 
881 	return (Key(k));
882 }
883 
beepError()884 void CursesScreen::beepError()
885 {
886 	beep();
887 }
888