/*********************************************************************** * ftermdetection.cpp - Detection of the terminal type * * * * This file is part of the Final Cut widget toolkit * * * * Copyright 2018 Markus Gans * * * * The Final Cut is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public License * * as published by the Free Software Foundation; either version 3 of * * the License, or (at your option) any later version. * * * * The Final Cut is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this program. If not, see * * . * ***********************************************************************/ #include "final/fterm.h" #include "final/ftermdetection.h" // static class attributes FTermDetection::terminalType FTermDetection::terminal_type = \ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; FTermDetection::colorEnv FTermDetection::color_env; FTermDetection::secondaryDA FTermDetection::secondary_da; char FTermDetection::termtype[256] = { }; char FTermDetection::termfilename[256] = { }; bool FTermDetection::decscusr_support; bool FTermDetection::terminal_detection; bool FTermDetection::color256; const FString* FTermDetection::answer_back = 0; const FString* FTermDetection::sec_da = 0; int FTermDetection::gnome_terminal_id; #if DEBUG char FTermDetection::termtype_256color[256] = { }; char FTermDetection::termtype_Answerback[256] = { }; char FTermDetection::termtype_SecDA[256] = { }; #endif //---------------------------------------------------------------------- // class FTermDetection //---------------------------------------------------------------------- // constructors and destructor //---------------------------------------------------------------------- FTermDetection::FTermDetection() { // Preset to true terminal_detection = true; // Preset to false decscusr_support = false; // Gnome terminal id from SecDA // Example: vte version 0.40.0 = 0 * 100 + 40 * 100 + 0 = 4000 // a.b.c = a * 100 + b * 100 + c gnome_terminal_id = 0; // Initialize the structs color_env.setDefault(); secondary_da.setDefault(); } //---------------------------------------------------------------------- FTermDetection::~FTermDetection() // destructor { if ( sec_da ) delete sec_da; if ( answer_back ) delete answer_back; } // public methods of FTermDetection //---------------------------------------------------------------------- void FTermDetection::setTermFileName (char term_filename[]) { if ( ! term_filename ) return; std::strncpy ( termfilename , term_filename , std::strlen(term_filename) + 1 ); } //---------------------------------------------------------------------- void FTermDetection::detect() { // Set the variable 'termtype' to the predefined type of the terminal getSystemTermType(); // Analysis the termtype termtypeAnalysis(); // Terminal detection detectTerminal(); } // private methods of FTermDetection //---------------------------------------------------------------------- void FTermDetection::getSystemTermType() { // Import the untrusted environment variable TERM const char* const& term_env = std::getenv(C_STR("TERM")); if ( term_env ) { // Save name in termtype std::strncpy (termtype, term_env, sizeof(termtype) - 1); return; } else if ( *termfilename ) // 1st fallback: use the teminal file name { getTTYtype(); // Look into /etc/ttytype #if F_HAVE_GETTTYNAM if ( getTTYSFileEntry() ) // Look into /etc/ttys return; #endif } // 2nd fallback: use vt100 if not found std::strncpy (termtype, C_STR("vt100"), 6); } //---------------------------------------------------------------------- void FTermDetection::getTTYtype() { // Analyse /etc/ttytype and get the term name // ------------------------------------------ // file format: // // // Example: // linux tty1 // vt100 ttys0 // Get term basename const char* term_basename = std::strrchr(termfilename, '/'); if ( term_basename == 0 ) term_basename = termfilename; else term_basename++; std::FILE *fp; if ( (fp = std::fopen("/etc/ttytype", "r")) != 0 ) { char* p; char str[BUFSIZ]; // Read and parse the file while ( fgets(str, sizeof(str) - 1, fp) != 0 ) { char* name; char* type; type = name = 0; // 0 == not found p = str; while ( *p ) { if ( std::isspace(uChar(*p)) ) *p = '\0'; else if ( type == 0 ) type = p; else if ( name == 0 && p != str && p[-1] == '\0' ) name = p; p++; } if ( type != 0 && name != 0 && ! std::strcmp(name, term_basename) ) { // Save name in termtype std::strncpy (termtype, type, sizeof(termtype) - 1); std::fclose(fp); return; } } std::fclose(fp); } } #if F_HAVE_GETTTYNAM //---------------------------------------------------------------------- bool FTermDetection::getTTYSFileEntry() { // Analyse /etc/ttys and get the term name // get term basename const char* term_basename = std::strrchr(termfilename, '/'); if ( term_basename == 0 ) term_basename = termfilename; else term_basename++; struct ttyent* ttys_entryt; ttys_entryt = getttynam(term_basename); if ( ttys_entryt ) { char* type = ttys_entryt->ty_type; if ( type != 0 ) { // Save name in termtype std::strncpy (termtype, type, sizeof(termtype) - 1); endttyent(); return true; } } endttyent(); return false; } #endif //---------------------------------------------------------------------- void FTermDetection::termtypeAnalysis() { // Cygwin console if ( std::strncmp(termtype, "cygwin", 6) == 0 ) terminal_type.cygwin = true; // rxvt terminal emulator (native MS Window System port) on cygwin if ( std::strncmp(termtype, "rxvt-cygwin-native", 18) == 0 ) terminal_type.rxvt = true; // Ansi terminal if ( std::strncmp(termtype, "ansi", 4) == 0 ) { terminal_detection = false; terminal_type.ansi = true; } // Sun Microsystems workstation console if ( std::strncmp(termtype, "sun", 3) == 0 ) { terminal_detection = false; terminal_type.sun_con = true; } // Kterm if ( std::strncmp(termtype, "kterm", 5) == 0 ) { terminal_detection = false; terminal_type.kterm = true; } // Linux console if ( std::strncmp(termtype, C_STR("linux"), 5) == 0 || std::strncmp(termtype, C_STR("con"), 3) == 0 ) terminal_type.linux_con = true; // NetBSD workstation console if ( std::strncmp(termtype, C_STR("wsvt25"), 6) == 0 ) terminal_type.netbsd_con = true; } //---------------------------------------------------------------------- void FTermDetection::detectTerminal() { // Terminal detection char* new_termtype = 0; if ( terminal_detection ) { FTermios::setCaptureSendCharacters(); // Initialize 256 colors terminals new_termtype = init_256colorTerminal(); // Identify the terminal via the answerback-message new_termtype = parseAnswerbackMsg (new_termtype); // Identify the terminal via the secondary device attributes (SEC_DA) new_termtype = parseSecDA (new_termtype); // Determines the maximum number of colors new_termtype = determineMaxColor(new_termtype); FTermios::unsetCaptureSendCharacters(); } // // Additional termtype analysis // // Test if the terminal is a xterm if ( std::strncmp(termtype, C_STR("xterm"), 5) == 0 || std::strncmp(termtype, C_STR("Eterm"), 5) == 0 ) { terminal_type.xterm = true; // Each xterm should be able to use at least 16 colors if ( ! new_termtype && std::strlen(termtype) == 5 ) new_termtype = C_STR("xterm-16color"); } // set the new environment variable TERM if ( new_termtype ) { setenv(C_STR("TERM"), new_termtype, 1); std::strncpy (termtype, new_termtype, std::strlen(new_termtype) + 1); } } //---------------------------------------------------------------------- char* FTermDetection::init_256colorTerminal() { char* new_termtype = 0; if ( get256colorEnvString() ) color256 = true; else if ( std::strstr (termtype, "256color") ) color256 = true; else color256 = false; new_termtype = termtype_256color_quirks(); #if DEBUG if ( new_termtype ) std::strncpy ( termtype_256color , new_termtype , std::strlen(new_termtype) + 1 ); #endif return new_termtype; } //---------------------------------------------------------------------- bool FTermDetection::get256colorEnvString() { // Enable 256 color capabilities color_env.string1 = std::getenv("COLORTERM"); color_env.string2 = std::getenv("VTE_VERSION"); color_env.string3 = std::getenv("XTERM_VERSION"); color_env.string4 = std::getenv("ROXTERM_ID"); color_env.string5 = std::getenv("KONSOLE_DBUS_SESSION"); color_env.string6 = std::getenv("KONSOLE_DCOP"); if ( color_env.string1 != 0 ) return true; if ( color_env.string2 != 0 ) return true; if ( color_env.string3 != 0 ) return true; if ( color_env.string4 != 0 ) return true; if ( color_env.string5 != 0 ) return true; if ( color_env.string6 != 0 ) return true; return false; } //---------------------------------------------------------------------- char* FTermDetection::termtype_256color_quirks() { char* new_termtype = 0; if ( ! color256 ) return new_termtype; if ( std::strncmp(termtype, "xterm", 5) == 0 ) new_termtype = C_STR("xterm-256color"); if ( std::strncmp(termtype, "screen", 6) == 0 ) { new_termtype = C_STR("screen-256color"); terminal_type.screen = true; char* tmux = std::getenv("TMUX"); if ( tmux && std::strlen(tmux) != 0 ) terminal_type.tmux = true; } if ( std::strncmp(termtype, "Eterm", 5) == 0 ) new_termtype = C_STR("Eterm-256color"); if ( std::strncmp(termtype, "mlterm", 6) == 0 ) { new_termtype = C_STR("mlterm-256color"); terminal_type.mlterm = true; } if ( std::strncmp(termtype, "rxvt", 4) != 0 && color_env.string1 && std::strncmp(color_env.string1, "rxvt-xpm", 8) == 0 ) { new_termtype = C_STR("rxvt-256color"); terminal_type.rxvt = true; } if ( (color_env.string5 && std::strlen(color_env.string5) > 0) || (color_env.string6 && std::strlen(color_env.string6) > 0) ) terminal_type.kde_konsole = true; if ( (color_env.string1 && std::strncmp(color_env.string1, "gnome-terminal", 14) == 0) || color_env.string2 ) { terminal_type.gnome_terminal = true; // Each gnome-terminal should be able to use 256 colors color256 = true; if ( ! isScreenTerm() ) new_termtype = C_STR("gnome-256color"); } return new_termtype; } //---------------------------------------------------------------------- char* FTermDetection::determineMaxColor (char current_termtype[]) { // Determine xterm maximum number of colors via OSC 4 char* new_termtype = current_termtype; if ( ! color256 && ! isCygwinTerminal() && ! isTeraTerm() && ! isLinuxTerm() && ! isNetBSDTerm() && ! getXTermColorName(0).isEmpty() ) { if ( ! getXTermColorName(256).isEmpty() ) { if ( isPuttyTerminal() ) new_termtype = C_STR("putty-256color"); else new_termtype = C_STR("xterm-256color"); } else if ( ! getXTermColorName(87).isEmpty() ) { new_termtype = C_STR("xterm-88color"); } else if ( ! getXTermColorName(15).isEmpty() ) { new_termtype = C_STR("xterm-16color"); } } return new_termtype; } //---------------------------------------------------------------------- const FString FTermDetection::getXTermColorName (int color) { FString color_str(""); fd_set ifds; struct timeval tv; int stdin_no = FTermios::getStdIn(); char temp[512] = { }; FTerm::putstringf (OSC "4;%d;?" BEL, color); // get color std::fflush(stdout); FD_ZERO(&ifds); FD_SET(stdin_no, &ifds); tv.tv_sec = 0; tv.tv_usec = 150000; // 150 ms // read the terminal answer if ( select (stdin_no + 1, &ifds, 0, 0, &tv) > 0 ) { if ( std::scanf("\033]4;%10d;%509[^\n]s", &color, temp) == 2 ) { std::size_t n = std::strlen(temp); // BEL + '\0' = string terminator if ( n >= 6 && temp[n - 1] == BEL[0] && temp[n] == '\0' ) temp[n - 1] = '\0'; // Esc + \ = OSC string terminator (mintty) if ( n >= 6 && temp[n - 2] == ESC[0] && temp[n - 1] == '\\' ) temp[n - 2] = '\0'; color_str = temp; } } return color_str; } //---------------------------------------------------------------------- char* FTermDetection::parseAnswerbackMsg (char current_termtype[]) { char* new_termtype = current_termtype; // send ENQ and read the answerback message try { answer_back = new FString(getAnswerbackMsg()); } catch (const std::bad_alloc& ex) { std::cerr << "not enough memory to alloc " << ex.what() << std::endl; return 0; } if ( *answer_back == "PuTTY" ) { terminal_type.putty = true; if ( color256 ) new_termtype = C_STR("putty-256color"); else new_termtype = C_STR("putty"); } // cygwin needs a backspace to delete the '♣' char if ( isCygwinTerminal() ) FTerm::putstring (BS " " BS); #if DEBUG if ( new_termtype ) std::strncpy ( termtype_Answerback , new_termtype , std::strlen(new_termtype) + 1 ); #endif return new_termtype; } //---------------------------------------------------------------------- const FString FTermDetection::getAnswerbackMsg() { FString answerback = ""; fd_set ifds; struct timeval tv; char temp[10] = { }; int stdin_no = FTermios::getStdIn(); std::putchar (ENQ[0]); // Send enquiry character std::fflush(stdout); FD_ZERO(&ifds); FD_SET(stdin_no, &ifds); tv.tv_sec = 0; tv.tv_usec = 150000; // 150 ms // Read the answerback message if ( select (stdin_no + 1, &ifds, 0, 0, &tv) > 0 ) if ( std::fgets (temp, sizeof(temp) - 1, stdin) != 0 ) answerback = temp; return answerback; } //---------------------------------------------------------------------- char* FTermDetection::parseSecDA (char current_termtype[]) { // The Linux console and older cygwin terminals knows no Sec_DA if ( isLinuxTerm() || isCygwinTerminal() ) return current_termtype; try { // Secondary device attributes (SEC_DA) <- decTerminalID string sec_da = new FString(getSecDA()); } catch (const std::bad_alloc& ex) { std::cerr << "not enough memory to alloc " << ex.what() << std::endl; return current_termtype; } if ( sec_da->getLength() < 6 ) return current_termtype; // remove the first 3 bytes ("\033[>") FString temp = sec_da->right(sec_da->getLength() - 3); // remove the last byte ("c") temp.remove(temp.getLength() - 1, 1); // split into components FStringList sec_da_list = temp.split(';'); uLong num_components = sec_da_list.size(); // The second device attribute (SEC_DA) always has 3 parameters, // otherwise it usually has a copy of the device attribute (primary DA) if ( num_components < 3 ) return current_termtype; const FString* sec_da_components = &sec_da_list[0]; if ( sec_da_components[0].isEmpty() ) return current_termtype; // Read the terminal type try { secondary_da.terminal_id_type = sec_da_components[0].toInt(); } catch (const std::exception&) { secondary_da.terminal_id_type = -1; } // Read the terminal (firmware) version try { if ( sec_da_components[1] ) secondary_da.terminal_id_version = sec_da_components[1].toInt(); else secondary_da.terminal_id_version = -1; } catch (const std::exception&) { secondary_da.terminal_id_version = -1; } // Read the terminal hardware option try { if ( sec_da_components[2] ) secondary_da.terminal_id_hardware = sec_da_components[2].toInt(); else secondary_da.terminal_id_hardware = -1; } catch (const std::exception&) { secondary_da.terminal_id_hardware = -1; } char* new_termtype = secDA_Analysis(current_termtype); #if DEBUG if ( new_termtype ) std::strncpy ( termtype_SecDA , new_termtype , std::strlen(new_termtype) + 1 ); #endif return new_termtype; } //---------------------------------------------------------------------- const FString FTermDetection::getSecDA() { FString sec_da_str = ""; int a = 0 , b = 0 , c = 0 , stdin_no = FTermios::getStdIn(); fd_set ifds; struct timeval tv; // Get the secondary device attributes #if defined(__CYGWIN__) puts (SECDA); #else FTerm::putstring (SECDA); #endif std::fflush(stdout); FD_ZERO(&ifds); FD_SET(stdin_no, &ifds); tv.tv_sec = 0; tv.tv_usec = 600000; // 600 ms // Read the answer if ( select (stdin_no + 1, &ifds, 0, 0, &tv) == 1 ) if ( std::scanf("\033[>%10d;%10d;%10dc", &a, &b, &c) == 3 ) sec_da_str.sprintf("\033[>%d;%d;%dc", a, b, c); return sec_da_str; } //---------------------------------------------------------------------- char* FTermDetection::secDA_Analysis (char current_termtype[]) { char* new_termtype = current_termtype; switch ( secondary_da.terminal_id_type ) { case 0: // DEC VT100 new_termtype = secDA_Analysis_0(current_termtype); break; case 1: // DEC VT220 new_termtype = secDA_Analysis_1(current_termtype); break; case 2: // DEC VT240 case 18: // DEC VT330 case 19: // DEC VT340 case 24: // DEC VT320 new_termtype = secDA_Analysis_24(current_termtype); break; case 32: // Tera Term new_termtype = secDA_Analysis_32(current_termtype); break; case 41: // DEC VT420 case 61: // DEC VT510 case 64: // DEC VT520 case 65: // DEC VT525 case 67: // Cygwin break; case 77: // mintty new_termtype = secDA_Analysis_77(current_termtype); break; case 82: // rxvt new_termtype = secDA_Analysis_82(current_termtype); break; case 83: // screen new_termtype = secDA_Analysis_83(current_termtype); break; case 85: // rxvt-unicode new_termtype = secDA_Analysis_85(current_termtype); break; default: break; } // Correct false assumptions if ( isGnomeTerminal() && secondary_da.terminal_id_type != 1 ) terminal_type.gnome_terminal = false; if ( isKdeTerminal() && secondary_da.terminal_id_type != 0 ) terminal_type.kde_konsole = false; return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_0 (char current_termtype[]) { // Terminal ID 0 - DEC VT100 char* new_termtype = current_termtype; if ( secondary_da.terminal_id_version == 115 ) terminal_type.kde_konsole = true; else if ( secondary_da.terminal_id_version == 136 ) terminal_type.putty = true; // PuTTY #if defined(__FreeBSD__) || defined(__DragonFly__) if ( FTermFreeBSD::isFreeBSDConsole() ) terminal_type.freebsd_con = true; #endif return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_1 (char current_termtype[]) { // Terminal ID 1 - DEC VT220 char* new_termtype = current_termtype; if ( secondary_da.terminal_id_version > 1000 ) { terminal_type.gnome_terminal = true; // Each gnome-terminal should be able to use 256 colors color256 = true; new_termtype = C_STR("gnome-256color"); gnome_terminal_id = secondary_da.terminal_id_version; // VTE 0.40.0 or higher and gnome-terminal 3.16 or higher if ( gnome_terminal_id >= 4000 ) decscusr_support = true; } return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_24 (char current_termtype[]) { // Terminal ID 24 - DEC VT320 char* new_termtype = current_termtype; #if defined(__NetBSD__) || defined(__OpenBSD__) if ( secondary_da.terminal_id_version == 20 && FTermOpenBSD::isBSDConsole() ) { // NetBSD/OpenBSD workstation console if ( std::strncmp(termtype, C_STR("wsvt25"), 6) == 0 ) terminal_type.netbsd_con = true; else if ( std::strncmp(termtype, C_STR("vt220"), 5) == 0 ) { terminal_type.openbsd_con = true; new_termtype = C_STR("pccon"); } } #endif return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_32 (char[]) { // Terminal ID 32 - Tera Term char* new_termtype; terminal_type.tera_term = true; new_termtype = C_STR("teraterm"); return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_77 (char[]) { // Terminal ID 77 - mintty char* new_termtype; terminal_type.mintty = true; new_termtype = C_STR("xterm-256color"); std::fflush(stdout); return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_82 (char current_termtype[]) { // Terminal ID 82 - rxvt char* new_termtype = current_termtype; terminal_type.rxvt = true; if ( std::strncmp(termtype, "rxvt-", 5) != 0 && std::strncmp(termtype, "rxvt-cygwin-native", 18) == 0 ) new_termtype = C_STR("rxvt-16color"); else new_termtype = termtype; return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_83 (char current_termtype[]) { // Terminal ID 83 - screen char* new_termtype = current_termtype; terminal_type.screen = true; return new_termtype; } //---------------------------------------------------------------------- inline char* FTermDetection::secDA_Analysis_85 (char current_termtype[]) { // Terminal ID 85 - rxvt-unicode char* new_termtype = current_termtype; terminal_type.rxvt = true; terminal_type.urxvt = true; if ( std::strncmp(termtype, "rxvt-", 5) != 0 ) { if ( color256 ) new_termtype = C_STR("rxvt-256color"); else new_termtype = C_STR("rxvt"); } else new_termtype = termtype; return new_termtype; }