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  */
21 enum cli_esc_state_t {
22 	ESC_REJECT,
23 	ESC_SAVE,
24 	ESC_CONVERTED
25 };
26 
cli_ch_init(struct cli_ch_state * cch)27 void 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  */
cli_ch_esc(struct cli_ch_state * cch,int ichar,enum cli_esc_state_t * actp)40 static 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 		break;
50 	case 2:
51 		switch (ichar) {
52 		case 'D':	/* <- key */
53 			ichar = CTL_CH('b');
54 			act = ESC_CONVERTED;
55 			break;	/* pass off to ^B handler */
56 		case 'C':	/* -> key */
57 			ichar = CTL_CH('f');
58 			act = ESC_CONVERTED;
59 			break;	/* pass off to ^F handler */
60 		case 'H':	/* Home key */
61 			ichar = CTL_CH('a');
62 			act = ESC_CONVERTED;
63 			break;	/* pass off to ^A handler */
64 		case 'F':	/* End key */
65 			ichar = CTL_CH('e');
66 			act = ESC_CONVERTED;
67 			break;	/* pass off to ^E handler */
68 		case 'A':	/* up arrow */
69 			ichar = CTL_CH('p');
70 			act = ESC_CONVERTED;
71 			break;	/* pass off to ^P handler */
72 		case 'B':	/* down arrow */
73 			ichar = CTL_CH('n');
74 			act = ESC_CONVERTED;
75 			break;	/* pass off to ^N handler */
76 		case '1':
77 		case '2':
78 		case '3':
79 		case '4':
80 		case '7':
81 		case '8':
82 			if (cch->esc_save[1] == '[') {
83 				/* see if next character is ~ */
84 				act = ESC_SAVE;
85 			}
86 			break;
87 		}
88 		break;
89 	case 3:
90 		switch (ichar) {
91 		case '~':
92 			switch (cch->esc_save[2]) {
93 			case '3':	/* Delete key */
94 				ichar = CTL_CH('d');
95 				act = ESC_CONVERTED;
96 				break;	/* pass to ^D handler */
97 			case '1':	/* Home key */
98 			case '7':
99 				ichar = CTL_CH('a');
100 				act = ESC_CONVERTED;
101 				break;	/* pass to ^A handler */
102 			case '4':	/* End key */
103 			case '8':
104 				ichar = CTL_CH('e');
105 				act = ESC_CONVERTED;
106 				break;	/* pass to ^E handler */
107 			}
108 			break;
109 		case '0':
110 			if (cch->esc_save[2] == '2')
111 				act = ESC_SAVE;
112 			break;
113 		}
114 		break;
115 	case 4:
116 		switch (ichar) {
117 		case '0':
118 		case '1':
119 			act = ESC_SAVE;
120 			break;		/* bracketed paste */
121 		}
122 		break;
123 	case 5:
124 		if (ichar == '~') {	/* bracketed paste */
125 			ichar = 0;
126 			act = ESC_CONVERTED;
127 		}
128 	}
129 
130 	*actp = act;
131 
132 	return ichar;
133 }
134 
cli_ch_process(struct cli_ch_state * cch,int ichar)135 int cli_ch_process(struct cli_ch_state *cch, int ichar)
136 {
137 	/*
138 	 * ichar=0x0 when error occurs in U-Boot getchar() or when the caller
139 	 * wants to check if there are more characters saved in the escape
140 	 * sequence
141 	 */
142 	if (!ichar) {
143 		if (cch->emitting) {
144 			if (cch->emit_upto < cch->esc_len)
145 				return cch->esc_save[cch->emit_upto++];
146 			cch->emit_upto = 0;
147 			cch->emitting = false;
148 			cch->esc_len = 0;
149 		}
150 		return 0;
151 	} else if (ichar == -ETIMEDOUT) {
152 		/*
153 		 * If we are in an escape sequence but nothing has followed the
154 		 * Escape character, then the user probably just pressed the
155 		 * Escape key. Return it and clear the sequence.
156 		 */
157 		if (cch->esc_len) {
158 			cch->esc_len = 0;
159 			return '\e';
160 		}
161 
162 		/* Otherwise there is nothing to return */
163 		return 0;
164 	}
165 
166 	if (ichar == '\n' || ichar == '\r')
167 		return '\n';
168 
169 	/* handle standard linux xterm esc sequences for arrow key, etc. */
170 	if (cch->esc_len != 0) {
171 		enum cli_esc_state_t act;
172 
173 		ichar = cli_ch_esc(cch, ichar, &act);
174 
175 		switch (act) {
176 		case ESC_SAVE:
177 			/* save this character and return nothing */
178 			cch->esc_save[cch->esc_len++] = ichar;
179 			ichar = 0;
180 			break;
181 		case ESC_REJECT:
182 			/*
183 			 * invalid escape sequence, start returning the
184 			 * characters in it
185 			 */
186 			cch->esc_save[cch->esc_len++] = ichar;
187 			ichar = cch->esc_save[cch->emit_upto++];
188 			cch->emitting = true;
189 			return ichar;
190 		case ESC_CONVERTED:
191 			/* valid escape sequence, return the resulting char */
192 			cch->esc_len = 0;
193 			break;
194 		}
195 	}
196 
197 	if (ichar == '\e') {
198 		if (!cch->esc_len) {
199 			cch->esc_save[cch->esc_len] = ichar;
200 			cch->esc_len = 1;
201 		} else {
202 			puts("impossible condition #876\n");
203 			cch->esc_len = 0;
204 		}
205 		return 0;
206 	}
207 
208 	return ichar;
209 }
210