]>
Commit | Line | Data |
---|---|---|
21aeafce JK |
1 | #include "git-compat-util.h" |
2 | #include "compat/terminal.h" | |
3 | #include "sigchain.h" | |
4 | #include "strbuf.h" | |
9ea416cb JS |
5 | #include "run-command.h" |
6 | #include "string-list.h" | |
12acdf57 | 7 | #include "hashmap.h" |
21aeafce | 8 | |
380395d0 | 9 | #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE) |
afb43561 EFL |
10 | |
11 | static void restore_term(void); | |
12 | ||
13 | static void restore_term_on_signal(int sig) | |
14 | { | |
15 | restore_term(); | |
16 | sigchain_pop(sig); | |
17 | raise(sig); | |
18 | } | |
19 | ||
21aeafce JK |
20 | #ifdef HAVE_DEV_TTY |
21 | ||
afb43561 EFL |
22 | #define INPUT_PATH "/dev/tty" |
23 | #define OUTPUT_PATH "/dev/tty" | |
24 | ||
21aeafce JK |
25 | static int term_fd = -1; |
26 | static struct termios old_term; | |
27 | ||
28 | static void restore_term(void) | |
29 | { | |
30 | if (term_fd < 0) | |
31 | return; | |
32 | ||
33 | tcsetattr(term_fd, TCSAFLUSH, &old_term); | |
9df92e63 | 34 | close(term_fd); |
21aeafce JK |
35 | term_fd = -1; |
36 | } | |
37 | ||
94ac3c31 | 38 | static int disable_bits(tcflag_t bits) |
9df92e63 EFL |
39 | { |
40 | struct termios t; | |
41 | ||
42 | term_fd = open("/dev/tty", O_RDWR); | |
43 | if (tcgetattr(term_fd, &t) < 0) | |
44 | goto error; | |
45 | ||
46 | old_term = t; | |
47 | sigchain_push_common(restore_term_on_signal); | |
48 | ||
94ac3c31 | 49 | t.c_lflag &= ~bits; |
9df92e63 EFL |
50 | if (!tcsetattr(term_fd, TCSAFLUSH, &t)) |
51 | return 0; | |
52 | ||
53 | error: | |
54 | close(term_fd); | |
55 | term_fd = -1; | |
56 | return -1; | |
57 | } | |
58 | ||
94ac3c31 JS |
59 | static int disable_echo(void) |
60 | { | |
61 | return disable_bits(ECHO); | |
62 | } | |
63 | ||
a5e46e6b JS |
64 | static int enable_non_canonical(void) |
65 | { | |
66 | return disable_bits(ICANON | ECHO); | |
67 | } | |
68 | ||
380395d0 | 69 | #elif defined(GIT_WINDOWS_NATIVE) |
afb43561 EFL |
70 | |
71 | #define INPUT_PATH "CONIN$" | |
72 | #define OUTPUT_PATH "CONOUT$" | |
73 | #define FORCE_TEXT "t" | |
74 | ||
9ea416cb JS |
75 | static int use_stty = 1; |
76 | static struct string_list stty_restore = STRING_LIST_INIT_DUP; | |
afb43561 EFL |
77 | static HANDLE hconin = INVALID_HANDLE_VALUE; |
78 | static DWORD cmode; | |
79 | ||
80 | static void restore_term(void) | |
81 | { | |
9ea416cb JS |
82 | if (use_stty) { |
83 | int i; | |
84 | struct child_process cp = CHILD_PROCESS_INIT; | |
85 | ||
86 | if (stty_restore.nr == 0) | |
87 | return; | |
88 | ||
ef8d7ac4 | 89 | strvec_push(&cp.args, "stty"); |
9ea416cb | 90 | for (i = 0; i < stty_restore.nr; i++) |
ef8d7ac4 | 91 | strvec_push(&cp.args, stty_restore.items[i].string); |
9ea416cb JS |
92 | run_command(&cp); |
93 | string_list_clear(&stty_restore, 0); | |
94 | return; | |
95 | } | |
96 | ||
afb43561 EFL |
97 | if (hconin == INVALID_HANDLE_VALUE) |
98 | return; | |
99 | ||
100 | SetConsoleMode(hconin, cmode); | |
101 | CloseHandle(hconin); | |
102 | hconin = INVALID_HANDLE_VALUE; | |
103 | } | |
104 | ||
94ac3c31 | 105 | static int disable_bits(DWORD bits) |
afb43561 | 106 | { |
9ea416cb JS |
107 | if (use_stty) { |
108 | struct child_process cp = CHILD_PROCESS_INIT; | |
109 | ||
ef8d7ac4 | 110 | strvec_push(&cp.args, "stty"); |
9ea416cb JS |
111 | |
112 | if (bits & ENABLE_LINE_INPUT) { | |
113 | string_list_append(&stty_restore, "icanon"); | |
ef8d7ac4 | 114 | strvec_push(&cp.args, "-icanon"); |
9ea416cb JS |
115 | } |
116 | ||
117 | if (bits & ENABLE_ECHO_INPUT) { | |
118 | string_list_append(&stty_restore, "echo"); | |
ef8d7ac4 | 119 | strvec_push(&cp.args, "-echo"); |
9ea416cb JS |
120 | } |
121 | ||
122 | if (bits & ENABLE_PROCESSED_INPUT) { | |
123 | string_list_append(&stty_restore, "-ignbrk"); | |
124 | string_list_append(&stty_restore, "intr"); | |
125 | string_list_append(&stty_restore, "^c"); | |
ef8d7ac4 JK |
126 | strvec_push(&cp.args, "ignbrk"); |
127 | strvec_push(&cp.args, "intr"); | |
128 | strvec_push(&cp.args, ""); | |
9ea416cb JS |
129 | } |
130 | ||
131 | if (run_command(&cp) == 0) | |
132 | return 0; | |
133 | ||
134 | /* `stty` could not be executed; access the Console directly */ | |
135 | use_stty = 0; | |
136 | } | |
137 | ||
afb43561 EFL |
138 | hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, |
139 | FILE_SHARE_READ, NULL, OPEN_EXISTING, | |
140 | FILE_ATTRIBUTE_NORMAL, NULL); | |
141 | if (hconin == INVALID_HANDLE_VALUE) | |
142 | return -1; | |
143 | ||
144 | GetConsoleMode(hconin, &cmode); | |
145 | sigchain_push_common(restore_term_on_signal); | |
94ac3c31 | 146 | if (!SetConsoleMode(hconin, cmode & ~bits)) { |
afb43561 EFL |
147 | CloseHandle(hconin); |
148 | hconin = INVALID_HANDLE_VALUE; | |
149 | return -1; | |
150 | } | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
94ac3c31 JS |
155 | static int disable_echo(void) |
156 | { | |
157 | return disable_bits(ENABLE_ECHO_INPUT); | |
158 | } | |
159 | ||
a5e46e6b JS |
160 | static int enable_non_canonical(void) |
161 | { | |
162 | return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT); | |
163 | } | |
94ac3c31 | 164 | |
e118f063 JS |
165 | /* |
166 | * Override `getchar()`, as the default implementation does not use | |
167 | * `ReadFile()`. | |
168 | * | |
169 | * This poses a problem when we want to see whether the standard | |
170 | * input has more characters, as the default of Git for Windows is to start the | |
171 | * Bash in a MinTTY, which uses a named pipe to emulate a pty, in which case | |
172 | * our `poll()` emulation calls `PeekNamedPipe()`, which seems to require | |
173 | * `ReadFile()` to be called first to work properly (it only reports 0 | |
174 | * available bytes, otherwise). | |
175 | * | |
176 | * So let's just override `getchar()` with a version backed by `ReadFile()` and | |
177 | * go our merry ways from here. | |
178 | */ | |
179 | static int mingw_getchar(void) | |
180 | { | |
181 | DWORD read = 0; | |
182 | unsigned char ch; | |
183 | ||
184 | if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), &ch, 1, &read, NULL)) | |
185 | return EOF; | |
186 | ||
187 | if (!read) { | |
188 | error("Unexpected 0 read"); | |
189 | return EOF; | |
190 | } | |
191 | ||
192 | return ch; | |
193 | } | |
194 | #define getchar mingw_getchar | |
195 | ||
afb43561 EFL |
196 | #endif |
197 | ||
198 | #ifndef FORCE_TEXT | |
199 | #define FORCE_TEXT | |
200 | #endif | |
201 | ||
21aeafce JK |
202 | char *git_terminal_prompt(const char *prompt, int echo) |
203 | { | |
204 | static struct strbuf buf = STRBUF_INIT; | |
205 | int r; | |
67fe7356 | 206 | FILE *input_fh, *output_fh; |
21aeafce | 207 | |
afb43561 | 208 | input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT); |
67fe7356 | 209 | if (!input_fh) |
21aeafce JK |
210 | return NULL; |
211 | ||
afb43561 | 212 | output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT); |
67fe7356 EFL |
213 | if (!output_fh) { |
214 | fclose(input_fh); | |
215 | return NULL; | |
216 | } | |
217 | ||
9df92e63 | 218 | if (!echo && disable_echo()) { |
67fe7356 EFL |
219 | fclose(input_fh); |
220 | fclose(output_fh); | |
9df92e63 | 221 | return NULL; |
21aeafce JK |
222 | } |
223 | ||
67fe7356 EFL |
224 | fputs(prompt, output_fh); |
225 | fflush(output_fh); | |
21aeafce | 226 | |
8f309aeb | 227 | r = strbuf_getline_lf(&buf, input_fh); |
21aeafce | 228 | if (!echo) { |
67fe7356 EFL |
229 | putc('\n', output_fh); |
230 | fflush(output_fh); | |
21aeafce JK |
231 | } |
232 | ||
233 | restore_term(); | |
67fe7356 EFL |
234 | fclose(input_fh); |
235 | fclose(output_fh); | |
21aeafce JK |
236 | |
237 | if (r == EOF) | |
238 | return NULL; | |
239 | return buf.buf; | |
240 | } | |
241 | ||
12acdf57 JS |
242 | /* |
243 | * The `is_known_escape_sequence()` function returns 1 if the passed string | |
244 | * corresponds to an Escape sequence that the terminal capabilities contains. | |
245 | * | |
246 | * To avoid depending on ncurses or other platform-specific libraries, we rely | |
247 | * on the presence of the `infocmp` executable to do the job for us (failing | |
248 | * silently if the program is not available or refused to run). | |
249 | */ | |
250 | struct escape_sequence_entry { | |
251 | struct hashmap_entry entry; | |
252 | char sequence[FLEX_ARRAY]; | |
253 | }; | |
254 | ||
255 | static int sequence_entry_cmp(const void *hashmap_cmp_fn_data, | |
256 | const struct escape_sequence_entry *e1, | |
257 | const struct escape_sequence_entry *e2, | |
258 | const void *keydata) | |
259 | { | |
260 | return strcmp(e1->sequence, keydata ? keydata : e2->sequence); | |
261 | } | |
262 | ||
263 | static int is_known_escape_sequence(const char *sequence) | |
264 | { | |
265 | static struct hashmap sequences; | |
266 | static int initialized; | |
267 | ||
268 | if (!initialized) { | |
269 | struct child_process cp = CHILD_PROCESS_INIT; | |
270 | struct strbuf buf = STRBUF_INIT; | |
271 | char *p, *eol; | |
272 | ||
273 | hashmap_init(&sequences, (hashmap_cmp_fn)sequence_entry_cmp, | |
274 | NULL, 0); | |
275 | ||
ef8d7ac4 | 276 | strvec_pushl(&cp.args, "infocmp", "-L", "-1", NULL); |
12acdf57 JS |
277 | if (pipe_command(&cp, NULL, 0, &buf, 0, NULL, 0)) |
278 | strbuf_setlen(&buf, 0); | |
279 | ||
280 | for (eol = p = buf.buf; *p; p = eol + 1) { | |
281 | p = strchr(p, '='); | |
282 | if (!p) | |
283 | break; | |
284 | p++; | |
285 | eol = strchrnul(p, '\n'); | |
286 | ||
287 | if (starts_with(p, "\\E")) { | |
288 | char *comma = memchr(p, ',', eol - p); | |
289 | struct escape_sequence_entry *e; | |
290 | ||
291 | p[0] = '^'; | |
292 | p[1] = '['; | |
293 | FLEX_ALLOC_MEM(e, sequence, p, comma - p); | |
294 | hashmap_entry_init(&e->entry, | |
295 | strhash(e->sequence)); | |
296 | hashmap_add(&sequences, &e->entry); | |
297 | } | |
298 | if (!*eol) | |
299 | break; | |
300 | } | |
301 | initialized = 1; | |
302 | } | |
303 | ||
304 | return !!hashmap_get_from_hash(&sequences, strhash(sequence), sequence); | |
305 | } | |
306 | ||
a5e46e6b JS |
307 | int read_key_without_echo(struct strbuf *buf) |
308 | { | |
309 | static int warning_displayed; | |
310 | int ch; | |
311 | ||
312 | if (warning_displayed || enable_non_canonical() < 0) { | |
313 | if (!warning_displayed) { | |
314 | warning("reading single keystrokes not supported on " | |
315 | "this platform; reading line instead"); | |
316 | warning_displayed = 1; | |
317 | } | |
318 | ||
319 | return strbuf_getline(buf, stdin); | |
320 | } | |
321 | ||
322 | strbuf_reset(buf); | |
323 | ch = getchar(); | |
324 | if (ch == EOF) { | |
325 | restore_term(); | |
326 | return EOF; | |
327 | } | |
a5e46e6b | 328 | strbuf_addch(buf, ch); |
e118f063 JS |
329 | |
330 | if (ch == '\033' /* ESC */) { | |
331 | /* | |
332 | * We are most likely looking at an Escape sequence. Let's try | |
333 | * to read more bytes, waiting at most half a second, assuming | |
334 | * that the sequence is complete if we did not receive any byte | |
335 | * within that time. | |
336 | * | |
337 | * Start by replacing the Escape byte with ^[ */ | |
338 | strbuf_splice(buf, buf->len - 1, 1, "^[", 2); | |
339 | ||
12acdf57 JS |
340 | /* |
341 | * Query the terminal capabilities once about all the Escape | |
342 | * sequences it knows about, so that we can avoid waiting for | |
343 | * half a second when we know that the sequence is complete. | |
344 | */ | |
345 | while (!is_known_escape_sequence(buf->buf)) { | |
e118f063 JS |
346 | struct pollfd pfd = { .fd = 0, .events = POLLIN }; |
347 | ||
348 | if (poll(&pfd, 1, 500) < 1) | |
349 | break; | |
350 | ||
351 | ch = getchar(); | |
352 | if (ch == EOF) | |
353 | return 0; | |
354 | strbuf_addch(buf, ch); | |
355 | } | |
356 | } | |
357 | ||
a5e46e6b JS |
358 | restore_term(); |
359 | return 0; | |
360 | } | |
361 | ||
21aeafce JK |
362 | #else |
363 | ||
364 | char *git_terminal_prompt(const char *prompt, int echo) | |
365 | { | |
366 | return getpass(prompt); | |
367 | } | |
368 | ||
a5e46e6b JS |
369 | int read_key_without_echo(struct strbuf *buf) |
370 | { | |
371 | static int warning_displayed; | |
372 | const char *res; | |
373 | ||
374 | if (!warning_displayed) { | |
375 | warning("reading single keystrokes not supported on this " | |
376 | "platform; reading line instead"); | |
377 | warning_displayed = 1; | |
378 | } | |
379 | ||
380 | res = getpass(""); | |
381 | strbuf_reset(buf); | |
382 | if (!res) | |
383 | return EOF; | |
384 | strbuf_addstr(buf, res); | |
385 | return 0; | |
386 | } | |
387 | ||
21aeafce | 388 | #endif |