Restore curses after running external command
[ncmpcpp.git] / src / curses / window.cpp
blobd9feebad5fbcde139d26291201ced24b6cd05ae7
1 /***************************************************************************
2 * Copyright (C) 2008-2017 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 ***************************************************************************/
21 #include <algorithm>
22 #include <cstring>
23 #include <cstdio>
24 #include <cstdlib>
25 #include <iostream>
26 #include <sys/select.h>
27 #include <unistd.h>
29 #include "utility/readline.h"
30 #include "utility/string.h"
31 #include "utility/wide_string.h"
32 #include "window.h"
34 namespace {
36 namespace rl {
38 bool aborted;
40 NC::Window *w;
41 size_t start_x;
42 size_t start_y;
43 size_t width;
44 bool encrypted;
45 const char *base;
47 int read_key(FILE *)
49 size_t x;
50 bool done;
51 int result;
54 x = w->getX();
55 if (w->runPromptHook(rl_line_buffer, &done))
57 // Do not end if readline is in one of its commands, e.g. searching
58 // through history, as it doesn't actually make readline exit and it
59 // becomes stuck in a loop.
60 if (!RL_ISSTATE(RL_STATE_DISPATCHING) && done)
62 rl_done = 1;
63 return EOF;
65 w->goToXY(x, start_y);
67 w->refresh();
68 result = w->readKey();
69 if (!w->FDCallbacksListEmpty())
71 w->goToXY(x, start_y);
72 w->refresh();
75 while (result == ERR);
76 return result;
79 void display_string()
81 auto print_char = [](wchar_t wc) {
82 if (encrypted)
83 *w << '*';
84 else
85 *w << wc;
87 auto print_string = [](wchar_t *ws, size_t len) {
88 if (encrypted)
89 for (size_t i = 0; i < len; ++i)
90 *w << '*';
91 else
92 *w << ws;
94 auto narrow_to_wide = [](wchar_t *dest, const char *src, size_t n) {
95 size_t result = 0;
96 // convert the string and substitute invalid multibyte chars with dots.
97 for (size_t i = 0; i < n;)
99 int ret = mbtowc(&dest[result], &src[i], n-i);
100 if (ret > 0)
102 i += ret;
103 ++result;
105 else if (ret == -1)
107 dest[result] = L'.';
108 ++i;
109 ++result;
111 else
112 throw std::runtime_error("mbtowc: unexpected return value");
114 return result;
117 // copy the part of the string that is before the cursor to pre_pos
118 char pt = rl_line_buffer[rl_point];
119 rl_line_buffer[rl_point] = 0;
120 wchar_t pre_pos[rl_point+1];
121 pre_pos[narrow_to_wide(pre_pos, rl_line_buffer, rl_point)] = 0;
122 rl_line_buffer[rl_point] = pt;
124 int pos = wcswidth(pre_pos, rl_point);
125 if (pos < 0)
126 pos = rl_point;
128 // clear the area for the string
129 mvwhline(w->raw(), start_y, start_x, ' ', width+1);
131 w->goToXY(start_x, start_y);
132 if (size_t(pos) <= width)
134 // if the current position in the string is not bigger than allowed
135 // width, print the part of the string before cursor position...
137 print_string(pre_pos, pos);
139 // ...and then print the rest char-by-char until there is no more area
140 wchar_t post_pos[rl_end-rl_point+1];
141 post_pos[narrow_to_wide(post_pos, rl_line_buffer+rl_point, rl_end-rl_point)] = 0;
143 size_t cpos = pos;
144 for (wchar_t *c = post_pos; *c != 0; ++c)
146 int n = wcwidth(*c);
147 if (n < 0)
149 print_char(L'.');
150 ++cpos;
152 else
154 if (cpos+n > width)
155 break;
156 cpos += n;
157 print_char(*c);
161 else
163 // if the current position in the string is bigger than allowed
164 // width, we always keep the cursor at the end of the line (it
165 // would be nice to have more flexible scrolling, but for now
166 // let's stick to that) by cutting the beginning of the part
167 // of the string before the cursor until it fits the area.
169 wchar_t *mod_pre_pos = pre_pos;
170 while (*mod_pre_pos != 0)
172 ++mod_pre_pos;
173 int n = wcwidth(*mod_pre_pos);
174 if (n < 0)
175 --pos;
176 else
177 pos -= n;
178 if (size_t(pos) <= width)
179 break;
181 print_string(mod_pre_pos, pos);
183 w->goToXY(start_x+pos, start_y);
186 int add_base()
188 rl_insert_text(base);
189 return 0;
194 int color_pair_counter;
195 std::vector<int> color_pair_map;
199 namespace NC {
201 const short Color::transparent = -1;
202 const short Color::current = -2;
204 Color Color::Default(0, 0, true, false);
205 Color Color::Black(COLOR_BLACK, Color::current);
206 Color Color::Red(COLOR_RED, Color::current);
207 Color Color::Green(COLOR_GREEN, Color::current);
208 Color Color::Yellow(COLOR_YELLOW, Color::current);
209 Color Color::Blue(COLOR_BLUE, Color::current);
210 Color Color::Magenta(COLOR_MAGENTA, Color::current);
211 Color Color::Cyan(COLOR_CYAN, Color::current);
212 Color Color::White(COLOR_WHITE, Color::current);
213 Color Color::End(0, 0, false, true);
215 int Color::pairNumber() const
217 // If colors are disabled, return default pair value.
218 if (color_pair_map.empty())
219 return 0;
221 int result = 0;
222 if (isEnd())
223 throw std::logic_error("'end' doesn't have a corresponding pair number");
224 else if (!isDefault())
226 if (!currentBackground())
227 result = (background() + 1) % COLORS;
228 result *= 256;
229 result += foreground() % COLORS;
231 assert(result < int(color_pair_map.size()));
233 // NCurses allows for a limited number of color pairs to be registered, so
234 // in order to be able to support all the combinations we want to, we need
235 // to dynamically register only pairs of colors we're actually using.
236 if (!color_pair_map[result])
238 // Check if there are any unused pairs left and either register the one
239 // that was requested or return a default one if there is no space left.
240 if (color_pair_counter >= COLOR_PAIRS)
241 result = 0;
242 else
244 init_pair(color_pair_counter, foreground(), background());
245 color_pair_map[result] = color_pair_counter;
246 ++color_pair_counter;
249 result = color_pair_map[result];
251 return result;
254 std::istream &operator>>(std::istream &is, Color &c)
256 const short invalid_color_value = -1337;
257 auto get_single_color = [](const std::string &s, bool background) {
258 short result = invalid_color_value;
259 if (s == "black")
260 result = COLOR_BLACK;
261 else if (s == "red")
262 result = COLOR_RED;
263 else if (s == "green")
264 result = COLOR_GREEN;
265 else if (s == "yellow")
266 result = COLOR_YELLOW;
267 else if (s == "blue")
268 result = COLOR_BLUE;
269 else if (s == "magenta")
270 result = COLOR_MAGENTA;
271 else if (s == "cyan")
272 result = COLOR_CYAN;
273 else if (s == "white")
274 result = COLOR_WHITE;
275 else if (background && s == "transparent")
276 result = NC::Color::transparent;
277 else if (background && s == "current")
278 result = NC::Color::current;
279 else if (std::all_of(s.begin(), s.end(), isdigit))
281 result = atoi(s.c_str());
282 if (result < (background ? 0 : 1) || result > 256)
283 result = invalid_color_value;
284 else
285 --result;
287 return result;
290 auto get_color = [](std::istream &is_) {
291 std::string result;
292 while (!is_.eof() && isalnum(is_.peek()))
293 result.push_back(is_.get());
294 return result;
297 std::string sc = get_color(is);
299 if (sc == "default")
300 c = Color::Default;
301 else if (sc == "end")
302 c = Color::End;
303 else
305 short fg = get_single_color(sc, false);
306 if (fg == invalid_color_value)
307 is.setstate(std::ios::failbit);
308 // Check if there is background color
309 else if (!is.eof() && is.peek() == '_')
311 is.get();
312 sc = get_color(is);
313 short bg = get_single_color(sc, true);
314 if (bg == invalid_color_value)
315 is.setstate(std::ios::failbit);
316 else
317 c = Color(fg, bg);
319 else
320 c = Color(fg, NC::Color::current);
322 return is;
325 NC::Format reverseFormat(NC::Format fmt)
327 switch (fmt)
329 case NC::Format::Bold:
330 return NC::Format::NoBold;
331 case NC::Format::NoBold:
332 return NC::Format::Bold;
333 case NC::Format::Underline:
334 return NC::Format::NoUnderline;
335 case NC::Format::NoUnderline:
336 return NC::Format::Underline;
337 case NC::Format::Reverse:
338 return NC::Format::NoReverse;
339 case NC::Format::NoReverse:
340 return NC::Format::Reverse;
341 case NC::Format::AltCharset:
342 return NC::Format::NoAltCharset;
343 case NC::Format::NoAltCharset:
344 return NC::Format::AltCharset;
346 // Unreachable, silence GCC.
347 return fmt;
350 namespace Mouse {
352 namespace {
354 bool supportEnabled = false;
358 void enable()
360 if (!supportEnabled)
361 return;
362 // save old highlight mouse tracking
363 std::printf("\e[?1001s");
364 // enable mouse tracking
365 std::printf("\e[?1000h");
366 // try to enable extended (urxvt) mouse tracking
367 std::printf("\e[?1015h");
368 // send the above to the terminal immediately
369 std::fflush(stdout);
372 void disable()
374 if (!supportEnabled)
375 return;
376 // disable extended (urxvt) mouse tracking
377 std::printf("\e[?1015l");
378 // disable mouse tracking
379 std::printf("\e[?1000l");
380 // restore old highlight mouse tracking
381 std::printf("\e[?1001r");
382 // send the above to the terminal immediately
383 std::fflush(stdout);
388 void initScreen(bool enable_colors, bool enable_mouse)
390 initscr();
391 if (has_colors() && enable_colors)
393 start_color();
394 use_default_colors();
395 color_pair_map.resize(256 * 256, 0);
397 // Predefine pairs for colors with transparent background, all the other
398 // ones will be dynamically registered in Color::pairNumber when they're
399 // used.
400 color_pair_counter = 1;
401 for (int fg = 0; fg < COLORS; ++fg, ++color_pair_counter)
403 init_pair(color_pair_counter, fg, -1);
404 color_pair_map[fg] = color_pair_counter;
407 raw();
408 nonl();
409 noecho();
410 timeout(0);
411 curs_set(0);
413 // setup mouse
414 Mouse::supportEnabled = enable_mouse;
415 Mouse::enable();
417 // initialize readline (needed, otherwise we get segmentation
418 // fault on SIGWINCH). also, initialize first as doing this
419 // later erases keys bound with rl_bind_key for some users.
420 rl_initialize();
421 // disable autocompletion
422 rl_attempted_completion_function = [](const char *, int, int) -> char ** {
423 rl_attempted_completion_over = 1;
424 return nullptr;
426 auto abort_prompt = [](int, int) -> int {
427 rl::aborted = true;
428 rl_done = 1;
429 return 0;
431 // if ctrl-c or ctrl-g is pressed, abort the prompt
432 rl_bind_key('\3', abort_prompt);
433 rl_bind_key('\7', abort_prompt);
434 // do not change the state of the terminal
435 rl_prep_term_function = nullptr;
436 rl_deprep_term_function = nullptr;
437 // do not catch signals
438 rl_catch_signals = 0;
439 rl_catch_sigwinch = 0;
440 // overwrite readline callbacks
441 rl_getc_function = rl::read_key;
442 rl_redisplay_function = rl::display_string;
443 rl_startup_hook = rl::add_base;
446 void pauseScreen()
448 def_prog_mode();
449 endwin();
452 void unpauseScreen()
454 refresh();
457 void destroyScreen()
459 Mouse::disable();
460 curs_set(1);
461 endwin();
464 Window::Window(size_t startx, size_t starty, size_t width, size_t height,
465 std::string title, Color color, Border border)
466 : m_window(nullptr),
467 m_start_x(startx),
468 m_start_y(starty),
469 m_width(width),
470 m_height(height),
471 m_window_timeout(-1),
472 m_border(std::move(border)),
473 m_prompt_hook(0),
474 m_title(std::move(title)),
475 m_escape_terminal_sequences(true),
476 m_bold_counter(0),
477 m_underline_counter(0),
478 m_reverse_counter(0),
479 m_alt_charset_counter(0)
481 if (m_start_x > size_t(COLS)
482 || m_start_y > size_t(LINES)
483 || m_width+m_start_x > size_t(COLS)
484 || m_height+m_start_y > size_t(LINES))
485 throw std::logic_error("constructed window doesn't fit into the terminal");
487 if (m_border)
489 ++m_start_x;
490 ++m_start_y;
491 m_width -= 2;
492 m_height -= 2;
494 if (!m_title.empty())
496 m_start_y += 2;
497 m_height -= 2;
500 m_window = newpad(m_height, m_width);
501 wtimeout(m_window, 0);
503 setBaseColor(color);
504 setColor(m_base_color);
507 Window::Window(const Window &rhs)
508 : m_window(dupwin(rhs.m_window))
509 , m_start_x(rhs.m_start_x)
510 , m_start_y(rhs.m_start_y)
511 , m_width(rhs.m_width)
512 , m_height(rhs.m_height)
513 , m_window_timeout(rhs.m_window_timeout)
514 , m_color(rhs.m_color)
515 , m_base_color(rhs.m_base_color)
516 , m_border(rhs.m_border)
517 , m_prompt_hook(rhs.m_prompt_hook)
518 , m_title(rhs.m_title)
519 , m_color_stack(rhs.m_color_stack)
520 , m_input_queue(rhs.m_input_queue)
521 , m_fds(rhs.m_fds)
522 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
523 , m_bold_counter(rhs.m_bold_counter)
524 , m_underline_counter(rhs.m_underline_counter)
525 , m_reverse_counter(rhs.m_reverse_counter)
526 , m_alt_charset_counter(rhs.m_alt_charset_counter)
528 setColor(m_color);
531 Window::Window(Window &&rhs)
532 : m_window(rhs.m_window)
533 , m_start_x(rhs.m_start_x)
534 , m_start_y(rhs.m_start_y)
535 , m_width(rhs.m_width)
536 , m_height(rhs.m_height)
537 , m_window_timeout(rhs.m_window_timeout)
538 , m_color(rhs.m_color)
539 , m_base_color(rhs.m_base_color)
540 , m_border(rhs.m_border)
541 , m_prompt_hook(rhs.m_prompt_hook)
542 , m_title(std::move(rhs.m_title))
543 , m_color_stack(std::move(rhs.m_color_stack))
544 , m_input_queue(std::move(rhs.m_input_queue))
545 , m_fds(std::move(rhs.m_fds))
546 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
547 , m_bold_counter(rhs.m_bold_counter)
548 , m_underline_counter(rhs.m_underline_counter)
549 , m_reverse_counter(rhs.m_reverse_counter)
550 , m_alt_charset_counter(rhs.m_alt_charset_counter)
552 rhs.m_window = nullptr;
555 Window &Window::operator=(Window rhs)
557 std::swap(m_window, rhs.m_window);
558 std::swap(m_start_x, rhs.m_start_x);
559 std::swap(m_start_y, rhs.m_start_y);
560 std::swap(m_width, rhs.m_width);
561 std::swap(m_height, rhs.m_height);
562 std::swap(m_window_timeout, rhs.m_window_timeout);
563 std::swap(m_color, rhs.m_color);
564 std::swap(m_base_color, rhs.m_base_color);
565 std::swap(m_border, rhs.m_border);
566 std::swap(m_prompt_hook, rhs.m_prompt_hook);
567 std::swap(m_title, rhs.m_title);
568 std::swap(m_color_stack, rhs.m_color_stack);
569 std::swap(m_input_queue, rhs.m_input_queue);
570 std::swap(m_fds, rhs.m_fds);
571 std::swap(m_escape_terminal_sequences, rhs.m_escape_terminal_sequences);
572 std::swap(m_bold_counter, rhs.m_bold_counter);
573 std::swap(m_underline_counter, rhs.m_underline_counter);
574 std::swap(m_reverse_counter, rhs.m_reverse_counter);
575 std::swap(m_alt_charset_counter, rhs.m_alt_charset_counter);
576 return *this;
579 Window::~Window()
581 delwin(m_window);
584 void Window::setColor(Color c)
586 if (c.isDefault())
587 c = m_base_color;
588 if (c != Color::Default)
590 assert(!c.currentBackground());
591 wcolor_set(m_window, c.pairNumber(), nullptr);
593 else
594 wcolor_set(m_window, m_base_color.pairNumber(), nullptr);
595 m_color = std::move(c);
598 void Window::setBaseColor(const Color &color)
600 if (color.currentBackground())
601 m_base_color = Color(color.foreground(), Color::transparent);
602 else
603 m_base_color = color;
606 void Window::setBorder(Border border)
608 if (!border && m_border)
610 --m_start_x;
611 --m_start_y;
612 m_height += 2;
613 m_width += 2;
614 recreate(m_width, m_height);
616 else if (border && !m_border)
618 ++m_start_x;
619 ++m_start_y;
620 m_height -= 2;
621 m_width -= 2;
622 recreate(m_width, m_height);
624 m_border = border;
627 void Window::setTitle(const std::string &new_title)
629 if (!new_title.empty() && m_title.empty())
631 m_start_y += 2;
632 m_height -= 2;
633 recreate(m_width, m_height);
635 else if (new_title.empty() && !m_title.empty())
637 m_start_y -= 2;
638 m_height += 2;
639 recreate(m_width, m_height);
641 m_title = new_title;
644 void Window::recreate(size_t width, size_t height)
646 delwin(m_window);
647 m_window = newpad(height, width);
648 wtimeout(m_window, 0);
649 setColor(m_color);
652 void Window::moveTo(size_t new_x, size_t new_y)
654 m_start_x = new_x;
655 m_start_y = new_y;
656 if (m_border)
658 ++m_start_x;
659 ++m_start_y;
661 if (!m_title.empty())
662 m_start_y += 2;
665 void Window::adjustDimensions(size_t width, size_t height)
667 if (m_border)
669 width -= 2;
670 height -= 2;
672 if (!m_title.empty())
673 height -= 2;
674 m_height = height;
675 m_width = width;
678 void Window::resize(size_t new_width, size_t new_height)
680 adjustDimensions(new_width, new_height);
681 recreate(m_width, m_height);
684 void Window::refreshBorder() const
686 if (m_border)
688 size_t start_x = getStartX(), start_y = getStarty();
689 size_t width = getWidth(), height = getHeight();
690 color_set(m_border->pairNumber(), nullptr);
691 attron(A_ALTCHARSET);
692 // corners
693 mvaddch(start_y, start_x, 'l');
694 mvaddch(start_y, start_x+width-1, 'k');
695 mvaddch(start_y+height-1, start_x, 'm');
696 mvaddch(start_y+height-1, start_x+width-1, 'j');
697 // lines
698 mvhline(start_y, start_x+1, 'q', width-2);
699 mvhline(start_y+height-1, start_x+1, 'q', width-2);
700 mvvline(start_y+1, start_x, 'x', height-2);
701 mvvline(start_y+1, start_x+width-1, 'x', height-2);
702 if (!m_title.empty())
704 mvaddch(start_y+2, start_x, 't');
705 mvaddch(start_y+2, start_x+width-1, 'u');
707 attroff(A_ALTCHARSET);
709 else
710 color_set(m_base_color.pairNumber(), nullptr);
711 if (!m_title.empty())
713 // clear title line
714 mvhline(m_start_y-2, m_start_x, ' ', m_width);
715 attron(A_BOLD);
716 mvaddstr(m_start_y-2, m_start_x, m_title.c_str());
717 attroff(A_BOLD);
718 // add separator
719 mvhline(m_start_y-1, m_start_x, 0, m_width);
721 standend();
722 ::refresh();
725 void Window::display()
727 refreshBorder();
728 refresh();
731 void Window::refresh()
733 prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);
736 void Window::clear()
738 werase(m_window);
739 setColor(m_base_color);
742 void Window::bold(bool bold_state) const
744 (bold_state ? wattron : wattroff)(m_window, A_BOLD);
747 void Window::underline(bool underline_state) const
749 (underline_state ? wattron : wattroff)(m_window, A_UNDERLINE);
752 void Window::reverse(bool reverse_state) const
754 (reverse_state ? wattron : wattroff)(m_window, A_REVERSE);
757 void Window::altCharset(bool altcharset_state) const
759 (altcharset_state ? wattron : wattroff)(m_window, A_ALTCHARSET);
762 void Window::setTimeout(int timeout)
764 m_window_timeout = timeout;
767 void Window::addFDCallback(int fd, void (*callback)())
769 m_fds.push_back(std::make_pair(fd, callback));
772 void Window::clearFDCallbacksList()
774 m_fds.clear();
777 bool Window::FDCallbacksListEmpty() const
779 return m_fds.empty();
782 Key::Type Window::getInputChar(int key)
784 if (!m_escape_terminal_sequences || key != Key::Escape)
785 return key;
786 auto define_mouse_event = [this](int type) {
787 switch (type & ~28)
789 case 32:
790 m_mouse_event.bstate = BUTTON1_PRESSED;
791 break;
792 case 33:
793 m_mouse_event.bstate = BUTTON2_PRESSED;
794 break;
795 case 34:
796 m_mouse_event.bstate = BUTTON3_PRESSED;
797 break;
798 case 96:
799 m_mouse_event.bstate = BUTTON4_PRESSED;
800 break;
801 case 97:
802 m_mouse_event.bstate = BUTTON5_PRESSED;
803 break;
804 default:
805 return Key::None;
807 if (type & 4)
808 m_mouse_event.bstate |= BUTTON_SHIFT;
809 if (type & 8)
810 m_mouse_event.bstate |= BUTTON_ALT;
811 if (type & 16)
812 m_mouse_event.bstate |= BUTTON_CTRL;
813 if (m_mouse_event.x < 0 || m_mouse_event.x >= COLS)
814 return Key::None;
815 if (m_mouse_event.y < 0 || m_mouse_event.y >= LINES)
816 return Key::None;
817 return Key::Mouse;
819 auto get_xterm_modifier_key = [](int ch) {
820 Key::Type modifier;
821 switch (ch)
823 case '2':
824 modifier = Key::Shift;
825 break;
826 case '3':
827 modifier = Key::Alt;
828 break;
829 case '4':
830 modifier = Key::Alt | Key::Shift;
831 break;
832 case '5':
833 modifier = Key::Ctrl;
834 break;
835 case '6':
836 modifier = Key::Ctrl | Key::Shift;
837 break;
838 case '7':
839 modifier = Key::Alt | Key::Ctrl;
840 break;
841 case '8':
842 modifier = Key::Alt | Key::Ctrl | Key::Shift;
843 break;
844 default:
845 modifier = Key::None;
847 return modifier;
849 auto parse_number = [this](int &result) {
850 int x;
851 while (true)
853 x = wgetch(m_window);
854 if (!isdigit(x))
855 return x;
856 result = result*10 + x - '0';
859 key = wgetch(m_window);
860 switch (key)
862 case '\t': // tty
863 return Key::Shift | Key::Tab;
864 case 'O':
865 key = wgetch(m_window);
866 switch (key)
868 // eterm
869 case 'A':
870 return Key::Up;
871 case 'B':
872 return Key::Down;
873 case 'C':
874 return Key::Right;
875 case 'D':
876 return Key::Left;
877 // terminator
878 case 'F':
879 return Key::End;
880 case 'H':
881 return Key::Home;
882 // rxvt
883 case 'a':
884 return Key::Ctrl | Key::Up;
885 case 'b':
886 return Key::Ctrl | Key::Down;
887 case 'c':
888 return Key::Ctrl | Key::Right;
889 case 'd':
890 return Key::Ctrl | Key::Left;
891 // xterm
892 case 'P':
893 return Key::F1;
894 case 'Q':
895 return Key::F2;
896 case 'R':
897 return Key::F3;
898 case 'S':
899 return Key::F4;
900 default:
901 return Key::None;
903 case '[':
904 key = wgetch(m_window);
905 switch (key)
907 case 'a':
908 return Key::Shift | Key::Up;
909 case 'b':
910 return Key::Shift | Key::Down;
911 case 'c':
912 return Key::Shift | Key::Right;
913 case 'd':
914 return Key::Shift | Key::Left;
915 case 'A':
916 return Key::Up;
917 case 'B':
918 return Key::Down;
919 case 'C':
920 return Key::Right;
921 case 'D':
922 return Key::Left;
923 case 'F': // xterm
924 return Key::End;
925 case 'H': // xterm
926 return Key::Home;
927 case 'M': // standard mouse event
929 key = wgetch(m_window);
930 int raw_x = wgetch(m_window);
931 int raw_y = wgetch(m_window);
932 // support coordinates up to 255
933 m_mouse_event.x = (raw_x - 33) & 0xff;
934 m_mouse_event.y = (raw_y - 33) & 0xff;
935 return define_mouse_event(key);
937 case 'Z':
938 return Key::Shift | Key::Tab;
939 case '[': // F1 to F5 in tty
940 key = wgetch(m_window);
941 switch (key)
943 case 'A':
944 return Key::F1;
945 case 'B':
946 return Key::F2;
947 case 'C':
948 return Key::F3;
949 case 'D':
950 return Key::F4;
951 case 'E':
952 return Key::F5;
953 default:
954 return Key::None;
956 case '1': case '2': case '3':
957 case '4': case '5': case '6':
958 case '7': case '8': case '9':
960 key -= '0';
961 int delim = parse_number(key);
962 if (key >= 2 && key <= 8)
964 Key::Type modifier;
965 switch (delim)
967 case '~':
968 modifier = Key::Null;
969 break;
970 case '^':
971 modifier = Key::Ctrl;
972 break;
973 case '$':
974 modifier = Key::Shift;
975 break;
976 case '@':
977 modifier = Key::Ctrl | Key::Shift;
978 break;
979 case ';': // xterm insert/delete/page up/page down
981 int local_key = wgetch(m_window);
982 modifier = get_xterm_modifier_key(local_key);
983 local_key = wgetch(m_window);
984 if (local_key != '~' || (key != 2 && key != 3 && key != 5 && key != 6))
985 return Key::None;
986 break;
988 default:
989 return Key::None;
991 switch (key)
993 case 2:
994 return modifier | Key::Insert;
995 case 3:
996 return modifier | Key::Delete;
997 case 4:
998 return modifier | Key::End;
999 case 5:
1000 return modifier | Key::PageUp;
1001 case 6:
1002 return modifier | Key::PageDown;
1003 case 7:
1004 return modifier | Key::Home;
1005 case 8:
1006 return modifier | Key::End;
1007 default:
1008 std::cerr << "Unreachable code, aborting.\n";
1009 std::terminate();
1012 switch (delim)
1014 case '~':
1016 switch (key)
1018 case 1: // tty
1019 return Key::Home;
1020 case 11:
1021 return Key::F1;
1022 case 12:
1023 return Key::F2;
1024 case 13:
1025 return Key::F3;
1026 case 14:
1027 return Key::F4;
1028 case 15:
1029 return Key::F5;
1030 case 17: // not a typo
1031 return Key::F6;
1032 case 18:
1033 return Key::F7;
1034 case 19:
1035 return Key::F8;
1036 case 20:
1037 return Key::F9;
1038 case 21:
1039 return Key::F10;
1040 case 23: // not a typo
1041 return Key::F11;
1042 case 24:
1043 return Key::F12;
1044 default:
1045 return Key::None;
1048 case ';':
1049 switch (key)
1051 case 1: // xterm
1053 key = wgetch(m_window);
1054 Key::Type modifier = get_xterm_modifier_key(key);
1055 if (modifier == Key::None)
1056 return Key::None;
1057 key = wgetch(m_window);
1058 switch (key)
1060 case 'A':
1061 return modifier | Key::Up;
1062 case 'B':
1063 return modifier | Key::Down;
1064 case 'C':
1065 return modifier | Key::Right;
1066 case 'D':
1067 return modifier | Key::Left;
1068 case 'F':
1069 return modifier | Key::End;
1070 case 'H':
1071 return modifier | Key::Home;
1072 default:
1073 return Key::None;
1076 default: // urxvt mouse
1077 m_mouse_event.x = 0;
1078 delim = parse_number(m_mouse_event.x);
1079 if (delim != ';')
1080 return Key::None;
1081 m_mouse_event.y = 0;
1082 delim = parse_number(m_mouse_event.y);
1083 if (delim != 'M')
1084 return Key::None;
1085 --m_mouse_event.x;
1086 --m_mouse_event.y;
1087 return define_mouse_event(key);
1089 default:
1090 return Key::None;
1093 default:
1094 return Key::None;
1096 case ERR:
1097 return Key::Escape;
1098 default: // alt + something
1100 auto key_prim = getInputChar(key);
1101 if (key_prim != Key::None)
1102 return Key::Alt | key_prim;
1103 return Key::None;
1108 Key::Type Window::readKey()
1110 Key::Type result;
1111 // if there are characters in input queue,
1112 // get them and return immediately.
1113 if (!m_input_queue.empty())
1115 result = m_input_queue.front();
1116 m_input_queue.pop();
1117 return result;
1120 fd_set fds_read;
1121 FD_ZERO(&fds_read);
1122 FD_SET(STDIN_FILENO, &fds_read);
1123 timeval timeout = { m_window_timeout/1000, (m_window_timeout%1000)*1000 };
1125 int fd_max = STDIN_FILENO;
1126 for (const auto &fd : m_fds)
1128 if (fd.first > fd_max)
1129 fd_max = fd.first;
1130 FD_SET(fd.first, &fds_read);
1133 auto tv_addr = m_window_timeout < 0 ? nullptr : &timeout;
1134 int res = select(fd_max+1, &fds_read, nullptr, nullptr, tv_addr);
1135 if (res > 0)
1137 if (FD_ISSET(STDIN_FILENO, &fds_read))
1139 int key = wgetch(m_window);
1140 if (key == EOF)
1141 result = Key::EoF;
1142 else
1143 result = getInputChar(key);
1145 else
1146 result = Key::None;
1148 for (const auto &fd : m_fds)
1149 if (FD_ISSET(fd.first, &fds_read))
1150 fd.second();
1152 else
1153 result = Key::None;
1154 return result;
1157 void Window::pushChar(const Key::Type ch)
1159 m_input_queue.push(ch);
1162 std::string Window::prompt(const std::string &base, size_t width, bool encrypted)
1164 std::string result;
1166 rl::aborted = false;
1167 rl::w = this;
1168 getyx(m_window, rl::start_y, rl::start_x);
1169 rl::width = std::min(m_width-rl::start_x-1, width-1);
1170 rl::encrypted = encrypted;
1171 rl::base = base.c_str();
1173 curs_set(1);
1174 Mouse::disable();
1175 m_escape_terminal_sequences = false;
1176 char *input = readline(nullptr);
1177 m_escape_terminal_sequences = true;
1178 Mouse::enable();
1179 curs_set(0);
1180 if (input != nullptr)
1182 #ifdef HAVE_READLINE_HISTORY_H
1183 if (!encrypted && input[0] != 0)
1184 add_history(input);
1185 #endif // HAVE_READLINE_HISTORY_H
1186 result = input;
1187 free(input);
1190 if (rl::aborted)
1191 throw PromptAborted(std::move(result));
1193 return result;
1196 void Window::goToXY(int x, int y)
1198 wmove(m_window, y, x);
1201 int Window::getX()
1203 return getcurx(m_window);
1206 int Window::getY()
1208 return getcury(m_window);
1211 bool Window::hasCoords(int &x, int &y)
1213 return wmouse_trafo(m_window, &y, &x, 0);
1216 bool Window::runPromptHook(const char *arg, bool *done) const
1218 if (m_prompt_hook)
1220 bool continue_ = m_prompt_hook(arg);
1221 if (done != nullptr)
1222 *done = !continue_;
1223 return true;
1225 else
1226 return false;
1229 size_t Window::getWidth() const
1231 if (m_border)
1232 return m_width+2;
1233 else
1234 return m_width;
1237 size_t Window::getHeight() const
1239 size_t height = m_height;
1240 if (m_border)
1241 height += 2;
1242 if (!m_title.empty())
1243 height += 2;
1244 return height;
1247 size_t Window::getStartX() const
1249 if (m_border)
1250 return m_start_x-1;
1251 else
1252 return m_start_x;
1255 size_t Window::getStarty() const
1257 size_t starty = m_start_y;
1258 if (m_border)
1259 --starty;
1260 if (!m_title.empty())
1261 starty -= 2;
1262 return starty;
1265 const std::string &Window::getTitle() const
1267 return m_title;
1270 const Color &Window::getColor() const
1272 return m_color;
1275 const Border &Window::getBorder() const
1277 return m_border;
1280 int Window::getTimeout() const
1282 return m_window_timeout;
1285 const MEVENT &Window::getMouseEvent()
1287 return m_mouse_event;
1290 void Window::scroll(Scroll where)
1292 idlok(m_window, 1);
1293 scrollok(m_window, 1);
1294 switch (where)
1296 case Scroll::Up:
1297 wscrl(m_window, 1);
1298 break;
1299 case Scroll::Down:
1300 wscrl(m_window, -1);
1301 break;
1302 case Scroll::PageUp:
1303 wscrl(m_window, m_width);
1304 break;
1305 case Scroll::PageDown:
1306 wscrl(m_window, -m_width);
1307 break;
1308 default:
1309 break;
1311 idlok(m_window, 0);
1312 scrollok(m_window, 0);
1316 Window &Window::operator<<(const Color &c)
1318 if (c.isDefault())
1320 while (!m_color_stack.empty())
1321 m_color_stack.pop();
1322 setColor(m_base_color);
1324 else if (c.isEnd())
1326 if (!m_color_stack.empty())
1327 m_color_stack.pop();
1328 if (!m_color_stack.empty())
1329 setColor(m_color_stack.top());
1330 else
1331 setColor(m_base_color);
1333 else
1335 if (c.currentBackground())
1337 short background = m_color.isDefault()
1338 ? Color::transparent
1339 : m_color.background();
1340 Color cc = Color(c.foreground(), background);
1341 setColor(cc);
1342 m_color_stack.push(cc);
1344 else
1346 setColor(c);
1347 m_color_stack.push(c);
1350 return *this;
1353 Window &Window::operator<<(Format format)
1355 auto increase_flag = [](Window &w, int &flag, auto set) {
1356 ++flag;
1357 (w.*set)(true);
1359 auto decrease_flag = [](Window &w, int &flag, auto set) {
1360 if (flag > 0)
1362 --flag;
1363 if (flag == 0)
1364 (w.*set)(false);
1367 switch (format)
1369 case Format::Bold:
1370 increase_flag(*this, m_bold_counter, &Window::bold);
1371 break;
1372 case Format::NoBold:
1373 decrease_flag(*this, m_bold_counter, &Window::bold);
1374 break;
1375 case Format::Underline:
1376 increase_flag(*this, m_underline_counter, &Window::underline);
1377 break;
1378 case Format::NoUnderline:
1379 decrease_flag(*this, m_underline_counter, &Window::underline);
1380 break;
1381 case Format::Reverse:
1382 increase_flag(*this, m_reverse_counter, &Window::reverse);
1383 break;
1384 case Format::NoReverse:
1385 decrease_flag(*this, m_reverse_counter, &Window::reverse);
1386 break;
1387 case Format::AltCharset:
1388 increase_flag(*this, m_alt_charset_counter, &Window::altCharset);
1389 break;
1390 case Format::NoAltCharset:
1391 decrease_flag(*this, m_alt_charset_counter, &Window::altCharset);
1392 break;
1394 return *this;
1397 Window &Window::operator<<(TermManip tm)
1399 switch (tm)
1401 case TermManip::ClearToEOL:
1403 auto x = getX(), y = getY();
1404 mvwhline(m_window, y, x, ' ', m_width-x);
1405 goToXY(x, y);
1407 break;
1409 return *this;
1412 Window &Window::operator<<(const XY &coords)
1414 goToXY(coords.x, coords.y);
1415 return *this;
1418 Window &Window::operator<<(const char *s)
1420 waddstr(m_window, s);
1421 return *this;
1424 Window &Window::operator<<(char c)
1426 // Might cause problem similar to
1427 // https://github.com/arybczak/ncmpcpp/issues/21, enable for testing as the
1428 // code in the ticket supposed to be culprit was rewritten.
1429 waddnstr(m_window, &c, 1);
1430 //wprintw(m_window, "%c", c);
1431 return *this;
1434 Window &Window::operator<<(const wchar_t *ws)
1436 waddwstr(m_window, ws);
1437 return *this;
1440 Window &Window::operator<<(wchar_t wc)
1442 waddnwstr(m_window, &wc, 1);
1443 return *this;
1446 Window &Window::operator<<(int i)
1448 wprintw(m_window, "%d", i);
1449 return *this;
1452 Window &Window::operator<<(double d)
1454 wprintw(m_window, "%f", d);
1455 return *this;
1458 Window &Window::operator<<(const std::string &s)
1460 waddnstr(m_window, s.c_str(), s.length());
1461 return *this;
1464 Window &Window::operator<<(const std::wstring &ws)
1466 waddnwstr(m_window, ws.c_str(), ws.length());
1467 return *this;
1470 Window &Window::operator<<(size_t s)
1472 wprintw(m_window, "%zu", s);
1473 return *this;