Manual pages: mcmcdiffmceditmcview

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 (csi_command_t *out, const char **sptr, const char *end)
     /* [previous][next][first][last][top][bottom][index][help]  */
  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)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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)
     /* [previous][next][first][last][top][bottom][index][help]  */
 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 /* --------------------------------------------------------------------------------------------- */

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