Make 'update_environment' action also update local mpd status
[ncmpcpp.git] / src / curses / window.cpp
blob93ca3a874a82eb691c20536e6e29872f63925ae4
1 /***************************************************************************
2 * Copyright (C) 2008-2016 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 if (done)
59 rl_done = 1;
60 return EOF;
62 w->goToXY(x, start_y);
64 w->refresh();
65 result = w->readKey();
66 if (!w->FDCallbacksListEmpty())
68 w->goToXY(x, start_y);
69 w->refresh();
72 while (result == ERR);
73 return result;
76 void display_string()
78 auto print_char = [](wchar_t wc) {
79 if (encrypted)
80 *w << '*';
81 else
82 *w << wc;
84 auto print_string = [](wchar_t *ws, size_t len) {
85 if (encrypted)
86 for (size_t i = 0; i < len; ++i)
87 *w << '*';
88 else
89 *w << ws;
91 auto narrow_to_wide = [](wchar_t *dest, const char *src, size_t n) {
92 size_t result = 0;
93 // convert the string and substitute invalid multibyte chars with dots.
94 for (size_t i = 0; i < n;)
96 int ret = mbtowc(&dest[result], &src[i], n-i);
97 if (ret > 0)
99 i += ret;
100 ++result;
102 else if (ret == -1)
104 dest[result] = L'.';
105 ++i;
106 ++result;
108 else
109 throw std::runtime_error("mbtowc: unexpected return value");
111 return result;
114 // copy the part of the string that is before the cursor to pre_pos
115 char pt = rl_line_buffer[rl_point];
116 rl_line_buffer[rl_point] = 0;
117 wchar_t pre_pos[rl_point+1];
118 pre_pos[narrow_to_wide(pre_pos, rl_line_buffer, rl_point)] = 0;
119 rl_line_buffer[rl_point] = pt;
121 int pos = wcswidth(pre_pos, rl_point);
122 if (pos < 0)
123 pos = rl_point;
125 // clear the area for the string
126 mvwhline(w->raw(), start_y, start_x, ' ', width+1);
128 w->goToXY(start_x, start_y);
129 if (size_t(pos) <= width)
131 // if the current position in the string is not bigger than allowed
132 // width, print the part of the string before cursor position...
134 print_string(pre_pos, pos);
136 // ...and then print the rest char-by-char until there is no more area
137 wchar_t post_pos[rl_end-rl_point+1];
138 post_pos[narrow_to_wide(post_pos, rl_line_buffer+rl_point, rl_end-rl_point)] = 0;
140 size_t cpos = pos;
141 for (wchar_t *c = post_pos; *c != 0; ++c)
143 int n = wcwidth(*c);
144 if (n < 0)
146 print_char(L'.');
147 ++cpos;
149 else
151 if (cpos+n > width)
152 break;
153 cpos += n;
154 print_char(*c);
158 else
160 // if the current position in the string is bigger than allowed
161 // width, we always keep the cursor at the end of the line (it
162 // would be nice to have more flexible scrolling, but for now
163 // let's stick to that) by cutting the beginning of the part
164 // of the string before the cursor until it fits the area.
166 wchar_t *mod_pre_pos = pre_pos;
167 while (*mod_pre_pos != 0)
169 ++mod_pre_pos;
170 int n = wcwidth(*mod_pre_pos);
171 if (n < 0)
172 --pos;
173 else
174 pos -= n;
175 if (size_t(pos) <= width)
176 break;
178 print_string(mod_pre_pos, pos);
180 w->goToXY(start_x+pos, start_y);
183 int add_base()
185 rl_insert_text(base);
186 return 0;
191 int color_pair_counter;
192 std::vector<int> color_pair_map;
196 namespace NC {
198 const short Color::transparent = -1;
199 const short Color::previous = -2;
201 Color Color::Default(0, 0, true, false);
202 Color Color::Black(COLOR_BLACK, Color::previous);
203 Color Color::Red(COLOR_RED, Color::previous);
204 Color Color::Green(COLOR_GREEN, Color::previous);
205 Color Color::Yellow(COLOR_YELLOW, Color::previous);
206 Color Color::Blue(COLOR_BLUE, Color::previous);
207 Color Color::Magenta(COLOR_MAGENTA, Color::previous);
208 Color Color::Cyan(COLOR_CYAN, Color::previous);
209 Color Color::White(COLOR_WHITE, Color::previous);
210 Color Color::End(0, 0, false, true);
212 int Color::pairNumber() const
214 int result = 0;
215 if (isEnd())
216 throw std::logic_error("'end' doesn't have a corresponding pair number");
217 else if (!isDefault())
219 if (!previousBackground())
220 result = background() + 1;
221 result *= 256;
222 result += foreground();
224 assert(result < int(color_pair_map.size()));
226 // NCurses allows for a limited number of color pairs to be registered, so
227 // in order to be able to support all the combinations we want to, we need
228 // to dynamically register only pairs of colors we're actually using.
229 if (!color_pair_map[result])
231 // Check if there are any unused pairs left and either register the one
232 // that was requested or return a default one if there is no space left.
233 if (color_pair_counter >= COLOR_PAIRS)
234 result = 0;
235 else
237 init_pair(color_pair_counter, foreground(), background());
238 color_pair_map[result] = color_pair_counter;
239 ++color_pair_counter;
242 result = color_pair_map[result];
244 return result;
247 std::istream &operator>>(std::istream &is, Color &c)
249 const short invalid_color_value = -1337;
250 auto get_single_color = [](const std::string &s, bool background) {
251 short result = invalid_color_value;
252 if (s == "black")
253 result = COLOR_BLACK;
254 else if (s == "red")
255 result = COLOR_RED;
256 else if (s == "green")
257 result = COLOR_GREEN;
258 else if (s == "yellow")
259 result = COLOR_YELLOW;
260 else if (s == "blue")
261 result = COLOR_BLUE;
262 else if (s == "magenta")
263 result = COLOR_MAGENTA;
264 else if (s == "cyan")
265 result = COLOR_CYAN;
266 else if (s == "white")
267 result = COLOR_WHITE;
268 else if (background && s == "transparent")
269 result = NC::Color::transparent;
270 else if (background && s == "previous")
271 result = NC::Color::previous;
272 else if (std::all_of(s.begin(), s.end(), isdigit))
274 result = atoi(s.c_str());
275 if (result < (background ? 0 : 1) || result > 256)
276 result = invalid_color_value;
277 else
278 --result;
280 return result;
283 auto get_color = [](std::istream &is_) {
284 std::string result;
285 while (!is_.eof() && isalnum(is_.peek()))
286 result.push_back(is_.get());
287 return result;
290 std::string sc = get_color(is);
292 if (sc == "default")
293 c = Color::Default;
294 else if (sc == "end")
295 c = Color::End;
296 else
298 short fg = get_single_color(sc, false);
299 if (fg == invalid_color_value)
300 is.setstate(std::ios::failbit);
301 // Check if there is background color
302 else if (!is.eof() && is.peek() == '_')
304 is.get();
305 sc = get_color(is);
306 short bg = get_single_color(sc, true);
307 if (bg == invalid_color_value)
308 is.setstate(std::ios::failbit);
309 else
310 c = Color(fg, bg);
312 else
313 c = Color(fg, NC::Color::previous);
315 return is;
318 NC::Format reverseFormat(NC::Format fmt)
320 switch (fmt)
322 case NC::Format::Bold:
323 return NC::Format::NoBold;
324 case NC::Format::NoBold:
325 return NC::Format::Bold;
326 case NC::Format::Underline:
327 return NC::Format::NoUnderline;
328 case NC::Format::NoUnderline:
329 return NC::Format::Underline;
330 case NC::Format::Reverse:
331 return NC::Format::NoReverse;
332 case NC::Format::NoReverse:
333 return NC::Format::Reverse;
334 case NC::Format::AltCharset:
335 return NC::Format::NoAltCharset;
336 case NC::Format::NoAltCharset:
337 return NC::Format::AltCharset;
339 // Unreachable, silence GCC.
340 return fmt;
343 namespace Mouse {
345 namespace {
347 bool supportEnabled = false;
351 void enable()
353 if (!supportEnabled)
354 return;
355 // save old highlight mouse tracking
356 std::printf("\e[?1001s");
357 // enable mouse tracking
358 std::printf("\e[?1000h");
359 // try to enable extended (urxvt) mouse tracking
360 std::printf("\e[?1015h");
361 // send the above to the terminal immediately
362 std::fflush(stdout);
365 void disable()
367 if (!supportEnabled)
368 return;
369 // disable extended (urxvt) mouse tracking
370 std::printf("\e[?1015l");
371 // disable mouse tracking
372 std::printf("\e[?1000l");
373 // restore old highlight mouse tracking
374 std::printf("\e[?1001r");
375 // send the above to the terminal immediately
376 std::fflush(stdout);
381 void initScreen(bool enable_colors, bool enable_mouse)
383 initscr();
384 if (has_colors() && enable_colors)
386 start_color();
387 use_default_colors();
388 color_pair_map.resize(256 * 256, 0);
390 // Predefine pairs for colors with transparent background, all the other
391 // ones will be dynamically registered in Color::pairNumber when they're
392 // used.
393 color_pair_counter = 1;
394 for (int fg = 0; fg < COLORS; ++fg, ++color_pair_counter)
396 init_pair(color_pair_counter, fg, -1);
397 color_pair_map[fg] = color_pair_counter;
400 raw();
401 nonl();
402 noecho();
403 timeout(0);
404 curs_set(0);
406 // setup mouse
407 Mouse::supportEnabled = enable_mouse;
408 Mouse::enable();
410 // initialize readline (needed, otherwise we get segmentation
411 // fault on SIGWINCH). also, initialize first as doing this
412 // later erases keys bound with rl_bind_key for some users.
413 rl_initialize();
414 // disable autocompletion
415 rl_attempted_completion_function = [](const char *, int, int) -> char ** {
416 rl_attempted_completion_over = 1;
417 return nullptr;
419 auto abort_prompt = [](int, int) -> int {
420 rl::aborted = true;
421 rl_done = 1;
422 return 0;
424 // if ctrl-c or ctrl-g is pressed, abort the prompt
425 rl_bind_key('\3', abort_prompt);
426 rl_bind_key('\7', abort_prompt);
427 // do not change the state of the terminal
428 rl_prep_term_function = nullptr;
429 rl_deprep_term_function = nullptr;
430 // do not catch signals
431 rl_catch_signals = 0;
432 rl_catch_sigwinch = 0;
433 // overwrite readline callbacks
434 rl_getc_function = rl::read_key;
435 rl_redisplay_function = rl::display_string;
436 rl_startup_hook = rl::add_base;
439 void destroyScreen()
441 Mouse::disable();
442 curs_set(1);
443 endwin();
446 Window::Window(size_t startx, size_t starty, size_t width, size_t height,
447 std::string title, Color color, Border border)
448 : m_window(nullptr),
449 m_start_x(startx),
450 m_start_y(starty),
451 m_width(width),
452 m_height(height),
453 m_window_timeout(-1),
454 m_border(std::move(border)),
455 m_prompt_hook(0),
456 m_title(std::move(title)),
457 m_escape_terminal_sequences(true),
458 m_bold_counter(0),
459 m_underline_counter(0),
460 m_reverse_counter(0),
461 m_alt_charset_counter(0)
463 if (m_start_x > size_t(COLS)
464 || m_start_y > size_t(LINES)
465 || m_width+m_start_x > size_t(COLS)
466 || m_height+m_start_y > size_t(LINES))
467 throw std::logic_error("constructed window doesn't fit into the terminal");
469 if (m_border)
471 ++m_start_x;
472 ++m_start_y;
473 m_width -= 2;
474 m_height -= 2;
476 if (!m_title.empty())
478 m_start_y += 2;
479 m_height -= 2;
482 m_window = newpad(m_height, m_width);
484 setBaseColor(color);
485 setColor(m_base_color);
488 Window::Window(const Window &rhs)
489 : m_window(dupwin(rhs.m_window))
490 , m_start_x(rhs.m_start_x)
491 , m_start_y(rhs.m_start_y)
492 , m_width(rhs.m_width)
493 , m_height(rhs.m_height)
494 , m_window_timeout(rhs.m_window_timeout)
495 , m_color(rhs.m_color)
496 , m_base_color(rhs.m_base_color)
497 , m_border(rhs.m_border)
498 , m_prompt_hook(rhs.m_prompt_hook)
499 , m_title(rhs.m_title)
500 , m_color_stack(rhs.m_color_stack)
501 , m_input_queue(rhs.m_input_queue)
502 , m_fds(rhs.m_fds)
503 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
504 , m_bold_counter(rhs.m_bold_counter)
505 , m_underline_counter(rhs.m_underline_counter)
506 , m_reverse_counter(rhs.m_reverse_counter)
507 , m_alt_charset_counter(rhs.m_alt_charset_counter)
511 Window::Window(Window &&rhs)
512 : m_window(rhs.m_window)
513 , m_start_x(rhs.m_start_x)
514 , m_start_y(rhs.m_start_y)
515 , m_width(rhs.m_width)
516 , m_height(rhs.m_height)
517 , m_window_timeout(rhs.m_window_timeout)
518 , m_color(rhs.m_color)
519 , m_base_color(rhs.m_base_color)
520 , m_border(rhs.m_border)
521 , m_prompt_hook(rhs.m_prompt_hook)
522 , m_title(std::move(rhs.m_title))
523 , m_color_stack(std::move(rhs.m_color_stack))
524 , m_input_queue(std::move(rhs.m_input_queue))
525 , m_fds(std::move(rhs.m_fds))
526 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
527 , m_bold_counter(rhs.m_bold_counter)
528 , m_underline_counter(rhs.m_underline_counter)
529 , m_reverse_counter(rhs.m_reverse_counter)
530 , m_alt_charset_counter(rhs.m_alt_charset_counter)
532 rhs.m_window = nullptr;
535 Window &Window::operator=(Window rhs)
537 std::swap(m_window, rhs.m_window);
538 std::swap(m_start_x, rhs.m_start_x);
539 std::swap(m_start_y, rhs.m_start_y);
540 std::swap(m_width, rhs.m_width);
541 std::swap(m_height, rhs.m_height);
542 std::swap(m_window_timeout, rhs.m_window_timeout);
543 std::swap(m_color, rhs.m_color);
544 std::swap(m_base_color, rhs.m_base_color);
545 std::swap(m_border, rhs.m_border);
546 std::swap(m_prompt_hook, rhs.m_prompt_hook);
547 std::swap(m_title, rhs.m_title);
548 std::swap(m_color_stack, rhs.m_color_stack);
549 std::swap(m_input_queue, rhs.m_input_queue);
550 std::swap(m_fds, rhs.m_fds);
551 std::swap(m_escape_terminal_sequences, rhs.m_escape_terminal_sequences);
552 std::swap(m_bold_counter, rhs.m_bold_counter);
553 std::swap(m_underline_counter, rhs.m_underline_counter);
554 std::swap(m_reverse_counter, rhs.m_reverse_counter);
555 std::swap(m_alt_charset_counter, rhs.m_alt_charset_counter);
556 return *this;
559 Window::~Window()
561 delwin(m_window);
564 void Window::setColor(Color c)
566 if (c.isDefault())
567 c = m_base_color;
568 if (c != Color::Default)
570 assert(!c.previousBackground());
571 wcolor_set(m_window, c.pairNumber(), nullptr);
573 else
574 wcolor_set(m_window, m_base_color.pairNumber(), nullptr);
575 m_color = std::move(c);
578 void Window::setBaseColor(const Color &color)
580 if (color.previousBackground())
581 m_base_color = Color(color.foreground(), Color::transparent);
582 else
583 m_base_color = color;
586 void Window::setBorder(Border border)
588 if (!border && m_border)
590 --m_start_x;
591 --m_start_y;
592 m_height += 2;
593 m_width += 2;
594 recreate(m_width, m_height);
596 else if (border && !m_border)
598 ++m_start_x;
599 ++m_start_y;
600 m_height -= 2;
601 m_width -= 2;
602 recreate(m_width, m_height);
604 m_border = border;
607 void Window::setTitle(const std::string &new_title)
609 if (!new_title.empty() && m_title.empty())
611 m_start_y += 2;
612 m_height -= 2;
613 recreate(m_width, m_height);
615 else if (new_title.empty() && !m_title.empty())
617 m_start_y -= 2;
618 m_height += 2;
619 recreate(m_width, m_height);
621 m_title = new_title;
624 void Window::recreate(size_t width, size_t height)
626 delwin(m_window);
627 m_window = newpad(height, width);
628 wtimeout(m_window, 0);
629 setColor(m_color);
632 void Window::moveTo(size_t new_x, size_t new_y)
634 m_start_x = new_x;
635 m_start_y = new_y;
636 if (m_border)
638 ++m_start_x;
639 ++m_start_y;
641 if (!m_title.empty())
642 m_start_y += 2;
645 void Window::adjustDimensions(size_t width, size_t height)
647 if (m_border)
649 width -= 2;
650 height -= 2;
652 if (!m_title.empty())
653 height -= 2;
654 m_height = height;
655 m_width = width;
658 void Window::resize(size_t new_width, size_t new_height)
660 adjustDimensions(new_width, new_height);
661 recreate(m_width, m_height);
664 void Window::refreshBorder() const
666 if (m_border)
668 size_t start_x = getStartX(), start_y = getStarty();
669 size_t width = getWidth(), height = getHeight();
670 color_set(m_border->pairNumber(), nullptr);
671 attron(A_ALTCHARSET);
672 // corners
673 mvaddch(start_y, start_x, 'l');
674 mvaddch(start_y, start_x+width-1, 'k');
675 mvaddch(start_y+height-1, start_x, 'm');
676 mvaddch(start_y+height-1, start_x+width-1, 'j');
677 // lines
678 mvhline(start_y, start_x+1, 'q', width-2);
679 mvhline(start_y+height-1, start_x+1, 'q', width-2);
680 mvvline(start_y+1, start_x, 'x', height-2);
681 mvvline(start_y+1, start_x+width-1, 'x', height-2);
682 if (!m_title.empty())
684 mvaddch(start_y+2, start_x, 't');
685 mvaddch(start_y+2, start_x+width-1, 'u');
687 attroff(A_ALTCHARSET);
689 else
690 color_set(m_base_color.pairNumber(), nullptr);
691 if (!m_title.empty())
693 // clear title line
694 mvhline(m_start_y-2, m_start_x, ' ', m_width);
695 attron(A_BOLD);
696 mvaddstr(m_start_y-2, m_start_x, m_title.c_str());
697 attroff(A_BOLD);
698 // add separator
699 mvhline(m_start_y-1, m_start_x, 0, m_width);
701 standend();
702 ::refresh();
705 void Window::display()
707 refreshBorder();
708 refresh();
711 void Window::refresh()
713 prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);
716 void Window::clear()
718 werase(m_window);
721 void Window::bold(bool bold_state) const
723 (bold_state ? wattron : wattroff)(m_window, A_BOLD);
726 void Window::underline(bool underline_state) const
728 (underline_state ? wattron : wattroff)(m_window, A_UNDERLINE);
731 void Window::reverse(bool reverse_state) const
733 (reverse_state ? wattron : wattroff)(m_window, A_REVERSE);
736 void Window::altCharset(bool altcharset_state) const
738 (altcharset_state ? wattron : wattroff)(m_window, A_ALTCHARSET);
741 void Window::setTimeout(int timeout)
743 m_window_timeout = timeout;
746 void Window::addFDCallback(int fd, void (*callback)())
748 m_fds.push_back(std::make_pair(fd, callback));
751 void Window::clearFDCallbacksList()
753 m_fds.clear();
756 bool Window::FDCallbacksListEmpty() const
758 return m_fds.empty();
761 Key::Type Window::getInputChar(int key)
763 if (!m_escape_terminal_sequences || key != Key::Escape)
764 return key;
765 auto define_mouse_event = [this](int type) {
766 switch (type & ~28)
768 case 32:
769 m_mouse_event.bstate = BUTTON1_PRESSED;
770 break;
771 case 33:
772 m_mouse_event.bstate = BUTTON2_PRESSED;
773 break;
774 case 34:
775 m_mouse_event.bstate = BUTTON3_PRESSED;
776 break;
777 case 96:
778 m_mouse_event.bstate = BUTTON4_PRESSED;
779 break;
780 case 97:
781 m_mouse_event.bstate = BUTTON5_PRESSED;
782 break;
783 default:
784 return Key::None;
786 if (type & 4)
787 m_mouse_event.bstate |= BUTTON_SHIFT;
788 if (type & 8)
789 m_mouse_event.bstate |= BUTTON_ALT;
790 if (type & 16)
791 m_mouse_event.bstate |= BUTTON_CTRL;
792 if (m_mouse_event.x < 0 || m_mouse_event.x >= COLS)
793 return Key::None;
794 if (m_mouse_event.y < 0 || m_mouse_event.y >= LINES)
795 return Key::None;
796 return Key::Mouse;
798 auto get_xterm_modifier_key = [](int ch) {
799 Key::Type modifier;
800 switch (ch)
802 case '2':
803 modifier = Key::Shift;
804 break;
805 case '3':
806 modifier = Key::Alt;
807 break;
808 case '4':
809 modifier = Key::Alt | Key::Shift;
810 break;
811 case '5':
812 modifier = Key::Ctrl;
813 break;
814 case '6':
815 modifier = Key::Ctrl | Key::Shift;
816 break;
817 case '7':
818 modifier = Key::Alt | Key::Ctrl;
819 break;
820 case '8':
821 modifier = Key::Alt | Key::Ctrl | Key::Shift;
822 break;
823 default:
824 modifier = Key::None;
826 return modifier;
828 auto parse_number = [this](int &result) {
829 int x;
830 while (true)
832 x = wgetch(m_window);
833 if (!isdigit(x))
834 return x;
835 result = result*10 + x - '0';
838 key = wgetch(m_window);
839 switch (key)
841 case '\t': // tty
842 return Key::Shift | Key::Tab;
843 case 'O':
844 key = wgetch(m_window);
845 switch (key)
847 // eterm
848 case 'A':
849 return Key::Up;
850 case 'B':
851 return Key::Down;
852 case 'C':
853 return Key::Right;
854 case 'D':
855 return Key::Left;
856 // terminator
857 case 'F':
858 return Key::End;
859 case 'H':
860 return Key::Home;
861 // rxvt
862 case 'a':
863 return Key::Ctrl | Key::Up;
864 case 'b':
865 return Key::Ctrl | Key::Down;
866 case 'c':
867 return Key::Ctrl | Key::Right;
868 case 'd':
869 return Key::Ctrl | Key::Left;
870 // xterm
871 case 'P':
872 return Key::F1;
873 case 'Q':
874 return Key::F2;
875 case 'R':
876 return Key::F3;
877 case 'S':
878 return Key::F4;
879 default:
880 return Key::None;
882 case '[':
883 key = wgetch(m_window);
884 switch (key)
886 case 'a':
887 return Key::Shift | Key::Up;
888 case 'b':
889 return Key::Shift | Key::Down;
890 case 'c':
891 return Key::Shift | Key::Right;
892 case 'd':
893 return Key::Shift | Key::Left;
894 case 'A':
895 return Key::Up;
896 case 'B':
897 return Key::Down;
898 case 'C':
899 return Key::Right;
900 case 'D':
901 return Key::Left;
902 case 'F': // xterm
903 return Key::End;
904 case 'H': // xterm
905 return Key::Home;
906 case 'M': // standard mouse event
908 key = wgetch(m_window);
909 int raw_x = wgetch(m_window);
910 int raw_y = wgetch(m_window);
911 // support coordinates up to 255
912 m_mouse_event.x = (raw_x - 33) & 0xff;
913 m_mouse_event.y = (raw_y - 33) & 0xff;
914 return define_mouse_event(key);
916 case 'Z':
917 return Key::Shift | Key::Tab;
918 case '[': // F1 to F5 in tty
919 key = wgetch(m_window);
920 switch (key)
922 case 'A':
923 return Key::F1;
924 case 'B':
925 return Key::F2;
926 case 'C':
927 return Key::F3;
928 case 'D':
929 return Key::F4;
930 case 'E':
931 return Key::F5;
932 default:
933 return Key::None;
935 case '1': case '2': case '3':
936 case '4': case '5': case '6':
937 case '7': case '8': case '9':
939 key -= '0';
940 int delim = parse_number(key);
941 if (key >= 2 && key <= 8)
943 Key::Type modifier;
944 switch (delim)
946 case '~':
947 modifier = Key::Null;
948 break;
949 case '^':
950 modifier = Key::Ctrl;
951 break;
952 case '$':
953 modifier = Key::Shift;
954 break;
955 case '@':
956 modifier = Key::Ctrl | Key::Shift;
957 break;
958 case ';': // xterm insert/delete/page up/page down
960 int local_key = wgetch(m_window);
961 modifier = get_xterm_modifier_key(local_key);
962 local_key = wgetch(m_window);
963 if (local_key != '~' || (key != 2 && key != 3 && key != 5 && key != 6))
964 return Key::None;
965 break;
967 default:
968 return Key::None;
970 switch (key)
972 case 2:
973 return modifier | Key::Insert;
974 case 3:
975 return modifier | Key::Delete;
976 case 4:
977 return modifier | Key::End;
978 case 5:
979 return modifier | Key::PageUp;
980 case 6:
981 return modifier | Key::PageDown;
982 case 7:
983 return modifier | Key::Home;
984 case 8:
985 return modifier | Key::End;
986 default:
987 std::cerr << "Unreachable code, aborting.\n";
988 std::terminate();
991 switch (delim)
993 case '~':
995 switch (key)
997 case 1: // tty
998 return Key::Home;
999 case 11:
1000 return Key::F1;
1001 case 12:
1002 return Key::F2;
1003 case 13:
1004 return Key::F3;
1005 case 14:
1006 return Key::F4;
1007 case 15:
1008 return Key::F5;
1009 case 17: // not a typo
1010 return Key::F6;
1011 case 18:
1012 return Key::F7;
1013 case 19:
1014 return Key::F8;
1015 case 20:
1016 return Key::F9;
1017 case 21:
1018 return Key::F10;
1019 case 23: // not a typo
1020 return Key::F11;
1021 case 24:
1022 return Key::F12;
1023 default:
1024 return Key::None;
1027 case ';':
1028 switch (key)
1030 case 1: // xterm
1032 key = wgetch(m_window);
1033 Key::Type modifier = get_xterm_modifier_key(key);
1034 if (modifier == Key::None)
1035 return Key::None;
1036 key = wgetch(m_window);
1037 switch (key)
1039 case 'A':
1040 return modifier | Key::Up;
1041 case 'B':
1042 return modifier | Key::Down;
1043 case 'C':
1044 return modifier | Key::Right;
1045 case 'D':
1046 return modifier | Key::Left;
1047 case 'F':
1048 return modifier | Key::End;
1049 case 'H':
1050 return modifier | Key::Home;
1051 default:
1052 return Key::None;
1055 default: // urxvt mouse
1056 m_mouse_event.x = 0;
1057 delim = parse_number(m_mouse_event.x);
1058 if (delim != ';')
1059 return Key::None;
1060 m_mouse_event.y = 0;
1061 delim = parse_number(m_mouse_event.y);
1062 if (delim != 'M')
1063 return Key::None;
1064 --m_mouse_event.x;
1065 --m_mouse_event.y;
1066 return define_mouse_event(key);
1068 default:
1069 return Key::None;
1072 default:
1073 return Key::None;
1075 case ERR:
1076 return Key::Escape;
1077 default: // alt + something
1079 auto key_prim = getInputChar(key);
1080 if (key_prim != Key::None)
1081 return Key::Alt | key_prim;
1082 return Key::None;
1087 Key::Type Window::readKey()
1089 Key::Type result;
1090 // if there are characters in input queue,
1091 // get them and return immediately.
1092 if (!m_input_queue.empty())
1094 result = m_input_queue.front();
1095 m_input_queue.pop();
1096 return result;
1099 fd_set fds_read;
1100 FD_ZERO(&fds_read);
1101 FD_SET(STDIN_FILENO, &fds_read);
1102 timeval timeout = { m_window_timeout/1000, (m_window_timeout%1000)*1000 };
1104 int fd_max = STDIN_FILENO;
1105 for (const auto &fd : m_fds)
1107 if (fd.first > fd_max)
1108 fd_max = fd.first;
1109 FD_SET(fd.first, &fds_read);
1112 auto tv_addr = m_window_timeout < 0 ? nullptr : &timeout;
1113 int res = select(fd_max+1, &fds_read, nullptr, nullptr, tv_addr);
1114 if (res > 0)
1116 if (FD_ISSET(STDIN_FILENO, &fds_read))
1118 int key = wgetch(m_window);
1119 if (key == EOF)
1120 result = Key::EoF;
1121 else
1122 result = getInputChar(key);
1124 else
1125 result = Key::None;
1127 for (const auto &fd : m_fds)
1128 if (FD_ISSET(fd.first, &fds_read))
1129 fd.second();
1131 else
1132 result = Key::None;
1133 return result;
1136 void Window::pushChar(const Key::Type ch)
1138 m_input_queue.push(ch);
1141 std::string Window::prompt(const std::string &base, size_t width, bool encrypted)
1143 std::string result;
1145 rl::aborted = false;
1146 rl::w = this;
1147 getyx(m_window, rl::start_y, rl::start_x);
1148 rl::width = std::min(m_width-rl::start_x-1, width-1);
1149 rl::encrypted = encrypted;
1150 rl::base = base.c_str();
1152 curs_set(1);
1153 Mouse::disable();
1154 m_escape_terminal_sequences = false;
1155 char *input = readline(nullptr);
1156 m_escape_terminal_sequences = true;
1157 Mouse::enable();
1158 curs_set(0);
1159 if (input != nullptr)
1161 #ifdef HAVE_READLINE_HISTORY_H
1162 if (!encrypted && input[0] != 0)
1163 add_history(input);
1164 #endif // HAVE_READLINE_HISTORY_H
1165 result = input;
1166 free(input);
1169 if (rl::aborted)
1170 throw PromptAborted(std::move(result));
1172 return result;
1175 void Window::goToXY(int x, int y)
1177 wmove(m_window, y, x);
1180 int Window::getX()
1182 return getcurx(m_window);
1185 int Window::getY()
1187 return getcury(m_window);
1190 bool Window::hasCoords(int &x, int &y)
1192 return wmouse_trafo(m_window, &y, &x, 0);
1195 bool Window::runPromptHook(const char *arg, bool *done) const
1197 if (m_prompt_hook)
1199 bool continue_ = m_prompt_hook(arg);
1200 if (done != nullptr)
1201 *done = !continue_;
1202 return true;
1204 else
1205 return false;
1208 size_t Window::getWidth() const
1210 if (m_border)
1211 return m_width+2;
1212 else
1213 return m_width;
1216 size_t Window::getHeight() const
1218 size_t height = m_height;
1219 if (m_border)
1220 height += 2;
1221 if (!m_title.empty())
1222 height += 2;
1223 return height;
1226 size_t Window::getStartX() const
1228 if (m_border)
1229 return m_start_x-1;
1230 else
1231 return m_start_x;
1234 size_t Window::getStarty() const
1236 size_t starty = m_start_y;
1237 if (m_border)
1238 --starty;
1239 if (!m_title.empty())
1240 starty -= 2;
1241 return starty;
1244 const std::string &Window::getTitle() const
1246 return m_title;
1249 const Color &Window::getColor() const
1251 return m_color;
1254 const Border &Window::getBorder() const
1256 return m_border;
1259 int Window::getTimeout() const
1261 return m_window_timeout;
1264 const MEVENT &Window::getMouseEvent()
1266 return m_mouse_event;
1269 void Window::scroll(Scroll where)
1271 idlok(m_window, 1);
1272 scrollok(m_window, 1);
1273 switch (where)
1275 case Scroll::Up:
1276 wscrl(m_window, 1);
1277 break;
1278 case Scroll::Down:
1279 wscrl(m_window, -1);
1280 break;
1281 case Scroll::PageUp:
1282 wscrl(m_window, m_width);
1283 break;
1284 case Scroll::PageDown:
1285 wscrl(m_window, -m_width);
1286 break;
1287 default:
1288 break;
1290 idlok(m_window, 0);
1291 scrollok(m_window, 0);
1295 Window &Window::operator<<(const Color &c)
1297 if (c.isDefault())
1299 while (!m_color_stack.empty())
1300 m_color_stack.pop();
1301 setColor(m_base_color);
1303 else if (c.isEnd())
1305 if (!m_color_stack.empty())
1306 m_color_stack.pop();
1307 if (!m_color_stack.empty())
1308 setColor(m_color_stack.top());
1309 else
1310 setColor(m_base_color);
1312 else
1314 if (c.previousBackground())
1316 short background = m_color.isDefault()
1317 ? Color::transparent
1318 : m_color.background();
1319 Color cc = Color(c.foreground(), background);
1320 setColor(cc);
1321 m_color_stack.push(cc);
1323 else
1325 setColor(c);
1326 m_color_stack.push(c);
1329 return *this;
1332 Window &Window::operator<<(Format format)
1334 auto increase_flag = [](Window &w, int &flag, auto set) {
1335 ++flag;
1336 (w.*set)(true);
1338 auto decrease_flag = [](Window &w, int &flag, auto set) {
1339 if (flag > 0)
1341 --flag;
1342 if (flag == 0)
1343 (w.*set)(false);
1346 switch (format)
1348 case Format::Bold:
1349 increase_flag(*this, m_bold_counter, &Window::bold);
1350 break;
1351 case Format::NoBold:
1352 decrease_flag(*this, m_bold_counter, &Window::bold);
1353 break;
1354 case Format::Underline:
1355 increase_flag(*this, m_underline_counter, &Window::underline);
1356 break;
1357 case Format::NoUnderline:
1358 decrease_flag(*this, m_underline_counter, &Window::underline);
1359 break;
1360 case Format::Reverse:
1361 increase_flag(*this, m_reverse_counter, &Window::reverse);
1362 break;
1363 case Format::NoReverse:
1364 decrease_flag(*this, m_reverse_counter, &Window::reverse);
1365 break;
1366 case Format::AltCharset:
1367 increase_flag(*this, m_alt_charset_counter, &Window::altCharset);
1368 break;
1369 case Format::NoAltCharset:
1370 decrease_flag(*this, m_alt_charset_counter, &Window::altCharset);
1371 break;
1373 return *this;
1376 Window &Window::operator<<(TermManip tm)
1378 switch (tm)
1380 case TermManip::ClearToEOL:
1382 auto x = getX(), y = getY();
1383 mvwhline(m_window, y, x, ' ', m_width-x);
1384 goToXY(x, y);
1386 break;
1388 return *this;
1391 Window &Window::operator<<(const XY &coords)
1393 goToXY(coords.x, coords.y);
1394 return *this;
1397 Window &Window::operator<<(const char *s)
1399 waddstr(m_window, s);
1400 return *this;
1403 Window &Window::operator<<(char c)
1405 // Might cause problem similar to
1406 // https://github.com/arybczak/ncmpcpp/issues/21, enable for testing as the
1407 // code in the ticket supposed to be culprit was rewritten.
1408 waddnstr(m_window, &c, 1);
1409 //wprintw(m_window, "%c", c);
1410 return *this;
1413 Window &Window::operator<<(const wchar_t *ws)
1415 waddwstr(m_window, ws);
1416 return *this;
1419 Window &Window::operator<<(wchar_t wc)
1421 waddnwstr(m_window, &wc, 1);
1422 return *this;
1425 Window &Window::operator<<(int i)
1427 wprintw(m_window, "%d", i);
1428 return *this;
1431 Window &Window::operator<<(double d)
1433 wprintw(m_window, "%f", d);
1434 return *this;
1437 Window &Window::operator<<(const std::string &s)
1439 waddnstr(m_window, s.c_str(), s.length());
1440 return *this;
1443 Window &Window::operator<<(const std::wstring &ws)
1445 waddnwstr(m_window, ws.c_str(), ws.length());
1446 return *this;
1449 Window &Window::operator<<(size_t s)
1451 wprintw(m_window, "%zu", s);
1452 return *this;