From 0b6febc9f5cd5a631ce4e267f4e74d18570b6855 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:20:55 +0530 Subject: [PATCH 1/9] Handle focus reporting race * Disable focus reporting after reading standard input * Some slow terminals may batch process the enable and disable requests if they are sent in succession. * The terminal may then not send focus escape sequences at all. * Make the standard input read time out from the start of the read instead of after a byte is available * The aforementioned slow terminals may not send escape sequences, so the read will then block forever. * Check for errors getting or setting terminal attributes before enabling focus reporting --- custom-prompt/focus_utils.cc | 48 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index 30058eaa..a64693be 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -33,50 +33,59 @@ bool terminal_has_focus(void) #include #include -#define BUFSIZE 255 - /** * Temporarily modify standard input to allow non-blocking reads. */ class NonBlockingStandardInputGuard { private: - bool error_occurred; + bool m_error_occurred; termios prev_termios, curr_termios; public: NonBlockingStandardInputGuard(void); + bool error_occurred(void) const; ~NonBlockingStandardInputGuard(); }; /** * Enable non-canonical mode. */ -NonBlockingStandardInputGuard::NonBlockingStandardInputGuard(void) : error_occurred(false) +NonBlockingStandardInputGuard::NonBlockingStandardInputGuard(void) : m_error_occurred(false) { if (tcgetattr(STDIN_FILENO, &this->prev_termios) == -1) { - this->error_occurred = true; + this->m_error_occurred = true; return; } this->curr_termios = this->prev_termios; this->curr_termios.c_lflag &= ~(ECHO | ICANON); - // Block until sufficiently many bytes are available from standard input. - this->curr_termios.c_cc[VMIN] = BUFSIZE; - // But not for too long. + // Read standard input with a timeout. + this->curr_termios.c_cc[VMIN] = 0; this->curr_termios.c_cc[VTIME] = 1; if (tcsetattr(STDIN_FILENO, TCSANOW, &this->curr_termios) == -1) { - this->error_occurred = true; + this->m_error_occurred = true; } } +/** + * Check whether an error occurred while trying to get or set terminal + * attributes. + * + * @return Error indicator. + */ +bool NonBlockingStandardInputGuard::error_occurred(void) const +{ + return this->m_error_occurred; +} + /** * Disable non-canonical mode. */ NonBlockingStandardInputGuard::~NonBlockingStandardInputGuard() { - if (!this->error_occurred) + if (!this->m_error_occurred) { tcsetattr(STDIN_FILENO, TCSANOW, &this->prev_termios); } @@ -84,13 +93,24 @@ NonBlockingStandardInputGuard::~NonBlockingStandardInputGuard() bool terminal_has_focus(void) { - char buf[BUFSIZE]; + char buf[1024]; ssize_t count; { - NonBlockingStandardInputGuard _; - // Enable and immediately disable focus reporting. - std::clog << "\x1b\x5b?1004h\x1b\x5b?1004l"; + NonBlockingStandardInputGuard guard; + if (guard.error_occurred()) + { + return false; + } + // Enable focus reporting. + std::clog << "\x1b\x5b?1004h"; count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); + // Disable focus reporting. If it is disabled immediately after + // enabling it instead of here, the terminal may never send any focus + // escape sequences. There is a small chance that the sequences get + // written to standard input before focus reporting gets disabled but + // after the previous sequences have been read. It should cause no + // damage, so I'll allow it. + std::clog << "\x1b\x5b?1004l"; } LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } }); if (count <= 0) From d6be0eff7d6c150c427d11c2ec2015d1ca0d54e1 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:32:23 +0530 Subject: [PATCH 2/9] Comment [skip ci] --- custom-prompt/focus_utils.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index a64693be..65ae6cfa 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -60,7 +60,7 @@ NonBlockingStandardInputGuard::NonBlockingStandardInputGuard(void) : m_error_occ } this->curr_termios = this->prev_termios; this->curr_termios.c_lflag &= ~(ECHO | ICANON); - // Read standard input with a timeout. + // Set a timeout on standard input reads. this->curr_termios.c_cc[VMIN] = 0; this->curr_termios.c_cc[VTIME] = 1; if (tcsetattr(STDIN_FILENO, TCSANOW, &this->curr_termios) == -1) From 828f8d98ffc6edc79e8b4c6c3eaf25c381345f7a Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:39:51 +0530 Subject: [PATCH 3/9] Experimenting with improvements [skip ci] --- custom-prompt/focus_utils.cc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index 65ae6cfa..c1571182 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -96,6 +96,20 @@ bool terminal_has_focus(void) char buf[1024]; ssize_t count; { + // Consume standard input so that anything entered previously does not + // get read instead of the focus escape sequence. + while (true) + { + count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); + if (count < 0) + { + return false; + } + if (count == 0) + { + break; + } + } NonBlockingStandardInputGuard guard; if (guard.error_occurred()) { From fe50e041455c353934e1cd68993c7a5e81b1ab92 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:42:52 +0530 Subject: [PATCH 4/9] Put standard input consumption in correct place [skip ci] --- custom-prompt/focus_utils.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index c1571182..fa977344 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -96,6 +96,13 @@ bool terminal_has_focus(void) char buf[1024]; ssize_t count; { + NonBlockingStandardInputGuard guard; + if (guard.error_occurred()) + { + return false; + } + // Enable focus reporting. + std::clog << "\x1b\x5b?1004h"; // Consume standard input so that anything entered previously does not // get read instead of the focus escape sequence. while (true) @@ -110,13 +117,6 @@ bool terminal_has_focus(void) break; } } - NonBlockingStandardInputGuard guard; - if (guard.error_occurred()) - { - return false; - } - // Enable focus reporting. - std::clog << "\x1b\x5b?1004h"; count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); // Disable focus reporting. If it is disabled immediately after // enabling it instead of here, the terminal may never send any focus From f7e10c81e101806a5d62624de33c4efbc79620e0 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:44:46 +0530 Subject: [PATCH 5/9] Put standard input consumption in correct place (again) [skip ci] --- custom-prompt/focus_utils.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index fa977344..ea70794a 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -101,8 +101,6 @@ bool terminal_has_focus(void) { return false; } - // Enable focus reporting. - std::clog << "\x1b\x5b?1004h"; // Consume standard input so that anything entered previously does not // get read instead of the focus escape sequence. while (true) @@ -117,6 +115,8 @@ bool terminal_has_focus(void) break; } } + // Enable focus reporting. + std::clog << "\x1b\x5b?1004h"; count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); // Disable focus reporting. If it is disabled immediately after // enabling it instead of here, the terminal may never send any focus From 4786ac652c79232495a732ad72ad90ac0c779caa Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 01:56:48 +0530 Subject: [PATCH 6/9] Comment wording and code simplification [skip ci] --- custom-prompt/focus_utils.cc | 77 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index ea70794a..12bafd63 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -101,48 +101,47 @@ bool terminal_has_focus(void) { return false; } - // Consume standard input so that anything entered previously does not - // get read instead of the focus escape sequence. - while (true) + // Consume standard input so that anything entered previously is + // removed and it is ready to receive the focus escape sequence. There + // is a small chance that the user types something after consuming + // standard input but before the terminal sends the sequence. This rare + // failure is all right. + while ((count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf)) > 0) { - count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); - if (count < 0) - { - return false; - } - if (count == 0) - { - break; - } } - // Enable focus reporting. - std::clog << "\x1b\x5b?1004h"; - count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); - // Disable focus reporting. If it is disabled immediately after - // enabling it instead of here, the terminal may never send any focus - // escape sequences. There is a small chance that the sequences get - // written to standard input before focus reporting gets disabled but - // after the previous sequences have been read. It should cause no - // damage, so I'll allow it. - std::clog << "\x1b\x5b?1004l"; - } - LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } }); - if (count <= 0) - { - return false; - } - std::string_view buf_view(buf, count); - size_t focus_out_seq_pos = buf_view.rfind("\x1b\x5bO"); - if (focus_out_seq_pos == std::string_view::npos) - { - return true; - } - size_t focus_in_seq_pos = buf_view.rfind("\x1b\x5bI"); - if (focus_in_seq_pos == std::string_view::npos) - { - return false; + if (count < 0) + { + return false; + } } - return focus_out_seq_pos < focus_in_seq_pos; + // Enable focus reporting. + std::clog << "\x1b\x5b?1004h"; + count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); + // Disable focus reporting. If it is disabled immediately after + // enabling it instead of here, the terminal may never send any focus + // escape sequences. There is a small chance that more sequences get + // written to standard input before focus reporting gets disabled but after + // the previous sequences have been read. This rare failure is all right. + std::clog << "\x1b\x5b?1004l"; +} + +LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } }); +if (count <= 0) +{ + return false; +} +std::string_view buf_view(buf, count); +size_t focus_out_seq_pos = buf_view.rfind("\x1b\x5bO"); +if (focus_out_seq_pos == std::string_view::npos) +{ + return true; +} +size_t focus_in_seq_pos = buf_view.rfind("\x1b\x5bI"); +if (focus_in_seq_pos == std::string_view::npos) +{ + return false; +} +return focus_out_seq_pos < focus_in_seq_pos; } #endif From 13d8aed58c6c1b78bb37a2c386421d8bb4b3a3d2 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:00:43 +0530 Subject: [PATCH 7/9] Wording [skip ci] --- custom-prompt/focus_utils.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index 12bafd63..2d2bfa48 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -102,10 +102,10 @@ bool terminal_has_focus(void) return false; } // Consume standard input so that anything entered previously is - // removed and it is ready to receive the focus escape sequence. There - // is a small chance that the user types something after consuming - // standard input but before the terminal sends the sequence. This rare - // failure is all right. + // removed and it is ready to receive focus escape sequences. There is + // a small chance that the user types something after consuming + // standard input but before the terminal sends the sequences. This + // rare failure is acceptable. while ((count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf)) > 0) { } @@ -117,11 +117,11 @@ bool terminal_has_focus(void) // Enable focus reporting. std::clog << "\x1b\x5b?1004h"; count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); - // Disable focus reporting. If it is disabled immediately after - // enabling it instead of here, the terminal may never send any focus - // escape sequences. There is a small chance that more sequences get - // written to standard input before focus reporting gets disabled but after - // the previous sequences have been read. This rare failure is all right. + // Disable focus reporting. If it is disabled immediately after enabling it + // instead of here, the terminal may never send any focus escape sequences. + // There is a small chance that more sequences get written to standard + // input before focus reporting gets disabled but after the previous + // sequences have been read. This rare failure is acceptable. std::clog << "\x1b\x5b?1004l"; } From d67cc3d6ffddb4cfce0e519a2132071f4cfa49e9 Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:01:55 +0530 Subject: [PATCH 8/9] Fixed wrong formatting [skip ci] --- custom-prompt/focus_utils.cc | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index 2d2bfa48..decfb0ed 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -113,35 +113,35 @@ bool terminal_has_focus(void) { return false; } + + // Enable focus reporting. + std::clog << "\x1b\x5b?1004h"; + count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); + // Disable focus reporting. If it is disabled immediately after enabling it + // instead of here, the terminal may never send any focus escape sequences. + // There is a small chance that more sequences get written to standard + // input before focus reporting gets disabled but after the previous + // sequences have been read. This rare failure is acceptable. + std::clog << "\x1b\x5b?1004l"; } - // Enable focus reporting. - std::clog << "\x1b\x5b?1004h"; - count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); - // Disable focus reporting. If it is disabled immediately after enabling it - // instead of here, the terminal may never send any focus escape sequences. - // There is a small chance that more sequences get written to standard - // input before focus reporting gets disabled but after the previous - // sequences have been read. This rare failure is acceptable. - std::clog << "\x1b\x5b?1004l"; -} -LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } }); -if (count <= 0) -{ - return false; -} -std::string_view buf_view(buf, count); -size_t focus_out_seq_pos = buf_view.rfind("\x1b\x5bO"); -if (focus_out_seq_pos == std::string_view::npos) -{ - return true; -} -size_t focus_in_seq_pos = buf_view.rfind("\x1b\x5bI"); -if (focus_in_seq_pos == std::string_view::npos) -{ - return false; -} -return focus_out_seq_pos < focus_in_seq_pos; + LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } }); + if (count <= 0) + { + return false; + } + std::string_view buf_view(buf, count); + size_t focus_out_seq_pos = buf_view.rfind("\x1b\x5bO"); + if (focus_out_seq_pos == std::string_view::npos) + { + return true; + } + size_t focus_in_seq_pos = buf_view.rfind("\x1b\x5bI"); + if (focus_in_seq_pos == std::string_view::npos) + { + return false; + } + return focus_out_seq_pos < focus_in_seq_pos; } #endif From f3092fa22e6c42c6bfd47b3479f67199f40f6b8a Mon Sep 17 00:00:00 2001 From: Vishal Pankaj Chandratreya <19171016+tfpf@users.noreply.github.com> Date: Thu, 23 Apr 2026 02:06:22 +0530 Subject: [PATCH 9/9] Comments --- custom-prompt/focus_utils.cc | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/custom-prompt/focus_utils.cc b/custom-prompt/focus_utils.cc index decfb0ed..49f80d06 100644 --- a/custom-prompt/focus_utils.cc +++ b/custom-prompt/focus_utils.cc @@ -101,13 +101,15 @@ bool terminal_has_focus(void) { return false; } + // Consume standard input so that anything entered previously is // removed and it is ready to receive focus escape sequences. There is - // a small chance that the user types something after consuming - // standard input but before the terminal sends the sequences. This - // rare failure is acceptable. + // a small chance that the user types something after standard input is + // consumed but before the terminal sends the sequences. This rare + // failure is acceptable. while ((count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf)) > 0) { + LOG_DEBUG(logger, "Cleared standard input", { { "count", count } }); } if (count < 0) { @@ -117,11 +119,13 @@ bool terminal_has_focus(void) // Enable focus reporting. std::clog << "\x1b\x5b?1004h"; count = read(STDIN_FILENO, buf, sizeof buf / sizeof *buf); - // Disable focus reporting. If it is disabled immediately after enabling it - // instead of here, the terminal may never send any focus escape sequences. - // There is a small chance that more sequences get written to standard - // input before focus reporting gets disabled but after the previous - // sequences have been read. This rare failure is acceptable. + + // Disable focus reporting. If it is disabled immediately after + // enabling it instead of here, the terminal may never send any focus + // escape sequences. There is a small chance that more sequences get + // written to standard input before focus reporting gets disabled but + // after the previous sequences have been read. This rare failure is + // acceptable. std::clog << "\x1b\x5b?1004l"; }