| 1999 | } |
| 2000 | } |
| 2001 | return NULL; |
| 2002 | } |
| 2003 | |
| 2004 | static void tui_start_input(void) { |
| 2005 | if (!isatty(STDIN_FILENO)) return; |
| 2006 | if (tcgetattr(STDIN_FILENO, &global_input.orig_termios) != 0) return; |
| 2007 | |
| 2008 | /* The input thread is intentionally boring: it never writes to the terminal, |
| 2009 | * it only queues navigation/control state. Rendering remains owned by the |
| 2010 | * main thread and follows the same full-frame redraw path as the |
| 2011 | * noninteractive UI. Keep ISIG set so Ctrl-C still restores the alternate |
| 2012 | * screen. */ |
| 2013 | struct termios raw = global_input.orig_termios; |
| 2014 | raw.c_lflag &= ~(ICANON | ECHO); |
| 2015 | raw.c_cc[VMIN] = 0; |
| 2016 | raw.c_cc[VTIME] = 1; |
| 2017 | if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) != 0) return; |
| 2018 | |
| 2019 | pthread_mutex_lock(&global_input.mu); |
| 2020 | global_input.move_delta = 0; |
| 2021 | global_input.enter_pressed = false; |
| 2022 | global_input.pause_pressed = false; |
| 2023 | global_input.quit_pressed = false; |
| 2024 | pthread_mutex_unlock(&global_input.mu); |
| 2025 | |
| 2026 | global_input.raw_mode = true; |
| 2027 | global_input.enabled = true; |
| 2028 | global_input.running = 1; |
| 2029 | if (pthread_create(&global_input.thread, NULL, input_thread_main, NULL) == 0) { |
| 2030 | global_input.thread_started = true; |
| 2031 | } else { |
| 2032 | global_input.running = 0; |
| 2033 | global_input.enabled = false; |
| 2034 | tcsetattr(STDIN_FILENO, TCSANOW, &global_input.orig_termios); |
| 2035 | global_input.raw_mode = false; |
| 2036 | } |
| 2037 | } |
| 2038 | |
| 2039 | static void tui_stop_input(void) { |
| 2040 | if (global_input.thread_started) { |
| 2041 | global_input.running = 0; |
| 2042 | pthread_join(global_input.thread, NULL); |
| 2043 | global_input.thread_started = false; |
| 2044 | } |
| 2045 | if (global_input.raw_mode) { |
| 2046 | tcsetattr(STDIN_FILENO, TCSANOW, &global_input.orig_termios); |
| 2047 | global_input.raw_mode = false; |
| 2048 | } |
| 2049 | global_input.enabled = false; |
| 2050 | } |
| 2051 | |
| 2052 | static void tui_consume_input(eval_ui *ui) { |
| 2053 | if (!ui->enabled || !global_input.enabled) return; |
| 2054 | |
| 2055 | pthread_mutex_lock(&global_input.mu); |
| 2056 | int move = global_input.move_delta; |
| 2057 | bool enter = global_input.enter_pressed; |
| 2058 | bool pause = global_input.pause_pressed; |
no test coverage detected