change version to 0.7.3
[ncmpcpp.git] / src / window.cpp
blob85bfd03b9340d25ec7f4c992551ae0874883c4f6
1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 struct ScopedWindowTimeout
38 ScopedWindowTimeout(WINDOW *w, int init_timeout, int term_timeout)
39 : m_w(w), m_term_timeout(term_timeout) {
40 wtimeout(w, init_timeout);
42 ~ScopedWindowTimeout() {
43 wtimeout(m_w, m_term_timeout);
46 private:
47 WINDOW *m_w;
48 int m_term_timeout;
51 namespace rl {
53 bool aborted;
55 NC::Window *w;
56 size_t start_x;
57 size_t start_y;
58 size_t width;
59 bool encrypted;
60 const char *base;
62 int read_key(FILE *)
64 size_t x;
65 bool done;
66 int result;
69 x = w->getX();
70 if (w->runPromptHook(rl_line_buffer, &done))
72 if (done)
74 rl_done = 1;
75 return EOF;
77 w->goToXY(x, start_y);
79 w->refresh();
80 result = w->readKey();
81 if (!w->FDCallbacksListEmpty())
83 w->goToXY(x, start_y);
84 w->refresh();
87 while (result == ERR);
88 return result;
91 void display_string()
93 auto print_char = [](wchar_t wc) {
94 if (encrypted)
95 *w << '*';
96 else
97 *w << wc;
99 auto print_string = [](wchar_t *ws, size_t len) {
100 if (encrypted)
101 for (size_t i = 0; i < len; ++i)
102 *w << '*';
103 else
104 *w << ws;
106 auto narrow_to_wide = [](wchar_t *dest, const char *src, size_t n) {
107 size_t result = 0;
108 // convert the string and substitute invalid multibyte chars with dots.
109 for (size_t i = 0; i < n;)
111 int ret = mbtowc(&dest[result], &src[i], n-i);
112 if (ret > 0)
114 i += ret;
115 ++result;
117 else if (ret == -1)
119 dest[result] = L'.';
120 ++i;
121 ++result;
123 else
124 throw std::runtime_error("mbtowc: unexpected return value");
126 return result;
129 // copy the part of the string that is before the cursor to pre_pos
130 char pt = rl_line_buffer[rl_point];
131 rl_line_buffer[rl_point] = 0;
132 wchar_t pre_pos[rl_point+1];
133 pre_pos[narrow_to_wide(pre_pos, rl_line_buffer, rl_point)] = 0;
134 rl_line_buffer[rl_point] = pt;
136 int pos = wcswidth(pre_pos, rl_point);
137 if (pos < 0)
138 pos = rl_point;
140 // clear the area for the string
141 mvwhline(w->raw(), start_y, start_x, ' ', width+1);
143 w->goToXY(start_x, start_y);
144 if (size_t(pos) <= width)
146 // if the current position in the string is not bigger than allowed
147 // width, print the part of the string before cursor position...
149 print_string(pre_pos, pos);
151 // ...and then print the rest char-by-char until there is no more area
152 wchar_t post_pos[rl_end-rl_point+1];
153 post_pos[narrow_to_wide(post_pos, rl_line_buffer+rl_point, rl_end-rl_point)] = 0;
155 size_t cpos = pos;
156 for (wchar_t *c = post_pos; *c != 0; ++c)
158 int n = wcwidth(*c);
159 if (n < 0)
161 print_char(L'.');
162 ++cpos;
164 else
166 if (cpos+n > width)
167 break;
168 cpos += n;
169 print_char(*c);
173 else
175 // if the current position in the string is bigger than allowed
176 // width, we always keep the cursor at the end of the line (it
177 // would be nice to have more flexible scrolling, but for now
178 // let's stick to that) by cutting the beginning of the part
179 // of the string before the cursor until it fits the area.
181 wchar_t *mod_pre_pos = pre_pos;
182 while (*mod_pre_pos != 0)
184 ++mod_pre_pos;
185 int n = wcwidth(*mod_pre_pos);
186 if (n < 0)
187 --pos;
188 else
189 pos -= n;
190 if (size_t(pos) <= width)
191 break;
193 print_string(mod_pre_pos, pos);
195 w->goToXY(start_x+pos, start_y);
198 int add_base()
200 rl_insert_text(base);
201 return 0;
207 namespace NC {
209 const short Color::transparent = -1;
210 const short Color::previous = -2;
212 Color Color::Default(0, 0, true, false);
213 Color Color::Black(COLOR_BLACK, Color::transparent);
214 Color Color::Red(COLOR_RED, Color::transparent);
215 Color Color::Green(COLOR_GREEN, Color::transparent);
216 Color Color::Yellow(COLOR_YELLOW, Color::transparent);
217 Color Color::Blue(COLOR_BLUE, Color::transparent);
218 Color Color::Magenta(COLOR_MAGENTA, Color::transparent);
219 Color Color::Cyan(COLOR_CYAN, Color::transparent);
220 Color Color::White(COLOR_WHITE, Color::transparent);
221 Color Color::End(0, 0, false, true);
223 int Color::pairNumber() const
225 int result;
226 if (isDefault())
227 result = 0;
228 else if (previousBackground())
229 throw std::logic_error("color depends on the previous background value");
230 else if (isEnd())
231 throw std::logic_error("'end' doesn't have a corresponding pair number");
232 else
234 // colors start with 0, but pairs start with 1. additionally
235 // first pairs are for transparent background, which has a
236 // value of -1, so we need to add 1 to both foreground and
237 // background value.
238 result = background() + 1;
239 result *= COLORS;
240 result += foreground() + 1;
242 return result;
245 std::istream &operator>>(std::istream &is, Color &c)
247 auto get_single_color = [](const std::string &s, bool background) {
248 short result = -1;
249 if (s == "black")
250 result = COLOR_BLACK;
251 else if (s == "red")
252 result = COLOR_RED;
253 else if (s == "green")
254 result = COLOR_GREEN;
255 else if (s == "yellow")
256 result = COLOR_YELLOW;
257 else if (s == "blue")
258 result = COLOR_BLUE;
259 else if (s == "magenta")
260 result = COLOR_MAGENTA;
261 else if (s == "cyan")
262 result = COLOR_CYAN;
263 else if (s == "white")
264 result = COLOR_WHITE;
265 else if (background && s == "previous")
266 result = NC::Color::previous;
267 else if (std::all_of(s.begin(), s.end(), isdigit))
269 result = atoi(s.c_str());
270 if (result < 1 || result > 256)
271 result = -1;
272 else
273 --result;
275 return result;
277 std::string sc;
278 is >> sc;
279 if (sc == "default")
280 c = Color::Default;
281 else if (sc == "end")
282 c = Color::End;
283 else
285 short value = get_single_color(sc, false);
286 if (value != -1)
287 c = Color(value, NC::Color::transparent);
288 else
290 size_t underscore = sc.find('_');
291 if (underscore != std::string::npos)
293 short fg = get_single_color(sc.substr(0, underscore), false);
294 short bg = get_single_color(sc.substr(underscore+1), true);
295 if (fg != -1 && bg != -1)
296 c = Color(fg, bg);
297 else
298 is.setstate(std::ios::failbit);
300 else
301 is.setstate(std::ios::failbit);
304 return is;
307 namespace Mouse {
309 namespace {
311 bool supportEnabled = false;
315 void enable()
317 if (!supportEnabled)
318 return;
319 // save old highlight mouse tracking
320 std::printf("\e[?1001s");
321 // enable mouse tracking
322 std::printf("\e[?1000h");
323 // try to enable extended (urxvt) mouse tracking
324 std::printf("\e[?1015h");
325 // send the above to the terminal immediately
326 std::fflush(stdout);
329 void disable()
331 if (!supportEnabled)
332 return;
333 // disable extended (urxvt) mouse tracking
334 std::printf("\e[?1015l");
335 // disable mouse tracking
336 std::printf("\e[?1000l");
337 // restore old highlight mouse tracking
338 std::printf("\e[?1001r");
339 // send the above to the terminal immediately
340 std::fflush(stdout);
345 void initScreen(bool enable_colors, bool enable_mouse)
347 initscr();
348 if (has_colors() && enable_colors)
350 start_color();
351 use_default_colors();
352 int npair = 1;
353 for (int bg = -1; bg < COLORS; ++bg)
355 for (int fg = 0; npair < COLOR_PAIRS && fg < COLORS; ++fg, ++npair)
356 init_pair(npair, fg, bg);
359 raw();
360 nonl();
361 noecho();
362 curs_set(0);
364 // setup mouse
365 Mouse::supportEnabled = enable_mouse;
366 Mouse::enable();
368 // initialize readline (needed, otherwise we get segmentation
369 // fault on SIGWINCH). also, initialize first as doing this
370 // later erases keys bound with rl_bind_key for some users.
371 rl_initialize();
372 // disable autocompletion
373 rl_attempted_completion_function = [](const char *, int, int) -> char ** {
374 rl_attempted_completion_over = 1;
375 return nullptr;
377 auto abort_prompt = [](int, int) -> int {
378 rl::aborted = true;
379 rl_done = 1;
380 return 0;
382 // if ctrl-c or ctrl-g is pressed, abort the prompt
383 rl_bind_key('\3', abort_prompt);
384 rl_bind_key('\7', abort_prompt);
385 // do not change the state of the terminal
386 rl_prep_term_function = nullptr;
387 rl_deprep_term_function = nullptr;
388 // do not catch signals
389 rl_catch_signals = 0;
390 // overwrite readline callbacks
391 rl_getc_function = rl::read_key;
392 rl_redisplay_function = rl::display_string;
393 rl_startup_hook = rl::add_base;
396 void destroyScreen()
398 Mouse::disable();
399 curs_set(1);
400 endwin();
403 Window::Window(size_t startx,
404 size_t starty,
405 size_t width,
406 size_t height,
407 std::string title,
408 Color color,
409 Border border)
410 : m_window(nullptr),
411 m_start_x(startx),
412 m_start_y(starty),
413 m_width(width),
414 m_height(height),
415 m_window_timeout(-1),
416 m_color(color),
417 m_base_color(color),
418 m_border(std::move(border)),
419 m_prompt_hook(0),
420 m_title(std::move(title)),
421 m_escape_terminal_sequences(true),
422 m_bold_counter(0),
423 m_underline_counter(0),
424 m_reverse_counter(0),
425 m_alt_charset_counter(0)
427 if (m_start_x > size_t(COLS)
428 || m_start_y > size_t(LINES)
429 || m_width+m_start_x > size_t(COLS)
430 || m_height+m_start_y > size_t(LINES))
431 throw std::logic_error("constructed window doesn't fit into the terminal");
433 if (m_border)
435 ++m_start_x;
436 ++m_start_y;
437 m_width -= 2;
438 m_height -= 2;
440 if (!m_title.empty())
442 m_start_y += 2;
443 m_height -= 2;
446 m_window = newpad(m_height, m_width);
448 setColor(m_color);
451 Window::Window(const Window &rhs)
452 : m_window(dupwin(rhs.m_window))
453 , m_start_x(rhs.m_start_x)
454 , m_start_y(rhs.m_start_y)
455 , m_width(rhs.m_width)
456 , m_height(rhs.m_height)
457 , m_window_timeout(rhs.m_window_timeout)
458 , m_color(rhs.m_color)
459 , m_base_color(rhs.m_base_color)
460 , m_border(rhs.m_border)
461 , m_prompt_hook(rhs.m_prompt_hook)
462 , m_title(rhs.m_title)
463 , m_color_stack(rhs.m_color_stack)
464 , m_input_queue(rhs.m_input_queue)
465 , m_fds(rhs.m_fds)
466 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
467 , m_bold_counter(rhs.m_bold_counter)
468 , m_underline_counter(rhs.m_underline_counter)
469 , m_reverse_counter(rhs.m_reverse_counter)
470 , m_alt_charset_counter(rhs.m_alt_charset_counter)
474 Window::Window(Window &&rhs)
475 : m_window(rhs.m_window)
476 , m_start_x(rhs.m_start_x)
477 , m_start_y(rhs.m_start_y)
478 , m_width(rhs.m_width)
479 , m_height(rhs.m_height)
480 , m_window_timeout(rhs.m_window_timeout)
481 , m_color(rhs.m_color)
482 , m_base_color(rhs.m_base_color)
483 , m_border(rhs.m_border)
484 , m_prompt_hook(rhs.m_prompt_hook)
485 , m_title(std::move(rhs.m_title))
486 , m_color_stack(std::move(rhs.m_color_stack))
487 , m_input_queue(std::move(rhs.m_input_queue))
488 , m_fds(std::move(rhs.m_fds))
489 , m_escape_terminal_sequences(rhs.m_escape_terminal_sequences)
490 , m_bold_counter(rhs.m_bold_counter)
491 , m_underline_counter(rhs.m_underline_counter)
492 , m_reverse_counter(rhs.m_reverse_counter)
493 , m_alt_charset_counter(rhs.m_alt_charset_counter)
495 rhs.m_window = nullptr;
498 Window &Window::operator=(Window rhs)
500 std::swap(m_window, rhs.m_window);
501 std::swap(m_start_x, rhs.m_start_x);
502 std::swap(m_start_y, rhs.m_start_y);
503 std::swap(m_width, rhs.m_width);
504 std::swap(m_height, rhs.m_height);
505 std::swap(m_window_timeout, rhs.m_window_timeout);
506 std::swap(m_color, rhs.m_color);
507 std::swap(m_base_color, rhs.m_base_color);
508 std::swap(m_border, rhs.m_border);
509 std::swap(m_prompt_hook, rhs.m_prompt_hook);
510 std::swap(m_title, rhs.m_title);
511 std::swap(m_color_stack, rhs.m_color_stack);
512 std::swap(m_input_queue, rhs.m_input_queue);
513 std::swap(m_fds, rhs.m_fds);
514 std::swap(m_escape_terminal_sequences, rhs.m_escape_terminal_sequences);
515 std::swap(m_bold_counter, rhs.m_bold_counter);
516 std::swap(m_underline_counter, rhs.m_underline_counter);
517 std::swap(m_reverse_counter, rhs.m_reverse_counter);
518 std::swap(m_alt_charset_counter, rhs.m_alt_charset_counter);
519 return *this;
522 Window::~Window()
524 delwin(m_window);
527 void Window::setColor(Color c)
529 if (c.isDefault())
530 c = m_base_color;
531 if (c != Color::Default)
533 if (c.previousBackground())
534 c = Color(c.foreground(), m_color.background());
535 wcolor_set(m_window, c.pairNumber(), nullptr);
537 else
538 wcolor_set(m_window, m_base_color.pairNumber(), nullptr);
539 m_color = std::move(c);
542 void Window::setBaseColor(Color c)
544 m_base_color = std::move(c);
547 void Window::setBorder(Border border)
549 if (!border && m_border)
551 --m_start_x;
552 --m_start_y;
553 m_height += 2;
554 m_width += 2;
555 recreate(m_width, m_height);
557 else if (border && !m_border)
559 ++m_start_x;
560 ++m_start_y;
561 m_height -= 2;
562 m_width -= 2;
563 recreate(m_width, m_height);
565 m_border = border;
568 void Window::setTitle(const std::string &new_title)
570 if (!new_title.empty() && m_title.empty())
572 m_start_y += 2;
573 m_height -= 2;
574 recreate(m_width, m_height);
576 else if (new_title.empty() && !m_title.empty())
578 m_start_y -= 2;
579 m_height += 2;
580 recreate(m_width, m_height);
582 m_title = new_title;
585 void Window::recreate(size_t width, size_t height)
587 delwin(m_window);
588 m_window = newpad(height, width);
589 setTimeout(m_window_timeout);
590 setColor(m_color);
593 void Window::moveTo(size_t new_x, size_t new_y)
595 m_start_x = new_x;
596 m_start_y = new_y;
597 if (m_border)
599 ++m_start_x;
600 ++m_start_y;
602 if (!m_title.empty())
603 m_start_y += 2;
606 void Window::adjustDimensions(size_t width, size_t height)
608 if (m_border)
610 width -= 2;
611 height -= 2;
613 if (!m_title.empty())
614 height -= 2;
615 m_height = height;
616 m_width = width;
619 void Window::resize(size_t new_width, size_t new_height)
621 adjustDimensions(new_width, new_height);
622 recreate(m_width, m_height);
625 void Window::refreshBorder() const
627 if (m_border)
629 size_t start_x = getStartX(), start_y = getStarty();
630 size_t width = getWidth(), height = getHeight();
631 color_set(m_border->pairNumber(), nullptr);
632 attron(A_ALTCHARSET);
633 // corners
634 mvaddch(start_y, start_x, 'l');
635 mvaddch(start_y, start_x+width-1, 'k');
636 mvaddch(start_y+height-1, start_x, 'm');
637 mvaddch(start_y+height-1, start_x+width-1, 'j');
638 // lines
639 mvhline(start_y, start_x+1, 'q', width-2);
640 mvhline(start_y+height-1, start_x+1, 'q', width-2);
641 mvvline(start_y+1, start_x, 'x', height-2);
642 mvvline(start_y+1, start_x+width-1, 'x', height-2);
643 if (!m_title.empty())
645 mvaddch(start_y+2, start_x, 't');
646 mvaddch(start_y+2, start_x+width-1, 'u');
648 attroff(A_ALTCHARSET);
650 else
651 color_set(m_base_color.pairNumber(), nullptr);
652 if (!m_title.empty())
654 // clear title line
655 mvhline(m_start_y-2, m_start_x, ' ', m_width);
656 attron(A_BOLD);
657 mvaddstr(m_start_y-2, m_start_x, m_title.c_str());
658 attroff(A_BOLD);
659 // add separator
660 mvhline(m_start_y-1, m_start_x, 0, m_width);
662 standend();
663 ::refresh();
666 void Window::display()
668 refreshBorder();
669 refresh();
672 void Window::refresh()
674 prefresh(m_window, 0, 0, m_start_y, m_start_x, m_start_y+m_height-1, m_start_x+m_width-1);
677 void Window::clear()
679 werase(m_window);
682 void Window::bold(bool bold_state) const
684 (bold_state ? wattron : wattroff)(m_window, A_BOLD);
687 void Window::underline(bool underline_state) const
689 (underline_state ? wattron : wattroff)(m_window, A_UNDERLINE);
692 void Window::reverse(bool reverse_state) const
694 (reverse_state ? wattron : wattroff)(m_window, A_REVERSE);
697 void Window::altCharset(bool altcharset_state) const
699 (altcharset_state ? wattron : wattroff)(m_window, A_ALTCHARSET);
702 void Window::setTimeout(int timeout)
704 if (timeout != m_window_timeout)
706 m_window_timeout = timeout;
707 wtimeout(m_window, timeout);
711 void Window::addFDCallback(int fd, void (*callback)())
713 m_fds.push_back(std::make_pair(fd, callback));
716 void Window::clearFDCallbacksList()
718 m_fds.clear();
721 bool Window::FDCallbacksListEmpty() const
723 return m_fds.empty();
726 Key::Type Window::getInputChar(int key)
728 if (!m_escape_terminal_sequences || key != Key::Escape)
729 return key;
730 auto define_mouse_event = [this](int type) {
731 switch (type & ~28)
733 case 32:
734 m_mouse_event.bstate = BUTTON1_PRESSED;
735 break;
736 case 33:
737 m_mouse_event.bstate = BUTTON2_PRESSED;
738 break;
739 case 34:
740 m_mouse_event.bstate = BUTTON3_PRESSED;
741 break;
742 case 96:
743 m_mouse_event.bstate = BUTTON4_PRESSED;
744 break;
745 case 97:
746 m_mouse_event.bstate = BUTTON5_PRESSED;
747 break;
748 default:
749 return Key::None;
751 if (type & 4)
752 m_mouse_event.bstate |= BUTTON_SHIFT;
753 if (type & 8)
754 m_mouse_event.bstate |= BUTTON_ALT;
755 if (type & 16)
756 m_mouse_event.bstate |= BUTTON_CTRL;
757 if (m_mouse_event.x < 0 || m_mouse_event.x >= COLS)
758 return Key::None;
759 if (m_mouse_event.y < 0 || m_mouse_event.y >= LINES)
760 return Key::None;
761 return Key::Mouse;
763 auto get_xterm_modifier_key = [](int ch) {
764 Key::Type modifier;
765 switch (ch)
767 case '2':
768 modifier = Key::Shift;
769 break;
770 case '3':
771 modifier = Key::Alt;
772 break;
773 case '4':
774 modifier = Key::Alt | Key::Shift;
775 break;
776 case '5':
777 modifier = Key::Ctrl;
778 break;
779 case '6':
780 modifier = Key::Ctrl | Key::Shift;
781 break;
782 case '7':
783 modifier = Key::Alt | Key::Ctrl;
784 break;
785 case '8':
786 modifier = Key::Alt | Key::Ctrl | Key::Shift;
787 break;
788 default:
789 modifier = Key::None;
791 return modifier;
793 auto parse_number = [this](int &result) {
794 int x;
795 while (true)
797 x = wgetch(m_window);
798 if (!isdigit(x))
799 return x;
800 result = result*10 + x - '0';
803 ScopedWindowTimeout swt(m_window, 0, m_window_timeout);
804 key = wgetch(m_window);
805 switch (key)
807 case '\t': // tty
808 return Key::Shift | Key::Tab;
809 case 'O':
810 key = wgetch(m_window);
811 switch (key)
813 // eterm
814 case 'A':
815 return Key::Up;
816 case 'B':
817 return Key::Down;
818 case 'C':
819 return Key::Right;
820 case 'D':
821 return Key::Left;
822 // terminator
823 case 'F':
824 return Key::End;
825 case 'H':
826 return Key::Home;
827 // rxvt
828 case 'a':
829 return Key::Ctrl | Key::Up;
830 case 'b':
831 return Key::Ctrl | Key::Down;
832 case 'c':
833 return Key::Ctrl | Key::Right;
834 case 'd':
835 return Key::Ctrl | Key::Left;
836 // xterm
837 case 'P':
838 return Key::F1;
839 case 'Q':
840 return Key::F2;
841 case 'R':
842 return Key::F3;
843 case 'S':
844 return Key::F4;
845 default:
846 return Key::None;
848 case '[':
849 key = wgetch(m_window);
850 switch (key)
852 case 'a':
853 return Key::Shift | Key::Up;
854 case 'b':
855 return Key::Shift | Key::Down;
856 case 'c':
857 return Key::Shift | Key::Right;
858 case 'd':
859 return Key::Shift | Key::Left;
860 case 'A':
861 return Key::Up;
862 case 'B':
863 return Key::Down;
864 case 'C':
865 return Key::Right;
866 case 'D':
867 return Key::Left;
868 case 'F': // xterm
869 return Key::End;
870 case 'H': // xterm
871 return Key::Home;
872 case 'M': // standard mouse event
874 key = wgetch(m_window);
875 int raw_x = wgetch(m_window);
876 int raw_y = wgetch(m_window);
877 // support coordinates up to 255
878 m_mouse_event.x = (raw_x - 33) & 0xff;
879 m_mouse_event.y = (raw_y - 33) & 0xff;
880 return define_mouse_event(key);
882 case 'Z':
883 return Key::Shift | Key::Tab;
884 case '[': // F1 to F5 in tty
885 key = wgetch(m_window);
886 switch (key)
888 case 'A':
889 return Key::F1;
890 case 'B':
891 return Key::F2;
892 case 'C':
893 return Key::F3;
894 case 'D':
895 return Key::F4;
896 case 'E':
897 return Key::F5;
898 default:
899 return Key::None;
901 case '1': case '2': case '3':
902 case '4': case '5': case '6':
903 case '7': case '8': case '9':
905 key -= '0';
906 int delim = parse_number(key);
907 if (key >= 2 && key <= 8)
909 Key::Type modifier;
910 switch (delim)
912 case '~':
913 modifier = Key::Null;
914 break;
915 case '^':
916 modifier = Key::Ctrl;
917 break;
918 case '$':
919 modifier = Key::Shift;
920 break;
921 case '@':
922 modifier = Key::Ctrl | Key::Shift;
923 break;
924 case ';': // xterm insert/delete/page up/page down
926 int local_key = wgetch(m_window);
927 modifier = get_xterm_modifier_key(local_key);
928 local_key = wgetch(m_window);
929 if (local_key != '~' || (key != 2 && key != 3 && key != 5 && key != 6))
930 return Key::None;
931 break;
933 default:
934 return Key::None;
936 switch (key)
938 case 2:
939 return modifier | Key::Insert;
940 case 3:
941 return modifier | Key::Delete;
942 case 4:
943 return modifier | Key::End;
944 case 5:
945 return modifier | Key::PageUp;
946 case 6:
947 return modifier | Key::PageDown;
948 case 7:
949 return modifier | Key::Home;
950 case 8:
951 return modifier | Key::End;
952 default:
953 std::cerr << "Unreachable code, aborting.\n";
954 std::terminate();
957 switch (delim)
959 case '~':
961 switch (key)
963 case 1: // tty
964 return Key::Home;
965 case 11:
966 return Key::F1;
967 case 12:
968 return Key::F2;
969 case 13:
970 return Key::F3;
971 case 14:
972 return Key::F4;
973 case 15:
974 return Key::F5;
975 case 17: // not a typo
976 return Key::F6;
977 case 18:
978 return Key::F7;
979 case 19:
980 return Key::F8;
981 case 20:
982 return Key::F9;
983 case 21:
984 return Key::F10;
985 case 23: // not a typo
986 return Key::F11;
987 case 24:
988 return Key::F12;
989 default:
990 return Key::None;
993 case ';':
994 switch (key)
996 case 1: // xterm
998 key = wgetch(m_window);
999 Key::Type modifier = get_xterm_modifier_key(key);
1000 if (modifier == Key::None)
1001 return Key::None;
1002 key = wgetch(m_window);
1003 switch (key)
1005 case 'A':
1006 return modifier | Key::Up;
1007 case 'B':
1008 return modifier | Key::Down;
1009 case 'C':
1010 return modifier | Key::Right;
1011 case 'D':
1012 return modifier | Key::Left;
1013 case 'F':
1014 return modifier | Key::End;
1015 case 'H':
1016 return modifier | Key::Home;
1017 default:
1018 return Key::None;
1021 default: // urxvt mouse
1022 m_mouse_event.x = 0;
1023 delim = parse_number(m_mouse_event.x);
1024 if (delim != ';')
1025 return Key::None;
1026 m_mouse_event.y = 0;
1027 delim = parse_number(m_mouse_event.y);
1028 if (delim != 'M')
1029 return Key::None;
1030 --m_mouse_event.x;
1031 --m_mouse_event.y;
1032 return define_mouse_event(key);
1034 default:
1035 return Key::None;
1038 default:
1039 return Key::None;
1041 case ERR:
1042 return Key::Escape;
1043 default: // alt + something
1045 auto key_prim = getInputChar(key);
1046 if (key_prim != Key::None)
1047 return Key::Alt | key_prim;
1048 return Key::None;
1053 Key::Type Window::readKey()
1055 Key::Type result;
1056 // if there are characters in input queue,
1057 // get them and return immediately.
1058 if (!m_input_queue.empty())
1060 result = m_input_queue.front();
1061 m_input_queue.pop();
1062 return result;
1065 fd_set fdset;
1066 FD_ZERO(&fdset);
1067 FD_SET(STDIN_FILENO, &fdset);
1068 timeval timeout = { m_window_timeout/1000, (m_window_timeout%1000)*1000 };
1070 int fd_max = STDIN_FILENO;
1071 for (FDCallbacks::const_iterator it = m_fds.begin(); it != m_fds.end(); ++it)
1073 if (it->first > fd_max)
1074 fd_max = it->first;
1075 FD_SET(it->first, &fdset);
1078 if (select(fd_max+1, &fdset, 0, 0, m_window_timeout < 0 ? 0 : &timeout) > 0)
1080 if (FD_ISSET(STDIN_FILENO, &fdset))
1081 result = getInputChar(wgetch(m_window));
1082 else
1083 result = Key::None;
1085 for (FDCallbacks::const_iterator it = m_fds.begin(); it != m_fds.end(); ++it)
1086 if (FD_ISSET(it->first, &fdset))
1087 it->second();
1089 else
1090 result = Key::None;
1091 return result;
1094 void Window::pushChar(const Key::Type ch)
1096 m_input_queue.push(ch);
1099 std::string Window::prompt(const std::string &base, size_t width, bool encrypted)
1101 std::string result;
1103 rl::aborted = false;
1104 rl::w = this;
1105 getyx(m_window, rl::start_y, rl::start_x);
1106 rl::width = std::min(m_width-rl::start_x-1, width-1);
1107 rl::encrypted = encrypted;
1108 rl::base = base.c_str();
1110 curs_set(1);
1111 Mouse::disable();
1112 m_escape_terminal_sequences = false;
1113 char *input = readline(nullptr);
1114 m_escape_terminal_sequences = true;
1115 Mouse::enable();
1116 curs_set(0);
1117 if (input != nullptr)
1119 #ifdef HAVE_READLINE_HISTORY_H
1120 if (!encrypted && input[0] != 0)
1121 add_history(input);
1122 #endif // HAVE_READLINE_HISTORY_H
1123 result = input;
1124 free(input);
1127 if (rl::aborted)
1128 throw PromptAborted(std::move(result));
1130 return result;
1133 void Window::goToXY(int x, int y)
1135 wmove(m_window, y, x);
1138 int Window::getX()
1140 return getcurx(m_window);
1143 int Window::getY()
1145 return getcury(m_window);
1148 bool Window::hasCoords(int &x, int &y)
1150 return wmouse_trafo(m_window, &y, &x, 0);
1153 bool Window::runPromptHook(const char *arg, bool *done) const
1155 if (m_prompt_hook)
1157 bool continue_ = m_prompt_hook(arg);
1158 if (done != nullptr)
1159 *done = !continue_;
1160 return true;
1162 else
1163 return false;
1166 size_t Window::getWidth() const
1168 if (m_border)
1169 return m_width+2;
1170 else
1171 return m_width;
1174 size_t Window::getHeight() const
1176 size_t height = m_height;
1177 if (m_border)
1178 height += 2;
1179 if (!m_title.empty())
1180 height += 2;
1181 return height;
1184 size_t Window::getStartX() const
1186 if (m_border)
1187 return m_start_x-1;
1188 else
1189 return m_start_x;
1192 size_t Window::getStarty() const
1194 size_t starty = m_start_y;
1195 if (m_border)
1196 --starty;
1197 if (!m_title.empty())
1198 starty -= 2;
1199 return starty;
1202 const std::string &Window::getTitle() const
1204 return m_title;
1207 const Color &Window::getColor() const
1209 return m_color;
1212 const Border &Window::getBorder() const
1214 return m_border;
1217 int Window::getTimeout() const
1219 return m_window_timeout;
1222 const MEVENT &Window::getMouseEvent()
1224 return m_mouse_event;
1227 void Window::scroll(Scroll where)
1229 idlok(m_window, 1);
1230 scrollok(m_window, 1);
1231 switch (where)
1233 case Scroll::Up:
1234 wscrl(m_window, 1);
1235 break;
1236 case Scroll::Down:
1237 wscrl(m_window, -1);
1238 break;
1239 case Scroll::PageUp:
1240 wscrl(m_window, m_width);
1241 break;
1242 case Scroll::PageDown:
1243 wscrl(m_window, -m_width);
1244 break;
1245 default:
1246 break;
1248 idlok(m_window, 0);
1249 scrollok(m_window, 0);
1253 Window &Window::operator<<(const Color &c)
1255 if (c.isDefault())
1257 while (!m_color_stack.empty())
1258 m_color_stack.pop();
1259 setColor(m_base_color);
1261 else if (c.isEnd())
1263 if (!m_color_stack.empty())
1264 m_color_stack.pop();
1265 if (!m_color_stack.empty())
1266 setColor(m_color_stack.top());
1267 else
1268 setColor(m_base_color);
1270 else
1272 setColor(c);
1273 m_color_stack.push(c);
1275 return *this;
1278 Window &Window::operator<<(Format format)
1280 switch (format)
1282 case Format::None:
1283 bold((m_bold_counter = 0));
1284 reverse((m_reverse_counter = 0));
1285 altCharset((m_alt_charset_counter = 0));
1286 break;
1287 case Format::Bold:
1288 bold(++m_bold_counter);
1289 break;
1290 case Format::NoBold:
1291 if (--m_bold_counter <= 0)
1292 bold((m_bold_counter = 0));
1293 break;
1294 case Format::Underline:
1295 underline(++m_underline_counter);
1296 break;
1297 case Format::NoUnderline:
1298 if (--m_underline_counter <= 0)
1299 underline((m_underline_counter = 0));
1300 break;
1301 case Format::Reverse:
1302 reverse(++m_reverse_counter);
1303 break;
1304 case Format::NoReverse:
1305 if (--m_reverse_counter <= 0)
1306 reverse((m_reverse_counter = 0));
1307 break;
1308 case Format::AltCharset:
1309 altCharset(++m_alt_charset_counter);
1310 break;
1311 case Format::NoAltCharset:
1312 if (--m_alt_charset_counter <= 0)
1313 altCharset((m_alt_charset_counter = 0));
1314 break;
1316 return *this;
1319 Window &Window::operator<<(TermManip tm)
1321 switch (tm)
1323 case TermManip::ClearToEOL:
1325 auto x = getX(), y = getY();
1326 mvwhline(m_window, y, x, ' ', m_width-x);
1327 goToXY(x, y);
1329 break;
1331 return *this;
1334 Window &Window::operator<<(const XY &coords)
1336 goToXY(coords.x, coords.y);
1337 return *this;
1340 Window &Window::operator<<(const char *s)
1342 waddstr(m_window, s);
1343 return *this;
1346 Window &Window::operator<<(char c)
1348 // the following causes problems: https://github.com/arybczak/ncmpcpp/issues/21
1349 // waddnstr(m_window, &c, 1);
1350 wprintw(m_window, "%c", c);
1351 return *this;
1354 Window &Window::operator<<(const wchar_t *ws)
1356 # ifdef NCMPCPP_UNICODE
1357 waddwstr(m_window, ws);
1358 # else
1359 wprintw(m_window, "%ls", ws);
1360 # endif // NCMPCPP_UNICODE
1361 return *this;
1364 Window &Window::operator<<(wchar_t wc)
1366 # ifdef NCMPCPP_UNICODE
1367 waddnwstr(m_window, &wc, 1);
1368 # else
1369 wprintw(m_window, "%lc", wc);
1370 # endif // NCMPCPP_UNICODE
1371 return *this;
1374 Window &Window::operator<<(int i)
1376 wprintw(m_window, "%d", i);
1377 return *this;
1380 Window &Window::operator<<(double d)
1382 wprintw(m_window, "%f", d);
1383 return *this;
1386 Window &Window::operator<<(const std::string &s)
1388 waddnstr(m_window, s.c_str(), s.length());
1389 return *this;
1392 Window &Window::operator<<(const std::wstring &ws)
1394 # ifdef NCMPCPP_UNICODE
1395 waddnwstr(m_window, ws.c_str(), ws.length());
1396 # else
1397 wprintw(m_window, "%lc", ws.c_str());
1398 # endif // NCMPCPP_UNICODE
1399 return *this;
1402 Window &Window::operator<<(size_t s)
1404 wprintw(m_window, "%zu", s);
1405 return *this;