bindinds: add support for alt/ctrl/shift modifiers and escape key
[ncmpcpp.git] / src / window.cpp
blob4cb98d734d3152550df8ddc1fe32b85e83d61ce1
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 <readline/history.h>
27 #include <readline/readline.h>
29 #include <sys/select.h>
30 #include <unistd.h>
32 #include "utility/string.h"
33 #include "utility/wide_string.h"
34 #include "window.h"
36 namespace {
38 struct ScopedWindowTimeout
40 ScopedWindowTimeout(WINDOW *w, int init_timeout, int term_timeout)
41 : m_w(w), m_term_timeout(term_timeout) {
42 wtimeout(w, init_timeout);
44 ~ScopedWindowTimeout() {
45 wtimeout(m_w, m_term_timeout);
48 private:
49 WINDOW *m_w;
50 int m_term_timeout;
53 namespace rl {
55 bool aborted;
57 NC::Window *w;
58 size_t start_x;
59 size_t start_y;
60 size_t width;
61 bool encrypted;
62 const char *base;
64 int read_key(FILE *)
66 size_t x;
67 bool done;
68 int result;
71 x = w->getX();
72 if (w->runPromptHook(rl_line_buffer, &done))
74 if (done)
76 rl_done = 1;
77 return EOF;
79 w->goToXY(x, start_y);
81 w->refresh();
82 result = w->readKey();
83 if (!w->FDCallbacksListEmpty())
85 w->goToXY(x, start_y);
86 w->refresh();
89 while (result == ERR);
90 return result;
93 void display_string()
95 auto print_char = [](wchar_t wc) {
96 if (encrypted)
97 *w << '*';
98 else
99 *w << wc;
101 auto print_string = [](wchar_t *ws, size_t len) {
102 if (encrypted)
103 for (size_t i = 0; i < len; ++i)
104 *w << '*';
105 else
106 *w << ws;
108 auto narrow_to_wide = [](wchar_t *dest, const char *src, size_t n) {
109 size_t result = 0;
110 // convert the string and substitute invalid multibyte chars with dots.
111 for (size_t i = 0; i < n;)
113 int ret = mbtowc(&dest[result], &src[i], n-i);
114 if (ret > 0)
116 i += ret;
117 ++result;
119 else if (ret == -1)
121 dest[result] = L'.';
122 ++i;
123 ++result;
125 else
126 throw std::runtime_error("mbtowc: unexpected return value");
128 return result;
131 // copy the part of the string that is before the cursor to pre_pos
132 char pt = rl_line_buffer[rl_point];
133 rl_line_buffer[rl_point] = 0;
134 wchar_t pre_pos[rl_point+1];
135 pre_pos[narrow_to_wide(pre_pos, rl_line_buffer, rl_point)] = 0;
136 rl_line_buffer[rl_point] = pt;
138 int pos = wcswidth(pre_pos, rl_point);
139 if (pos < 0)
140 pos = rl_point;
142 // clear the area for the string
143 mvwhline(w->raw(), start_y, start_x, ' ', width+1);
145 w->goToXY(start_x, start_y);
146 if (size_t(pos) <= width)
148 // if the current position in the string is not bigger than allowed
149 // width, print the part of the string before cursor position...
151 print_string(pre_pos, pos);
153 // ...and then print the rest char-by-char until there is no more area
154 wchar_t post_pos[rl_end-rl_point+1];
155 post_pos[narrow_to_wide(post_pos, rl_line_buffer+rl_point, rl_end-rl_point)] = 0;
157 size_t cpos = pos;
158 for (wchar_t *c = post_pos; *c != 0; ++c)
160 int n = wcwidth(*c);
161 if (n < 0)
163 print_char(L'.');
164 ++cpos;
166 else
168 if (cpos+n > width)
169 break;
170 cpos += n;
171 print_char(*c);
175 else
177 // if the current position in the string is bigger than allowed
178 // width, we always keep the cursor at the end of the line (it
179 // would be nice to have more flexible scrolling, but for now
180 // let's stick to that) by cutting the beginning of the part
181 // of the string before the cursor until it fits the area.
183 wchar_t *mod_pre_pos = pre_pos;
184 while (*mod_pre_pos != 0)
186 ++mod_pre_pos;
187 int n = wcwidth(*mod_pre_pos);
188 if (n < 0)
189 --pos;
190 else
191 pos -= n;
192 if (size_t(pos) <= width)
193 break;
195 print_string(mod_pre_pos, pos);
197 w->goToXY(start_x+pos, start_y);
200 int add_base()
202 rl_insert_text(base);
203 return 0;
209 namespace NC {
211 const short Color::transparent = -1;
212 const short Color::previous = -2;
214 Color Color::Default(0, 0, true, false);
215 Color Color::Black(COLOR_BLACK, Color::transparent);
216 Color Color::Red(COLOR_RED, Color::transparent);
217 Color Color::Green(COLOR_GREEN, Color::transparent);
218 Color Color::Yellow(COLOR_YELLOW, Color::transparent);
219 Color Color::Blue(COLOR_BLUE, Color::transparent);
220 Color Color::Magenta(COLOR_MAGENTA, Color::transparent);
221 Color Color::Cyan(COLOR_CYAN, Color::transparent);
222 Color Color::White(COLOR_WHITE, Color::transparent);
223 Color Color::End(0, 0, false, true);
225 int Color::pairNumber() const
227 int result;
228 if (isDefault())
229 result = 0;
230 else if (previousBackground())
231 throw std::logic_error("color depends on the previous background value");
232 else if (isEnd())
233 throw std::logic_error("'end' doesn't have a corresponding pair number");
234 else
236 // colors start with 0, but pairs start with 1. additionally
237 // first pairs are for transparent background, which has a
238 // value of -1, so we need to add 1 to both foreground and
239 // background value.
240 result = background() + 1;
241 result *= COLORS;
242 result += foreground() + 1;
244 return result;
247 std::istream &operator>>(std::istream &is, Color &c)
249 auto get_single_color = [](const std::string &s, bool background) {
250 short result = -1;
251 if (s == "black")
252 result = COLOR_BLACK;
253 else if (s == "red")
254 result = COLOR_RED;
255 else if (s == "green")
256 result = COLOR_GREEN;
257 else if (s == "yellow")
258 result = COLOR_YELLOW;
259 else if (s == "blue")
260 result = COLOR_BLUE;
261 else if (s == "magenta")
262 result = COLOR_MAGENTA;
263 else if (s == "cyan")
264 result = COLOR_CYAN;
265 else if (s == "white")
266 result = COLOR_WHITE;
267 else if (background && s == "previous")
268 result = NC::Color::previous;
269 else if (std::all_of(s.begin(), s.end(), isdigit))
271 result = atoi(s.c_str());
272 if (result < 1 || result > 256)
273 result = -1;
274 else
275 --result;
277 return result;
279 std::string sc;
280 is >> sc;
281 if (sc == "default")
282 c = Color::Default;
283 else if (sc == "end")
284 c = Color::End;
285 else
287 short value = get_single_color(sc, false);
288 if (value != -1)
289 c = Color(value, NC::Color::transparent);
290 else
292 size_t underscore = sc.find('_');
293 if (underscore != std::string::npos)
295 short fg = get_single_color(sc.substr(0, underscore), false);
296 short bg = get_single_color(sc.substr(underscore+1), true);
297 if (fg != -1 && bg != -1)
298 c = Color(fg, bg);
299 else
300 is.setstate(std::ios::failbit);
302 else
303 is.setstate(std::ios::failbit);
306 return is;
309 namespace Mouse {
311 namespace {
313 bool mouseEnabled = false;
317 void enable()
319 if (mouseEnabled)
320 return;
321 // save old highlight mouse tracking
322 std::printf("\e[?1001s");
323 // enable mouse tracking
324 std::printf("\e[?1000h");
325 // try to enable extended (urxvt) mouse tracking
326 std::printf("\e[?1015h");
327 mouseEnabled = true;
330 void disable()
332 if (!mouseEnabled)
333 return;
334 // disable extended (urxvt) mouse tracking
335 std::printf("\e[?1015l");
336 // disable mouse tracking
337 std::printf("\e[?1000l");
338 // restore old highlight mouse tracking
339 std::printf("\e[?1001r");
340 mouseEnabled = false;
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 if (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': // ctrl+arrows in rxvt, F1 to F4 in xterm
810 key = wgetch(m_window);
811 switch (key)
813 case 'a':
814 return Key::Ctrl | Key::Up;
815 case 'b':
816 return Key::Ctrl | Key::Down;
817 case 'c':
818 return Key::Ctrl | Key::Right;
819 case 'd':
820 return Key::Ctrl | Key::Left;
821 case 'P':
822 return Key::F1;
823 case 'Q':
824 return Key::F2;
825 case 'R':
826 return Key::F3;
827 case 'S':
828 return Key::F4;
829 default:
830 return Key::None;
832 case '[':
833 key = wgetch(m_window);
834 switch (key)
836 case 'a':
837 return Key::Shift | Key::Up;
838 case 'b':
839 return Key::Shift | Key::Down;
840 case 'c':
841 return Key::Shift | Key::Right;
842 case 'd':
843 return Key::Shift | Key::Left;
844 case 'A':
845 return Key::Up;
846 case 'B':
847 return Key::Down;
848 case 'C':
849 return Key::Right;
850 case 'D':
851 return Key::Left;
852 case 'F': // xterm
853 return Key::End;
854 case 'H': // xterm
855 return Key::Home;
856 case 'M': // standard mouse event
858 key = wgetch(m_window);
859 int raw_x = wgetch(m_window);
860 int raw_y = wgetch(m_window);
861 // support coordinates up to 255
862 m_mouse_event.x = (raw_x - 33) & 0xff;
863 m_mouse_event.y = (raw_y - 33) & 0xff;
864 return define_mouse_event(key);
866 case 'Z':
867 return Key::Shift | Key::Tab;
868 case '[': // F1 to F5 in tty
869 key = wgetch(m_window);
870 switch (key)
872 case 'A':
873 return Key::F1;
874 case 'B':
875 return Key::F2;
876 case 'C':
877 return Key::F3;
878 case 'D':
879 return Key::F4;
880 case 'E':
881 return Key::F5;
882 default:
883 return Key::None;
885 case '1': case '2': case '3':
886 case '4': case '5': case '6':
887 case '7': case '8': case '9':
889 key -= '0';
890 int delim = parse_number(key);
891 if (key >= 2 && key <= 8)
893 Key::Type modifier;
894 switch (delim)
896 case '~':
897 modifier = Key::Null;
898 break;
899 case '^':
900 modifier = Key::Ctrl;
901 break;
902 case '$':
903 modifier = Key::Shift;
904 break;
905 case '@':
906 modifier = Key::Ctrl | Key::Shift;
907 break;
908 case ';': // xterm insert/delete/page up/page down
910 int local_key = wgetch(m_window);
911 modifier = get_xterm_modifier_key(local_key);
912 local_key = wgetch(m_window);
913 if (local_key != '~' || (key != 2 && key != 3 && key != 5 && key != 6))
914 return Key::None;
915 break;
917 default:
918 return Key::None;
920 switch (key)
922 case 2:
923 return modifier | Key::Insert;
924 case 3:
925 return modifier | Key::Delete;
926 case 4:
927 return modifier | Key::End;
928 case 5:
929 return modifier | Key::PageUp;
930 case 6:
931 return modifier | Key::PageDown;
932 case 7:
933 return modifier | Key::Home;
934 case 8:
935 return modifier | Key::End;
936 default:
937 std::cerr << "Unreachable code, aborting.\n";
938 std::terminate();
941 switch (delim)
943 case '~':
945 switch (key)
947 case 1: // tty
948 return Key::Home;
949 case 11:
950 return Key::F1;
951 case 12:
952 return Key::F2;
953 case 13:
954 return Key::F3;
955 case 14:
956 return Key::F4;
957 case 15:
958 return Key::F5;
959 case 17: // not a typo
960 return Key::F6;
961 case 18:
962 return Key::F7;
963 case 19:
964 return Key::F8;
965 case 20:
966 return Key::F9;
967 case 21:
968 return Key::F10;
969 case 23: // not a typo
970 return Key::F11;
971 case 24:
972 return Key::F12;
973 default:
974 return Key::None;
977 case ';':
978 switch (key)
980 case 1: // xterm
982 key = wgetch(m_window);
983 Key::Type modifier = get_xterm_modifier_key(key);
984 if (modifier == Key::None)
985 return Key::None;
986 key = wgetch(m_window);
987 switch (key)
989 case 'A':
990 return modifier | Key::Up;
991 case 'B':
992 return modifier | Key::Down;
993 case 'C':
994 return modifier | Key::Right;
995 case 'D':
996 return modifier | Key::Left;
997 case 'F':
998 return modifier | Key::End;
999 case 'H':
1000 return modifier | Key::Home;
1001 default:
1002 return Key::None;
1005 default: // urxvt mouse
1006 m_mouse_event.x = 0;
1007 delim = parse_number(m_mouse_event.x);
1008 if (delim != ';')
1009 return Key::None;
1010 m_mouse_event.y = 0;
1011 delim = parse_number(m_mouse_event.y);
1012 if (delim != 'M')
1013 return Key::None;
1014 --m_mouse_event.x;
1015 --m_mouse_event.y;
1016 return define_mouse_event(key);
1018 default:
1019 return Key::None;
1022 default:
1023 return Key::None;
1025 case ERR:
1026 return Key::Escape;
1027 default: // alt + something
1029 auto key_prim = getInputChar(key);
1030 if (key_prim != Key::None)
1031 return Key::Alt | key_prim;
1032 return Key::None;
1037 Key::Type Window::readKey()
1039 Key::Type result;
1040 // if there are characters in input queue,
1041 // get them and return immediately.
1042 if (!m_input_queue.empty())
1044 result = m_input_queue.front();
1045 m_input_queue.pop();
1046 return result;
1049 fd_set fdset;
1050 FD_ZERO(&fdset);
1051 FD_SET(STDIN_FILENO, &fdset);
1052 timeval timeout = { m_window_timeout/1000, (m_window_timeout%1000)*1000 };
1054 int fd_max = STDIN_FILENO;
1055 for (FDCallbacks::const_iterator it = m_fds.begin(); it != m_fds.end(); ++it)
1057 if (it->first > fd_max)
1058 fd_max = it->first;
1059 FD_SET(it->first, &fdset);
1062 if (select(fd_max+1, &fdset, 0, 0, m_window_timeout < 0 ? 0 : &timeout) > 0)
1064 if (FD_ISSET(STDIN_FILENO, &fdset))
1065 result = getInputChar(wgetch(m_window));
1066 else
1067 result = Key::None;
1069 for (FDCallbacks::const_iterator it = m_fds.begin(); it != m_fds.end(); ++it)
1070 if (FD_ISSET(it->first, &fdset))
1071 it->second();
1073 else
1074 result = Key::None;
1075 return result;
1078 void Window::pushChar(const Key::Type ch)
1080 m_input_queue.push(ch);
1083 std::string Window::prompt(const std::string &base, size_t width, bool encrypted)
1085 std::string result;
1087 rl::aborted = false;
1088 rl::w = this;
1089 getyx(m_window, rl::start_y, rl::start_x);
1090 rl::width = std::min(m_width-rl::start_x-1, width-1);
1091 rl::encrypted = encrypted;
1092 rl::base = base.c_str();
1094 curs_set(1);
1095 Mouse::disable();
1096 m_escape_terminal_sequences = false;
1097 char *input = readline(nullptr);
1098 m_escape_terminal_sequences = true;
1099 Mouse::enable();
1100 curs_set(0);
1101 if (input != nullptr)
1103 if (!encrypted && input[0] != 0)
1104 add_history(input);
1105 result = input;
1106 free(input);
1109 if (rl::aborted)
1110 throw PromptAborted(std::move(result));
1112 return result;
1115 void Window::goToXY(int x, int y)
1117 wmove(m_window, y, x);
1120 int Window::getX()
1122 return getcurx(m_window);
1125 int Window::getY()
1127 return getcury(m_window);
1130 bool Window::hasCoords(int &x, int &y)
1132 return wmouse_trafo(m_window, &y, &x, 0);
1135 bool Window::runPromptHook(const char *arg, bool *done) const
1137 if (m_prompt_hook)
1139 bool continue_ = m_prompt_hook(arg);
1140 if (done != nullptr)
1141 *done = !continue_;
1142 return true;
1144 else
1145 return false;
1148 size_t Window::getWidth() const
1150 if (m_border)
1151 return m_width+2;
1152 else
1153 return m_width;
1156 size_t Window::getHeight() const
1158 size_t height = m_height;
1159 if (m_border)
1160 height += 2;
1161 if (!m_title.empty())
1162 height += 2;
1163 return height;
1166 size_t Window::getStartX() const
1168 if (m_border)
1169 return m_start_x-1;
1170 else
1171 return m_start_x;
1174 size_t Window::getStarty() const
1176 size_t starty = m_start_y;
1177 if (m_border)
1178 --starty;
1179 if (!m_title.empty())
1180 starty -= 2;
1181 return starty;
1184 const std::string &Window::getTitle() const
1186 return m_title;
1189 const Color &Window::getColor() const
1191 return m_color;
1194 const Border &Window::getBorder() const
1196 return m_border;
1199 int Window::getTimeout() const
1201 return m_window_timeout;
1204 const MEVENT &Window::getMouseEvent()
1206 return m_mouse_event;
1209 void Window::scroll(Scroll where)
1211 idlok(m_window, 1);
1212 scrollok(m_window, 1);
1213 switch (where)
1215 case Scroll::Up:
1216 wscrl(m_window, 1);
1217 break;
1218 case Scroll::Down:
1219 wscrl(m_window, -1);
1220 break;
1221 case Scroll::PageUp:
1222 wscrl(m_window, m_width);
1223 break;
1224 case Scroll::PageDown:
1225 wscrl(m_window, -m_width);
1226 break;
1227 default:
1228 break;
1230 idlok(m_window, 0);
1231 scrollok(m_window, 0);
1235 Window &Window::operator<<(const Color &c)
1237 if (c.isDefault())
1239 while (!m_color_stack.empty())
1240 m_color_stack.pop();
1241 setColor(m_base_color);
1243 else if (c.isEnd())
1245 if (!m_color_stack.empty())
1246 m_color_stack.pop();
1247 if (!m_color_stack.empty())
1248 setColor(m_color_stack.top());
1249 else
1250 setColor(m_base_color);
1252 else
1254 setColor(c);
1255 m_color_stack.push(c);
1257 return *this;
1260 Window &Window::operator<<(Format format)
1262 switch (format)
1264 case Format::None:
1265 bold((m_bold_counter = 0));
1266 reverse((m_reverse_counter = 0));
1267 altCharset((m_alt_charset_counter = 0));
1268 break;
1269 case Format::Bold:
1270 bold(++m_bold_counter);
1271 break;
1272 case Format::NoBold:
1273 if (--m_bold_counter <= 0)
1274 bold((m_bold_counter = 0));
1275 break;
1276 case Format::Underline:
1277 underline(++m_underline_counter);
1278 break;
1279 case Format::NoUnderline:
1280 if (--m_underline_counter <= 0)
1281 underline((m_underline_counter = 0));
1282 break;
1283 case Format::Reverse:
1284 reverse(++m_reverse_counter);
1285 break;
1286 case Format::NoReverse:
1287 if (--m_reverse_counter <= 0)
1288 reverse((m_reverse_counter = 0));
1289 break;
1290 case Format::AltCharset:
1291 altCharset(++m_alt_charset_counter);
1292 break;
1293 case Format::NoAltCharset:
1294 if (--m_alt_charset_counter <= 0)
1295 altCharset((m_alt_charset_counter = 0));
1296 break;
1298 return *this;
1301 Window &Window::operator<<(TermManip tm)
1303 switch (tm)
1305 case TermManip::ClearToEOL:
1307 auto x = getX(), y = getY();
1308 mvwhline(m_window, y, x, ' ', m_width-x);
1309 goToXY(x, y);
1311 break;
1313 return *this;
1316 Window &Window::operator<<(const XY &coords)
1318 goToXY(coords.x, coords.y);
1319 return *this;
1322 Window &Window::operator<<(const char *s)
1324 waddstr(m_window, s);
1325 return *this;
1328 Window &Window::operator<<(char c)
1330 // waddchr doesn't display non-ascii multibyte characters properly
1331 waddnstr(m_window, &c, 1);
1332 return *this;
1335 Window &Window::operator<<(const wchar_t *ws)
1337 # ifdef NCMPCPP_UNICODE
1338 waddwstr(m_window, ws);
1339 # else
1340 wprintw(m_window, "%ls", ws);
1341 # endif // NCMPCPP_UNICODE
1342 return *this;
1345 Window &Window::operator<<(wchar_t wc)
1347 # ifdef NCMPCPP_UNICODE
1348 waddnwstr(m_window, &wc, 1);
1349 # else
1350 wprintw(m_window, "%lc", wc);
1351 # endif // NCMPCPP_UNICODE
1352 return *this;
1355 Window &Window::operator<<(int i)
1357 wprintw(m_window, "%d", i);
1358 return *this;
1361 Window &Window::operator<<(double d)
1363 wprintw(m_window, "%f", d);
1364 return *this;
1367 Window &Window::operator<<(const std::string &s)
1369 waddnstr(m_window, s.c_str(), s.length());
1370 return *this;
1373 Window &Window::operator<<(const std::wstring &ws)
1375 # ifdef NCMPCPP_UNICODE
1376 waddnwstr(m_window, ws.c_str(), ws.length());
1377 # else
1378 wprintw(m_window, "%lc", ws.c_str());
1379 # endif // NCMPCPP_UNICODE
1380 return *this;
1383 Window &Window::operator<<(size_t s)
1385 wprintw(m_window, "%zu", s);
1386 return *this;