terminal.cpp
Go to the documentation of this file.
1 // Encapsulates terminal control (colors, cursor, ...)
2 // Author: Max Schwarz <max.schwarz@uni-bonn.de>
3 
4 #include "terminal.h"
5 
6 #include <termios.h>
7 
8 #include <term.h>
9 #include <curses.h>
10 
11 // really?
12 #ifdef columns
13 #undef columns
14 #endif
15 
16 #include <cstdio>
17 
18 
19 #include <sys/ioctl.h>
20 #include <sys/types.h>
21 
22 #include <boost/lexical_cast.hpp>
23 #include <boost/regex.hpp>
24 #include <boost/tokenizer.hpp>
25 
26 #include <fmt/format.h>
27 
28 namespace rosmon
29 {
30 
32  : m_term{terminal}
33 {
34 }
35 
36 void Terminal::Parser::parseSetAttributes(const std::string& str)
37 {
38  using Tokenizer = boost::tokenizer<boost::char_separator<char>>;
39 
40  boost::char_separator<char> sep(";");
41  Tokenizer tok(str, sep);
42 
43  for(Tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
44  {
45  errno = 0;
46  auto endptr = const_cast<char*>(it->c_str());
47  int code = strtoul(it->c_str(), &endptr, 10);
48 
49  if(errno != 0 || *endptr != 0)
50  {
51  // Error in specification, break out of here
52  m_fgColor = {};
53  m_bgColor = {};
54  return;
55  }
56 
57  if(code == 0)
58  {
59  m_fgColor = {};
60  m_bgColor = {};
61  }
62  else if(code >= 30 && code <= 37)
63  m_fgColor = m_term->color(static_cast<SimpleColor>(code - 30));
64  else if(code >= 40 && code <= 47)
65  m_bgColor = m_term->color(static_cast<SimpleColor>(code - 40));
66  else if(code == 1)
67  m_bold = true;
68  }
69 }
70 
72 {
73  if(!m_term)
74  return false;
75 
76  switch(m_state)
77  {
78  case STATE_ESCAPE:
79  if(c == '\033')
81  else
82  return true;
83  break;
84  case STATE_TYPE:
85  if(c == '[')
86  {
88  m_buf.clear();
89  }
90  else
92  break;
93  case STATE_CSI:
94  if(c == 'm')
95  {
98  }
99  else
100  {
101  m_buf.push_back(c);
102  if(m_buf.length() >= 16)
104  }
105  break;
106  }
107 
108  return false;
109 }
110 
111 void Terminal::Parser::parse(const std::string& str)
112 {
113  if(!m_term)
114  return;
115 
116  for(char c : str)
117  parse(c);
118 }
119 
120 std::vector<std::string> Terminal::Parser::wrap(const std::string& str, unsigned int columns)
121 {
122  if(!m_term)
123  return {};
124 
125  unsigned int col = 0;
126  std::vector<std::string> ret;
127  std::string currentLine;
128 
129  auto setupLine = [&](){
130  currentLine = m_term->standardColorCode();
131  currentLine += m_fgColor.foregroundCode();
132  currentLine += m_bgColor.backgroundCode();
133  };
134 
135  setupLine();
136 
137  for(char c : str)
138  {
139  if(c == '\r' || c == '\n')
140  continue;
141 
142  if(parse(c))
143  col++;
144 
145  currentLine.push_back(c);
146 
147  if(col == columns)
148  {
149  ret.push_back(std::move(currentLine));
150  setupLine();
151  col = 0;
152  }
153  }
154 
155  if(col != 0)
156  ret.push_back(std::move(currentLine));
157 
158  return ret;
159 }
160 
162 {
163  if(!m_term)
164  return;
165 
169 }
170 
171 std::string safe_tigetstr(const char* key)
172 {
173  const char* ret = tigetstr(key);
174  if(!ret || ret == reinterpret_cast<const char*>(-1))
175  return {};
176 
177  return ret;
178 }
179 
181  : m_valid(false)
182  , m_256colors(false)
183  , m_truecolor(false)
184 {
185  // Override using environment variable
186  char* overrideMode = getenv("ROSMON_COLOR_MODE");
187  const char* termOverride = nullptr;
188  if(overrideMode)
189  {
190  if(strcasecmp(overrideMode, "truecolor") == 0)
191  {
192  termOverride = "xterm-256color";
193  m_256colors = true;
194  m_truecolor = true;
195  }
196  else if(strcasecmp(overrideMode, "256colors") == 0)
197  {
198  termOverride = "xterm-256color";
199  m_256colors = true;
200  m_truecolor = false;
201  }
202  else if(strcasecmp(overrideMode, "ansi") == 0)
203  {
204  m_256colors = false;
205  m_truecolor = false;
206  }
207  else
208  {
209  fmt::print(stderr, "Warning: Unknown ROSMON_COLOR_MODE value: '{}'\n", overrideMode);
210  }
211  }
212  else
213  {
214  // Detect truecolor-capable terminals
215  if(getenv("KONSOLE_DBUS_SESSION"))
216  {
217  // Sadly, there is no way to determine the Konsole version. Since
218  // any reasonably recent version supports true colors, just assume
219  // true color support
220  termOverride = "xterm-256color";
221  m_truecolor = true;
222  m_256colors = true;
223  }
224 
225  char* vte_version = getenv("VTE_VERSION");
226  if(vte_version && boost::lexical_cast<unsigned int>(vte_version) >= 3600)
227  {
228  termOverride = "xterm-256color";
229  m_256colors = true;
230  m_truecolor = true;
231  }
232  }
233 
234  int ret;
235  if(setupterm(termOverride, STDOUT_FILENO, &ret) != OK)
236  {
237  fmt::print("Could not setup the terminal. Disabling all colors...\n");
238  return;
239  }
240 
241  m_valid = true;
242 
243  if(!m_256colors && !overrideMode)
244  {
245  // Detect 256 color terminals
246  int num_colors = tigetnum("colors");
247  m_256colors = num_colors >= 256;
248  }
249 
250  m_bgColorStr = safe_tigetstr("setab");
251  if(m_bgColorStr.empty())
252  fmt::print("Your terminal does not support ANSI background!\n");
253 
254  m_fgColorStr = safe_tigetstr("setaf");
255  if(m_fgColorStr.empty())
256  fmt::print("Your terminal does not support ANSI foreground!\n");
257 
258  m_opStr = safe_tigetstr("op");
259  m_sgr0Str = safe_tigetstr("sgr0");
260  m_elStr = safe_tigetstr("el");
261  m_upStr = safe_tigetstr("cuu");
262 
263  m_boldStr = safe_tigetstr("bold");
264 
265  // The terminfo db says screen doesn't support rmam/smam, but both screen
266  // and tmux do. *sigh*
267  const char* TERM = getenv("TERM");
268  const bool isScreen = TERM && strncmp(TERM, "screen", strlen("screen")) == 0;
269 
271  if(m_lineWrapOffStr.empty() && isScreen)
272  m_lineWrapOffStr = "\033[?7l";
273 
274  m_lineWrapOnStr = safe_tigetstr("smam");
275  if(m_lineWrapOnStr.empty() && isScreen)
276  m_lineWrapOnStr = "\033[?7h";
277 
278  auto registerKey = [&](const char* name, SpecialKey key, const std::string& fallback = ""){
279  char* code = tigetstr(name);
280 
281  // Who comes up with these return codes?
282  if(code && code != reinterpret_cast<char*>(-1))
283  m_specialKeys[code] = key;
284  else if(!fallback.empty())
285  m_specialKeys[fallback] = key;
286  };
287 
288  // Map function keys
289  for(int i = 0; i < 12; ++i)
290  {
291  registerKey(
292  fmt::format("kf{}", i+1).c_str(),
293  static_cast<SpecialKey>(SK_F1 + i)
294  );
295  }
296 
297  // Backspace
298  registerKey(key_backspace, SK_Backspace);
299 
300  // Arrow keys
301  registerKey(key_up, SK_Up, "\033[A");
302  registerKey(key_down, SK_Down, "\033[B");
303  registerKey(key_right, SK_Right, "\033[C");
304  registerKey(key_left, SK_Left, "\033[D");
305 }
306 
308 {
309  return m_256colors;
310 }
311 
313 {
314  if(!m_valid)
315  return;
316 
317  putp(tigetstr("civis"));
318 }
319 
321 {
322  if(!m_valid)
323  return;
324 
325  putp(tigetstr("cnorm"));
326 }
327 
328 static int ansiColor(uint32_t rgb)
329 {
330  int r = (rgb & 0xFF);
331  int g = (rgb >> 8) & 0xFF;
332  int b = (rgb >> 16) & 0xFF;
333 
334  r = r * 6 / 256;
335  g = g * 6 / 256;
336  b = b * 6 / 256;
337 
338  return 16 + 36 * r + 6 * g + b;
339 }
340 
342 {
343  if(!m_valid)
344  return;
345 
346  if(m_truecolor)
347  {
348  char buf[256];
349  snprintf(buf, sizeof(buf), "\033[48;2;%d;%d;%dm", color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
350  fputs(buf, stdout);
351  }
352  else
353  {
354  char* out = tiparm(m_bgColorStr.c_str(), ansiColor(color));
355  putp(out);
356  }
357 }
358 
360 {
361  if(!m_valid)
362  return;
363 
364  if(m_truecolor)
365  {
366  char buf[256];
367  snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
368  fputs(buf, stdout);
369  }
370  else
371  {
372  char* out = tiparm(m_fgColorStr.c_str(), ansiColor(color));
373  putp(out);
374  }
375 }
376 
377 void Terminal::setEcho(bool on)
378 {
379  // Switch character echo on
380  termios ios;
381  if(tcgetattr(STDIN_FILENO, &ios) == 0)
382  {
383  if(on)
384  {
385  ios.c_lflag |= ECHO;
386  ios.c_lflag |= ICANON;
387  }
388  else
389  {
390  ios.c_lflag &= ~ECHO;
391  ios.c_lflag &= ~ICANON;
392  }
393 
394  tcsetattr(STDIN_FILENO, TCSANOW, &ios);
395  }
396 }
397 
398 void Terminal::setBold(bool on)
399 {
400  if(!m_valid)
401  return;
402 
403  if(on)
404  putp(m_boldStr.c_str());
405 }
406 
408 {
409  if(!m_valid)
410  return;
411 
412  char* out = tiparm(m_fgColorStr.c_str(), color);
413  putp(out);
414 }
415 
417 {
418  if(!m_valid)
419  return;
420 
421  char* out = tiparm(m_bgColorStr.c_str(), color);
422  putp(out);
423 }
424 
426 {
427  if(!m_valid)
428  return;
429 
432 }
433 
435 {
436  if(!m_valid)
437  return;
438 
439  putp(m_opStr.c_str());
440  putp(m_sgr0Str.c_str());
441 }
442 
444 {
445  if(!m_valid)
446  return {};
447 
448  return m_opStr + m_sgr0Str;
449 }
450 
452 {
453  if(!m_valid)
454  return;
455 
456  putp(m_elStr.c_str());
457 }
458 
459 void Terminal::moveCursorUp(int numLines)
460 {
461  if(!m_valid)
462  return;
463 
464  putp(tparm(m_upStr.c_str(), numLines));
465 }
466 
468 {
469  putchar('\r');
470 }
471 
473 {
474  if(on)
475  putp(m_lineWrapOnStr.c_str());
476  else
477  putp(m_lineWrapOffStr.c_str());
478 }
479 
480 bool Terminal::getSize(int* outColumns, int* outRows)
481 {
482  struct winsize w;
483  if(ioctl(STDIN_FILENO, TIOCGWINSZ, &w) == 0)
484  {
485  *outColumns = w.ws_col;
486  *outRows = w.ws_row;
487  return true;
488  }
489  else
490  return false;
491 }
492 
493 void Terminal::setWindowTitle(const std::string& title)
494 {
495  char buf[256];
496 
497  // Konsole style
498  snprintf(buf, sizeof(buf), "\033]30;%s\007", title.c_str());
499  fputs(buf, stdout);
500 
501  // screen/tmux style
502  snprintf(buf, sizeof(buf), "\033k%s\033\\", title.c_str());
503  fputs(buf, stdout);
504 }
505 
506 void Terminal::clearWindowTitle(const std::string& backup)
507 {
508  fputs("\033]30;%d : %n\007", stdout);
509 
510  // screen/tmux style
511  fmt::print("\033k{}\033\\", backup);
512 }
513 
515 {
516  // Are we currently aborting an escape string that we did not recognize?
518  {
521  {
522  m_currentEscapeAborted = false;
523  m_currentEscapeStr.clear();
524  }
525 
526  return c;
527  }
528 
529  // Should we abort the current escape since it is taking too long?
530  if(!m_currentEscapeStr.empty())
531  {
532  auto now = std::chrono::steady_clock::now();
533  if(now - m_escapeStartTime > std::chrono::milliseconds(100))
534  {
535  m_currentEscapeAborted = true;
537  return readLeftover(); // immediately return first character
538  }
539  }
540 
541  return -1;
542 }
543 
545 {
546  // Are we currently aborting an escape string that we did not recognize?
548  {
551  {
552  m_currentEscapeAborted = false;
553  m_currentEscapeStr.clear();
554  }
555 
556  return c;
557  }
558 
559  char c;
560  if(read(STDIN_FILENO, &c, 1) != 1)
561  return -1;
562 
563  if(m_currentEscapeStr.empty() && c == '\E')
564  {
565  m_currentEscapeStr.push_back(c);
566  m_escapeStartTime = std::chrono::steady_clock::now();
567  return -1;
568  }
569  else if(!m_currentEscapeStr.empty())
570  {
571  m_currentEscapeStr.push_back(c);
572  m_escapeStartTime = std::chrono::steady_clock::now();
573 
574  std::size_t matches = 0;
575  int lastMatch = -1;
576  bool completeMatch = false;
577 
578  for(auto& pair : m_specialKeys)
579  {
580  if(m_currentEscapeStr.length() > pair.first.length())
581  continue;
582 
583  if(pair.first.substr(0, m_currentEscapeStr.length()) == m_currentEscapeStr)
584  {
585  matches++;
586  lastMatch = pair.second;
587 
588  if(m_currentEscapeStr.length() == pair.first.length())
589  {
590  completeMatch = true;
591  break;
592  }
593  }
594  }
595 
596  if(matches == 0)
597  {
598  // We don't understand this code, just switch back to normal mode
599  m_currentEscapeStr.clear();
600  }
601  else if(completeMatch)
602  {
603  m_currentEscapeStr.clear();
604  return lastMatch;
605  }
606  else
607  return -1;
608  }
609 
610  if(c == 0x7f) // ASCII delete
611  return SK_Backspace;
612 
613  return c;
614 }
615 
617 {
618  return Color{
619  std::string{tiparm(m_fgColorStr.c_str(), code)},
620  std::string{tiparm(m_bgColorStr.c_str(), code)}
621  };
622 }
623 
625 {
626  if(!has256Colors())
627  return color(fallback);
628 
629  if(!m_truecolor)
630  return color(static_cast<SimpleColor>(ansiColor(rgb)));
631 
632  return Color{
633  fmt::format("\033[38;2;{};{};{}m", rgb & 0xFF, (rgb >> 8) & 0xFF, (rgb >> 16) & 0xFF),
634  fmt::format("\033[48;2;{};{};{}m", rgb & 0xFF, (rgb >> 8) & 0xFF, (rgb >> 16) & 0xFF),
635  };
636 }
637 
638 }
std::string m_lineWrapOnStr
Definition: terminal.h:256
static int ansiColor(uint32_t rgb)
Definition: terminal.cpp:328
std::string safe_tigetstr(const char *key)
Definition: terminal.cpp:171
std::string standardColorCode()
Escape codes for standard colors.
Definition: terminal.cpp:443
SimpleColor
Simple colors.
Definition: terminal.h:31
bool getSize(int *columns, int *rows)
Get current window size.
Definition: terminal.cpp:480
void setForegroundColor(uint32_t color)
Set 24-bit foreground color.
Definition: terminal.cpp:359
void setSimpleBackground(SimpleColor color)
Definition: terminal.cpp:416
std::map< std::string, SpecialKey > m_specialKeys
Definition: terminal.h:258
void moveCursorToStartOfLine()
Move cursor to start of the line.
Definition: terminal.cpp:467
Color color(SimpleColor code)
Definition: terminal.cpp:616
void setBackgroundColor(uint32_t color)
Set 24-bit background color.
Definition: terminal.cpp:341
std::chrono::steady_clock::time_point m_escapeStartTime
Definition: terminal.h:261
void parseSetAttributes(const std::string &str)
Definition: terminal.cpp:36
std::string m_lineWrapOffStr
Definition: terminal.h:255
std::string m_fgColorStr
Definition: terminal.h:249
void apply()
Apply the current internal state (colors) on the terminal.
Definition: terminal.cpp:161
void setEcho(bool on)
Definition: terminal.cpp:377
void setSimplePair(SimpleColor fg, SimpleColor bg)
Definition: terminal.cpp:425
std::string m_currentEscapeStr
Definition: terminal.h:260
std::vector< std::string > wrap(const std::string &str, unsigned int columns)
Apply line wrapping.
Definition: terminal.cpp:120
const std::string & backgroundCode() const
Definition: terminal.h:64
std::string m_opStr
Definition: terminal.h:250
void setLineWrap(bool on)
Definition: terminal.cpp:472
std::string m_boldStr
Definition: terminal.h:254
void clearToEndOfLine()
Clear characters from cursor to end of line.
Definition: terminal.cpp:451
string b
Definition: busy_node.py:4
void setStandardColors()
Reset fg + bg to standard terminal colors.
Definition: terminal.cpp:434
std::string m_bgColorStr
Definition: terminal.h:248
std::string m_elStr
Definition: terminal.h:252
std::string m_upStr
Definition: terminal.h:253
void setCursorVisible()
restore cursor
Definition: terminal.cpp:320
void setSimpleForeground(SimpleColor color)
Definition: terminal.cpp:407
Encapsulates terminal control.
Definition: terminal.h:22
void moveCursorUp(int numLines)
Move cursor up by numLines.
Definition: terminal.cpp:459
bool has256Colors() const
Definition: terminal.cpp:307
void setWindowTitle(const std::string &title)
Definition: terminal.cpp:493
bool parse(char c)
parse single character c
Definition: terminal.cpp:71
std::string m_sgr0Str
Definition: terminal.h:251
void setCursorInvisible()
hide cursor
Definition: terminal.cpp:312
const std::string & foregroundCode() const
Definition: terminal.h:63
unsigned int m_currentEscapeAbortIdx
Definition: terminal.h:263
void clearWindowTitle(const std::string &backup)
Definition: terminal.cpp:506
void setBold(bool on)
Definition: terminal.cpp:398
bool m_currentEscapeAborted
Definition: terminal.h:262


rosmon_core
Author(s): Max Schwarz
autogenerated on Sat Jan 9 2021 03:35:43