1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2000
4 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
5 *
6 * Copyright 2022 Google LLC
7 */
8
9#include <common.h>
10#include <cli.h>
11
12/**
13 * enum cli_esc_state_t - indicates what to do with an escape character
14 *
15 * @ESC_REJECT: Invalid escape sequence, so the esc_save[] characters are
16 *	returned from each subsequent call to cli_ch_esc()
17 * @ESC_SAVE: Character should be saved in esc_save until we have another one
18 * @ESC_CONVERTED: Escape sequence has been completed and the resulting
19 *	character is available
20 */
21enum cli_esc_state_t {
22	ESC_REJECT,
23	ESC_SAVE,
24	ESC_CONVERTED
25};
26
27void cli_ch_init(struct cli_ch_state *cch)
28{
29	memset(cch, '\0', sizeof(*cch));
30}
31
32/**
33 * cli_ch_esc() - Process a character in an ongoing escape sequence
34 *
35 * @cch: State information
36 * @ichar: Character to process
37 * @actp: Returns the action to take
38 * Returns: Output character if *actp is ESC_CONVERTED, else 0
39 */
40static int cli_ch_esc(struct cli_ch_state *cch, int ichar,
41		      enum cli_esc_state_t *actp)
42{
43	enum cli_esc_state_t act = ESC_REJECT;
44
45	switch (cch->esc_len) {
46	case 1:
47		if (ichar == '[' || ichar == 'O')
48			act = ESC_SAVE;
49		else
50			act = ESC_CONVERTED;
51		break;
52	case 2:
53		switch (ichar) {
54		case 'D':	/* <- key */
55			ichar = CTL_CH('b');
56			act = ESC_CONVERTED;
57			break;	/* pass off to ^B handler */
58		case 'C':	/* -> key */
59			ichar = CTL_CH('f');
60			act = ESC_CONVERTED;
61			break;	/* pass off to ^F handler */
62		case 'H':	/* Home key */
63			ichar = CTL_CH('a');
64			act = ESC_CONVERTED;
65			break;	/* pass off to ^A handler */
66		case 'F':	/* End key */
67			ichar = CTL_CH('e');
68			act = ESC_CONVERTED;
69			break;	/* pass off to ^E handler */
70		case 'A':	/* up arrow */
71			ichar = CTL_CH('p');
72			act = ESC_CONVERTED;
73			break;	/* pass off to ^P handler */
74		case 'B':	/* down arrow */
75			ichar = CTL_CH('n');
76			act = ESC_CONVERTED;
77			break;	/* pass off to ^N handler */
78		case '1':
79		case '2':
80		case '3':
81		case '4':
82		case '7':
83		case '8':
84			if (cch->esc_save[1] == '[') {
85				/* see if next character is ~ */
86				act = ESC_SAVE;
87			}
88			break;
89		}
90		break;
91	case 3:
92		switch (ichar) {
93		case '~':
94			switch (cch->esc_save[2]) {
95			case '3':	/* Delete key */
96				ichar = CTL_CH('d');
97				act = ESC_CONVERTED;
98				break;	/* pass to ^D handler */
99			case '1':	/* Home key */
100			case '7':
101				ichar = CTL_CH('a');
102				act = ESC_CONVERTED;
103				break;	/* pass to ^A handler */
104			case '4':	/* End key */
105			case '8':
106				ichar = CTL_CH('e');
107				act = ESC_CONVERTED;
108				break;	/* pass to ^E handler */
109			}
110			break;
111		case '0':
112			if (cch->esc_save[2] == '2')
113				act = ESC_SAVE;
114			break;
115		}
116		break;
117	case 4:
118		switch (ichar) {
119		case '0':
120		case '1':
121			act = ESC_SAVE;
122			break;		/* bracketed paste */
123		}
124		break;
125	case 5:
126		if (ichar == '~') {	/* bracketed paste */
127			ichar = 0;
128			act = ESC_CONVERTED;
129		}
130	}
131
132	*actp = act;
133
134	return ichar;
135}
136
137int cli_ch_process(struct cli_ch_state *cch, int ichar)
138{
139	/*
140	 * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
141	 * wants to check if there are more characters saved in the escape
142	 * sequence
143	 */
144	if (!ichar) {
145		if (cch->emitting) {
146			if (cch->emit_upto < cch->esc_len)
147				return cch->esc_save[cch->emit_upto++];
148			cch->emit_upto = 0;
149			cch->emitting = false;
150			cch->esc_len = 0;
151		}
152		return 0;
153	} else if (ichar == -ETIMEDOUT) {
154		/*
155		 * If we are in an escape sequence but nothing has followed the
156		 * Escape character, then the user probably just pressed the
157		 * Escape key. Return it and clear the sequence.
158		 */
159		if (cch->esc_len) {
160			cch->esc_len = 0;
161			return '\e';
162		}
163
164		/* Otherwise there is nothing to return */
165		return 0;
166	}
167
168	if (ichar == '\n' || ichar == '\r')
169		return '\n';
170
171	/* handle standard linux xterm esc sequences for arrow key, etc. */
172	if (cch->esc_len != 0) {
173		enum cli_esc_state_t act;
174
175		ichar = cli_ch_esc(cch, ichar, &act);
176
177		switch (act) {
178		case ESC_SAVE:
179			/* save this character and return nothing */
180			cch->esc_save[cch->esc_len++] = ichar;
181			ichar = 0;
182			break;
183		case ESC_REJECT:
184			/*
185			 * invalid escape sequence, start returning the
186			 * characters in it
187			 */
188			cch->esc_save[cch->esc_len++] = ichar;
189			ichar = cch->esc_save[cch->emit_upto++];
190			cch->emitting = true;
191			return ichar;
192		case ESC_CONVERTED:
193			/* valid escape sequence, return the resulting char */
194			cch->esc_len = 0;
195			break;
196		}
197	}
198
199	if (ichar == '\e') {
200		if (!cch->esc_len) {
201			cch->esc_save[cch->esc_len] = ichar;
202			cch->esc_len = 1;
203		} else {
204			puts("impossible condition #876\n");
205			cch->esc_len = 0;
206		}
207		return 0;
208	}
209
210	return ichar;
211}
212