1 /***************************************************************************
2 * Copyright (C) 2008-2021 by Andrzej Rybczak *
3 * andrzej@rybczak.net *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21 #include <algorithm>
22 #include <cstring>
23 #include <cstdio>
24 #include <cstdlib>
25 #include <iostream>
26 #include <sys/select.h>
27 #include <unistd.h>
28
29 #include "utility/readline.h"
30 #include "utility/string.h"
31 #include "utility/wide_string.h"
32 #include "window.h"
33
34 namespace {
35
36 // In a DirectColor setup, COLORS as returned by ncurses (via terminfo) can
37 // run as high as 2^24. We only work with up to 256.
38 int maxColor;
39
40 namespace rl {
41
42 bool aborted;
43
44 NC::Window *w;
45 size_t start_x;
46 size_t start_y;
47 size_t width;
48 bool encrypted;
49 const char *base;
50
read_key(FILE *)51 int read_key(FILE *)
52 {
53 size_t x;
54 bool done;
55 int result;
56 do
57 {
58 x = w->getX();
59 if (w->runPromptHook(rl_line_buffer, &done))
60 {
61 // Do not end if readline is in one of its commands, e.g. searching
62 // through history, as it doesn't actually make readline exit and it
63 // becomes stuck in a loop.
64 if (!RL_ISSTATE(RL_STATE_DISPATCHING) && done)
65 {
66 rl_done = 1;
67 return EOF;
68 }
69 w->goToXY(x, start_y);
70 }
71 w->refresh();
72 result = w->readKey();
73 if (!w->FDCallbacksListEmpty())
74 {
75 w->goToXY(x, start_y);
76 w->refresh();
77 }
78 }
79 while (result == ERR);
80 return result;
81 }
82
display_string()83 void display_string()
84 {
85 auto print_char = [](wchar_t wc) {
86 if (encrypted)
87 *w << '*';
88 else
89 *w << wc;
90 };
91 auto print_string = [](wchar_t *ws, size_t len) {
92 if (encrypted)
93 for (size_t i = 0; i < len; ++i)
94 *w << '*';
95 else
96 *w << ws;
97 };
98 auto narrow_to_wide = [](wchar_t *dest, const char *src, size_t n) {
99 size_t result = 0;
100 // convert the string and substitute invalid multibyte chars with dots.
101 for (size_t i = 0; i < n;)
102 {
103 int ret = mbtowc(&dest[result], &src[i], n-i);
104 if (ret > 0)
105 {
106 i += ret;
107 ++result;
108 }
109 else if (ret == -1)
110 {
111 dest[result] = L'.';
112 ++i;
113 ++result;
114 }
115 else
116 throw std::runtime_error("mbtowc: unexpected return value");
117 }
118 return result;
119 };
120
121 // copy the part of the string that is before the cursor to pre_pos
122 char pt = rl_line_buffer[rl_point];
123 rl_line_buffer[rl_point] = 0;
124 wchar_t pre_pos[rl_point+1];
125 pre_pos[narrow_to_wide(pre_pos, rl_line_buffer, rl_point)] = 0;
126 rl_line_buffer[rl_point] = pt;
127
128 int pos = wcswidth(pre_pos, rl_point);
129 if (pos < 0)
130 pos = rl_point;
131
132 // clear the area for the string
133 mvwhline(w->raw(), start_y, start_x, ' ', width+1);
134
135 w->goToXY(start_x, start_y);
136 if (size_t(pos) <= width)
137 {
138 // if the current position in the string is not bigger than allowed
139 // width, print the part of the string before cursor position...
140
141 print_string(pre_pos, pos);
142
143 // ...and then print the rest char-by-char until there is no more area
144 wchar_t post_pos[rl_end-rl_point+1];
145 post_pos[narrow_to_wide(post_pos, rl_line_buffer+rl_point, rl_end-rl_point)] = 0;
146
147 size_t cpos = pos;
148 for (wchar_t *c = post_pos; *c != 0; ++c)
149 {
150 int n = wcwidth(*c);
151 if (n < 0)
152 {
153 print_char(L'.');
154 ++cpos;
155 }
156 else
157 {
158 if (cpos+n > width)
159 break;
160 cpos += n;
161 print_char(*c);
162 }
163 }
164 }
165 else
166 {
167 // if the current position in the string is bigger than allowed
168 // width, we always keep the cursor at the end of the line (it
169 // would be nice to have more flexible scrolling, but for now
170 // let's stick to that) by cutting the beginning of the part
171 // of the string before the cursor until it fits the area.
172
173 wchar_t *mod_pre_pos = pre_pos;
174 while (*mod_pre_pos != 0)
175 {
176 ++mod_pre_pos;
177 int n = wcwidth(*mod_pre_pos);
178 if (n < 0)
179 --pos;
180 else
181 pos -= n;
182 if (size_t(pos) <= width)
183 break;
184 }
185 print_string(mod_pre_pos, pos);
186 }
187 w->goToXY(start_x+pos, start_y);
188 }
189
add_base()190 int add_base()
191 {
192 rl_insert_text(base);
193 return 0;
194 }
195
196 }
197
198 int color_pair_counter;
199 std::vector<int> color_pair_map;
200
201 }
202
203 namespace NC {
204
205 const short Color::transparent = -1;
206 const short Color::current = -2;
207
208 Color Color::Default(0, 0, true, false);
209 Color Color::Black(COLOR_BLACK, Color::current);
210 Color Color::Red(COLOR_RED, Color::current);
211 Color Color::Green(COLOR_GREEN, Color::current);
212 Color Color::Yellow(COLOR_YELLOW, Color::current);
213 Color Color::Blue(COLOR_BLUE, Color::current);
214 Color Color::Magenta(COLOR_MAGENTA, Color::current);
215 Color Color::Cyan(COLOR_CYAN, Color::current);
216 Color Color::White(COLOR_WHITE, Color::current);
217 Color Color::End(0, 0, false, true);
218
pairNumber() const219 int Color::pairNumber() const
220 {
221 // If colors are disabled, return default pair value.
222 if (color_pair_map.empty())
223 return 0;
224
225 int result = 0;
226 if (isEnd())
227 throw std::logic_error("'end' doesn't have a corresponding pair number");
228 else if (!isDefault())
229 {
230 if (!currentBackground())
231 result = (background() + 1) % colorCount();
232 result *= 256;
233 result += foreground() % colorCount();
234
235 assert(result < int(color_pair_map.size()));
236
237 // NCurses allows for a limited number of color pairs to be registered, so
238 // in order to be able to support all the combinations we want to, we need
239 // to dynamically register only pairs of colors we're actually using.
240 if (!color_pair_map[result])
241 {
242 // Check if there are any unused pairs left and either register the one
243 // that was requested or return a default one if there is no space left.
244 if (color_pair_counter >= COLOR_PAIRS)
245 result = 0;
246 else
247 {
248 init_pair(color_pair_counter, foreground(), background());
249 color_pair_map[result] = color_pair_counter;
250 ++color_pair_counter;
251 }
252 }
253 result = color_pair_map[result];
254 }
255 return result;
256 }
257
operator >>(std::istream & is,Color & c)258 std::istream &operator>>(std::istream &is, Color &c)
259 {
260 const short invalid_color_value = -1337;
261 auto get_single_color = [](const std::string &s, bool background) {
262 short result = invalid_color_value;
263 if (s == "black")
264 result = COLOR_BLACK;
265 else if (s == "red")
266 result = COLOR_RED;
267 else if (s == "green")
268 result = COLOR_GREEN;
269 else if (s == "yellow")
270 result = COLOR_YELLOW;
271 else if (s == "blue")
272 result = COLOR_BLUE;
273 else if (s == "magenta")
274 result = COLOR_MAGENTA;
275 else if (s == "cyan")
276 result = COLOR_CYAN;
277 else if (s == "white")
278 result = COLOR_WHITE;
279 else if (background && s == "transparent")
280 result = NC::Color::transparent;
281 else if (background && s == "current")
282 result = NC::Color::current;
283 else if (std::all_of(s.begin(), s.end(), isdigit))
284 {
285 result = atoi(s.c_str());
286 if (result < (background ? 0 : 1) || result > 256)
287 result = invalid_color_value;
288 else
289 --result;
290 }
291 return result;
292 };
293
294 auto get_color = [](std::istream &is_) {
295 std::string result;
296 while (!is_.eof() && isalnum(is_.peek()))
297 result.push_back(is_.get());
298 return result;
299 };
300
301 std::string sc = get_color(is);
302
303 if (sc == "default")
304 c = Color::Default;
305 else if (sc == "end")
306 c = Color::End;
307 else
308 {
309 short fg = get_single_color(sc, false);
310 if (fg == invalid_color_value)
311 is.setstate(std::ios::failbit);
312 // Check if there is background color
313 else if (!is.eof() && is.peek() == '_')
314 {
315 is.get();
316 sc = get_color(is);
317 short bg = get_single_color(sc, true);
318 if (bg == invalid_color_value)
319 is.setstate(std::ios::failbit);
320 else
321 c = Color(fg, bg);
322 }
323 else
324 c = Color(fg, NC::Color::current);
325 }
326 return is;
327 }
328
reverseFormat(NC::Format fmt)329 NC::Format reverseFormat(NC::Format fmt)
330 {
331 switch (fmt)
332 {
333 case NC::Format::Bold:
334 return NC::Format::NoBold;
335 case NC::Format::NoBold:
336 return NC::Format::Bold;
337 case NC::Format::Underline:
338 return NC::Format::NoUnderline;
339 case NC::Format::NoUnderline:
340 return NC::Format::Underline;
341 case NC::Format::Reverse:
342 return NC::Format::NoReverse;
343 case NC::Format::NoReverse:
344 return NC::Format::Reverse;
345 case NC::Format::AltCharset:
346 return NC::Format::NoAltCharset;
347 case NC::Format::NoAltCharset:
348 return NC::Format::AltCharset;
349 }
350 // Unreachable, silence GCC.
351 return fmt;
352 }
353
354 namespace Mouse {
355
356 namespace {
357
358 bool supportEnabled = false;
359
360 }
361
enable()362 void enable()
363 {
364 if (!supportEnabled)
365 return;
366 // save old highlight mouse tracking
367 std::printf("\e[?1001s");
368 // enable mouse tracking
369 std::printf("\e[?1000h");
370 // try to enable extended (urxvt) mouse tracking
371 std::printf("\e[?1015h");
372 // send the above to the terminal immediately
373 std::fflush(stdout);
374 }
375
disable()376 void disable()
377 {
378 if (!supportEnabled)
379 return;
380 // disable extended (urxvt) mouse tracking
381 std::printf("\e[?1015l");
382 // disable mouse tracking
383 std::printf("\e[?1000l");
384 // restore old highlight mouse tracking
385 std::printf("\e[?1001r");
386 // send the above to the terminal immediately
387 std::fflush(stdout);
388 }
389
390 }
391
colorCount()392 int colorCount()
393 {
394 return maxColor;
395 }
396
initScreen(bool enable_colors,bool enable_mouse)397 void initScreen(bool enable_colors, bool enable_mouse)
398 {
399 initscr();
400 if (has_colors() && enable_colors)
401 {
402 start_color();
403 use_default_colors();
404 maxColor = COLORS;
405 if (maxColor > 256)
406 {
407 maxColor = 256;
408 }
409 color_pair_map.resize(256 * 256, 0);
410
411 // Predefine pairs for colors with transparent background, all the other
412 // ones will be dynamically registered in Color::pairNumber when they're
413 // used.
414 color_pair_counter = 1;
415 for (int fg = 0; fg < colorCount(); ++fg, ++color_pair_counter)
416 {
417 init_pair(color_pair_counter, fg, -1);
418 color_pair_map[fg] = color_pair_counter;
419 }
420 }
421 raw();
422 nonl();
423 noecho();
424 timeout(0);
425 curs_set(0);
426
427 // setup mouse
428 Mouse::supportEnabled = enable_mouse;
429 Mouse::enable();
430
431 // initialize readline (needed, otherwise we get segmentation
432 // fault on SIGWINCH). also, initialize first as doing this
433 // later erases keys bound with rl_bind_key for some users.
434 rl_initialize();
435 // disable autocompletion
436 rl_attempted_completion_function = [](const char *, int, int) -> char ** {
437 rl_attempted_completion_over = 1;
438 return nullptr;
439 };
440 auto abort_prompt = [](int, int) -> int {
441 rl::aborted = true;
442 rl_done = 1;
443 return 0;
444 };
445 // if ctrl-c or ctrl-g is pressed, abort the prompt
446 rl_bind_key('\3', abort_prompt);
447 rl_bind_key('\7', abort_prompt);
448 // do not change the state of the terminal
449 rl_prep_term_function = nullptr;
450 rl_deprep_term_function = nullptr;
451 // do not catch signals
452 rl_catch_signals = 0;
453 rl_catch_sigwinch = 0;
454 // overwrite readline callbacks
455 rl_getc_function = rl::read_key;
456 rl_redisplay_function = rl::display_string;
457 rl_startup_hook = rl::add_base;
458 }
459
pauseScreen()460 void pauseScreen()
461 {
462 if (Mouse::supportEnabled)
463 Mouse::disable();
464 def_prog_mode();
465 endwin();
466 }
467
unpauseScreen()468 void unpauseScreen()
469 {
470 if (Mouse::supportEnabled)
471 Mouse::enable();
472 refresh();
473 }
474
destroyScreen()475 void destroyScreen()
476 {
477 Mouse::disable();
478 curs_set(1);
479 endwin();
480 }
481
Window(size_t startx,size_t starty,size_t width,size_t height,std::string title,Color color,Border border)482 Window::Window(size_t startx, size_t starty, size_t width, size_t height,
483 std::string title, Color color, Border border)
484 : m_window(nullptr),
485 m_start_x(startx),
486 m_start_y(starty),
487 m_width(width),
488 m_height(height),
489 m_window_timeout(-1),
490 m_border(std::move(border)),
491 m_prompt_hook(0),
492 m_title(std::move(title)),
493 m_escape_terminal_sequences(true),
494 m_bold_counter(0),
495 m_underline_counter(0),
496 m_reverse_counter(0),
497 m_alt_charset_counter(0)
498 {
499 if (m_start_x > size_t(COLS)
500 || m_start_y > size_t(LINES)
501 || m_width+m_start_x > size_t(COLS)
502 || m_height+m_start_y > size_t(LINES))
503 throw std::logic_error("constructed window doesn't fit into the terminal");
504
505 if (m_border)
506 {
507 ++m_start_x;
508 ++m_start_y;
509 m_width -= 2;
510 m_height -= 2;
511 }
512 if (!m_title.empty())
513 {
514 m_start_y += 2;
515 m_height -= 2;
516 }
517
518 m_window = newpad(m_height, m_width);
519 wtimeout(m_window, 0);
520
521 setBaseColor(color);
522 setColor(m_base_color);
523 }
524
Window(const Window & rhs)525 Window::Window(const Window &rhs)
526 : m_window(dupwin(rhs.m_window))
527 , m_start_x(rhs.m_start_x)
528 , m_start_y(rhs.m_start_y)
529 , m_width(rhs.m_width)
530 , m_height(rhs.m_height)
531 , m_window_timeout(rhs.m_window_timeout)
532 , m_color(rhs.m_color)
533 , m_base_color(rhs.m_base_color)
534 , m_border(rhs.m_border)
535 , m_prompt_hook(rhs.m_prompt_hook)
536 , m_title(rhs.m_title)
537 , m_color_stack(rhs.m_color_stack)
538 , m_input_queue(rhs.m_input_queue)
539 , m_fds(rhs.m_fds)
540 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
541 , m_bold_counter(rhs.m_bold_counter)
542 , m_underline_counter(rhs.m_underline_counter)
543 , m_reverse_counter(rhs.m_reverse_counter)
544 , m_alt_charset_counter(rhs.m_alt_charset_counter)
545 {
546 setColor(m_color);
547 }
548
Window(Window && rhs)549 Window::Window(Window &&rhs)
550 : m_window(rhs.m_window)
551 , m_start_x(rhs.m_start_x)
552 , m_start_y(rhs.m_start_y)
553 , m_width(rhs.m_width)
554 , m_height(rhs.m_height)
555 , m_window_timeout(rhs.m_window_timeout)
556 , m_color(rhs.m_color)
557 , m_base_color(rhs.m_base_color)
558 , m_border(rhs.m_border)
559 , m_prompt_hook(rhs.m_prompt_hook)
560 , m_title(std::move(rhs.m_title))
561 , m_color_stack(std::move(rhs.m_color_stack))
562 , m_input_queue(std::move(rhs.m_input_queue))
563 , m_fds(std::move(rhs.m_fds))
564 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
565 , m_bold_counter(rhs.m_bold_counter)
566 , m_underline_counter(rhs.m_underline_counter)
567 , m_reverse_counter(rhs.m_reverse_counter)
568 , m_alt_charset_counter(rhs.m_alt_charset_counter)
569 {
570 rhs.m_window = nullptr;
571 }
572
operator =(Window rhs)573 Window &Window::operator=(Window rhs)
574 {
575 std::swap(m_window, rhs.m_window);
576 std::swap(m_start_x, rhs.m_start_x);
577 std::swap(m_start_y, rhs.m_start_y);
578 std::swap(m_width, rhs.m_width);
579 std::swap(m_height, rhs.m_height);
580 std::swap(m_window_timeout, rhs.m_window_timeout);
581 std::swap(m_color, rhs.m_color);
582 std::swap(m_base_color, rhs.m_base_color);
583 std::swap(m_border, rhs.m_border);
584 std::swap(m_prompt_hook, rhs.m_prompt_hook);
585 std::swap(m_title, rhs.m_title);
586 std::swap(m_color_stack, rhs.m_color_stack);
587 std::swap(m_input_queue, rhs.m_input_queue);
588 std::swap(m_fds, rhs.m_fds);
589 std::swap(m_escape_terminal_sequences, rhs.m_escape_terminal_sequences);
590 std::swap(m_bold_counter, rhs.m_bold_counter);
591 std::swap(m_underline_counter, rhs.m_underline_counter);
592 std::swap(m_reverse_counter, rhs.m_reverse_counter);
593 std::swap(m_alt_charset_counter, rhs.m_alt_charset_counter);
594 return *this;
595 }
596
~Window()597 Window::~Window()
598 {
599 delwin(m_window);
600 }
601
setColor(Color c)602 void Window::setColor(Color c)
603 {
604 if (c.isDefault())
605 c = m_base_color;
606 if (c != Color::Default)
607 {
608 assert(!c.currentBackground());
609 wcolor_set(m_window, c.pairNumber(), nullptr);
610 }
611 else
612 wcolor_set(m_window, m_base_color.pairNumber(), nullptr);
613 m_color = std::move(c);
614 }
615
setBaseColor(const Color & color)616 void Window::setBaseColor(const Color &color)
617 {
618 if (color.currentBackground())
619 m_base_color = Color(color.foreground(), Color::transparent);
620 else
621 m_base_color = color;
622 }
623
setBorder(Border border)624 void Window::setBorder(Border border)
625 {
626 if (!border && m_border)
627 {
628 --m_start_x;
629 --m_start_y;
630 m_height += 2;
631 m_width += 2;
632 recreate(m_width, m_height);
633 }
634 else if (border && !m_border)
635 {
636 ++m_start_x;
637 ++m_start_y;
638 m_height -= 2;
639 m_width -= 2;
640 recreate(m_width, m_height);
641 }
642 m_border = border;
643 }
644
setTitle(const std::string & new_title)645 void Window::setTitle(const std::string &new_title)
646 {
647 if (!new_title.empty() && m_title.empty())
648 {
649 m_start_y += 2;
650 m_height -= 2;
651 recreate(m_width, m_height);
652 }
653 else if (new_title.empty() && !m_title.empty())
654 {
655 m_start_y -= 2;
656 m_height += 2;
657 recreate(m_width, m_height);
658 }
659 m_title = new_title;
660 }
661
recreate(size_t width,size_t height)662 void Window::recreate(size_t width, size_t height)
663 {
664 delwin(m_window);
665 m_window = newpad(height, width);
666 wtimeout(m_window, 0);
667 setColor(m_color);
668 }
669
moveTo(size_t new_x,size_t new_y)670 void Window::moveTo(size_t new_x, size_t new_y)
671 {
672 m_start_x = new_x;
673 m_start_y = new_y;
674 if (m_border)
675 {
676 ++m_start_x;
677 ++m_start_y;
678 }
679 if (!m_title.empty())
680 m_start_y += 2;
681 }
682
adjustDimensions(size_t width,size_t height)683 void Window::adjustDimensions(size_t width, size_t height)
684 {
685 if (m_border)
686 {
687 width -= 2;
688 height -= 2;
689 }
690 if (!m_title.empty())
691 height -= 2;
692 m_height = height;
693 m_width = width;
694 }
695
resize(size_t new_width,size_t new_height)696 void Window::resize(size_t new_width, size_t new_height)
697 {
698 adjustDimensions(new_width, new_height);
699 recreate(m_width, m_height);
700 }
701
refreshBorder() const702 void Window::refreshBorder() const
703 {
704 if (m_border)
705 {
706 size_t start_x = getStartX(), start_y = getStarty();
707 size_t width = getWidth(), height = getHeight();
708 color_set(m_border->pairNumber(), nullptr);
709 attron(A_ALTCHARSET);
710 // corners
711 mvaddch(start_y, start_x, 'l');
712 mvaddch(start_y, start_x+width-1, 'k');
713 mvaddch(start_y+height-1, start_x, 'm');
714 mvaddch(start_y+height-1, start_x+width-1, 'j');
715 // lines
716 mvhline(start_y, start_x+1, 'q', width-2);
717 mvhline(start_y+height-1, start_x+1, 'q', width-2);
718 mvvline(start_y+1, start_x, 'x', height-2);
719 mvvline(start_y+1, start_x+width-1, 'x', height-2);
720 if (!m_title.empty())
721 {
722 mvaddch(start_y+2, start_x, 't');
723 mvaddch(start_y+2, start_x+width-1, 'u');
724 }
725 attroff(A_ALTCHARSET);
726 }
727 else
728 color_set(m_base_color.pairNumber(), nullptr);
729 if (!m_title.empty())
730 {
731 // clear title line
732 mvhline(m_start_y-2, m_start_x, ' ', m_width);
733 attron(A_BOLD);
734 mvaddstr(m_start_y-2, m_start_x, m_title.c_str());
735 attroff(A_BOLD);
736 // add separator
737 mvhline(m_start_y-1, m_start_x, 0, m_width);
738 }
739 standend();
740 ::refresh();
741 }
742
display()743 void Window::display()
744 {
745 refreshBorder();
746 refresh();
747 }
748
refresh()749 void Window::refresh()
750 {
751 prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);
752 }
753
clear()754 void Window::clear()
755 {
756 werase(m_window);
757 setColor(m_base_color);
758 }
759
bold(bool bold_state) const760 void Window::bold(bool bold_state) const
761 {
762 (bold_state ? wattron : wattroff)(m_window, A_BOLD);
763 }
764
underline(bool underline_state) const765 void Window::underline(bool underline_state) const
766 {
767 (underline_state ? wattron : wattroff)(m_window, A_UNDERLINE);
768 }
769
reverse(bool reverse_state) const770 void Window::reverse(bool reverse_state) const
771 {
772 (reverse_state ? wattron : wattroff)(m_window, A_REVERSE);
773 }
774
altCharset(bool altcharset_state) const775 void Window::altCharset(bool altcharset_state) const
776 {
777 (altcharset_state ? wattron : wattroff)(m_window, A_ALTCHARSET);
778 }
779
setTimeout(int timeout)780 void Window::setTimeout(int timeout)
781 {
782 m_window_timeout = timeout;
783 }
784
addFDCallback(int fd,void (* callback)())785 void Window::addFDCallback(int fd, void (*callback)())
786 {
787 m_fds.push_back(std::make_pair(fd, callback));
788 }
789
clearFDCallbacksList()790 void Window::clearFDCallbacksList()
791 {
792 m_fds.clear();
793 }
794
FDCallbacksListEmpty() const795 bool Window::FDCallbacksListEmpty() const
796 {
797 return m_fds.empty();
798 }
799
getInputChar(int key)800 Key::Type Window::getInputChar(int key)
801 {
802 if (!m_escape_terminal_sequences || key != Key::Escape)
803 return key;
804 auto define_mouse_event = [this](int type) {
805 switch (type & ~28)
806 {
807 case 32:
808 m_mouse_event.bstate = BUTTON1_PRESSED;
809 break;
810 case 33:
811 m_mouse_event.bstate = BUTTON2_PRESSED;
812 break;
813 case 34:
814 m_mouse_event.bstate = BUTTON3_PRESSED;
815 break;
816 case 96:
817 m_mouse_event.bstate = BUTTON4_PRESSED;
818 break;
819 case 97:
820 m_mouse_event.bstate = BUTTON5_PRESSED;
821 break;
822 default:
823 return Key::None;
824 }
825 if (type & 4)
826 m_mouse_event.bstate |= BUTTON_SHIFT;
827 if (type & 8)
828 m_mouse_event.bstate |= BUTTON_ALT;
829 if (type & 16)
830 m_mouse_event.bstate |= BUTTON_CTRL;
831 if (m_mouse_event.x < 0 || m_mouse_event.x >= COLS)
832 return Key::None;
833 if (m_mouse_event.y < 0 || m_mouse_event.y >= LINES)
834 return Key::None;
835 return Key::Mouse;
836 };
837 auto get_xterm_modifier_key = [](int ch) {
838 Key::Type modifier;
839 switch (ch)
840 {
841 case '2':
842 modifier = Key::Shift;
843 break;
844 case '3':
845 modifier = Key::Alt;
846 break;
847 case '4':
848 modifier = Key::Alt | Key::Shift;
849 break;
850 case '5':
851 modifier = Key::Ctrl;
852 break;
853 case '6':
854 modifier = Key::Ctrl | Key::Shift;
855 break;
856 case '7':
857 modifier = Key::Alt | Key::Ctrl;
858 break;
859 case '8':
860 modifier = Key::Alt | Key::Ctrl | Key::Shift;
861 break;
862 default:
863 modifier = Key::None;
864 }
865 return modifier;
866 };
867 auto parse_number = [this](int &result) {
868 int x;
869 while (true)
870 {
871 x = wgetch(m_window);
872 if (!isdigit(x))
873 return x;
874 result = result*10 + x - '0';
875 }
876 };
877 key = wgetch(m_window);
878 switch (key)
879 {
880 case '\t': // tty
881 return Key::Shift | Key::Tab;
882 case 'O':
883 key = wgetch(m_window);
884 switch (key)
885 {
886 // eterm
887 case 'A':
888 return Key::Up;
889 case 'B':
890 return Key::Down;
891 case 'C':
892 return Key::Right;
893 case 'D':
894 return Key::Left;
895 // terminator
896 case 'F':
897 return Key::End;
898 case 'H':
899 return Key::Home;
900 // rxvt
901 case 'a':
902 return Key::Ctrl | Key::Up;
903 case 'b':
904 return Key::Ctrl | Key::Down;
905 case 'c':
906 return Key::Ctrl | Key::Right;
907 case 'd':
908 return Key::Ctrl | Key::Left;
909 // xterm
910 case 'P':
911 return Key::F1;
912 case 'Q':
913 return Key::F2;
914 case 'R':
915 return Key::F3;
916 case 'S':
917 return Key::F4;
918 default:
919 return Key::None;
920 }
921 case '[':
922 key = wgetch(m_window);
923 switch (key)
924 {
925 case 'a':
926 return Key::Shift | Key::Up;
927 case 'b':
928 return Key::Shift | Key::Down;
929 case 'c':
930 return Key::Shift | Key::Right;
931 case 'd':
932 return Key::Shift | Key::Left;
933 case 'A':
934 return Key::Up;
935 case 'B':
936 return Key::Down;
937 case 'C':
938 return Key::Right;
939 case 'D':
940 return Key::Left;
941 case 'F': // xterm
942 return Key::End;
943 case 'H': // xterm
944 return Key::Home;
945 case 'M': // standard mouse event
946 {
947 key = wgetch(m_window);
948 int raw_x = wgetch(m_window);
949 int raw_y = wgetch(m_window);
950 // support coordinates up to 255
951 m_mouse_event.x = (raw_x - 33) & 0xff;
952 m_mouse_event.y = (raw_y - 33) & 0xff;
953 return define_mouse_event(key);
954 }
955 case 'P': // st
956 return Key::Delete;
957 case 'Z':
958 return Key::Shift | Key::Tab;
959 case '[': // F1 to F5 in tty
960 key = wgetch(m_window);
961 switch (key)
962 {
963 case 'A':
964 return Key::F1;
965 case 'B':
966 return Key::F2;
967 case 'C':
968 return Key::F3;
969 case 'D':
970 return Key::F4;
971 case 'E':
972 return Key::F5;
973 default:
974 return Key::None;
975 }
976 case '1': case '2': case '3':
977 case '4': case '5': case '6':
978 case '7': case '8': case '9':
979 {
980 key -= '0';
981 int delim = parse_number(key);
982 if (key >= 2 && key <= 8)
983 {
984 Key::Type modifier;
985 switch (delim)
986 {
987 case '~':
988 modifier = Key::Null;
989 break;
990 case '^':
991 modifier = Key::Ctrl;
992 break;
993 case '$':
994 modifier = Key::Shift;
995 break;
996 case '@':
997 modifier = Key::Ctrl | Key::Shift;
998 break;
999 case ';': // xterm insert/delete/page up/page down
1000 {
1001 int local_key = wgetch(m_window);
1002 modifier = get_xterm_modifier_key(local_key);
1003 local_key = wgetch(m_window);
1004 if (local_key != '~' || (key != 2 && key != 3 && key != 5 && key != 6))
1005 return Key::None;
1006 break;
1007 }
1008 default:
1009 return Key::None;
1010 }
1011 switch (key)
1012 {
1013 case 2:
1014 return modifier | Key::Insert;
1015 case 3:
1016 return modifier | Key::Delete;
1017 case 4:
1018 return modifier | Key::End;
1019 case 5:
1020 return modifier | Key::PageUp;
1021 case 6:
1022 return modifier | Key::PageDown;
1023 case 7:
1024 return modifier | Key::Home;
1025 case 8:
1026 return modifier | Key::End;
1027 default:
1028 std::cerr << "Unreachable code, aborting.\n";
1029 std::terminate();
1030 }
1031 }
1032 switch (delim)
1033 {
1034 case '~':
1035 {
1036 switch (key)
1037 {
1038 case 1: // tty
1039 return Key::Home;
1040 case 11:
1041 return Key::F1;
1042 case 12:
1043 return Key::F2;
1044 case 13:
1045 return Key::F3;
1046 case 14:
1047 return Key::F4;
1048 case 15:
1049 return Key::F5;
1050 case 17: // not a typo
1051 return Key::F6;
1052 case 18:
1053 return Key::F7;
1054 case 19:
1055 return Key::F8;
1056 case 20:
1057 return Key::F9;
1058 case 21:
1059 return Key::F10;
1060 case 23: // not a typo
1061 return Key::F11;
1062 case 24:
1063 return Key::F12;
1064 default:
1065 return Key::None;
1066 }
1067 }
1068 case ';':
1069 switch (key)
1070 {
1071 case 1: // xterm
1072 {
1073 key = wgetch(m_window);
1074 Key::Type modifier = get_xterm_modifier_key(key);
1075 if (modifier == Key::None)
1076 return Key::None;
1077 key = wgetch(m_window);
1078 switch (key)
1079 {
1080 case 'A':
1081 return modifier | Key::Up;
1082 case 'B':
1083 return modifier | Key::Down;
1084 case 'C':
1085 return modifier | Key::Right;
1086 case 'D':
1087 return modifier | Key::Left;
1088 case 'F':
1089 return modifier | Key::End;
1090 case 'H':
1091 return modifier | Key::Home;
1092 default:
1093 return Key::None;
1094 }
1095 }
1096 default: // urxvt mouse
1097 m_mouse_event.x = 0;
1098 delim = parse_number(m_mouse_event.x);
1099 if (delim != ';')
1100 return Key::None;
1101 m_mouse_event.y = 0;
1102 delim = parse_number(m_mouse_event.y);
1103 if (delim != 'M')
1104 return Key::None;
1105 --m_mouse_event.x;
1106 --m_mouse_event.y;
1107 return define_mouse_event(key);
1108 }
1109 default:
1110 return Key::None;
1111 }
1112 }
1113 default:
1114 return Key::None;
1115 }
1116 case ERR:
1117 return Key::Escape;
1118 default: // alt + something
1119 {
1120 auto key_prim = getInputChar(key);
1121 if (key_prim != Key::None)
1122 return Key::Alt | key_prim;
1123 return Key::None;
1124 }
1125 }
1126 }
1127
readKey()1128 Key::Type Window::readKey()
1129 {
1130 Key::Type result;
1131 // if there are characters in input queue,
1132 // get them and return immediately.
1133 if (!m_input_queue.empty())
1134 {
1135 result = m_input_queue.front();
1136 m_input_queue.pop();
1137 return result;
1138 }
1139
1140 fd_set fds_read;
1141 FD_ZERO(&fds_read);
1142 FD_SET(STDIN_FILENO, &fds_read);
1143 timeval timeout = { m_window_timeout/1000, (m_window_timeout%1000)*1000 };
1144
1145 int fd_max = STDIN_FILENO;
1146 for (const auto &fd : m_fds)
1147 {
1148 if (fd.first > fd_max)
1149 fd_max = fd.first;
1150 FD_SET(fd.first, &fds_read);
1151 }
1152
1153 auto tv_addr = m_window_timeout < 0 ? nullptr : &timeout;
1154 int res = select(fd_max+1, &fds_read, nullptr, nullptr, tv_addr);
1155 if (res > 0)
1156 {
1157 if (FD_ISSET(STDIN_FILENO, &fds_read))
1158 {
1159 int key = wgetch(m_window);
1160 if (key == EOF)
1161 result = Key::EoF;
1162 else
1163 result = getInputChar(key);
1164 }
1165 else
1166 result = Key::None;
1167
1168 for (const auto &fd : m_fds)
1169 if (FD_ISSET(fd.first, &fds_read))
1170 fd.second();
1171 }
1172 else
1173 result = Key::None;
1174 return result;
1175 }
1176
pushChar(const Key::Type ch)1177 void Window::pushChar(const Key::Type ch)
1178 {
1179 m_input_queue.push(ch);
1180 }
1181
prompt(const std::string & base,size_t width,bool encrypted)1182 std::string Window::prompt(const std::string &base, size_t width, bool encrypted)
1183 {
1184 std::string result;
1185
1186 rl::aborted = false;
1187 rl::w = this;
1188 getyx(m_window, rl::start_y, rl::start_x);
1189 rl::width = std::min(m_width-rl::start_x-1, width-1);
1190 rl::encrypted = encrypted;
1191 rl::base = base.c_str();
1192
1193 curs_set(1);
1194 Mouse::disable();
1195 m_escape_terminal_sequences = false;
1196 char *input = readline(nullptr);
1197 m_escape_terminal_sequences = true;
1198 Mouse::enable();
1199 curs_set(0);
1200 if (input != nullptr)
1201 {
1202 #ifdef HAVE_READLINE_HISTORY_H
1203 if (!encrypted && input[0] != 0)
1204 add_history(input);
1205 #endif // HAVE_READLINE_HISTORY_H
1206 result = input;
1207 free(input);
1208 }
1209
1210 if (rl::aborted)
1211 throw PromptAborted(std::move(result));
1212
1213 return result;
1214 }
1215
goToXY(int x,int y)1216 void Window::goToXY(int x, int y)
1217 {
1218 wmove(m_window, y, x);
1219 }
1220
getX()1221 int Window::getX()
1222 {
1223 return getcurx(m_window);
1224 }
1225
getY()1226 int Window::getY()
1227 {
1228 return getcury(m_window);
1229 }
1230
hasCoords(int & x,int & y)1231 bool Window::hasCoords(int &x, int &y)
1232 {
1233 return wmouse_trafo(m_window, &y, &x, 0);
1234 }
1235
runPromptHook(const char * arg,bool * done) const1236 bool Window::runPromptHook(const char *arg, bool *done) const
1237 {
1238 if (m_prompt_hook)
1239 {
1240 bool continue_ = m_prompt_hook(arg);
1241 if (done != nullptr)
1242 *done = !continue_;
1243 return true;
1244 }
1245 else
1246 return false;
1247 }
1248
getWidth() const1249 size_t Window::getWidth() const
1250 {
1251 if (m_border)
1252 return m_width+2;
1253 else
1254 return m_width;
1255 }
1256
getHeight() const1257 size_t Window::getHeight() const
1258 {
1259 size_t height = m_height;
1260 if (m_border)
1261 height += 2;
1262 if (!m_title.empty())
1263 height += 2;
1264 return height;
1265 }
1266
getStartX() const1267 size_t Window::getStartX() const
1268 {
1269 if (m_border)
1270 return m_start_x-1;
1271 else
1272 return m_start_x;
1273 }
1274
getStarty() const1275 size_t Window::getStarty() const
1276 {
1277 size_t starty = m_start_y;
1278 if (m_border)
1279 --starty;
1280 if (!m_title.empty())
1281 starty -= 2;
1282 return starty;
1283 }
1284
getTitle() const1285 const std::string &Window::getTitle() const
1286 {
1287 return m_title;
1288 }
1289
getColor() const1290 const Color &Window::getColor() const
1291 {
1292 return m_color;
1293 }
1294
getBorder() const1295 const Border &Window::getBorder() const
1296 {
1297 return m_border;
1298 }
1299
getTimeout() const1300 int Window::getTimeout() const
1301 {
1302 return m_window_timeout;
1303 }
1304
getMouseEvent()1305 const MEVENT &Window::getMouseEvent()
1306 {
1307 return m_mouse_event;
1308 }
1309
scroll(Scroll where)1310 void Window::scroll(Scroll where)
1311 {
1312 idlok(m_window, 1);
1313 scrollok(m_window, 1);
1314 switch (where)
1315 {
1316 case Scroll::Up:
1317 wscrl(m_window, 1);
1318 break;
1319 case Scroll::Down:
1320 wscrl(m_window, -1);
1321 break;
1322 case Scroll::PageUp:
1323 wscrl(m_window, m_width);
1324 break;
1325 case Scroll::PageDown:
1326 wscrl(m_window, -m_width);
1327 break;
1328 default:
1329 break;
1330 }
1331 idlok(m_window, 0);
1332 scrollok(m_window, 0);
1333 }
1334
1335
operator <<(const Color & c)1336 Window &Window::operator<<(const Color &c)
1337 {
1338 if (c.isDefault())
1339 {
1340 while (!m_color_stack.empty())
1341 m_color_stack.pop();
1342 setColor(m_base_color);
1343 }
1344 else if (c.isEnd())
1345 {
1346 if (!m_color_stack.empty())
1347 m_color_stack.pop();
1348 if (!m_color_stack.empty())
1349 setColor(m_color_stack.top());
1350 else
1351 setColor(m_base_color);
1352 }
1353 else
1354 {
1355 if (c.currentBackground())
1356 {
1357 short background = m_color.isDefault()
1358 ? Color::transparent
1359 : m_color.background();
1360 Color cc = Color(c.foreground(), background);
1361 setColor(cc);
1362 m_color_stack.push(cc);
1363 }
1364 else
1365 {
1366 setColor(c);
1367 m_color_stack.push(c);
1368 }
1369 }
1370 return *this;
1371 }
1372
operator <<(Format format)1373 Window &Window::operator<<(Format format)
1374 {
1375 auto increase_flag = [](Window &w, int &flag, auto set) {
1376 ++flag;
1377 (w.*set)(true);
1378 };
1379 auto decrease_flag = [](Window &w, int &flag, auto set) {
1380 if (flag > 0)
1381 {
1382 --flag;
1383 if (flag == 0)
1384 (w.*set)(false);
1385 }
1386 };
1387 switch (format)
1388 {
1389 case Format::Bold:
1390 increase_flag(*this, m_bold_counter, &Window::bold);
1391 break;
1392 case Format::NoBold:
1393 decrease_flag(*this, m_bold_counter, &Window::bold);
1394 break;
1395 case Format::Underline:
1396 increase_flag(*this, m_underline_counter, &Window::underline);
1397 break;
1398 case Format::NoUnderline:
1399 decrease_flag(*this, m_underline_counter, &Window::underline);
1400 break;
1401 case Format::Reverse:
1402 increase_flag(*this, m_reverse_counter, &Window::reverse);
1403 break;
1404 case Format::NoReverse:
1405 decrease_flag(*this, m_reverse_counter, &Window::reverse);
1406 break;
1407 case Format::AltCharset:
1408 increase_flag(*this, m_alt_charset_counter, &Window::altCharset);
1409 break;
1410 case Format::NoAltCharset:
1411 decrease_flag(*this, m_alt_charset_counter, &Window::altCharset);
1412 break;
1413 }
1414 return *this;
1415 }
1416
operator <<(TermManip tm)1417 Window &Window::operator<<(TermManip tm)
1418 {
1419 switch (tm)
1420 {
1421 case TermManip::ClearToEOL:
1422 {
1423 auto x = getX(), y = getY();
1424 mvwhline(m_window, y, x, ' ', m_width-x);
1425 goToXY(x, y);
1426 }
1427 break;
1428 }
1429 return *this;
1430 }
1431
operator <<(const XY & coords)1432 Window &Window::operator<<(const XY &coords)
1433 {
1434 goToXY(coords.x, coords.y);
1435 return *this;
1436 }
1437
operator <<(const char * s)1438 Window &Window::operator<<(const char *s)
1439 {
1440 waddstr(m_window, s);
1441 return *this;
1442 }
1443
operator <<(char c)1444 Window &Window::operator<<(char c)
1445 {
1446 // Might cause problem similar to
1447 // https://github.com/arybczak/ncmpcpp/issues/21, enable for testing as the
1448 // code in the ticket supposed to be culprit was rewritten.
1449 waddnstr(m_window, &c, 1);
1450 //wprintw(m_window, "%c", c);
1451 return *this;
1452 }
1453
operator <<(const wchar_t * ws)1454 Window &Window::operator<<(const wchar_t *ws)
1455 {
1456 waddwstr(m_window, ws);
1457 return *this;
1458 }
1459
operator <<(wchar_t wc)1460 Window &Window::operator<<(wchar_t wc)
1461 {
1462 waddnwstr(m_window, &wc, 1);
1463 return *this;
1464 }
1465
operator <<(int i)1466 Window &Window::operator<<(int i)
1467 {
1468 wprintw(m_window, "%d", i);
1469 return *this;
1470 }
1471
operator <<(double d)1472 Window &Window::operator<<(double d)
1473 {
1474 wprintw(m_window, "%f", d);
1475 return *this;
1476 }
1477
operator <<(const std::string & s)1478 Window &Window::operator<<(const std::string &s)
1479 {
1480 waddnstr(m_window, s.c_str(), s.length());
1481 return *this;
1482 }
1483
operator <<(const std::wstring & ws)1484 Window &Window::operator<<(const std::wstring &ws)
1485 {
1486 waddnwstr(m_window, ws.c_str(), ws.length());
1487 return *this;
1488 }
1489
operator <<(size_t s)1490 Window &Window::operator<<(size_t s)
1491 {
1492 wprintw(m_window, "%zu", s);
1493 return *this;
1494 }
1495
1496 }
1497