Require ncursesw
[ncmpcpp.git] / src / window.cpp
blobde5e295443feaef2b5e172fc7464279a5c356da3
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;
192 namespace NC {
194 const short Color::transparent = -1;
195 const short Color::previous = -2;
197 Color Color::Default(0, 0, true, false);
198 Color Color::Black(COLOR_BLACK, Color::transparent);
199 Color Color::Red(COLOR_RED, Color::transparent);
200 Color Color::Green(COLOR_GREEN, Color::transparent);
201 Color Color::Yellow(COLOR_YELLOW, Color::transparent);
202 Color Color::Blue(COLOR_BLUE, Color::transparent);
203 Color Color::Magenta(COLOR_MAGENTA, Color::transparent);
204 Color Color::Cyan(COLOR_CYAN, Color::transparent);
205 Color Color::White(COLOR_WHITE, Color::transparent);
206 Color Color::End(0, 0, false, true);
208 int Color::pairNumber() const
210 int result;
211 if (isDefault())
212 result = 0;
213 else if (previousBackground())
214 throw std::logic_error("color depends on the previous background value");
215 else if (isEnd())
216 throw std::logic_error("'end' doesn't have a corresponding pair number");
217 else
219 // colors start with 0, but pairs start with 1. additionally
220 // first pairs are for transparent background, which has a
221 // value of -1, so we need to add 1 to both foreground and
222 // background value.
223 result = background() + 1;
224 result *= COLORS;
225 result += foreground() + 1;
227 return result;
230 std::istream &operator>>(std::istream &is, Color &c)
232 auto get_single_color = [](const std::string &s, bool background) {
233 short result = -1;
234 if (s == "black")
235 result = COLOR_BLACK;
236 else if (s == "red")
237 result = COLOR_RED;
238 else if (s == "green")
239 result = COLOR_GREEN;
240 else if (s == "yellow")
241 result = COLOR_YELLOW;
242 else if (s == "blue")
243 result = COLOR_BLUE;
244 else if (s == "magenta")
245 result = COLOR_MAGENTA;
246 else if (s == "cyan")
247 result = COLOR_CYAN;
248 else if (s == "white")
249 result = COLOR_WHITE;
250 else if (background && s == "previous")
251 result = NC::Color::previous;
252 else if (std::all_of(s.begin(), s.end(), isdigit))
254 result = atoi(s.c_str());
255 if (result < 1 || result > 256)
256 result = -1;
257 else
258 --result;
260 return result;
262 std::string sc;
263 is >> sc;
264 if (sc == "default")
265 c = Color::Default;
266 else if (sc == "end")
267 c = Color::End;
268 else
270 short value = get_single_color(sc, false);
271 if (value != -1)
272 c = Color(value, NC::Color::transparent);
273 else
275 size_t underscore = sc.find('_');
276 if (underscore != std::string::npos)
278 short fg = get_single_color(sc.substr(0, underscore), false);
279 short bg = get_single_color(sc.substr(underscore+1), true);
280 if (fg != -1 && bg != -1)
281 c = Color(fg, bg);
282 else
283 is.setstate(std::ios::failbit);
285 else
286 is.setstate(std::ios::failbit);
289 return is;
292 namespace Mouse {
294 namespace {
296 bool supportEnabled = false;
300 void enable()
302 if (!supportEnabled)
303 return;
304 // save old highlight mouse tracking
305 std::printf("\e[?1001s");
306 // enable mouse tracking
307 std::printf("\e[?1000h");
308 // try to enable extended (urxvt) mouse tracking
309 std::printf("\e[?1015h");
310 // send the above to the terminal immediately
311 std::fflush(stdout);
314 void disable()
316 if (!supportEnabled)
317 return;
318 // disable extended (urxvt) mouse tracking
319 std::printf("\e[?1015l");
320 // disable mouse tracking
321 std::printf("\e[?1000l");
322 // restore old highlight mouse tracking
323 std::printf("\e[?1001r");
324 // send the above to the terminal immediately
325 std::fflush(stdout);
330 void initScreen(bool enable_colors, bool enable_mouse)
332 initscr();
333 if (has_colors() && enable_colors)
335 start_color();
336 use_default_colors();
337 int npair = 1;
338 for (int bg = -1; bg < COLORS; ++bg)
340 for (int fg = 0; npair < COLOR_PAIRS && fg < COLORS; ++fg, ++npair)
341 init_pair(npair, fg, bg);
344 raw();
345 nonl();
346 noecho();
347 timeout(0);
348 curs_set(0);
350 // setup mouse
351 Mouse::supportEnabled = enable_mouse;
352 Mouse::enable();
354 // initialize readline (needed, otherwise we get segmentation
355 // fault on SIGWINCH). also, initialize first as doing this
356 // later erases keys bound with rl_bind_key for some users.
357 rl_initialize();
358 // disable autocompletion
359 rl_attempted_completion_function = [](const char *, int, int) -> char ** {
360 rl_attempted_completion_over = 1;
361 return nullptr;
363 auto abort_prompt = [](int, int) -> int {
364 rl::aborted = true;
365 rl_done = 1;
366 return 0;
368 // if ctrl-c or ctrl-g is pressed, abort the prompt
369 rl_bind_key('\3', abort_prompt);
370 rl_bind_key('\7', abort_prompt);
371 // do not change the state of the terminal
372 rl_prep_term_function = nullptr;
373 rl_deprep_term_function = nullptr;
374 // do not catch signals
375 rl_catch_signals = 0;
376 rl_catch_sigwinch = 0;
377 // overwrite readline callbacks
378 rl_getc_function = rl::read_key;
379 rl_redisplay_function = rl::display_string;
380 rl_startup_hook = rl::add_base;
383 void destroyScreen()
385 Mouse::disable();
386 curs_set(1);
387 endwin();
390 Window::Window(size_t startx,
391 size_t starty,
392 size_t width,
393 size_t height,
394 std::string title,
395 Color color,
396 Border border)
397 : m_window(nullptr),
398 m_start_x(startx),
399 m_start_y(starty),
400 m_width(width),
401 m_height(height),
402 m_window_timeout(-1),
403 m_color(color),
404 m_base_color(color),
405 m_border(std::move(border)),
406 m_prompt_hook(0),
407 m_title(std::move(title)),
408 m_escape_terminal_sequences(true),
409 m_bold_counter(0),
410 m_underline_counter(0),
411 m_reverse_counter(0),
412 m_alt_charset_counter(0)
414 if (m_start_x > size_t(COLS)
415 || m_start_y > size_t(LINES)
416 || m_width+m_start_x > size_t(COLS)
417 || m_height+m_start_y > size_t(LINES))
418 throw std::logic_error("constructed window doesn't fit into the terminal");
420 if (m_border)
422 ++m_start_x;
423 ++m_start_y;
424 m_width -= 2;
425 m_height -= 2;
427 if (!m_title.empty())
429 m_start_y += 2;
430 m_height -= 2;
433 m_window = newpad(m_height, m_width);
435 setColor(m_color);
438 Window::Window(const Window &rhs)
439 : m_window(dupwin(rhs.m_window))
440 , m_start_x(rhs.m_start_x)
441 , m_start_y(rhs.m_start_y)
442 , m_width(rhs.m_width)
443 , m_height(rhs.m_height)
444 , m_window_timeout(rhs.m_window_timeout)
445 , m_color(rhs.m_color)
446 , m_base_color(rhs.m_base_color)
447 , m_border(rhs.m_border)
448 , m_prompt_hook(rhs.m_prompt_hook)
449 , m_title(rhs.m_title)
450 , m_color_stack(rhs.m_color_stack)
451 , m_input_queue(rhs.m_input_queue)
452 , m_fds(rhs.m_fds)
453 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
454 , m_bold_counter(rhs.m_bold_counter)
455 , m_underline_counter(rhs.m_underline_counter)
456 , m_reverse_counter(rhs.m_reverse_counter)
457 , m_alt_charset_counter(rhs.m_alt_charset_counter)
461 Window::Window(Window &&rhs)
462 : m_window(rhs.m_window)
463 , m_start_x(rhs.m_start_x)
464 , m_start_y(rhs.m_start_y)
465 , m_width(rhs.m_width)
466 , m_height(rhs.m_height)
467 , m_window_timeout(rhs.m_window_timeout)
468 , m_color(rhs.m_color)
469 , m_base_color(rhs.m_base_color)
470 , m_border(rhs.m_border)
471 , m_prompt_hook(rhs.m_prompt_hook)
472 , m_title(std::move(rhs.m_title))
473 , m_color_stack(std::move(rhs.m_color_stack))
474 , m_input_queue(std::move(rhs.m_input_queue))
475 , m_fds(std::move(rhs.m_fds))
476 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
477 , m_bold_counter(rhs.m_bold_counter)
478 , m_underline_counter(rhs.m_underline_counter)
479 , m_reverse_counter(rhs.m_reverse_counter)
480 , m_alt_charset_counter(rhs.m_alt_charset_counter)
482 rhs.m_window = nullptr;
485 Window &Window::operator=(Window rhs)
487 std::swap(m_window, rhs.m_window);
488 std::swap(m_start_x, rhs.m_start_x);
489 std::swap(m_start_y, rhs.m_start_y);
490 std::swap(m_width, rhs.m_width);
491 std::swap(m_height, rhs.m_height);
492 std::swap(m_window_timeout, rhs.m_window_timeout);
493 std::swap(m_color, rhs.m_color);
494 std::swap(m_base_color, rhs.m_base_color);
495 std::swap(m_border, rhs.m_border);
496 std::swap(m_prompt_hook, rhs.m_prompt_hook);
497 std::swap(m_title, rhs.m_title);
498 std::swap(m_color_stack, rhs.m_color_stack);
499 std::swap(m_input_queue, rhs.m_input_queue);
500 std::swap(m_fds, rhs.m_fds);
501 std::swap(m_escape_terminal_sequences, rhs.m_escape_terminal_sequences);
502 std::swap(m_bold_counter, rhs.m_bold_counter);
503 std::swap(m_underline_counter, rhs.m_underline_counter);
504 std::swap(m_reverse_counter, rhs.m_reverse_counter);
505 std::swap(m_alt_charset_counter, rhs.m_alt_charset_counter);
506 return *this;
509 Window::~Window()
511 delwin(m_window);
514 void Window::setColor(Color c)
516 if (c.isDefault())
517 c = m_base_color;
518 if (c != Color::Default)
520 if (c.previousBackground())
521 c = Color(c.foreground(), m_color.background());
522 wcolor_set(m_window, c.pairNumber(), nullptr);
524 else
525 wcolor_set(m_window, m_base_color.pairNumber(), nullptr);
526 m_color = std::move(c);
529 void Window::setBaseColor(Color c)
531 m_base_color = std::move(c);
534 void Window::setBorder(Border border)
536 if (!border && m_border)
538 --m_start_x;
539 --m_start_y;
540 m_height += 2;
541 m_width += 2;
542 recreate(m_width, m_height);
544 else if (border && !m_border)
546 ++m_start_x;
547 ++m_start_y;
548 m_height -= 2;
549 m_width -= 2;
550 recreate(m_width, m_height);
552 m_border = border;
555 void Window::setTitle(const std::string &new_title)
557 if (!new_title.empty() && m_title.empty())
559 m_start_y += 2;
560 m_height -= 2;
561 recreate(m_width, m_height);
563 else if (new_title.empty() && !m_title.empty())
565 m_start_y -= 2;
566 m_height += 2;
567 recreate(m_width, m_height);
569 m_title = new_title;
572 void Window::recreate(size_t width, size_t height)
574 delwin(m_window);
575 m_window = newpad(height, width);
576 wtimeout(m_window, 0);
577 setColor(m_color);
580 void Window::moveTo(size_t new_x, size_t new_y)
582 m_start_x = new_x;
583 m_start_y = new_y;
584 if (m_border)
586 ++m_start_x;
587 ++m_start_y;
589 if (!m_title.empty())
590 m_start_y += 2;
593 void Window::adjustDimensions(size_t width, size_t height)
595 if (m_border)
597 width -= 2;
598 height -= 2;
600 if (!m_title.empty())
601 height -= 2;
602 m_height = height;
603 m_width = width;
606 void Window::resize(size_t new_width, size_t new_height)
608 adjustDimensions(new_width, new_height);
609 recreate(m_width, m_height);
612 void Window::refreshBorder() const
614 if (m_border)
616 size_t start_x = getStartX(), start_y = getStarty();
617 size_t width = getWidth(), height = getHeight();
618 color_set(m_border->pairNumber(), nullptr);
619 attron(A_ALTCHARSET);
620 // corners
621 mvaddch(start_y, start_x, 'l');
622 mvaddch(start_y, start_x+width-1, 'k');
623 mvaddch(start_y+height-1, start_x, 'm');
624 mvaddch(start_y+height-1, start_x+width-1, 'j');
625 // lines
626 mvhline(start_y, start_x+1, 'q', width-2);
627 mvhline(start_y+height-1, start_x+1, 'q', width-2);
628 mvvline(start_y+1, start_x, 'x', height-2);
629 mvvline(start_y+1, start_x+width-1, 'x', height-2);
630 if (!m_title.empty())
632 mvaddch(start_y+2, start_x, 't');
633 mvaddch(start_y+2, start_x+width-1, 'u');
635 attroff(A_ALTCHARSET);
637 else
638 color_set(m_base_color.pairNumber(), nullptr);
639 if (!m_title.empty())
641 // clear title line
642 mvhline(m_start_y-2, m_start_x, ' ', m_width);
643 attron(A_BOLD);
644 mvaddstr(m_start_y-2, m_start_x, m_title.c_str());
645 attroff(A_BOLD);
646 // add separator
647 mvhline(m_start_y-1, m_start_x, 0, m_width);
649 standend();
650 ::refresh();
653 void Window::display()
655 refreshBorder();
656 refresh();
659 void Window::refresh()
661 prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);
664 void Window::clear()
666 werase(m_window);
669 void Window::bold(bool bold_state) const
671 (bold_state ? wattron : wattroff)(m_window, A_BOLD);
674 void Window::underline(bool underline_state) const
676 (underline_state ? wattron : wattroff)(m_window, A_UNDERLINE);
679 void Window::reverse(bool reverse_state) const
681 (reverse_state ? wattron : wattroff)(m_window, A_REVERSE);
684 void Window::altCharset(bool altcharset_state) const
686 (altcharset_state ? wattron : wattroff)(m_window, A_ALTCHARSET);
689 void Window::setTimeout(int timeout)
691 m_window_timeout = timeout;
694 void Window::addFDCallback(int fd, void (*callback)())
696 m_fds.push_back(std::make_pair(fd, callback));
699 void Window::clearFDCallbacksList()
701 m_fds.clear();
704 bool Window::FDCallbacksListEmpty() const
706 return m_fds.empty();
709 Key::Type Window::getInputChar(int key)
711 if (!m_escape_terminal_sequences || key != Key::Escape)
712 return key;
713 auto define_mouse_event = [this](int type) {
714 switch (type & ~28)
716 case 32:
717 m_mouse_event.bstate = BUTTON1_PRESSED;
718 break;
719 case 33:
720 m_mouse_event.bstate = BUTTON2_PRESSED;
721 break;
722 case 34:
723 m_mouse_event.bstate = BUTTON3_PRESSED;
724 break;
725 case 96:
726 m_mouse_event.bstate = BUTTON4_PRESSED;
727 break;
728 case 97:
729 m_mouse_event.bstate = BUTTON5_PRESSED;
730 break;
731 default:
732 return Key::None;
734 if (type & 4)
735 m_mouse_event.bstate |= BUTTON_SHIFT;
736 if (type & 8)
737 m_mouse_event.bstate |= BUTTON_ALT;
738 if (type & 16)
739 m_mouse_event.bstate |= BUTTON_CTRL;
740 if (m_mouse_event.x < 0 || m_mouse_event.x >= COLS)
741 return Key::None;
742 if (m_mouse_event.y < 0 || m_mouse_event.y >= LINES)
743 return Key::None;
744 return Key::Mouse;
746 auto get_xterm_modifier_key = [](int ch) {
747 Key::Type modifier;
748 switch (ch)
750 case '2':
751 modifier = Key::Shift;
752 break;
753 case '3':
754 modifier = Key::Alt;
755 break;
756 case '4':
757 modifier = Key::Alt | Key::Shift;
758 break;
759 case '5':
760 modifier = Key::Ctrl;
761 break;
762 case '6':
763 modifier = Key::Ctrl | Key::Shift;
764 break;
765 case '7':
766 modifier = Key::Alt | Key::Ctrl;
767 break;
768 case '8':
769 modifier = Key::Alt | Key::Ctrl | Key::Shift;
770 break;
771 default:
772 modifier = Key::None;
774 return modifier;
776 auto parse_number = [this](int &result) {
777 int x;
778 while (true)
780 x = wgetch(m_window);
781 if (!isdigit(x))
782 return x;
783 result = result*10 + x - '0';
786 key = wgetch(m_window);
787 switch (key)
789 case '\t': // tty
790 return Key::Shift | Key::Tab;
791 case 'O':
792 key = wgetch(m_window);
793 switch (key)
795 // eterm
796 case 'A':
797 return Key::Up;
798 case 'B':
799 return Key::Down;
800 case 'C':
801 return Key::Right;
802 case 'D':
803 return Key::Left;
804 // terminator
805 case 'F':
806 return Key::End;
807 case 'H':
808 return Key::Home;
809 // rxvt
810 case 'a':
811 return Key::Ctrl | Key::Up;
812 case 'b':
813 return Key::Ctrl | Key::Down;
814 case 'c':
815 return Key::Ctrl | Key::Right;
816 case 'd':
817 return Key::Ctrl | Key::Left;
818 // xterm
819 case 'P':
820 return Key::F1;
821 case 'Q':
822 return Key::F2;
823 case 'R':
824 return Key::F3;
825 case 'S':
826 return Key::F4;
827 default:
828 return Key::None;
830 case '[':
831 key = wgetch(m_window);
832 switch (key)
834 case 'a':
835 return Key::Shift | Key::Up;
836 case 'b':
837 return Key::Shift | Key::Down;
838 case 'c':
839 return Key::Shift | Key::Right;
840 case 'd':
841 return Key::Shift | Key::Left;
842 case 'A':
843 return Key::Up;
844 case 'B':
845 return Key::Down;
846 case 'C':
847 return Key::Right;
848 case 'D':
849 return Key::Left;
850 case 'F': // xterm
851 return Key::End;
852 case 'H': // xterm
853 return Key::Home;
854 case 'M': // standard mouse event
856 key = wgetch(m_window);
857 int raw_x = wgetch(m_window);
858 int raw_y = wgetch(m_window);
859 // support coordinates up to 255
860 m_mouse_event.x = (raw_x - 33) & 0xff;
861 m_mouse_event.y = (raw_y - 33) & 0xff;
862 return define_mouse_event(key);
864 case 'Z':
865 return Key::Shift | Key::Tab;
866 case '[': // F1 to F5 in tty
867 key = wgetch(m_window);
868 switch (key)
870 case 'A':
871 return Key::F1;
872 case 'B':
873 return Key::F2;
874 case 'C':
875 return Key::F3;
876 case 'D':
877 return Key::F4;
878 case 'E':
879 return Key::F5;
880 default:
881 return Key::None;
883 case '1': case '2': case '3':
884 case '4': case '5': case '6':
885 case '7': case '8': case '9':
887 key -= '0';
888 int delim = parse_number(key);
889 if (key >= 2 && key <= 8)
891 Key::Type modifier;
892 switch (delim)
894 case '~':
895 modifier = Key::Null;
896 break;
897 case '^':
898 modifier = Key::Ctrl;
899 break;
900 case '$':
901 modifier = Key::Shift;
902 break;
903 case '@':
904 modifier = Key::Ctrl | Key::Shift;
905 break;
906 case ';': // xterm insert/delete/page up/page down
908 int local_key = wgetch(m_window);
909 modifier = get_xterm_modifier_key(local_key);
910 local_key = wgetch(m_window);
911 if (local_key != '~' || (key != 2 && key != 3 && key != 5 && key != 6))
912 return Key::None;
913 break;
915 default:
916 return Key::None;
918 switch (key)
920 case 2:
921 return modifier | Key::Insert;
922 case 3:
923 return modifier | Key::Delete;
924 case 4:
925 return modifier | Key::End;
926 case 5:
927 return modifier | Key::PageUp;
928 case 6:
929 return modifier | Key::PageDown;
930 case 7:
931 return modifier | Key::Home;
932 case 8:
933 return modifier | Key::End;
934 default:
935 std::cerr << "Unreachable code, aborting.\n";
936 std::terminate();
939 switch (delim)
941 case '~':
943 switch (key)
945 case 1: // tty
946 return Key::Home;
947 case 11:
948 return Key::F1;
949 case 12:
950 return Key::F2;
951 case 13:
952 return Key::F3;
953 case 14:
954 return Key::F4;
955 case 15:
956 return Key::F5;
957 case 17: // not a typo
958 return Key::F6;
959 case 18:
960 return Key::F7;
961 case 19:
962 return Key::F8;
963 case 20:
964 return Key::F9;
965 case 21:
966 return Key::F10;
967 case 23: // not a typo
968 return Key::F11;
969 case 24:
970 return Key::F12;
971 default:
972 return Key::None;
975 case ';':
976 switch (key)
978 case 1: // xterm
980 key = wgetch(m_window);
981 Key::Type modifier = get_xterm_modifier_key(key);
982 if (modifier == Key::None)
983 return Key::None;
984 key = wgetch(m_window);
985 switch (key)
987 case 'A':
988 return modifier | Key::Up;
989 case 'B':
990 return modifier | Key::Down;
991 case 'C':
992 return modifier | Key::Right;
993 case 'D':
994 return modifier | Key::Left;
995 case 'F':
996 return modifier | Key::End;
997 case 'H':
998 return modifier | Key::Home;
999 default:
1000 return Key::None;
1003 default: // urxvt mouse
1004 m_mouse_event.x = 0;
1005 delim = parse_number(m_mouse_event.x);
1006 if (delim != ';')
1007 return Key::None;
1008 m_mouse_event.y = 0;
1009 delim = parse_number(m_mouse_event.y);
1010 if (delim != 'M')
1011 return Key::None;
1012 --m_mouse_event.x;
1013 --m_mouse_event.y;
1014 return define_mouse_event(key);
1016 default:
1017 return Key::None;
1020 default:
1021 return Key::None;
1023 case ERR:
1024 return Key::Escape;
1025 default: // alt + something
1027 auto key_prim = getInputChar(key);
1028 if (key_prim != Key::None)
1029 return Key::Alt | key_prim;
1030 return Key::None;
1035 Key::Type Window::readKey()
1037 Key::Type result;
1038 // if there are characters in input queue,
1039 // get them and return immediately.
1040 if (!m_input_queue.empty())
1042 result = m_input_queue.front();
1043 m_input_queue.pop();
1044 return result;
1047 fd_set fds_read;
1048 FD_ZERO(&fds_read);
1049 FD_SET(STDIN_FILENO, &fds_read);
1050 timeval timeout = { m_window_timeout/1000, (m_window_timeout%1000)*1000 };
1052 int fd_max = STDIN_FILENO;
1053 for (const auto &fd : m_fds)
1055 if (fd.first > fd_max)
1056 fd_max = fd.first;
1057 FD_SET(fd.first, &fds_read);
1060 auto tv_addr = m_window_timeout < 0 ? nullptr : &timeout;
1061 int res = select(fd_max+1, &fds_read, nullptr, nullptr, tv_addr);
1062 if (res > 0)
1064 if (FD_ISSET(STDIN_FILENO, &fds_read))
1066 int key = wgetch(m_window);
1067 if (key == EOF)
1068 result = Key::EoF;
1069 else
1070 result = getInputChar(key);
1072 else
1073 result = Key::None;
1075 for (const auto &fd : m_fds)
1076 if (FD_ISSET(fd.first, &fds_read))
1077 fd.second();
1079 else
1080 result = Key::None;
1081 return result;
1084 void Window::pushChar(const Key::Type ch)
1086 m_input_queue.push(ch);
1089 std::string Window::prompt(const std::string &base, size_t width, bool encrypted)
1091 std::string result;
1093 rl::aborted = false;
1094 rl::w = this;
1095 getyx(m_window, rl::start_y, rl::start_x);
1096 rl::width = std::min(m_width-rl::start_x-1, width-1);
1097 rl::encrypted = encrypted;
1098 rl::base = base.c_str();
1100 curs_set(1);
1101 Mouse::disable();
1102 m_escape_terminal_sequences = false;
1103 char *input = readline(nullptr);
1104 m_escape_terminal_sequences = true;
1105 Mouse::enable();
1106 curs_set(0);
1107 if (input != nullptr)
1109 #ifdef HAVE_READLINE_HISTORY_H
1110 if (!encrypted && input[0] != 0)
1111 add_history(input);
1112 #endif // HAVE_READLINE_HISTORY_H
1113 result = input;
1114 free(input);
1117 if (rl::aborted)
1118 throw PromptAborted(std::move(result));
1120 return result;
1123 void Window::goToXY(int x, int y)
1125 wmove(m_window, y, x);
1128 int Window::getX()
1130 return getcurx(m_window);
1133 int Window::getY()
1135 return getcury(m_window);
1138 bool Window::hasCoords(int &x, int &y)
1140 return wmouse_trafo(m_window, &y, &x, 0);
1143 bool Window::runPromptHook(const char *arg, bool *done) const
1145 if (m_prompt_hook)
1147 bool continue_ = m_prompt_hook(arg);
1148 if (done != nullptr)
1149 *done = !continue_;
1150 return true;
1152 else
1153 return false;
1156 size_t Window::getWidth() const
1158 if (m_border)
1159 return m_width+2;
1160 else
1161 return m_width;
1164 size_t Window::getHeight() const
1166 size_t height = m_height;
1167 if (m_border)
1168 height += 2;
1169 if (!m_title.empty())
1170 height += 2;
1171 return height;
1174 size_t Window::getStartX() const
1176 if (m_border)
1177 return m_start_x-1;
1178 else
1179 return m_start_x;
1182 size_t Window::getStarty() const
1184 size_t starty = m_start_y;
1185 if (m_border)
1186 --starty;
1187 if (!m_title.empty())
1188 starty -= 2;
1189 return starty;
1192 const std::string &Window::getTitle() const
1194 return m_title;
1197 const Color &Window::getColor() const
1199 return m_color;
1202 const Border &Window::getBorder() const
1204 return m_border;
1207 int Window::getTimeout() const
1209 return m_window_timeout;
1212 const MEVENT &Window::getMouseEvent()
1214 return m_mouse_event;
1217 void Window::scroll(Scroll where)
1219 idlok(m_window, 1);
1220 scrollok(m_window, 1);
1221 switch (where)
1223 case Scroll::Up:
1224 wscrl(m_window, 1);
1225 break;
1226 case Scroll::Down:
1227 wscrl(m_window, -1);
1228 break;
1229 case Scroll::PageUp:
1230 wscrl(m_window, m_width);
1231 break;
1232 case Scroll::PageDown:
1233 wscrl(m_window, -m_width);
1234 break;
1235 default:
1236 break;
1238 idlok(m_window, 0);
1239 scrollok(m_window, 0);
1243 Window &Window::operator<<(const Color &c)
1245 if (c.isDefault())
1247 while (!m_color_stack.empty())
1248 m_color_stack.pop();
1249 setColor(m_base_color);
1251 else if (c.isEnd())
1253 if (!m_color_stack.empty())
1254 m_color_stack.pop();
1255 if (!m_color_stack.empty())
1256 setColor(m_color_stack.top());
1257 else
1258 setColor(m_base_color);
1260 else
1262 setColor(c);
1263 m_color_stack.push(c);
1265 return *this;
1268 Window &Window::operator<<(Format format)
1270 switch (format)
1272 case Format::None:
1273 bold((m_bold_counter = 0));
1274 reverse((m_reverse_counter = 0));
1275 altCharset((m_alt_charset_counter = 0));
1276 break;
1277 case Format::Bold:
1278 bold(++m_bold_counter);
1279 break;
1280 case Format::NoBold:
1281 if (--m_bold_counter <= 0)
1282 bold((m_bold_counter = 0));
1283 break;
1284 case Format::Underline:
1285 underline(++m_underline_counter);
1286 break;
1287 case Format::NoUnderline:
1288 if (--m_underline_counter <= 0)
1289 underline((m_underline_counter = 0));
1290 break;
1291 case Format::Reverse:
1292 reverse(++m_reverse_counter);
1293 break;
1294 case Format::NoReverse:
1295 if (--m_reverse_counter <= 0)
1296 reverse((m_reverse_counter = 0));
1297 break;
1298 case Format::AltCharset:
1299 altCharset(++m_alt_charset_counter);
1300 break;
1301 case Format::NoAltCharset:
1302 if (--m_alt_charset_counter <= 0)
1303 altCharset((m_alt_charset_counter = 0));
1304 break;
1306 return *this;
1309 Window &Window::operator<<(TermManip tm)
1311 switch (tm)
1313 case TermManip::ClearToEOL:
1315 auto x = getX(), y = getY();
1316 mvwhline(m_window, y, x, ' ', m_width-x);
1317 goToXY(x, y);
1319 break;
1321 return *this;
1324 Window &Window::operator<<(const XY &coords)
1326 goToXY(coords.x, coords.y);
1327 return *this;
1330 Window &Window::operator<<(const char *s)
1332 waddstr(m_window, s);
1333 return *this;
1336 Window &Window::operator<<(char c)
1338 // Might cause problem similar to
1339 // https://github.com/arybczak/ncmpcpp/issues/21, enable for testing as the
1340 // code in the ticket supposed to be culprit was rewritten.
1341 waddnstr(m_window, &c, 1);
1342 //wprintw(m_window, "%c", c);
1343 return *this;
1346 Window &Window::operator<<(const wchar_t *ws)
1348 #if NCURSES_WADDWSTR
1349 waddwstr(m_window, ws);
1350 #else
1351 wprintw(m_window, "%ls", ws);
1352 #endif // NCURSES_WADDWSTR
1353 return *this;
1356 Window &Window::operator<<(wchar_t wc)
1358 #if NCURSES_WADDNWSTR
1359 waddnwstr(m_window, &wc, 1);
1360 #else
1361 wprintw(m_window, "%lc", wc);
1362 #endif // NCURSES_WADDNWSTR
1363 return *this;
1366 Window &Window::operator<<(int i)
1368 wprintw(m_window, "%d", i);
1369 return *this;
1372 Window &Window::operator<<(double d)
1374 wprintw(m_window, "%f", d);
1375 return *this;
1378 Window &Window::operator<<(const std::string &s)
1380 waddnstr(m_window, s.c_str(), s.length());
1381 return *this;
1384 Window &Window::operator<<(const std::wstring &ws)
1386 #if NCURSES_WADDNWSTR
1387 waddnwstr(m_window, ws.c_str(), ws.length());
1388 #else
1389 wprintw(m_window, "%lc", ws.c_str());
1390 #endif // NCURSES_WADDNWSTR
1391 return *this;
1394 Window &Window::operator<<(size_t s)
1396 wprintw(m_window, "%zu", s);
1397 return *this;