root/lib/terminal.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. parse_csi
  2. strip_ctrl_codes
  3. convert_controls

   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)
     /* [previous][next][first][last][top][bottom][index][help]  */
  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)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */