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