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]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/right.png)
![[first]](../icons/first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
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 /* --------------------------------------------------------------------------------------------- */