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 #include <cstdio>
12 
13 
14 #include <sys/ioctl.h>
15 #include <sys/types.h>
16 
17 #include <boost/lexical_cast.hpp>
18 #include <boost/regex.hpp>
19 #include <boost/tokenizer.hpp>
20 
21 #include <fmt/format.h>
22 
23 namespace rosbag_fancy
24 {
25 
27  : m_state(STATE_ESCAPE)
28  , m_fgColor(-1)
29  , m_bgColor(-1)
30  , m_bold(false)
31 {
32 }
33 
34 void Terminal::Parser::parseSetAttributes(const std::string& str)
35 {
36  using Tokenizer = boost::tokenizer<boost::char_separator<char>>;
37 
38  boost::char_separator<char> sep(";");
39  Tokenizer tok(str, sep);
40 
41  for(Tokenizer::iterator it = tok.begin(); it != tok.end(); ++it)
42  {
43  errno = 0;
44  auto endptr = const_cast<char*>(it->c_str());
45  int code = strtoul(it->c_str(), &endptr, 10);
46 
47  if(errno != 0 || *endptr != 0)
48  {
49  // Error in specification, break out of here
50  m_fgColor = -1;
51  m_bgColor = -1;
52  return;
53  }
54 
55  if(code == 0)
56  {
57  m_fgColor = -1;
58  m_bgColor = -1;
59  }
60  else if(code >= 30 && code <= 37)
61  m_fgColor = code - 30;
62  else if(code >= 40 && code <= 47)
63  m_bgColor = code - 40;
64  else if(code == 1)
65  m_bold = true;
66  }
67 }
68 
70 {
71  switch(m_state)
72  {
73  case STATE_ESCAPE:
74  if(c == '\033')
75  m_state = STATE_TYPE;
76  break;
77  case STATE_TYPE:
78  if(c == '[')
79  {
80  m_state = STATE_CSI;
81  m_buf.clear();
82  }
83  else
84  m_state = STATE_ESCAPE;
85  break;
86  case STATE_CSI:
87  if(c == 'm')
88  {
89  parseSetAttributes(m_buf);
90  m_state = STATE_ESCAPE;
91  }
92  else
93  {
94  m_buf.push_back(c);
95  if(m_buf.length() >= 16)
96  m_state = STATE_ESCAPE;
97  }
98  break;
99  }
100 }
101 
102 void Terminal::Parser::parse(const std::string& str)
103 {
104  for(char c : str)
105  parse(c);
106 }
107 
109 {
110  if(m_fgColor >= 0 && m_bgColor >= 0)
111  term->setSimplePair((SimpleColor)m_fgColor, (SimpleColor)m_bgColor);
112  else
113  {
114  term->setStandardColors();
115  if(m_fgColor >= 0)
116  term->setSimpleForeground((SimpleColor)m_fgColor);
117  else if(m_bgColor >= 0)
118  term->setSimpleBackground((SimpleColor)m_fgColor);
119  }
120 }
121 
123  : m_valid(false)
124  , m_256colors(false)
125  , m_truecolor(false)
126 {
127  // Override using environment variable
128  char* overrideMode = getenv("ROSBAG_FANCY_COLOR_MODE");
129  const char* termOverride = nullptr;
130  if(overrideMode)
131  {
132  if(strcasecmp(overrideMode, "truecolor") == 0)
133  {
134  termOverride = "xterm-256color";
135  m_256colors = true;
136  m_truecolor = true;
137  }
138  else if(strcasecmp(overrideMode, "256colors") == 0)
139  {
140  termOverride = "xterm-256color";
141  m_256colors = true;
142  m_truecolor = false;
143  }
144  else if(strcasecmp(overrideMode, "ansi") == 0)
145  {
146  m_256colors = false;
147  m_truecolor = false;
148  }
149  else
150  {
151  fmt::print(stderr, "Warning: Unknown ROSBAG_FANCY_COLOR_MODE value: '{}'\n", overrideMode);
152  }
153  }
154  else
155  {
156  // Detect truecolor-capable terminals
157  if(getenv("KONSOLE_DBUS_SESSION"))
158  {
159  // Sadly, there is no way to determine the Konsole version. Since
160  // any reasonably recent version supports true colors, just assume
161  // true color support
162  m_truecolor = true;
163  m_256colors = true;
164  }
165 
166  char* vte_version = getenv("VTE_VERSION");
167  if(vte_version && boost::lexical_cast<unsigned int>(vte_version) >= 3600)
168  {
169  m_256colors = true;
170  m_truecolor = true;
171  }
172  }
173 
174  int ret;
175  if(setupterm(termOverride, STDOUT_FILENO, &ret) != OK)
176  {
177  fmt::print("Could not setup the terminal. Disabling all colors...\n");
178  return;
179  }
180 
181  m_valid = true;
182 
183  if(!m_256colors && !overrideMode)
184  {
185  // Detect 256 color terminals
186  int num_colors = tigetnum("colors");
187  m_256colors = num_colors >= 256;
188  }
189 
190  {
191  const char* bgColor = tigetstr("setab");
192  if(bgColor && bgColor != (char*)-1)
193  m_bgColorStr = bgColor;
194  else
195  fmt::print("Your terminal does not support ANSI background!\n");
196  }
197  {
198  const char* fgColor = tigetstr("setaf");
199  if(fgColor && fgColor != (char*)-1)
200  m_fgColorStr = fgColor;
201  else
202  fmt::print("Your terminal does not support ANSI foreground!\n");
203  }
204 
205  m_opStr = tigetstr("op");
206  m_sgr0Str = tigetstr("sgr0");
207  m_elStr = tigetstr("el");
208  m_upStr = tigetstr("cuu");
209 
210  m_boldStr = tigetstr("bold");
211 
212  auto registerKey = [&](const char* name, SpecialKey key, const std::string& fallback = ""){
213  char* code = tigetstr(name);
214 
215  // Who comes up with these return codes?
216  if(code && code != reinterpret_cast<char*>(-1))
217  m_specialKeys[code] = key;
218  else if(!fallback.empty())
219  m_specialKeys[fallback] = key;
220  };
221 
222  // Map function keys
223  for(int i = 0; i < 12; ++i)
224  {
225  registerKey(
226  fmt::format("kf{}", i+1).c_str(),
227  static_cast<SpecialKey>(SK_F1 + i)
228  );
229  }
230 
231  // Backspace
232  registerKey(key_backspace, SK_Backspace);
233 
234  // Arrow keys
235  registerKey(key_up, SK_Up, "\033[A");
236  registerKey(key_down, SK_Down, "\033[B");
237  registerKey(key_right, SK_Right, "\033[C");
238  registerKey(key_left, SK_Left, "\033[D");
239 }
240 
242 {
243  return m_256colors;
244 }
245 
247 {
248  if(!m_valid)
249  return;
250 
251  putp(tigetstr("civis"));
252 }
253 
255 {
256  if(!m_valid)
257  return;
258 
259  putp(tigetstr("cnorm"));
260 }
261 
262 static int ansiColor(uint32_t rgb)
263 {
264  int r = (rgb & 0xFF);
265  int g = (rgb >> 8) & 0xFF;
266  int b = (rgb >> 16) & 0xFF;
267 
268  r = r * 6 / 256;
269  g = g * 6 / 256;
270  b = b * 6 / 256;
271 
272  return 16 + 36 * r + 6 * g + b;
273 }
274 
275 void Terminal::setBackgroundColor(uint32_t color)
276 {
277  if(!m_valid)
278  return;
279 
280  if(m_truecolor)
281  {
282  char buf[256];
283  snprintf(buf, sizeof(buf), "\033[48;2;%d;%d;%dm", color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
284  fputs(buf, stdout);
285  }
286  else
287  {
288  char* out = tiparm(m_bgColorStr.c_str(), ansiColor(color));
289  putp(out);
290  }
291 }
292 
293 void Terminal::setForegroundColor(uint32_t color)
294 {
295  if(!m_valid)
296  return;
297 
298  if(m_truecolor)
299  {
300  char buf[256];
301  snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF);
302  fputs(buf, stdout);
303  }
304  else
305  {
306  char* out = tiparm(m_fgColorStr.c_str(), ansiColor(color));
307  putp(out);
308  }
309 }
310 
311 void Terminal::setEcho(bool on)
312 {
313  // Switch character echo on
314  termios ios;
315  if(tcgetattr(STDIN_FILENO, &ios) == 0)
316  {
317  if(on)
318  {
319  ios.c_lflag |= ECHO;
320  ios.c_lflag |= ICANON;
321  }
322  else
323  {
324  ios.c_lflag &= ~ECHO;
325  ios.c_lflag &= ~ICANON;
326  }
327 
328  tcsetattr(STDIN_FILENO, TCSANOW, &ios);
329  }
330 }
331 
332 void Terminal::setBold(bool on)
333 {
334  if(!m_valid)
335  return;
336 
337  if(on)
338  putp(m_boldStr.c_str());
339 }
340 
342 {
343  if(!m_valid)
344  return;
345 
346  char* out = tiparm(m_fgColorStr.c_str(), color);
347  putp(out);
348 }
349 
351 {
352  if(!m_valid)
353  return;
354 
355  char* out = tiparm(m_bgColorStr.c_str(), color);
356  putp(out);
357 }
358 
360 {
361  if(!m_valid)
362  return;
363 
366 }
367 
369 {
370  if(!m_valid)
371  return;
372 
373  putp(m_opStr.c_str());
374  putp(m_sgr0Str.c_str());
375 }
376 
378 {
379  if(!m_valid)
380  return;
381 
382  putp(m_elStr.c_str());
383 }
384 
385 void Terminal::moveCursorUp(int numLines)
386 {
387  if(!m_valid)
388  return;
389 
390  putp(tparm(m_upStr.c_str(), numLines));
391 }
392 
394 {
395  putchar('\r');
396 }
397 
398 bool Terminal::getSize(int* outColumns, int* outRows)
399 {
400  struct winsize w;
401  if(ioctl(STDIN_FILENO, TIOCGWINSZ, &w) == 0)
402  {
403  *outColumns = w.ws_col;
404  *outRows = w.ws_row;
405  return true;
406  }
407  else
408  return false;
409 }
410 
411 void Terminal::setWindowTitle(const std::string& title)
412 {
413  char buf[256];
414 
415  // Konsole style
416  snprintf(buf, sizeof(buf), "\033]30;%s\007", title.c_str());
417  fputs(buf, stdout);
418 
419  // screen/tmux style
420  snprintf(buf, sizeof(buf), "\033k%s\033\\", title.c_str());
421  fputs(buf, stdout);
422 }
423 
424 void Terminal::clearWindowTitle(const std::string& backup)
425 {
426  fputs("\033]30;%d : %n\007", stdout);
427 
428  // screen/tmux style
429  fmt::print("\033k{}\033\\", backup);
430 }
431 
433 {
434  // Are we currently aborting an escape string that we did not recognize?
436  {
439  {
440  m_currentEscapeAborted = false;
441  m_currentEscapeStr.clear();
442  }
443 
444  return c;
445  }
446 
447  char c;
448  if(read(STDIN_FILENO, &c, 1) != 1)
449  return -1;
450 
451  if(m_currentEscapeStr.empty() && c == '\E')
452  {
453  m_currentEscapeStr.push_back(c);
454  m_escapeStartTime = std::chrono::steady_clock::now();
455  return -1;
456  }
457  else if(!m_currentEscapeStr.empty())
458  {
459  m_currentEscapeStr.push_back(c);
460  m_escapeStartTime = std::chrono::steady_clock::now();
461 
462  std::size_t matches = 0;
463  int lastMatch = -1;
464  bool completeMatch = false;
465 
466  for(auto& pair : m_specialKeys)
467  {
468  if(m_currentEscapeStr.length() > pair.first.length())
469  continue;
470 
471  if(pair.first.substr(0, m_currentEscapeStr.length()) == m_currentEscapeStr)
472  {
473  matches++;
474  lastMatch = pair.second;
475 
476  if(m_currentEscapeStr.length() == pair.first.length())
477  {
478  completeMatch = true;
479  break;
480  }
481  }
482  }
483 
484  if(matches == 0)
485  {
486  // We don't understand this code, just switch back to normal mode
487  m_currentEscapeStr.clear();
488  }
489  else if(completeMatch)
490  {
491  m_currentEscapeStr.clear();
492  return lastMatch;
493  }
494  else
495  return -1;
496  }
497 
498  if(c == 0x7f) // ASCII delete
499  return SK_Backspace;
500 
501  return c;
502 }
503 
504 }
rosbag_fancy::Terminal::m_valid
bool m_valid
Definition: terminal.h:175
rosbag_fancy::Terminal::readKey
int readKey()
Definition: terminal.cpp:432
rosbag_fancy::Terminal::m_truecolor
bool m_truecolor
Definition: terminal.h:177
rosbag_fancy::Terminal::SK_F1
@ SK_F1
Definition: terminal.h:166
rosbag_fancy::Terminal::setForegroundColor
void setForegroundColor(uint32_t color)
Set 24-bit foreground color.
Definition: terminal.cpp:293
rosbag_fancy::Terminal::setCursorVisible
void setCursorVisible()
restore cursor
Definition: terminal.cpp:254
rosbag_fancy::Terminal::m_fgColorStr
std::string m_fgColorStr
Definition: terminal.h:180
rosbag_fancy::Terminal::moveCursorUp
void moveCursorUp(int numLines)
Move cursor up by numLines.
Definition: terminal.cpp:385
rosbag_fancy::Terminal::setCursorInvisible
void setCursorInvisible()
hide cursor
Definition: terminal.cpp:246
rosbag_fancy
Definition: bag_reader.cpp:240
rosbag_fancy::Terminal::SimpleColor
SimpleColor
Simple colors.
Definition: terminal.h:30
rosbag_fancy::Terminal::m_currentEscapeAborted
bool m_currentEscapeAborted
Definition: terminal.h:191
rosbag_fancy::Terminal::has256Colors
bool has256Colors() const
Definition: terminal.cpp:241
rosbag_fancy::Terminal::setBold
void setBold(bool on)
Definition: terminal.cpp:332
std::size_t
decltype(sizeof(void *)) typede size_t)
Definition: doctest.h:501
rosbag_fancy::Terminal::Parser::parseSetAttributes
void parseSetAttributes(const std::string &str)
Definition: terminal.cpp:34
rosbag_fancy::Terminal::Parser::apply
void apply(Terminal *term)
Apply the current internal state (colors) on the terminal.
Definition: terminal.cpp:108
rosbag_fancy::Terminal::SK_Right
@ SK_Right
Definition: terminal.h:169
rosbag_fancy::ansiColor
static int ansiColor(uint32_t rgb)
Definition: terminal.cpp:262
rosbag_fancy::Terminal::m_escapeStartTime
std::chrono::steady_clock::time_point m_escapeStartTime
Definition: terminal.h:190
rosbag_fancy::Terminal::m_boldStr
std::string m_boldStr
Definition: terminal.h:185
rosbag_fancy::Terminal::setSimplePair
void setSimplePair(SimpleColor fg, SimpleColor bg)
Definition: terminal.cpp:359
rosbag_fancy::Terminal::setSimpleForeground
void setSimpleForeground(SimpleColor color)
Definition: terminal.cpp:341
rosbag_fancy::Terminal
Encapsulates terminal control.
Definition: terminal.h:21
rosbag_fancy::Terminal::m_currentEscapeAbortIdx
unsigned int m_currentEscapeAbortIdx
Definition: terminal.h:192
rosbag_fancy::Terminal::setSimpleBackground
void setSimpleBackground(SimpleColor color)
Definition: terminal.cpp:350
rosbag_fancy::Terminal::getSize
bool getSize(int *columns, int *rows)
Get current window size.
Definition: terminal.cpp:398
rosbag_fancy::Terminal::SK_Down
@ SK_Down
Definition: terminal.h:169
rosbag_fancy::Terminal::setWindowTitle
void setWindowTitle(const std::string &title)
Definition: terminal.cpp:411
format
std::string format
Definition: ui.cpp:76
rosbag_fancy::Terminal::m_upStr
std::string m_upStr
Definition: terminal.h:184
rosbag_fancy::Terminal::m_sgr0Str
std::string m_sgr0Str
Definition: terminal.h:182
rosbag_fancy::Terminal::m_256colors
bool m_256colors
Definition: terminal.h:176
rosbag_fancy::Terminal::Parser::Parser
Parser()
Definition: terminal.cpp:26
rosbag_fancy::Terminal::setStandardColors
void setStandardColors()
Reset fg + bg to standard terminal colors.
Definition: terminal.cpp:368
rosbag_fancy::Terminal::m_bgColorStr
std::string m_bgColorStr
Definition: terminal.h:179
rosbag_fancy::Terminal::m_opStr
std::string m_opStr
Definition: terminal.h:181
rosbag_fancy::Terminal::SK_Left
@ SK_Left
Definition: terminal.h:169
rosbag_fancy::Terminal::setEcho
void setEcho(bool on)
Definition: terminal.cpp:311
rosbag_fancy::Terminal::moveCursorToStartOfLine
void moveCursorToStartOfLine()
Move cursor to start of the line.
Definition: terminal.cpp:393
terminal.h
rosbag_fancy::Terminal::SK_Backspace
@ SK_Backspace
Definition: terminal.h:169
rosbag_fancy::Terminal::SK_Up
@ SK_Up
Definition: terminal.h:169
rosbag_fancy::Terminal::Terminal
Terminal()
Definition: terminal.cpp:122
rosbag_fancy::Terminal::Parser::parse
void parse(char c)
parse single character c
Definition: terminal.cpp:69
rosbag_fancy::Terminal::m_specialKeys
std::map< std::string, SpecialKey > m_specialKeys
Definition: terminal.h:187
rosbag_fancy::Terminal::clearWindowTitle
void clearWindowTitle(const std::string &backup)
Definition: terminal.cpp:424
rosbag_fancy::Terminal::m_elStr
std::string m_elStr
Definition: terminal.h:183
rosbag_fancy::Terminal::SpecialKey
SpecialKey
Definition: terminal.h:164
rosbag_fancy::Terminal::setBackgroundColor
void setBackgroundColor(uint32_t color)
Set 24-bit background color.
Definition: terminal.cpp:275
rosbag_fancy::Terminal::clearToEndOfLine
void clearToEndOfLine()
Clear characters from cursor to end of line.
Definition: terminal.cpp:377
rosbag_fancy::Terminal::m_currentEscapeStr
std::string m_currentEscapeStr
Definition: terminal.h:189


rosbag_fancy
Author(s):
autogenerated on Tue Feb 20 2024 03:20:59