SourceXtractorPlusPlus  0.19
SourceXtractor++, the next generation SExtractor
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
ProgressNCurses.cpp
Go to the documentation of this file.
1 
18 #include "SEMain/ProgressNCurses.h"
19 
20 #include <poll.h>
21 #include <semaphore.h>
22 #include <ncurses.h>
23 #include <fcntl.h>
24 #include <readline/readline.h>
25 #include <csignal>
26 #include <chrono>
27 #include <iostream>
28 #include <iomanip>
29 #include <mutex>
30 #include <boost/algorithm/string/trim.hpp>
31 #include <boost/thread.hpp>
32 
33 
35 
36 
37 namespace SourceXtractor {
38 
39 // Signal handlers
40 static struct sigaction sigterm_action;
41 static struct sigaction sigstop_action;
42 static struct sigaction sigcont_action;
43 static struct sigaction sigwich_action;
45 
46 // Used for sending signals to the UI thread
47 static int signal_fds[2];
48 
49 // Used by the UI thread to notify that is is done
51  sem_t m_semaphore;
53  sem_init(&m_semaphore, 0, 1);
54  }
55 };
57 
58 // Forward declaration of signal handlers
59 static void handleTerminatingSignal(int s);
60 static void handleStopSignal(int s);
61 static void handleContinuationSignal(int s);
62 static void handleResizeSignal(int);
63 
64 
74 static int interceptFileDescriptor(int old_fd, int *backup_fd) {
75  int pipe_fds[2];
76 
77  *backup_fd = dup(old_fd);
78  if (*backup_fd < 0) {
80  }
81 
82  if (pipe(pipe_fds) < 0) {
84  }
85 
86  int flags = fcntl(pipe_fds[0], F_GETFL, 0);
87  if (fcntl(pipe_fds[0], F_SETFL, flags | O_NONBLOCK) < 0) {
89  }
90 
91  if (dup2(pipe_fds[1], old_fd) < 0) {
93  }
94  close(pipe_fds[1]);
95 
96  return pipe_fds[0];
97 }
98 
107 static void override_rl_display(void) {
108 }
109 
113 class Screen : public boost::noncopyable {
114 public:
115 
123  Screen(FILE *outfd, FILE *infd) {
124  if (pipe(signal_fds) < 0) {
126  }
127 
128  m_old_redisplay = rl_redisplay_function;
129  rl_redisplay_function = override_rl_display;
130 
131  // Tell readline to leave the terminal be
132  rl_catch_signals = 0;
133  rl_deprep_term_function = nullptr;
134  rl_prep_term_function = nullptr;
135 
136  // It seems like the readline in MacOSX is not the "real" readline, but a compatibility
137  // layer which misses some things, like the following:
138 #ifndef __APPLE__
139  rl_catch_sigwinch = 0;
140 #endif
141 
142  // Setup signal handlers
143  ::memset(&sigterm_action, 0, sizeof(sigterm_action));
144  ::memset(&sigstop_action, 0, sizeof(sigstop_action));
145  ::memset(&sigcont_action, 0, sizeof(sigcont_action));
146  ::memset(&sigwich_action, 0, sizeof(sigwich_action));
147 
148  // Termination
150  ::sigaction(SIGINT, &sigterm_action, &prev_signal[SIGINT]);
151  ::sigaction(SIGTERM, &sigterm_action, &prev_signal[SIGTERM]);
152  ::sigaction(SIGABRT, &sigterm_action, &prev_signal[SIGABRT]);
153  ::sigaction(SIGSEGV, &sigterm_action, &prev_signal[SIGSEGV]);
154  ::sigaction(SIGHUP, &sigterm_action, &prev_signal[SIGHUP]);
155 
156  // bg
157  sigstop_action.sa_handler = &handleStopSignal;
158  ::sigaction(SIGTSTP, &sigstop_action, &prev_signal[SIGTSTP]);
159 
160  // fg
162  ::sigaction(SIGCONT, &sigcont_action, &prev_signal[SIGCONT]);
163 
164  // Resizing
165  // Some versions of ncurses handle this by themselves, some other do not, so
166  // we do it ourselves in anycase
167  sigwich_action.sa_handler = &handleResizeSignal;
168  ::sigaction(SIGWINCH, &sigwich_action, &prev_signal[SIGWINCH]);
169 
170  // Enter ncurses
171  initscr();
172  m_screen = newterm(nullptr, outfd, infd);
173  set_term(m_screen);
174 
175  // Hide cursor
176  curs_set(0);
177 
178  // Get input without echo, but leave Ctrl+<Key> to the terminal
179  cbreak();
180  keypad(stdscr, TRUE);
181  noecho();
182 
183  // Setup colors
184  use_default_colors();
185  start_color();
186  }
187 
191  virtual ~Screen() {
192  // Exit ncurses
193  endwin();
194  delscreen(m_screen);
195  rl_redisplay_function = m_old_redisplay;
196  // Restore signal handlers
197  ::sigaction(SIGINT, &prev_signal[SIGINT], nullptr);
198  ::sigaction(SIGTERM, &prev_signal[SIGTERM], nullptr);
199  ::sigaction(SIGABRT, &prev_signal[SIGABRT], nullptr);
200  ::sigaction(SIGSEGV, &prev_signal[SIGSEGV], nullptr);
201  ::sigaction(SIGHUP, &prev_signal[SIGHUP], nullptr);
202  ::sigaction(SIGCONT, &prev_signal[SIGCONT], nullptr);
203  ::sigaction(SIGWINCH, &prev_signal[SIGWINCH], nullptr);
204  close(signal_fds[0]);
205  close(signal_fds[1]);
206  }
207 
211  short initColor(short fg, short bg) {
212  init_pair(m_color_idx, fg, bg);
213  return m_color_idx++;
214  }
215 
216 private:
217  short m_color_idx = 1;
218  SCREEN *m_screen;
219  rl_voidfunc_t* m_old_redisplay;
220 };
221 
238 static void handleTerminatingSignal(int s) {
239  // Restore handler (so if we get stuck somewhere, and second
240  // signal occurs, like a SIGTERM, the process is killed for good)
241  sigaction(s, &prev_signal[s], nullptr);
242 
243  // Notify
244  if (write(signal_fds[1], &s, sizeof(s)) == sizeof(s)) {
245  close(signal_fds[1]);
246  // Wait for UI thread to be done
247 #if _POSIX_C_SOURCE >= 200112L
248  timespec timeout;
249  clock_gettime(CLOCK_REALTIME, &timeout);
250  timeout.tv_sec += 5;
251  sem_timedwait(&ncurses_done.m_semaphore, &timeout);
252 #else
253  // MacOSX does not have timedwait
254  int timeout = 5;
255  while(timeout > 0 && sem_trywait(&ncurses_done.m_semaphore) < 0) {
256  sleep(1);
257  --timeout;
258  }
259 #endif
260  }
261 
262  // Call the previous handler
263  raise(s);
264 }
265 
269 static void handleStopSignal(int s) {
270  // Restore handler
271  sigaction(s, &prev_signal[s], nullptr);
272 
273  // Exit ncurses
274  endwin();
275 
276  // Trigger the previous handler
277  raise(s);
278 }
279 
283 static void handleContinuationSignal(int) {
284  // Restore handlers
285  sigaction(SIGCONT, &sigcont_action, nullptr);
286  sigaction(SIGTSTP, &sigstop_action, nullptr);
287 }
288 
292 static void handleResizeSignal(int s) {
293  if (write(signal_fds[1], &s, sizeof(s)) < 0) {
294  // Just ignore
295  }
296 }
297 
301 class LogWidget {
302 private:
303  WINDOW* m_pad;
304  WINDOW* m_scroll;
305  // Screen coordinates!
310  // Number of total lines being written so far
312  // Last line being *displayed*
313  int m_active_line = 0;
314  // Colors
317 
318  static const int BUFFER_INCREASE_STEP_SIZE = 10;
319  static const int BUFFER_MAX_SIZE = 16384;
320 
321 public:
322 
338  LogWidget(int display_height, int display_width, int display_y, int display_x, short bar_color, short ind_color)
339  : m_pad(newpad(BUFFER_INCREASE_STEP_SIZE, display_width)),
340  m_scroll(newpad(display_height, 1)),
341  m_display_height(display_height), m_display_width(display_width), m_display_y(display_y), m_display_x(display_x),
342  m_scroll_bar_color(bar_color), m_scroll_ind_color(ind_color) {
343  scrollok(m_pad, TRUE);
344  }
345 
349  virtual ~LogWidget() {
350  delwin(m_pad);
351  delwin(m_scroll);
352  }
353 
354  LogWidget(const LogWidget&) = delete;
355  const LogWidget& operator=(const LogWidget&) = delete;
356 
360  void write(const char *data, ssize_t nchars) {
361  while (nchars > 0) {
362  if (*data == '\n') {
363  // If the current line is the last one, follow the log
365  ++m_active_line;
366  }
368  // Increase buffer if we ran out of lines on the pad
369  if (getmaxy(m_pad) <= m_written_lines) {
371  }
372  }
373  waddch(m_pad, *data);
374  ++data;
375  --nchars;
376  }
377  drawLog();
378  drawScroll();
379  }
380 
384  void resize(int display_height, int display_width) {
385  m_display_height = display_height;
386  m_display_width = display_width;
387 
388  // Resize to make place for the new width only if it is bigger.
389  // Note that the pad height depends on the number of written lines, not displayed size!
390  if (display_width > getmaxx(m_pad)) {
391  wresize(m_pad, getmaxy(m_pad), display_width);
392  }
393  wresize(m_scroll, display_height, 1);
394  drawLog();
395  drawScroll();
396  }
397 
402  void scrollText(int d) {
403  m_active_line += d;
404  if (m_active_line > getcury(m_pad) + 1) {
405  m_active_line = getcury(m_pad) + 1;
406  }
409  }
412  }
413  drawLog();
414  drawScroll();
415  }
416 
420  void handleKeyPress(int key) {
421  switch (key) {
422  case KEY_DOWN:
423  scrollText(1);
424  break;
425  case KEY_UP:
426  scrollText(-1);
427  break;
428  case KEY_NPAGE:
429  scrollText(LINES);
430  break;
431  case KEY_PPAGE:
432  scrollText(-LINES);
433  break;
434  default:
435  break;
436  }
437  }
438 
443  // Scan line by line
444  std::vector<std::string> term_lines;
445  for (int i = 0; i < m_written_lines; ++i) {
446  // Note: We do not want the '\0' to be part of the final string, so we use the string constructor to prune those
447  std::vector<char> buffer(m_display_width + 1, '\0');
448  mvwinnstr(m_pad, i, 0, buffer.data(), m_display_width - 2);
449  term_lines.emplace_back(buffer.data());
450  boost::algorithm::trim(term_lines.back());
451  }
452  // Prune trailing empty lines
453  while (!term_lines.empty() && term_lines.back().empty()) {
454  term_lines.pop_back();
455  }
456  return term_lines;
457  }
458 
459 private:
463  void drawScroll() const {
464  werase(m_scroll);
465 
466  int max_selectable_line = m_written_lines;
467  int min_selectable_line = std::min(m_written_lines, m_display_height);
468  int displayed_line_offset = m_active_line - min_selectable_line;
469  float p = std::max(0.f, std::min(1.f, static_cast<float>(displayed_line_offset) /
470  static_cast<float>(max_selectable_line - min_selectable_line)));
471 
472  auto scroll_marker_pos = static_cast<int>(p * static_cast<float>(m_display_height - 1));
473  for (int i = 0; i < m_display_height; ++i) {
474  if (i == scroll_marker_pos)
475  waddch(m_scroll, ACS_CKBOARD | COLOR_PAIR(m_scroll_ind_color));
476  else
477  waddch(m_scroll, '|' | COLOR_PAIR(m_scroll_bar_color));
478  }
479  pnoutrefresh(m_scroll,
480  0, 0,
482  m_display_y + m_display_height - 1, m_display_x + m_display_width - 1
483  );
484  }
485 
489  void drawLog() const {
490  int pad_y = std::max(m_active_line - m_display_height, 0);
491  pnoutrefresh(m_pad,
492  pad_y, 0, // Pad coordinates
493  m_display_y, m_display_x, // Start screen coordinates
494  m_display_y + m_display_height - 1, m_display_x + m_display_width - 2 // End screen coordinates
495  );
496  }
497 };
498 
502 class ProgressWidget : public boost::noncopyable {
503 public:
519  ProgressWidget(int height, int width, int y, int x, short done_color, short progress_color)
520  : m_window(newwin(height, width, y, x)), m_done_color(done_color), m_progress_color(progress_color) {}
521 
526  delwin(m_window);
527  }
528 
536  void move(int y, int x) {
537  mvwin(m_window, y, x);
538  wnoutrefresh(m_window);
539  }
540 
548  void resize(int height, int width) {
549  wresize(m_window, height, width);
550  wnoutrefresh(m_window);
551  }
552 
556  unsigned getHeight() const {
557  return getmaxy(m_window);
558  }
559 
563  void update(const std::list<ProgressInfo>& info) {
564  // Precalculate layout, so labels are aligned
565  size_t value_position = sizeof("Elapsed");
566 
567  for (auto& entry: info) {
568  if (entry.m_label.size() > value_position) {
569  value_position = entry.m_label.size();
570  }
571  }
572  value_position++; // Plus space
573 
574  // Width of the bar is the with of the windows - a space - two brackets []
575  int bar_width = getmaxx(m_window) - 2 - static_cast<int>(value_position);
576 
577  // Elapsed
578  auto now = std::chrono::steady_clock::now();
579  auto elapsed = now - m_started;
580 
581  // Restore position to the beginning
582  werase(m_window);
583 
584  // Now, print the actual progress
585  int line = 0;
586  for (auto& entry : info) {
587  drawProgressLine(static_cast<int>(value_position), bar_width, line, entry.m_label, entry.m_total, entry.m_done);
588  ++line;
589  }
590 
591  // Elapsed time
592  drawElapsed(static_cast<int>(value_position), elapsed, line);
593 
594  // Flush
595  wnoutrefresh(m_window);
596  }
597 
598 private:
602  void drawElapsed(size_t value_position, const std::chrono::steady_clock::duration& elapsed, int line) const {
605  auto s = std::chrono::duration_cast<std::chrono::seconds>(elapsed - h - m);
606  std::ostringstream elapsed_str;
607  elapsed_str.fill('0');
608  elapsed_str << std::setw(2) << h.count() << ':' << std::setw(2) << m.count() << ':' << std::setw(2) << s.count();
609 
610  wattron(m_window, A_BOLD);
611  mvwaddstr(m_window, line, 0, "Elapsed");
612  wattroff(m_window, A_BOLD);
613  mvwaddstr(
614  m_window,
615  line, value_position + 1,
616  elapsed_str.str().c_str()
617  );
618  }
619 
623  void drawProgressLine(int value_position, int bar_width, int line, const std::string& label,
624  int total, int done) const {
625  // Label
626  wattron(m_window, A_BOLD);
627  mvwaddstr(m_window, line, 0, label.c_str());
628  wattroff(m_window, A_BOLD);
629 
630  // Total number is unknown
631  if (total <= 0) {
632  mvwprintw(m_window, line, value_position + 1, "%d", done);
633  return;
634  }
635 
636  // Otherwise, report progress as a bar
637  float ratio = static_cast<float>(done) / static_cast<float>(total);
638  // This can happens sometimes, as a measurement could be notified before the deblending, for instance
639  if (ratio > 1)
640  ratio = 1.;
641 
642  // Build the report string
644  bar << done << " / " << total << " (" << std::fixed << std::setprecision(2) << ratio * 100. << "%)";
645 
646  // Attach as many spaces as needed to fill the screen width, minus brackets
647  bar << std::string(bar_width - bar.str().size(), ' ');
648 
649  // Print label
650  wattron(m_window, A_BOLD);
651  mvwaddstr(m_window, line, 0, label.c_str());
652  wattroff(m_window, A_BOLD);
653  mvwaddch(m_window, line, value_position, '[');
654 
655  // Completed
656  auto bar_content = bar.str();
657  auto completed = static_cast<int>(static_cast<float>(bar_content.size()) * ratio);
658 
659  wattron(m_window, COLOR_PAIR(m_done_color));
660  waddstr(m_window, bar_content.substr(0, completed).c_str());
661  wattroff(m_window, COLOR_PAIR(m_done_color));
662 
663  // Rest
664  wattron(m_window, COLOR_PAIR(m_progress_color));
665  waddstr(m_window, bar_content.substr(completed).c_str());
666  wattroff(m_window, COLOR_PAIR(2));
667 
668  // Closing bracket
669  waddch(m_window, ']');
670  }
671 
672  WINDOW *m_window;
673  std::chrono::steady_clock::time_point m_started = std::chrono::steady_clock::now();
676 };
677 
689 private:
693 
694  // stderr intercept
697  FILE* m_stderr;
698  // stdout intercept
701 
702  // Used to recover log into the standard output
704 
705  std::atomic_bool m_trigger_resize{false};
706  std::atomic_bool m_exit_loop{false};
707 
711  void uiThread() {
712  sem_wait(&ncurses_done.m_semaphore);
713  // SIGTERM, SIGINT and SIGHUP should not be handled by this thread, or we will not be able to properly
714  // exit ncurses.
715  // Hopefully there should be no SIGABRT or SIGSEGV here. If there were, we will exit but we will not be able
716  // to restore the terminal state. Having an abort or a segmentation fault is a bug anyway.
717  sigset_t set;
718  sigaddset(&set, SIGTERM);
719  sigaddset(&set, SIGINT);
720  sigaddset(&set, SIGHUP);
721  pthread_sigmask(SIG_BLOCK, &set, nullptr);
722  // Enter ncurses
723  ncursesMode();
724  // Recover file descriptors
725  dup2(m_stderr_original, STDERR_FILENO);
726  dup2(m_stdout_original, STDOUT_FILENO);
727  // Dump recovered text
728  for (const auto& line : m_log_text) {
729  std::cerr << line << std::endl;
730  }
731  sem_post(&ncurses_done.m_semaphore);
732  }
733 
734  void handleSignal(const struct pollfd& poll_fd, LogWidget& logWidget) {
735  if (poll_fd.revents & POLLIN) {
736  int signal_no;
737  if (read(signal_fds[0], &signal_no, sizeof(signal_no)) == sizeof(signal_no) && signal_no == SIGWINCH) {
738  m_trigger_resize = true;
739  endwin();
740  refresh();
741  clear();
742  }
743  else {
744  char buf[64];
745  logWidget.write(buf, snprintf(buf, sizeof(buf), "Caught signal %s\n", strsignal(signal_no)));
746  m_exit_loop = true;
747  }
748  }
749  }
750 
751  void pipeToLog(const struct pollfd& poll_fd, int pipe, LogWidget& out) const {
752  if (poll_fd.revents & POLLIN) {
753  ssize_t nbytes;
754  char buf[64];
755  while ((nbytes = read(pipe, &buf, sizeof(buf))) > 0) {
756  out.write(buf, nbytes);
757  }
758  }
759  }
760 
761  void handleKeyPress(const struct pollfd& poll_fd, LogWidget& logWidget) const {
762  if (poll_fd.revents & POLLIN) {
763  int key = wgetch(stdscr);
764  if (key != KEY_RESIZE) {
765  logWidget.handleKeyPress(key);
766  }
767  }
768  }
769 
773  void ncursesMode() {
774  Screen screen(m_stderr, stdin);
775 
776  // Log area
777  LogWidget logWidget(
778  LINES - 1, COLS, 0, 0,
779  screen.initColor(COLOR_WHITE, COLOR_BLACK), screen.initColor(COLOR_WHITE, COLOR_WHITE)
780  );
781 
782  // Progress widget
783  ProgressWidget progressWidget(
784  1, COLS, LINES - 1, 0,
785  screen.initColor(COLOR_WHITE, COLOR_GREEN), screen.initColor(COLOR_WHITE, COLOR_BLACK)
786  );
787 
788  // File descriptors to watch for
789  struct pollfd poll_fds[] = {
790  {m_stderr_pipe, POLLIN, 0},
791  {m_stdout_pipe, POLLIN, 0},
792  {STDIN_FILENO, POLLIN, 0},
793  {signal_fds[0], POLLIN, 0}
794  };
795 
796  // Event loop
797  m_exit_loop = false;
798 
799  do {
800  // There has been a signal
801  handleSignal(poll_fds[3], logWidget);
802 
803  // Resize widgets if needed
804  if (m_trigger_resize) {
806  progressWidget.move(static_cast<int>(LINES - m_progress_info.size() - 1), 0);
807  progressWidget.resize(static_cast<int>(m_progress_info.size() + 1), COLS);
808  logWidget.resize(LINES - progressWidget.getHeight(), COLS);
809  m_trigger_resize = false;
810  }
811 
812  // There is output/error to redirect
813  pipeToLog(poll_fds[0], m_stderr_pipe, logWidget);
814  pipeToLog(poll_fds[1], m_stdout_pipe, logWidget);
815 
816  // There is a key to read
817  handleKeyPress(poll_fds[2], logWidget);
818 
819  {
821  progressWidget.update(m_progress_info);
822  }
823 
824  // Update screen
825  doupdate();
826 
827  // Wait for events
828  if (poll(poll_fds, 4, 1000) < 0) {
829  // poll may return with EINTR if a signal happened halfway
830  m_exit_loop = (errno != EINTR);
831  }
832  } while (!m_exit_loop && !boost::this_thread::interruption_requested());
833  m_log_text = logWidget.getText();
834  }
835 
836 public:
845  int new_stderr_fd = dup(m_stderr_original);
846  if (new_stderr_fd < 0) {
848  }
849  m_stderr = fdopen(new_stderr_fd, "w");
850  m_ui_thread = Euclid::make_unique<boost::thread>(std::bind(&Dashboard::uiThread, this));
851  }
852 
858  if (m_ui_thread) {
859  try {
860  m_ui_thread->interrupt();
861  if (m_ui_thread->joinable()) {
862  m_ui_thread->join();
863  }
864  }
865  catch (...) {
866  // Ignore
867  }
868  }
869  fclose(m_stderr);
870  // Unneeded duplicates now
871  close(m_stderr_original);
872  close(m_stdout_original);
873  close(m_stderr_pipe);
874  close(m_stdout_pipe);
875  }
876 
880  void update(const std::list<ProgressInfo>& info) {
882  m_trigger_resize = (m_progress_info.size() != info.size()) || m_trigger_resize;
883  m_progress_info = info;
884  }
885 };
886 
888  m_dashboard = make_unique<Dashboard>();
889 }
890 
892 
894  return isatty(STDERR_FILENO);
895 }
896 
898  if (m_dashboard)
899  m_dashboard->update(info);
900 }
901 
902 void ProgressNCurses::handleMessage(const bool& done) {
903  if (done && m_dashboard)
904  m_dashboard.reset(nullptr);
905 }
906 
907 } // end SourceXtractor
static std::map< int, struct sigaction > prev_signal
rl_voidfunc_t * m_old_redisplay
T empty(T...args)
void handleKeyPress(const struct pollfd &poll_fd, LogWidget &logWidget) const
ProgressWidget(int height, int width, int y, int x, short done_color, short progress_color)
void drawProgressLine(int value_position, int bar_width, int line, const std::string &label, int total, int done) const
const LogWidget & operator=(const LogWidget &)=delete
static const int BUFFER_MAX_SIZE
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > x
T generic_category(T...args)
T endl(T...args)
T duration_cast(T...args)
static const int BUFFER_INCREASE_STEP_SIZE
STL class.
static void override_rl_display(void)
std::shared_ptr< DependentParameter< std::shared_ptr< EngineParameter > > > y
static struct sigaction sigcont_action
T setw(T...args)
void drawElapsed(size_t value_position, const std::chrono::steady_clock::duration &elapsed, int line) const
T fclose(T...args)
static void handleTerminatingSignal(int s)
STL class.
T min(T...args)
constexpr double bar
static void handleStopSignal(int s)
T data(T...args)
static struct sigaction sigstop_action
void pipeToLog(const struct pollfd &poll_fd, int pipe, LogWidget &out) const
constexpr double s
void write(const char *data, ssize_t nchars)
std::chrono::steady_clock::time_point m_started
void resize(int display_height, int display_width)
T pop_back(T...args)
T bind(T...args)
static int interceptFileDescriptor(int old_fd, int *backup_fd)
STL class.
T max(T...args)
T fixed(T...args)
void handleMessage(const std::list< ProgressInfo > &info) override
void handleSignal(const struct pollfd &poll_fd, LogWidget &logWidget)
static void handleResizeSignal(int)
Wrap the terminal into a singleton.
T size(T...args)
STL class.
LogWidget(int display_height, int display_width, int display_y, int display_x, short bar_color, short ind_color)
static struct sigaction sigwich_action
static int signal_fds[2]
T c_str(T...args)
static ncurses_done_t ncurses_done
std::unique_ptr< T > make_unique(Args &&...args)
Set of progress bars/information entries.
T back(T...args)
void update(const std::list< ProgressInfo > &info)
short initColor(short fg, short bg)
static void handleContinuationSignal(int s)
constexpr double m
T fill(T...args)
std::unique_ptr< Dashboard > m_dashboard
T setprecision(T...args)
std::vector< std::string > getText()
static struct sigaction sigterm_action
T snprintf(T...args)
void update(const std::list< ProgressInfo > &info)
void resize(int height, int width)
std::unique_ptr< boost::thread > m_ui_thread
Screen(FILE *outfd, FILE *infd)
T emplace_back(T...args)