diff --git a/ds4_agent.c b/ds4_agent.c index bbd62e9d..8ac2e987 100644 --- a/ds4_agent.c +++ b/ds4_agent.c @@ -145,6 +145,7 @@ typedef struct { bool more_valid; agent_bash_job *bash_jobs; int next_bash_job_id; + bool raw_mode_needs_restore; } agent_worker; static unsigned agent_next_prefill_label(void); @@ -6586,6 +6587,7 @@ struct agent_bash_job { bool running; bool timed_out; struct agent_bash_job *next; + agent_worker *worker; /* back-pointer for terminal state restoration */ }; static int agent_bash_display_lines(const agent_bash_job *job) { @@ -6674,6 +6676,13 @@ static void agent_bash_finalize(agent_bash_job *job, int status) { else if (WIFSIGNALED(status)) job->exit_status = 128 + WTERMSIG(status); else job->exit_status = -1; job->running = false; + /* A child process (especially sh) may have changed the terminal mode + * from raw to cooked. Notify the UI thread to restore raw mode. */ + if (job->worker) { + pthread_mutex_lock(&job->worker->mu); + job->worker->raw_mode_needs_restore = true; + pthread_mutex_unlock(&job->worker->mu); + } } /* Drain available output, notice process exit, and enforce timeout. This is @@ -6741,6 +6750,17 @@ static agent_bash_job *agent_bash_start(agent_worker *w, const char *cmd, if (pid == 0) { setpgid(0, 0); close(tmpfd); + /* Redirect stdin to /dev/null so the shell does not attempt to + * change the terminal mode (cooked/raw) on the controlling + * terminal. If the shell sets cooked mode while linenoise has + * raw mode active, the terminal becomes unresponsive to user + * input until the process exits and linenoise re-enables raw + * mode. */ + int null_fd = open("/dev/null", O_RDONLY); + if (null_fd >= 0) { + dup2(null_fd, STDIN_FILENO); + close(null_fd); + } close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); dup2(pipefd[1], STDERR_FILENO); @@ -6767,6 +6787,7 @@ static agent_bash_job *agent_bash_start(agent_worker *w, const char *cmd, job->timeout_sec = timeout_sec; job->exit_status = -1; job->running = true; + job->worker = w; job->next = w->bash_jobs; w->bash_jobs = job; return job; @@ -8030,6 +8051,19 @@ static int set_nonblock(int fd, bool on, int *old_flags) { return fcntl(fd, F_SETFL, next); } +/* Check and clear the raw_mode_needs_restore flag under the worker mutex. + * Returns true if the UI thread should call linenoiseRestoreRawMode(). */ +static bool worker_check_raw_mode_restore(agent_worker *w) { + bool needs = false; + pthread_mutex_lock(&w->mu); + if (w->raw_mode_needs_restore) { + w->raw_mode_needs_restore = false; + needs = true; + } + pthread_mutex_unlock(&w->mu); + return needs; +} + static void drain_wake_fd(int fd) { char buf[128]; for (;;) { @@ -9704,6 +9738,11 @@ static int run_agent(ds4_engine *engine, agent_config *cfg) { bool force_status_redraw_after_restart = false; char *restore_line = NULL; while (running) { + /* If a bash child process changed the terminal mode (e.g., from raw + * to cooked), restore raw mode so linenoise continues to work. */ + if (worker_check_raw_mode_restore(&worker)) { + linenoiseRestoreRawMode(); + } struct pollfd pfd[2] = { {.fd = STDIN_FILENO, .events = POLLIN}, {.fd = worker.wake_fd[0], .events = POLLIN}, diff --git a/linenoise.c b/linenoise.c index f0b78eb8..7b1d8bba 100644 --- a/linenoise.c +++ b/linenoise.c @@ -624,6 +624,29 @@ static void disableRawMode(int fd) { } } +/* Re-enable raw mode if a child process changed the terminal state. This is + * useful when the application spawns external processes that may reset the + * terminal to cooked mode. The caller should ensure that no concurrent + * linenoise operations are in progress (e.g., not inside a refresh cycle). */ +void linenoiseRestoreRawMode(void) { + if (getenv("LINENOISE_ASSUME_TTY")) return; + if (!isatty(STDIN_FILENO)) return; + /* Re-fetch the original terminal attributes (they may have been changed + * by a child process) and re-apply raw mode. */ + struct termios raw; + if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) return; + raw = orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); + rawmode = 1; + /* Re-enable bracketed paste mode. */ + if (write(rawmode_output, "\x1b[?2004h", 8) == -1) {} +} + /* Use the ESC [6n escape sequence to query the horizontal cursor position * and return it. On error -1 is returned, on success the position of the * cursor. */ diff --git a/linenoise.h b/linenoise.h index 0657e0f7..3f37a63c 100644 --- a/linenoise.h +++ b/linenoise.h @@ -139,6 +139,8 @@ void linenoiseSetMultiLine(int ml); void linenoisePrintKeyCodes(void); void linenoiseMaskModeEnable(void); void linenoiseMaskModeDisable(void); +/* Re-enable raw mode if a child process changed the terminal state. */ +void linenoiseRestoreRawMode(void); #ifdef __cplusplus }