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 (struct csi_command_t *out, const char **sptr, const char *end) /**/ 77 { 78 gboolean ok = FALSE; 79 80 const char *s = *sptr; 81 if (s == end) 82 goto invalid_sequence; 83 84 char c = *s; 85 86 #define NEXT_CHAR \ 87 do \ 88 { \ 89 if (++s == end) \ 90 goto invalid_sequence; \ 91 c = *s; \ 92 } \ 93 while (0) 94 95 char private_mode = '\0'; 96 97 if (c >= '<' && c <= '?') // "<=>?" 98 { 99 private_mode = c; 100 NEXT_CHAR; 101 } 102 103 // parameter bytes 104 size_t param_count = 0; 105 106 if (private_mode != '\0') 107 { 108 while (c >= 0x30 && c <= 0x3F) 109 NEXT_CHAR; 110 } 111 else 112 { 113 if (out != NULL) 114 // N.B. empty parameter strings are allowed. For our current use, 115 // treating them as zeroes happens to work. 116 memset (out->params, 0, sizeof (out->params)); 117 118 uint32_t tmp = 0; 119 size_t sub_index = 0; 120 121 while (c >= 0x30 && c <= 0x3F) 122 { 123 if (c >= '0' && c <= '9') 124 { 125 if (param_count == 0) 126 param_count = 1; 127 if (tmp * 10 < tmp) 128 goto invalid_sequence; // overflow 129 tmp *= 10; 130 if (tmp + c - '0' < tmp) 131 goto invalid_sequence; // overflow 132 tmp += c - '0'; 133 if (out != NULL) 134 out->params[param_count - 1][sub_index] = tmp; 135 } 136 else if (c == ':' && ++sub_index < G_N_ELEMENTS (out->params[0])) 137 tmp = 0; 138 else if (c == ';' && ++param_count <= G_N_ELEMENTS (out->params)) 139 tmp = 0, sub_index = 0; 140 else 141 goto invalid_sequence; 142 NEXT_CHAR; 143 } 144 } 145 146 while (c >= 0x20 && c <= 0x2F) // intermediate bytes 147 NEXT_CHAR; 148 #undef NEXT_CHAR 149 150 if (c < 0x40 || c > 0x7E) // final byte 151 goto invalid_sequence; 152 153 ++s; 154 ok = TRUE; 155 156 if (out != NULL) 157 { 158 out->private_mode = private_mode; 159 out->param_count = param_count; 160 } 161 162 invalid_sequence: 163 *sptr = s; 164 return ok; 165 } 166 167 /* --------------------------------------------------------------------------------------------- */ 168 /** 169 * Remove all control sequences (CSI, OSC) from the argument string. 170 * 171 * The 256-color and true-color escape sequences should allow either ';' or ':' inside as 172 * separator, actually, ':' is the more correct according to ECMA-48. Some terminal emulators 173 * (e.g. xterm, gnome-terminal) support this. 174 * 175 * Non-printable characters are also removed. 176 */ 177 178 char * 179 strip_ctrl_codes (char *s) /*
*/ 180 { 181 char *w; // Current position where the stripped data is written 182 const char *r; // Current position where the original data is read 183 184 if (s == NULL) 185 return NULL; 186 187 const char *end = s + strlen (s); 188 189 for (w = s, r = s; *r != '\0';) 190 { 191 if (*r == ESC_CHAR) 192 { 193 // Skip the control sequence's arguments 194 // '(' need to avoid strange 'B' letter in *Suse (if mc runs under root user) 195 if (*(++r) == '[' || *r == '(') 196 { 197 ++r; 198 parse_csi (NULL, &r, end); 199 // We're already past the sequence, no need to increment. 200 continue; 201 } 202 if (*r == ']') 203 { 204 /* 205 * Skip xterm's OSC (Operating System Command) 206 * https://www.xfree86.org/current/ctlseqs.html 207 * OSC P s ; P t ST 208 * OSC P s ; P t BEL 209 */ 210 const char *new_r; 211 212 for (new_r = r; *new_r != '\0'; new_r++) 213 { 214 switch (*new_r) 215 { 216 // BEL 217 case '\a': 218 r = new_r; 219 goto osc_out; 220 case ESC_CHAR: 221 // ST 222 if (new_r[1] == '\\') 223 { 224 r = new_r + 1; 225 goto osc_out; 226 } 227 break; 228 default: 229 break; 230 } 231 } 232 osc_out:; 233 } 234 235 /* 236 * Now we are at the last character of the sequence. 237 * Skip it unless it's binary 0. 238 */ 239 if (*r != '\0') 240 r++; 241 } 242 else 243 { 244 const char *n; 245 246 n = str_cget_next_char (r); 247 if (str_isprint (r)) 248 { 249 memmove (w, r, n - r); 250 w += n - r; 251 } 252 r = n; 253 } 254 } 255 256 *w = '\0'; 257 return s; 258 } 259 260 /* --------------------------------------------------------------------------------------------- */ 261 /** 262 * Convert "\E" -> esc character and ^x to control-x key and ^^ to ^ key 263 * 264 * @param p pointer to string 265 * 266 * @return newly allocated string 267 */ 268 269 char * 270 convert_controls (const char *p) /*
*/ 271 { 272 char *valcopy; 273 char *q; 274 275 valcopy = g_strdup (p); 276 277 // Parse the escape special character 278 for (q = valcopy; *p != '\0';) 279 switch (*p) 280 { 281 case '\\': 282 p++; 283 284 if (*p == 'e' || *p == 'E') 285 { 286 p++; 287 *q++ = ESC_CHAR; 288 } 289 break; 290 291 case '^': 292 p++; 293 if (*p == '^') 294 *q++ = *p++; 295 else 296 { 297 char c; 298 299 c = *p | 0x20; 300 if (c >= 'a' && c <= 'z') 301 { 302 *q++ = c - 'a' + 1; 303 p++; 304 } 305 else if (*p != '\0') 306 p++; 307 } 308 break; 309 310 default: 311 *q++ = *p++; 312 } 313 314 *q = '\0'; 315 return valcopy; 316 } 317 318 /* --------------------------------------------------------------------------------------------- */