1 /** 2 * Copyright (c) 2007-2012, Timothy Stack 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * * Redistributions of source code must retain the above copyright notice, this 10 * list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * * Neither the name of Timothy Stack nor the names of its contributors 15 * may be used to endorse or promote products derived from this software 16 * without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 * 29 * @file view_curses.hh 30 */ 31 32 #ifndef view_curses_hh 33 #define view_curses_hh 34 35 #include "config.h" 36 37 #include <zlib.h> 38 #include <stdint.h> 39 #include <limits.h> 40 #include <signal.h> 41 #include <sys/time.h> 42 43 #if defined HAVE_NCURSESW_CURSES_H 44 # include <ncursesw/curses.h> 45 #elif defined HAVE_NCURSESW_H 46 # include <ncursesw.h> 47 #elif defined HAVE_NCURSES_CURSES_H 48 # include <ncurses/curses.h> 49 #elif defined HAVE_NCURSES_H 50 # include <ncurses.h> 51 #elif defined HAVE_CURSES_H 52 # include <curses.h> 53 #else 54 # error "SysV or X/Open-compatible Curses header file required" 55 #endif 56 57 #include <map> 58 #include <string> 59 #include <vector> 60 #include <functional> 61 62 #include "base/lnav_log.hh" 63 #include "base/lrucache.hpp" 64 #include "attr_line.hh" 65 #include "optional.hpp" 66 #include "styling.hh" 67 #include "log_level.hh" 68 #include "lnav_config_fwd.hh" 69 70 #define KEY_CTRL_G 7 71 #define KEY_CTRL_L 12 72 #define KEY_CTRL_P 16 73 #define KEY_CTRL_R 18 74 #define KEY_CTRL_W 23 75 76 class view_curses; 77 78 /** 79 * An RAII class that initializes and deinitializes curses. 80 */ 81 class screen_curses : public log_crash_recoverer { 82 public: log_crash_recover()83 void log_crash_recover() override { 84 endwin(); 85 }; 86 screen_curses()87 screen_curses() 88 : sc_main_window(initscr()) { 89 }; 90 ~screen_curses()91 virtual ~screen_curses() 92 { 93 endwin(); 94 }; 95 get_window()96 WINDOW *get_window() { return this->sc_main_window; }; 97 98 private: 99 WINDOW *sc_main_window; 100 }; 101 102 template<typename T> 103 class action_broadcaster : public std::vector<std::function<void(T *)>> { 104 public: operator ()(T * t)105 void operator()(T *t) { 106 for (auto& func : *this) { 107 func(t); 108 } 109 } 110 }; 111 112 class ui_periodic_timer { 113 public: 114 static const struct itimerval INTERVAL; 115 116 static ui_periodic_timer &singleton(); 117 time_to_update(sig_atomic_t & counter) const118 bool time_to_update(sig_atomic_t &counter) const { 119 if (this->upt_counter != counter) { 120 counter = this->upt_counter; 121 return true; 122 } 123 return false; 124 }; 125 start_fade(sig_atomic_t & counter,size_t decay) const126 void start_fade(sig_atomic_t &counter, size_t decay) const { 127 counter = this->upt_counter + decay; 128 }; 129 fade_diff(sig_atomic_t & counter) const130 int fade_diff(sig_atomic_t &counter) const { 131 if (this->upt_counter >= counter) { 132 return 0; 133 } 134 return counter - this->upt_counter; 135 }; 136 137 private: 138 ui_periodic_timer(); 139 140 static void sigalrm(int sig); 141 142 volatile sig_atomic_t upt_counter; 143 }; 144 145 class alerter { 146 147 public: 148 static alerter &singleton(); 149 enabled(bool enable)150 void enabled(bool enable) { this->a_enabled = enable; }; 151 chime()152 bool chime() { 153 if (!this->a_enabled) { 154 return true; 155 } 156 157 bool retval = this->a_do_flash; 158 if (this->a_do_flash) { 159 ::flash(); 160 } 161 this->a_do_flash = false; 162 return retval; 163 }; 164 new_input(int ch)165 void new_input(int ch) { 166 if (this->a_last_input != ch) { 167 this->a_do_flash = true; 168 } 169 this->a_last_input = ch; 170 }; 171 172 private: 173 bool a_enabled{true}; 174 bool a_do_flash{true}; 175 int a_last_input{-1}; 176 }; 177 178 /** 179 * Singleton used to manage the colorspace. 180 */ 181 class view_colors { 182 public: 183 static constexpr unsigned long HI_COLOR_COUNT = 6 * 3 * 3; 184 185 /** Roles that can be mapped to curses attributes using attrs_for_role() */ 186 typedef enum { 187 VCR_NONE = -1, 188 189 VCR_TEXT, /*< Raw text. */ 190 VCR_IDENTIFIER, 191 VCR_SEARCH, /*< A search hit. */ 192 VCR_OK, 193 VCR_ERROR, /*< An error message. */ 194 VCR_WARNING, /*< A warning message. */ 195 VCR_ALT_ROW, /*< Highlight for alternating rows in a list */ 196 VCR_HIDDEN, 197 VCR_ADJUSTED_TIME, 198 VCR_SKEWED_TIME, 199 VCR_OFFSET_TIME, 200 VCR_INVALID_MSG, 201 VCR_STATUS, /*< Normal status line text. */ 202 VCR_WARN_STATUS, 203 VCR_ALERT_STATUS, /*< Alert status line text. */ 204 VCR_ACTIVE_STATUS, /*< */ 205 VCR_ACTIVE_STATUS2, /*< */ 206 VCR_STATUS_TITLE, 207 VCR_STATUS_SUBTITLE, 208 VCR_STATUS_STITCH_TITLE_TO_SUB, 209 VCR_STATUS_STITCH_SUB_TO_TITLE, 210 VCR_STATUS_STITCH_SUB_TO_NORMAL, 211 VCR_STATUS_STITCH_NORMAL_TO_SUB, 212 VCR_STATUS_STITCH_TITLE_TO_NORMAL, 213 VCR_STATUS_STITCH_NORMAL_TO_TITLE, 214 VCR_STATUS_TITLE_HOTKEY, 215 VCR_STATUS_DISABLED_TITLE, 216 VCR_STATUS_HOTKEY, 217 VCR_INACTIVE_STATUS, 218 VCR_INACTIVE_ALERT_STATUS, 219 VCR_SCROLLBAR, 220 VCR_SCROLLBAR_ERROR, 221 VCR_SCROLLBAR_WARNING, 222 VCR_FOCUSED, 223 VCR_DISABLED_FOCUSED, 224 VCR_POPUP, 225 VCR_COLOR_HINT, 226 227 VCR_KEYWORD, 228 VCR_STRING, 229 VCR_COMMENT, 230 VCR_DOC_DIRECTIVE, 231 VCR_VARIABLE, 232 VCR_SYMBOL, 233 VCR_NUMBER, 234 VCR_RE_SPECIAL, 235 VCR_RE_REPEAT, 236 VCR_FILE, 237 238 VCR_DIFF_DELETE, /*< Deleted line in a diff. */ 239 VCR_DIFF_ADD, /*< Added line in a diff. */ 240 VCR_DIFF_SECTION, /*< Section marker in a diff. */ 241 242 VCR_LOW_THRESHOLD, 243 VCR_MED_THRESHOLD, 244 VCR_HIGH_THRESHOLD, 245 246 VCR__MAX 247 } role_t; 248 249 /** @return A reference to the singleton. */ 250 static view_colors &singleton(); 251 252 /** 253 * Performs curses-specific initialization. The other methods can be 254 * called before this method, but the returned attributes cannot be used 255 * with curses code until this method is called. 256 */ 257 static void init(); 258 259 void init_roles(const lnav_theme <, lnav_config_listener::error_reporter &reporter); 260 261 /** 262 * @param role The role to retrieve character attributes for. 263 * @return The attributes to use for the given role. 264 */ attrs_for_role(role_t role,bool selected=false) const265 attr_t attrs_for_role(role_t role, bool selected = false) const 266 { 267 if (role == VCR_NONE) { 268 return 0; 269 } 270 271 require(role >= 0); 272 require(role < VCR__MAX); 273 274 return selected ? this->vc_role_colors[role].second : 275 this->vc_role_colors[role].first; 276 }; 277 reverse_attrs_for_role(role_t role) const278 attr_t reverse_attrs_for_role(role_t role) const 279 { 280 require(role >= 0); 281 require(role < VCR__MAX); 282 283 return this->vc_role_reverse_colors[role]; 284 }; 285 286 int color_for_ident(const char *str, size_t len) const; 287 288 attr_t attrs_for_ident(const char *str, size_t len); 289 attrs_for_ident(intern_string_t str)290 attr_t attrs_for_ident(intern_string_t str) { 291 return this->attrs_for_ident(str.get(), str.size()); 292 } 293 attrs_for_ident(const std::string & str)294 attr_t attrs_for_ident(const std::string &str) { 295 return this->attrs_for_ident(str.c_str(), str.length()); 296 }; 297 298 int ensure_color_pair(short fg, short bg); 299 300 int ensure_color_pair(const styling::color_unit &fg, 301 const styling::color_unit &bg); 302 303 static constexpr short MATCH_COLOR_DEFAULT = -1; 304 static constexpr short MATCH_COLOR_SEMANTIC = -10; 305 306 short match_color(const styling::color_unit &color) const; 307 ansi_color_pair_index(int fg,int bg)308 static inline int ansi_color_pair_index(int fg, int bg) 309 { 310 return VC_ANSI_START + ((fg * 8) + bg); 311 }; 312 ansi_color_pair(int fg,int bg)313 static inline attr_t ansi_color_pair(int fg, int bg) 314 { 315 return COLOR_PAIR(ansi_color_pair_index(fg, bg)); 316 }; 317 318 static const int VC_ANSI_START = 0; 319 static const int VC_ANSI_END = VC_ANSI_START + (8 * 8); 320 321 std::pair<attr_t, attr_t> to_attrs( 322 int &pair_base, 323 const lnav_theme <, const style_config &sc, const style_config &fallback_sc, 324 lnav_config_listener::error_reporter &reporter); 325 326 std::pair<attr_t, attr_t> vc_level_attrs[LEVEL__MAX]; 327 ansi_to_theme_color(short ansi_fg) const328 short ansi_to_theme_color(short ansi_fg) const { 329 return this->vc_ansi_to_theme[ansi_fg]; 330 } 331 332 static bool initialized; 333 334 private: 335 static term_color_palette *vc_active_palette; 336 337 /** Private constructor that initializes the member fields. */ 338 view_colors(); 339 340 struct dyn_pair { 341 int dp_color_pair; 342 }; 343 344 /** Map of role IDs to attribute values. */ 345 std::pair<attr_t, attr_t> vc_role_colors[VCR__MAX]; 346 /** Map of role IDs to reverse-video attribute values. */ 347 attr_t vc_role_reverse_colors[VCR__MAX]; 348 short vc_ansi_to_theme[8]; 349 short vc_highlight_colors[HI_COLOR_COUNT]; 350 int vc_color_pair_end{0}; 351 cache::lru_cache<std::pair<short, short>, dyn_pair> vc_dyn_pairs; 352 }; 353 354 enum class mouse_button_t { 355 BUTTON_LEFT, 356 BUTTON_MIDDLE, 357 BUTTON_RIGHT, 358 359 BUTTON_SCROLL_UP, 360 BUTTON_SCROLL_DOWN, 361 }; 362 363 enum class mouse_button_state_t { 364 BUTTON_STATE_PRESSED, 365 BUTTON_STATE_DRAGGED, 366 BUTTON_STATE_RELEASED, 367 }; 368 369 struct mouse_event { mouse_eventmouse_event370 mouse_event(mouse_button_t button = mouse_button_t::BUTTON_LEFT, 371 mouse_button_state_t state = mouse_button_state_t::BUTTON_STATE_PRESSED, 372 int x = -1, 373 int y = -1) 374 : me_button(button), 375 me_state(state), 376 me_x(x), 377 me_y(y) { 378 memset(&this->me_time, 0, sizeof(this->me_time)); 379 }; 380 381 mouse_button_t me_button; 382 mouse_button_state_t me_state; 383 struct timeval me_time; 384 int me_x; 385 int me_y; 386 }; 387 388 /** 389 * Interface for "view" classes that will update a curses(3) display. 390 */ 391 class view_curses { 392 public: 393 virtual ~view_curses() = default; 394 395 /** 396 * Update the curses display. 397 */ do_update()398 virtual void do_update() { 399 if (!this->vc_visible) { 400 return; 401 } 402 403 for (auto child : this->vc_children) { 404 child->do_update(); 405 } 406 }; 407 handle_mouse(mouse_event & me)408 virtual bool handle_mouse(mouse_event &me) { return false; }; 409 set_needs_update()410 void set_needs_update() { 411 this->vc_needs_update = true; 412 for (auto child : this->vc_children) { 413 child->set_needs_update(); 414 } 415 }; 416 add_child_view(view_curses * child)417 view_curses &add_child_view(view_curses *child) { 418 this->vc_children.push_back(child); 419 420 return *this; 421 } 422 set_default_role(view_colors::role_t role)423 void set_default_role(view_colors::role_t role) { 424 this->vc_default_role = role; 425 } 426 set_visible(bool value)427 void set_visible(bool value) { 428 this->vc_visible = value; 429 } 430 is_visible() const431 bool is_visible() const { 432 return this->vc_visible; 433 } 434 set_width(long width)435 void set_width(long width) { 436 this->vc_width = width; 437 } 438 get_width() const439 long get_width() const { 440 return this->vc_width; 441 } 442 443 static string_attr_type VC_ROLE; 444 static string_attr_type VC_ROLE_FG; 445 static string_attr_type VC_STYLE; 446 static string_attr_type VC_GRAPHIC; 447 static string_attr_type VC_SELECTED; 448 static string_attr_type VC_FOREGROUND; 449 static string_attr_type VC_BACKGROUND; 450 451 static void awaiting_user_input(); 452 453 static void mvwattrline(WINDOW *window, 454 int y, 455 int x, 456 attr_line_t &al, 457 const struct line_range &lr, 458 view_colors::role_t base_role = 459 view_colors::VCR_TEXT); 460 461 protected: 462 bool vc_visible{true}; 463 /** Flag to indicate if a display update is needed. */ 464 bool vc_needs_update{true}; 465 long vc_width; 466 std::vector<view_curses *> vc_children; 467 view_colors::role_t vc_default_role{view_colors::VCR_TEXT}; 468 }; 469 470 template<class T> 471 class view_stack : public view_curses { 472 public: 473 using iterator = typename std::vector<T *>::iterator; 474 top()475 nonstd::optional<T *> top() { 476 if (this->vs_views.empty()) { 477 return nonstd::nullopt; 478 } else { 479 return this->vs_views.back(); 480 } 481 } 482 do_update()483 void do_update() override 484 { 485 if (!this->vc_visible) { 486 return; 487 } 488 489 this->top() | [this] (T *vc) { 490 if (this->vc_needs_update) { 491 vc->set_needs_update(); 492 } 493 vc->do_update(); 494 }; 495 496 view_curses::do_update(); 497 498 this->vc_needs_update = false; 499 } 500 push_back(T * view)501 void push_back(T *view) { 502 this->vs_views.push_back(view); 503 if (this->vs_change_handler) { 504 this->vs_change_handler(view); 505 } 506 this->set_needs_update(); 507 } 508 pop_back()509 void pop_back() { 510 this->vs_views.pop_back(); 511 if (!this->vs_views.empty() && this->vs_change_handler) { 512 this->vs_change_handler(this->vs_views.back()); 513 } 514 this->set_needs_update(); 515 } 516 begin()517 iterator begin() { 518 return this->vs_views.begin(); 519 } 520 end()521 iterator end() { 522 return this->vs_views.end(); 523 } 524 size()525 size_t size() { 526 return this->vs_views.size(); 527 } 528 empty()529 bool empty() { 530 return this->vs_views.empty(); 531 } 532 533 std::function<void(T *)> vs_change_handler; 534 535 private: 536 std::vector<T *> vs_views; 537 }; 538 539 #endif 540