SameBoy | Accurate GB/GBC emulator |
| download: https://git.y1.nz/archives/sameboy.tar.gz | |
| README | Files | Log | Refs | LICENSE |
SDL/console.c
1 #include "console.h"
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <stdbool.h>
6 #include <errno.h>
7 #include <string.h>
8 #include <signal.h>
9 #include <ctype.h>
10 #include <assert.h>
11 #include <pthread.h>
12 #include <stdarg.h>
13 #include <stdint.h>
14
15 #define ESC(x) "\x1B" x
16 #define CSI(x) ESC("[" x)
17 #define SGR(x) CSI(x "m")
18
19 static bool initialized = false;
20 static bool no_csi = false;
21 typedef struct listent_s listent_t;
22
23 struct listent_s {
24 listent_t *prev;
25 listent_t *next;
26 char content[];
27 };
28
29 typedef struct {
30 listent_t *first;
31 listent_t *last;
32 } fifo_t;
33
34 static fifo_t lines;
35 static fifo_t history;
36
37 static void remove_entry(fifo_t *fifo, listent_t *entry)
38 {
39 if (fifo->last == entry) {
40 fifo->last = entry->prev;
41 }
42 if (fifo->first == entry) {
43 fifo->first = entry->next;
44 }
45 if (entry->next) {
46 entry->next->prev = entry->prev;
47 }
48 if (entry->prev) {
49 entry->prev->next = entry->next;
50 }
51 free(entry);
52 }
53
54 static void add_entry(fifo_t *fifo, const char *content)
55 {
56 size_t length = strlen(content);
57 listent_t *entry = malloc(sizeof(*entry) + length + 1);
58 entry->next = NULL;
59 entry->prev = fifo->last;
60 memcpy(entry->content, content, length);
61 entry->content[length] = 0;
62 if (fifo->last) {
63 fifo->last->next = entry;
64 }
65 fifo->last = entry;
66 if (!fifo->first) {
67 fifo->first = entry;
68 }
69 }
70
71 static listent_t *reverse_find(listent_t *entry, const char *string, bool exact)
72 {
73 while (entry) {
74 if (exact && strcmp(entry->content, string) == 0) {
75 return entry;
76 }
77 if (!exact && strstr(entry->content, string)) {
78 return entry;
79 }
80 entry = entry->prev;
81 }
82 return NULL;
83 }
84
85 static bool is_term(void)
86 {
87 if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) return false;
88 #ifdef _WIN32
89 unsigned long input_mode, output_mode;
90 bool has_con_output;
91
92 HANDLE stdin_handle = GetStdHandle(STD_INPUT_HANDLE);
93 HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
94
95 GetConsoleMode(stdin_handle, &input_mode);
96 has_con_output = GetConsoleMode(stdout_handle, &output_mode);
97 if (!has_con_output) {
98 return false; // stdout has been redirected to a file or pipe
99 }
100
101 SetConsoleMode(stdin_handle, ENABLE_VIRTUAL_TERMINAL_INPUT);
102 SetConsoleMode(stdout_handle, ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
103
104 CONSOLE_SCREEN_BUFFER_INFO before = {0,};
105 GetConsoleScreenBufferInfo(stdout_handle, &before);
106
107 printf(SGR("0"));
108
109 CONSOLE_SCREEN_BUFFER_INFO after = {0,};
110 GetConsoleScreenBufferInfo(stdout_handle, &after);
111
112 SetConsoleMode(stdin_handle, input_mode);
113 SetConsoleMode(stdout_handle, output_mode);
114
115 if (before.dwCursorPosition.X != after.dwCursorPosition.X ||
116 before.dwCursorPosition.Y != after.dwCursorPosition.Y) {
117 printf("\r \r");
118 no_csi = true;
119 }
120
121 return true;
122 #else
123 return getenv("TERM");
124 #endif
125 }
126
127 static unsigned width, height;
128
129 static char raw_getc(void)
130 {
131 #ifdef _WIN32
132 char c;
133 unsigned long ret;
134 ReadFile(GetStdHandle(STD_INPUT_HANDLE), &c, 1, &ret, NULL);
135 #else
136 ssize_t ret;
137 char c;
138
139 do {
140 ret = read(STDIN_FILENO, &c, 1);
141 } while (ret == -1 && errno == EINTR);
142 #endif
143 return ret == 1? c : EOF;
144 }
145
146 #ifdef _WIN32
147 #pragma clang diagnostic ignored "-Wmacro-redefined"
148 #include <Windows.h>
149
150 static void update_size(void)
151 {
152 CONSOLE_SCREEN_BUFFER_INFO csbi;
153 GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
154 width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
155 height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
156 }
157
158 static unsigned long input_mode, output_mode;
159
160 static void cleanup(void)
161 {
162 if (!no_csi) {
163 printf(CSI("!p")); // reset
164 }
165 fflush(stdout);
166 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), input_mode);
167 SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), output_mode);
168 }
169
170 static bool initialize(void)
171 {
172 if (!is_term()) return false;
173 update_size();
174 if (width == 0 || height == 0) {
175 return false;
176 }
177
178 static bool once = false;
179 if (!once) {
180 atexit(cleanup);
181 GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &input_mode);
182 GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &output_mode);
183 once = true;
184 }
185 if (no_csi) {
186 initialized = true;
187 return true;
188 }
189 SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT);
190 SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_WRAP_AT_EOL_OUTPUT | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
191
192 fprintf(stdout, CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
193
194 fflush(stdout);
195 initialized = true;
196 return true;
197 }
198 #else
199 #include <sys/ioctl.h>
200
201 static void update_size(void)
202 {
203 struct winsize winsize;
204 ioctl(STDOUT_FILENO, TIOCGWINSZ, &winsize);
205 width = winsize.ws_col;
206 height = winsize.ws_row;
207 }
208
209 static void terminal_resized(int ignored)
210 {
211 update_size();
212 }
213
214 #include <termios.h>
215 static struct termios terminal;
216
217
218 static void cleanup(void)
219 {
220 if (!no_csi) {
221 printf(CSI("!p")); // reset
222 }
223 tcsetattr(STDIN_FILENO, TCSAFLUSH, &terminal);
224 fflush(stdout);
225 }
226
227 static bool initialize(void)
228 {
229 if (!is_term()) return false;
230 update_size();
231 if (width == 0 || height == 0) {
232 return false;
233 }
234
235 static bool once = false;
236 if (!once) {
237 atexit(cleanup);
238 signal(SIGWINCH, terminal_resized);
239 tcgetattr(STDIN_FILENO, &terminal);
240 #ifdef _WIN32
241 _setmode(STDIN_FILENO, _O_TEXT);
242 #endif
243 once = true;
244 }
245 struct termios raw_terminal;
246 raw_terminal = terminal;
247 raw_terminal.c_lflag = 0;
248 tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw_terminal);
249
250 printf(CSI("%dB") "\n" CSI("A") ESC("7") CSI("B"), height);
251
252 fflush(stdout);
253 initialized = true;
254 return true;
255 }
256 #endif
257
258 static struct {
259 char *content;
260 size_t allocation_size;
261 size_t length;
262 size_t position;
263 size_t scroll;
264 bool reverse_search;
265 listent_t *search_line;
266 } line;
267
268 #define CTL(x) ((x) - 'A' + 1)
269
270 static const char *prompt = "";
271 static size_t prompt_length = 0;
272 static bool repeat_empty = false;
273
274 static bool redraw_prompt(bool force)
275 {
276 if (no_csi) return true;
277
278 if (line.reverse_search) {
279 if (!force) return false;
280 if (line.length == 0) {
281 printf("\r" CSI("K") "%s" SGR("2") "Reverse Search..." SGR("0") CSI("%zuG"), prompt, prompt_length + 1);
282 return true;
283 }
284 if (!line.search_line) {
285 printf("\r" CSI("K") "%s" SGR("1") "%s" SGR("0"), prompt, line.content);
286 return true;
287 }
288 const char *loc = strstr(line.search_line->content, line.content);
289 printf("\r" CSI("K") "%s" "%.*s" SGR("1") "%s" SGR("0") "%s" CSI("%uG"),
290 prompt,
291 (int)(loc - line.search_line->content),
292 line.search_line->content,
293 line.content,
294 loc + line.length,
295 (unsigned)(loc - line.search_line->content + line.length + prompt_length + 1));
296 return true;
297 }
298
299 size_t max = width - 1 - prompt_length;
300
301 if (line.scroll && line.length <= max) {
302 line.scroll = 0;
303 force = true;
304 }
305
306 if (line.scroll > line.length - max) {
307 line.scroll = line.length - max;
308 force = true;
309 }
310
311 if (line.position < line.scroll + 1 && line.position) {
312 line.scroll = line.position - 1;
313 force = true;
314 }
315
316 if (line.position == 0 && line.scroll) {
317 line.scroll = 0;
318 force = true;
319 }
320
321 if (line.position > line.scroll + max) {
322 line.scroll = line.position - max;
323 force = true;
324 }
325
326 if (!force && line.length <= max) {
327 return false;
328 }
329
330 if (line.length <= max) {
331 printf("\r" CSI("K") "%s%s" CSI("%uG"), prompt, line.content, (unsigned)(line.position + prompt_length + 1));
332 return true;
333 }
334
335 size_t left = max;
336 const char *string = line.content + line.scroll;
337 printf("\r" CSI("K") "%s", prompt);
338 if (line.scroll) {
339 printf(SGR("2") "%c" SGR("0"), *string);
340 string++;
341 left--;
342 }
343 if (line.scroll + max == line.length) {
344 printf("%s", string);
345 }
346 else {
347 printf("%.*s", (int)(left - 1), string);
348 string += left;
349 printf(SGR("2") "%c" SGR("0"), *string);
350 }
351 printf(CSI("%uG"), (unsigned)(line.position - line.scroll + prompt_length + 1));
352
353 return true;
354 }
355
356 static void set_position(size_t position)
357 {
358 if (position > line.length) {
359 printf("\a");
360 return;
361 }
362 line.position = position;
363 if (!redraw_prompt(false)) {
364 printf(CSI("%uG"), (unsigned)(position + prompt_length + 1));
365 }
366 }
367
368 static void set_line(const char *content)
369 {
370 line.length = strlen(content);
371 if (line.length + 1 > line.allocation_size) {
372 line.content = realloc(line.content, line.length + 1);
373 line.allocation_size = line.length + 1;
374 }
375 else if (line.allocation_size > 256 && line.length < 128) {
376 line.content = realloc(line.content, line.length + 1);
377 line.allocation_size = line.length + 1;
378 }
379 line.position = line.length;
380 strcpy(line.content, content);
381 redraw_prompt(true);
382 }
383
384 static void insert(const char *string)
385 {
386 size_t insertion_length = strlen(string);
387 size_t new_length = insertion_length + line.length;
388 bool need_realloc = false;
389 while (line.allocation_size < new_length + 1) {
390 line.allocation_size *= 2;
391 need_realloc = true;
392 }
393 if (need_realloc) {
394 line.content = realloc(line.content, line.allocation_size);
395 }
396 memmove(line.content + line.position + insertion_length,
397 line.content + line.position,
398 line.length - line.position);
399 memcpy(line.content + line.position, string, insertion_length);
400 line.position += insertion_length;
401 line.content[new_length] = 0;
402 line.length = new_length;
403 if (!redraw_prompt(line.position != line.length)) {
404 printf("%s", string);
405 }
406 }
407
408 static void delete(size_t size, bool forward)
409 {
410 if (line.length < size) {
411 printf("\a");
412 return;
413 }
414 if (forward) {
415 if (line.position > line.length - size) {
416 printf("\a");
417 return;
418 }
419 else {
420 line.position += size;
421 }
422 }
423 else if (line.position < size) {
424 printf("\a");
425 return;
426 }
427 memmove(line.content + line.position - size,
428 line.content + line.position,
429 line.length - line.position);
430 line.length -= size;
431 line.content[line.length] = 0;
432 line.position -= size;
433
434 if (!redraw_prompt(line.position != line.length)) {
435 printf(CSI("%uG") CSI("K"),
436 (unsigned)(line.position + prompt_length + 1));
437 }
438 }
439
440 static void move_word(bool forward)
441 {
442 signed offset = forward? 1 : -1;
443 size_t end = forward? line.length : 0;
444 signed check_offset = forward? 0 : -1;
445 if (line.position == end) {
446 printf("\a");
447 return;
448 }
449 line.position += offset;
450 while (line.position != end && isalnum(line.content[line.position + check_offset])) {
451 line.position += offset;
452 }
453 if (!redraw_prompt(false)) {
454 printf(CSI("%uG"), (unsigned)(line.position + prompt_length + 1));
455 }
456 }
457
458 static void delete_word(bool forward)
459 {
460 size_t original_pos = line.position;
461 signed offset = forward? 1 : -1;
462 size_t end = forward? line.length : 0;
463 signed check_offset = forward? 0 : -1;
464 if (line.position == end) {
465 printf("\a");
466 return;
467 }
468 line.position += offset;
469 while (line.position != end && isalnum(line.content[line.position + check_offset])) {
470 line.position += offset;
471 }
472 if (forward) {
473 delete(line.position - original_pos, false);
474 }
475 else {
476 delete(original_pos - line.position, true);
477 }
478 }
479
480 #define MOD_ALT(x) (0x100 | x)
481 #define MOD_SHIFT(x) (0x200 | x)
482 #define MOD_CTRL(x) (0x400 | x)
483 #define MOD_SPECIAL(x) (0x800 | x)
484
485 static unsigned get_extended_key(void)
486 {
487 unsigned modifiers = 0;
488 char c = 0;
489 restart:
490 c = raw_getc();
491 if (c == 0x1B) {
492 modifiers = MOD_SHIFT(MOD_ALT(0));
493 goto restart;
494 }
495 else if (c != '[' && c != 'O') {
496 return MOD_ALT(c);
497 }
498 unsigned ret = 0;
499 while (true) {
500 c = raw_getc();
501 if (c >= '0' && c <= '9') {
502 ret = ret * 10 + c - '0';
503 }
504 else if (c == ';') {
505 if (ret == 1) {
506 modifiers |= MOD_ALT(0);
507 }
508 else if (ret == 2) {
509 modifiers |= MOD_SHIFT(0);
510 }
511 else if (ret == 5) {
512 modifiers |= MOD_CTRL(0);
513 }
514 ret = 0;
515 }
516 else if (c == '~') {
517 return MOD_SPECIAL(ret) | modifiers;
518 }
519 else {
520 if (ret == 1) {
521 modifiers |= MOD_ALT(0);
522 }
523 else if (ret == 2) {
524 modifiers |= MOD_SHIFT(0);
525 }
526 else if (ret == 5) {
527 modifiers |= MOD_CTRL(0);
528 }
529 return c | modifiers;
530 }
531 }
532 }
533
534 #define SWAP(x, y) do {typeof(*(x)) _tmp = *(x); *(x) = *(y);*(y) = _tmp;} while (0)
535 static pthread_mutex_t terminal_lock = PTHREAD_MUTEX_INITIALIZER;
536 static pthread_mutex_t lines_lock = PTHREAD_MUTEX_INITIALIZER;
537 static pthread_cond_t lines_cond = PTHREAD_COND_INITIALIZER;
538 static void (*line_ready_callback)(void) = NULL;
539
540 static char reverse_search_mainloop(void)
541 {
542 while (true) {
543 char c = raw_getc();
544 pthread_mutex_lock(&terminal_lock);
545
546 switch (c) {
547 case CTL('C'):
548 line.search_line = NULL;
549 set_line("");
550 pthread_mutex_unlock(&terminal_lock);
551 return CTL('A');
552 case CTL('R'):
553 line.search_line = reverse_find(line.search_line? line.search_line->prev : history.last, line.content, false);
554 if (!line.search_line) {
555 printf("\a");
556 }
557 redraw_prompt(true);
558 break;
559 case CTL('W'):
560 delete_word(false);
561 redraw_prompt(true);
562 break;
563 case CTL('H'):
564 case 0x7F: // Backspace
565 delete(1, false);
566 redraw_prompt(true);
567 break;
568 default:
569 if (c >= ' ') {
570 char string[2] = {c, 0};
571 insert(string);
572 line.search_line = reverse_find(line.search_line?: history.last, line.content, false);
573 if (!line.search_line) {
574 printf("\a");
575 }
576 redraw_prompt(true);
577 }
578 else {
579 pthread_mutex_unlock(&terminal_lock);
580 return c;
581 }
582 break;
583 }
584 pthread_mutex_unlock(&terminal_lock);
585 fflush(stdout);
586 }
587
588 }
589 static void no_csi_mainloop(void)
590 {
591 while (true) {
592 char *expression = NULL;
593 size_t size = 0;
594
595 errno = 0;
596 if (getline(&expression, &size, stdin) <= 0) {
597 if (expression) {
598 free(expression);
599 }
600 if (!errno) {
601 continue;
602 }
603 return;
604 }
605
606 pthread_mutex_lock(&lines_lock);
607 if (!expression) {
608 add_entry(&lines, "");
609 }
610 else {
611 size_t length = strlen(expression);
612 if (expression[length - 1] == '\n') {
613 expression[length - 1] = 0;
614 }
615 add_entry(&lines, expression);
616 free(expression);
617 }
618 pthread_cond_signal(&lines_cond);
619 pthread_mutex_unlock(&lines_lock);
620 if (line_ready_callback) {
621 line_ready_callback();
622 }
623 }
624 }
625
626
627 static
628 #ifdef _WIN32
629 int __stdcall
630 #else
631 void *
632 #endif
633 mainloop(char *(*completer)(const char *substring, uintptr_t *context))
634 {
635 if (no_csi) {
636 no_csi_mainloop();
637 return 0;
638 }
639
640 listent_t *history_line = NULL;
641 uintptr_t complete_context = 0;
642 size_t completion_length = 0;
643 while (true) {
644 char c;
645 if (line.reverse_search) {
646 c = reverse_search_mainloop();
647 line.reverse_search = false;
648 if (line.search_line) {
649 size_t pos = strstr(line.search_line->content, line.content) - line.search_line->content + line.length;
650 set_line(line.search_line->content);
651 line.search_line = NULL;
652 set_position(pos);
653 }
654 else {
655 redraw_prompt(true);
656 }
657 }
658 else {
659 c = raw_getc();
660 }
661 if (c == (char)EOF) {
662 return 0;
663 }
664
665 pthread_mutex_lock(&terminal_lock);
666
667 switch (c) {
668 case CTL('A'):
669 set_position(0);
670 complete_context = completion_length = 0;
671 break;
672 case CTL('B'):
673 set_position(line.position - 1);
674 complete_context = completion_length = 0;
675 break;
676 case CTL('C'):
677 if (line.length) {
678 set_line("");
679 history_line = NULL;
680 complete_context = completion_length = 0;
681 }
682 else {
683 #ifdef _WIN32
684 raise(SIGINT);
685 #else
686 kill(getpid(), SIGINT);
687 #endif
688 }
689 break;
690 case CTL('D'):
691 if (line.length) {
692 delete(1, true);
693 complete_context = completion_length = 0;
694 }
695 else {
696 pthread_mutex_lock(&lines_lock);
697 add_entry(&lines, CON_EOF);
698 pthread_cond_signal(&lines_cond);
699 pthread_mutex_unlock(&lines_lock);
700 if (line_ready_callback) {
701 line_ready_callback();
702 }
703 }
704 break;
705 case CTL('E'):
706 set_position(line.length);
707 complete_context = completion_length = 0;
708 break;
709 case CTL('F'):
710 set_position(line.position + 1);
711 complete_context = completion_length = 0;
712 break;
713 case CTL('K'):
714 printf(CSI("K"));
715 if (!redraw_prompt(false)) {
716 line.length = line.position;
717 line.content[line.length] = 0;
718 }
719 complete_context = completion_length = 0;
720 break;
721 case CTL('R'):
722 complete_context = completion_length = 0;
723 line.reverse_search = true;
724 set_line("");
725
726 break;
727 case CTL('T'):
728 if (line.length < 2) {
729 printf("\a");
730 break;
731 }
732 if (line.position && line.position == line.length) {
733 line.position--;
734 }
735 if (line.position == 0) {
736 printf("\a");
737 break;
738 }
739 SWAP(line.content + line.position,
740 line.content + line.position - 1);
741 line.position++;
742 redraw_prompt(true);
743 complete_context = completion_length = 0;
744 break;
745 case CTL('W'):
746 delete_word(false);
747 complete_context = completion_length = 0;
748 break;
749 case '\r':
750 case '\n':
751 pthread_mutex_lock(&lines_lock);
752 if (line.length == 0 && repeat_empty && history.last) {
753 add_entry(&lines, history.last->content);
754 }
755 else {
756 add_entry(&lines, line.content);
757 }
758 pthread_cond_signal(&lines_cond);
759 pthread_mutex_unlock(&lines_lock);
760 if (line_ready_callback) {
761 line_ready_callback();
762 }
763 if (line.length) {
764 listent_t *dup = reverse_find(history.last, line.content, true);
765 if (dup) {
766 remove_entry(&history, dup);
767 }
768 add_entry(&history, line.content);
769 set_line("");
770 history_line = NULL;
771 }
772 complete_context = completion_length = 0;
773 break;
774 case CTL('H'):
775 case 0x7F: // Backspace
776 delete(1, false);
777 complete_context = completion_length = 0;
778 break;
779 case 0x1B:
780 switch (get_extended_key()) {
781 case MOD_SPECIAL(1): // Home
782 case MOD_SPECIAL(7):
783 case 'H':
784 set_position(0);
785 complete_context = completion_length = 0;
786 break;
787 case MOD_SPECIAL(8): // End
788 case 'F':
789 set_position(line.length);
790 complete_context = completion_length = 0;
791 break;
792 case MOD_SPECIAL(3): // Delete
793 delete(1, true);
794 complete_context = completion_length = 0;
795 break;
796 case 'A': // Up
797 if (!history_line) {
798 history_line = history.last;
799 }
800 else {
801 history_line = history_line->prev;
802 }
803 if (history_line) {
804 set_line(history_line->content);
805 complete_context = completion_length = 0;
806 }
807 else {
808 history_line = history.first;
809 printf("\a");
810 }
811
812 break;
813 case 'B': // Down
814 if (!history_line) {
815 printf("\a");
816 break;
817 }
818 history_line = history_line->next;
819 if (history_line) {
820 set_line(history_line->content);
821 complete_context = completion_length = 0;
822 }
823 else {
824 set_line("");
825 complete_context = completion_length = 0;
826 }
827 break;
828 case 'C': // Right
829 set_position(line.position + 1);
830 complete_context = completion_length = 0;
831 break;
832 case 'D': // Left
833 set_position(line.position - 1);
834 complete_context = completion_length = 0;
835 break;
836 case MOD_ALT('b'):
837 case MOD_ALT('D'):
838 move_word(false);
839 complete_context = completion_length = 0;
840 break;
841 case MOD_ALT('f'):
842 case MOD_ALT('C'):
843 move_word(true);
844 complete_context = completion_length = 0;
845 break;
846 case MOD_ALT(0x7F): // ALT+Backspace
847 delete_word(false);
848 complete_context = completion_length = 0;
849 break;
850 case MOD_ALT('('): // ALT+Delete
851 delete_word(true);
852 complete_context = completion_length = 0;
853 break;
854 default:
855 printf("\a");
856 break;
857 }
858 break;
859 case '\t': {
860 if (!no_csi) {
861 char temp = line.content[line.position - completion_length];
862 line.content[line.position - completion_length] = 0;
863 char *completion = completer? completer(line.content, &complete_context) : NULL;
864 line.content[line.position - completion_length] = temp;
865 if (completion) {
866 if (completion_length) {
867 delete(completion_length, false);
868 }
869 insert(completion);
870 completion_length = strlen(completion);
871 free(completion);
872 }
873 else {
874 printf("\a");
875 }
876 break;
877 }
878 }
879 default:
880 if (c >= ' ') {
881 char string[2] = {c, 0};
882 insert(string);
883 complete_context = completion_length = 0;
884 }
885 else {
886 printf("\a");
887 }
888 break;
889 }
890 fflush(stdout);
891 pthread_mutex_unlock(&terminal_lock);
892 }
893 return 0;
894 }
895
896
897 void CON_set_line_ready_callback(void (*callback)(void))
898 {
899 line_ready_callback = callback;
900 }
901
902 char *CON_readline(const char *new_prompt)
903 {
904 pthread_mutex_lock(&terminal_lock);
905 const char *old_prompt = prompt;
906 prompt = new_prompt;
907 prompt_length = strlen(prompt);
908 redraw_prompt(true);
909 fflush(stdout);
910 pthread_mutex_unlock(&terminal_lock);
911
912 pthread_mutex_lock(&lines_lock);
913 while (!lines.first) {
914 pthread_cond_wait(&lines_cond, &lines_lock);
915 }
916 char *ret = strdup(lines.first->content);
917 remove_entry(&lines, lines.first);
918 pthread_mutex_unlock(&lines_lock);
919
920 pthread_mutex_lock(&terminal_lock);
921 prompt = old_prompt;
922 prompt_length = strlen(prompt);
923 redraw_prompt(true);
924 fflush(stdout);
925 pthread_mutex_unlock(&terminal_lock);
926 return ret;
927 }
928
929 char *CON_readline_async(void)
930 {
931 char *ret = NULL;
932 pthread_mutex_lock(&lines_lock);
933 if (lines.first) {
934 ret = strdup(lines.first->content);
935 remove_entry(&lines, lines.first);
936 }
937 pthread_mutex_unlock(&lines_lock);
938 return ret;
939 }
940
941 bool CON_start(char *(*completer)(const char *substring, uintptr_t *context))
942 {
943 if (!initialize()) {
944 return false;
945 }
946 set_line("");
947 pthread_t thread;
948 return pthread_create(&thread, NULL, (void *)mainloop, completer) == 0;
949 }
950
951 void CON_attributed_print(const char *string, CON_attributes_t *attributes)
952 {
953 if (!initialized || no_csi) {
954 fprintf(stdout, "%s", string);
955 return;
956 }
957 static bool pending_newline = false;
958 pthread_mutex_lock(&terminal_lock);
959 printf(ESC("8"));
960 bool needs_reset = false;
961 if (attributes) {
962 if (attributes->color) {
963 if (attributes->color >= 0x10) {
964 printf(SGR("%d"), attributes->color - 0x11 + 90);
965 }
966 else {
967 printf(SGR("%d"), attributes->color - 1 + 30);
968 }
969 needs_reset = true;
970 }
971 if (attributes->background) {
972 if (attributes->background >= 0x10) {
973 printf(SGR("%d"), attributes->background - 0x11 + 100);
974 }
975 else {
976 printf(SGR("%d"), attributes->background - 1 + 40);
977 }
978 needs_reset = true;
979 }
980 if (attributes->bold) {
981 printf(SGR("1"));
982 needs_reset = true;
983 }
984 if (attributes->italic) {
985 printf(SGR("3"));
986 needs_reset = true;
987 }
988 if (attributes->underline) {
989 printf(SGR("4"));
990 needs_reset = true;
991 }
992 }
993 const char *it = string;
994 bool need_redraw_prompt = false;
995 while (*it) {
996 if (pending_newline) {
997 need_redraw_prompt = true;
998 printf("\n" CSI("K") "\n" CSI("A"));
999 pending_newline = false;
1000 continue;
1001 }
1002 if (*it == '\n') {
1003 printf("%.*s", (int)(it - string), string);
1004 string = it + 1;
1005 pending_newline = true;
1006 }
1007 it++;
1008 }
1009 if (*string) {
1010 printf("%s", string);
1011 }
1012 if (needs_reset) {
1013 printf(SGR("0"));
1014 }
1015 printf(ESC("7") CSI("B"));
1016 if (need_redraw_prompt) {
1017 redraw_prompt(true);
1018 }
1019 else {
1020 set_position(line.position);
1021 }
1022 fflush(stdout);
1023 pthread_mutex_unlock(&terminal_lock);
1024 }
1025
1026 void CON_print(const char *string)
1027 {
1028 CON_attributed_print(string, NULL);
1029 }
1030
1031 void CON_vprintf(const char *fmt, va_list args)
1032 {
1033 char *string = NULL;
1034 vasprintf(&string, fmt, args);
1035 CON_attributed_print(string, NULL);
1036 free(string);
1037 }
1038
1039 void CON_attributed_vprintf(const char *fmt, CON_attributes_t *attributes, va_list args)
1040 {
1041 char *string = NULL;
1042 vasprintf(&string, fmt, args);
1043 CON_attributed_print(string, attributes);
1044 free(string);
1045 }
1046
1047 void CON_printf(const char *fmt, ...)
1048 {
1049 va_list args;
1050 va_start(args, fmt);
1051 CON_vprintf(fmt, args);
1052 va_end(args);
1053 }
1054
1055
1056 void CON_attributed_printf(const char *fmt, CON_attributes_t *attributes,...)
1057 {
1058 va_list args;
1059 va_start(args, attributes);
1060 CON_attributed_vprintf(fmt, attributes, args);
1061 va_end(args);
1062 }
1063
1064 void CON_set_async_prompt(const char *string)
1065 {
1066 pthread_mutex_lock(&terminal_lock);
1067 prompt = string;
1068 prompt_length = strlen(string);
1069 redraw_prompt(true);
1070 fflush(stdout);
1071 pthread_mutex_unlock(&terminal_lock);
1072 }
1073
1074 void CON_set_repeat_empty(bool repeat)
1075 {
1076 repeat_empty = repeat;
1077 }
1078
1079 bool CON_no_csi_mode(void)
1080 {
1081 return no_csi;
1082 }
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.