Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 51 additions & 14 deletions custom-prompt/focus_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,65 +33,102 @@ bool terminal_has_focus(void)
#include <termios.h>
#include <unistd.h>

#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.
// 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)
{
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);
}
}

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;
}

// 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 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)
{
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";
}

LOG_DEBUG(logger, "Read non-blocking standard input", { { "count", count } });
if (count <= 0)
{
Expand Down
Loading