1 /* 2 Terminal emulation. 3 4 Copyright (C) 2025 5 Free Software Foundation, Inc. 6 7 This file is part of the Midnight Commander. 8 Authors: 9 Miguel de Icaza, 1994, 1995, 1996 10 Johannes Altmanninger, 2025 11 12 The Midnight Commander is free software: you can redistribute it 13 and/or modify it under the terms of the GNU General Public License as 14 published by the Free Software Foundation, either version 3 of the License, 15 or (at your option) any later version. 16 17 The Midnight Commander is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU General Public License for more details. 21 22 You should have received a copy of the GNU General Public License 23 along with this program. If not, see <https://www.gnu.org/licenses/>. 24 */ 25 26 /** \file terminal.c 27 * \brief Source: terminal emulation. 28 * \author Johannes Altmanninger 29 * \date 2025 30 * 31 * Subshells running inside Midnight Commander may assume they run inside 32 * a terminal. This module helps us act like a real terminal in relevant 33 * aspects. 34 */ 35 36 #include <config.h> 37 38 #include "lib/util.h" 39 #include "lib/strutil.h" 40 41 #include "lib/terminal.h" 42 43 /*** global variables ****************************************************************************/ 44 45 /*** file scope macro definitions ****************************************************************/ 46 47 /*** file scope type declarations ****************************************************************/ 48 49 /*** forward declarations (file scope functions) *************************************************/ 50 51 /* --------------------------------------------------------------------------------------------- */ 52 /*** file scope functions ************************************************************************/ 53 /* --------------------------------------------------------------------------------------------- */ 54 55 /* --------------------------------------------------------------------------------------------- */ 56 /*** public functions ****************************************************************************/ 57 /* --------------------------------------------------------------------------------------------- */ 58 59 /** 60 * Parse a CSI command, starting from the third byte (i.e. the first 61 * parameter byte, if any). 62 * 63 * On success, *sptr will point to one-past the end of the sequence. 64 * On failure, *sptr will point to the first invalid byte. 65 * 66 * Here's the format in a sort of pidgin BNF: 67 * 68 * CSI-command = Esc '[' parameters (intermediate-byte)* final-byte 69 * parameters = [0-9;:]+ 70 * | [<=>?] (parameter-byte)* # private mode 71 * parameter-byte = [\x30-\x3F] # one of "0-9;:<=>?" 72 * intermediate-byte = [\x20–\x2F] # one of " !\"#$%&'()*+,-./" 73 * final-byte = [\x40-\x7e] # one of "@A–Z[\]^_`a–z{|}~" 74 */ 75 gboolean 76 parse_csi (csi_command_t *out, const char **sptr, const char *end) /**/ 77 { 78 gboolean ok = FALSE; 79 char c; 80 char private_mode = '\0'; 81 // parameter bytes 82 size_t param_count = 0; 83 84 const char *s = *sptr; 85 if (s == end) 86 goto invalid_sequence; 87 88 c = *s; 89 90 #define NEXT_CHAR \ 91 do \ 92 { \ 93 if (++s == end) \ 94 goto invalid_sequence; \ 95 c = *s; \ 96 } \ 97 while (FALSE) 98 99 if (c >= '<' && c <= '?') // "<=>?" 100 { 101 private_mode = c; 102 NEXT_CHAR; 103 } 104 105 if (private_mode != '\0') 106 { 107 while (c >= 0x30 && c <= 0x3F) 108 NEXT_CHAR; 109 } 110 else 111 { 112 uint32_t tmp = 0; 113 size_t sub_index = 0; 114 115 if (out != NULL) 116 // N.B. empty parameter strings are allowed. For our current use, 117 // treating them as zeroes happens to work. 118 memset (out->params, 0, sizeof (out->params)); 119 120 while (c >= 0x30 && c <= 0x3F) 121 { 122 if (c >= '0' && c <= '9') 123 { 124 if (param_count == 0) 125 param_count = 1; 126 if (tmp * 10 < tmp) 127 goto invalid_sequence; // overflow 128 tmp *= 10; 129 if (tmp + c - '0' < tmp) 130 goto invalid_sequence; // overflow 131 tmp += c - '0'; 132 if (out != NULL) 133 out->params[param_count - 1][sub_index] = tmp; 134 } 135 else if (c == ':' && ++sub_index < G_N_ELEMENTS (out->params[0])) 136 tmp = 0; 137 else if (c == ';' && ++param_count <= G_N_ELEMENTS (out->params)) 138 tmp = 0, sub_index = 0; 139 else 140 goto invalid_sequence; 141 NEXT_CHAR; 142 } 143 } 144 145 while (c >= 0x20 && c <= 0x2F) // intermediate bytes 146 NEXT_CHAR; 147 #undef NEXT_CHAR 148 149 if (c < 0x40 || c > 0x7E) // final byte 150 goto invalid_sequence; 151 152 ++s; 153 ok = TRUE; 154 155 if (out != NULL) 156 { 157 out->private_mode = private_mode; 158 out->param_count = param_count; 159 } 160 161 invalid_sequence: 162 *sptr = s; 163 return ok; 164 } 165 166 /* --------------------------------------------------------------------------------------------- */ 167 /** 168 * Remove all control sequences (CSI, OSC) from the argument string. 169 * 170 * The 256-color and true-color escape sequences should allow either ';' or ':' inside as 171 * separator, actually, ':' is the more correct according to ECMA-48. Some terminal emulators 172 * (e.g. xterm, gnome-terminal) support this. 173 * 174 * Non-printable characters are also removed. 175 */ 176 177 char * 178 strip_ctrl_codes (char *s) /*
*/ 179 { 180 char *w; // Current position where the stripped data is written 181 const char *r; // Current position where the original data is read 182 183 if (s == NULL) 184 return NULL; 185 186 const char *end = s + strlen (s); 187 188 for (w = s, r = s; *r != '\0';) 189 { 190 if (*r == ESC_CHAR) 191 { 192 // Skip the control sequence's arguments 193 // '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) 194 if (*(++r) == '[' || *r == '(') 195 { 196 ++r; 197 parse_csi (NULL, &r, end); 198 // We're already past the sequence, no need to increment. 199 continue; 200 } 201 if (*r == ']') 202 { 203 /* 204 * Skip xterm's OSC (Operating System Command) 205 * https://www.xfree86.org/current/ctlseqs.html 206 * OSC P s ; P t ST 207 * OSC P s ; P t BEL 208 */ 209 for (const char *new_r = r; *new_r != '\0'; new_r++) 210 { 211 switch (*new_r) 212 { 213 // BEL 214 case '\a': 215 r = new_r; 216 goto osc_out; 217 case ESC_CHAR: 218 // ST 219 if (new_r[1] == '\\') 220 { 221 r = new_r + 1; 222 goto osc_out; 223 } 224 break; 225 default: 226 break; 227 } 228 } 229 osc_out:; 230 } 231 232 /* 233 * Now we are at the last character of the sequence. 234 * Skip it unless it's binary 0. 235 */ 236 if (*r != '\0') 237 r++; 238 } 239 else 240 { 241 const char *n = str_cget_next_char (r); 242 243 if (str_isprint (r)) 244 { 245 memmove (w, r, n - r); 246 w += n - r; 247 } 248 r = n; 249 } 250 } 251 252 *w = '\0'; 253 return s; 254 } 255 256 /* --------------------------------------------------------------------------------------------- */ 257 /** 258 * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key 259 * 260 * @param p pointer to string 261 * 262 * @return newly allocated string 263 */ 264 265 char * 266 convert_controls (const char *p) /*
*/ 267 { 268 char *valcopy; 269 char *q; 270 271 valcopy = g_strdup (p); 272 273 // Parse the escape special character 274 for (q = valcopy; *p != '\0';) 275 switch (*p) 276 { 277 case '\\': 278 p++; 279 280 if (*p == 'e' || *p == 'E') 281 { 282 p++; 283 *q++ = ESC_CHAR; 284 } 285 break; 286 287 case '^': 288 p++; 289 if (*p == '^') 290 *q++ = *p++; 291 else 292 { 293 char c; 294 295 c = *p | 0x20; 296 if (c >= 'a' && c <= 'z') 297 { 298 *q++ = c - 'a' + 1; 299 p++; 300 } 301 else if (*p != '\0') 302 p++; 303 } 304 break; 305 306 default: 307 *q++ = *p++; 308 } 309 310 *q = '\0'; 311 return valcopy; 312 } 313 314 /* --------------------------------------------------------------------------------------------- */