Skip to content

Commit

Permalink
platform: find a universal solution for consuming unprocessed input
Browse files Browse the repository at this point in the history
The problem that we fixed for far2l (#49) can be also reproduced with Conpty and win32-input-mode.

I figured out that we can get any terminal to reply to us with a Device Status Report. So use that instead of relying on the far2l protocol.

This commit also fixes a bug when consuming unprocessed input. With a slow connection, we would fail to read the terminal response, which is why we were forced to add a timeout in 08d8357. This was caused by calling NcursesInput::getEvent, which would at times interpret the response sequence as an Alt+Key combination. In order to prevent confusion when debugging, wgetch is now only called directly from NcursesInputGetter.
  • Loading branch information
magiblot committed Oct 1, 2023
1 parent 57fa150 commit 4df1e3c
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 95 deletions.
5 changes: 0 additions & 5 deletions include/tvision/internal/far2l.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,13 @@ class EventSource;

#define far2lEnableSeq "\x1B_far2l1\x1B\\"
#define far2lDisableSeq "\x1B_far2l0\x1B\\"
#define far2lPingSeq "\x1B_far2l:FAR=\x1B\\"

enum { pingTimeout = 200 };

ParseResult parseFar2lAnswer(GetChBuf &, TEvent &, InputState &) noexcept;
ParseResult parseFar2lInput(GetChBuf &, TEvent &, InputState &) noexcept;

bool setFar2lClipboard(StdioCtl &, TStringView, InputState &) noexcept;
bool requestFar2lClipboard(StdioCtl &, InputState &) noexcept;

void waitFar2lPing(EventSource &, InputState &) noexcept;

} // namespace tvision

#endif // TVISION_FAR2L_H
23 changes: 16 additions & 7 deletions include/tvision/internal/ncursinp.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,47 @@
#define Uses_TEvent
#include <tvision/tv.h>

#include <internal/terminal.h>

namespace tvision
{

class NcursesDisplay;
struct InputState;

struct NcursesInputGetter final : public InputGetter
{
size_t pendingCount {0};

int get() noexcept override;
void unget(int k) noexcept override;
};

class NcursesInput : public InputStrategy
{
enum : char { KEY_ESC = '\x1B' };
enum { readTimeout = 10 };
enum { readTimeoutMs = 10 };

StdioCtl &io;
InputState &state;
bool mouseEnabled;
NcursesInputGetter in;

static int getch_nb() noexcept;
int getChNb() noexcept;
void detectAlt(int keys[4], bool &Alt) noexcept;
void parsePrintableChar(TEvent &ev, int keys[4], int &num_keys) noexcept;
void readUtf8Char(int keys[4], int &num_keys) noexcept;

bool parseCursesMouse(TEvent&) noexcept;
void consumeUnprocessedInput() noexcept;

public:

// Lifetimes of 'io', 'display' and 'state' must exceed that of 'this'.
NcursesInput(StdioCtl &io, NcursesDisplay &display, InputState &state, bool mouse) noexcept;
~NcursesInput();

bool getEvent(TEvent &ev) noexcept;
int getButtonCount() noexcept;
bool hasPendingEvents() noexcept;
bool getEvent(TEvent &ev) noexcept override;
int getButtonCount() noexcept override;
bool hasPendingEvents() noexcept override;
};

} // namespace tvision
Expand Down
6 changes: 4 additions & 2 deletions include/tvision/internal/terminal.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ namespace tvision
{

class StdioCtl;
class EventSource;

struct Far2lState
{
Expand All @@ -27,6 +26,7 @@ struct InputState
Far2lState far2l;
bool hasFullOsc52 {false};
bool bracketedPaste {false};
bool gotDsrResponse {false};
void (*putPaste)(TStringView) {nullptr};
};

Expand Down Expand Up @@ -123,7 +123,7 @@ namespace TermIO
void mouseOn(StdioCtl &) noexcept;
void mouseOff(StdioCtl &) noexcept;
void keyModsOn(StdioCtl &) noexcept;
void keyModsOff(StdioCtl &, EventSource &, InputState &) noexcept;
void keyModsOff(StdioCtl &) noexcept;

void normalizeKey(KeyDownEvent &keyDown) noexcept;

Expand All @@ -141,9 +141,11 @@ namespace TermIO
ParseResult parseFixTermKey(const CSIData &csi, TEvent&) noexcept;
ParseResult parseDCS(GetChBuf&, InputState&) noexcept;
ParseResult parseOSC(GetChBuf&, InputState&) noexcept;
ParseResult parseCPR(const CSIData &csi, InputState&) noexcept;
ParseResult parseWin32InputModeKeyOrEscapeSeq(const CSIData &, InputGetter&, TEvent&, InputState&) noexcept;

char *readUntilBelOrSt(GetChBuf &) noexcept;
void consumeUnprocessedInput(StdioCtl &, InputGetter &, InputState &) noexcept;
}

} // namespace tvision
Expand Down
26 changes: 0 additions & 26 deletions source/platform/far2l.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@
#include <internal/constmap.h>
#include <internal/base64.h>
#include <internal/events.h>
#include <chrono>

using std::chrono::milliseconds;
using std::chrono::steady_clock;

#include <time.h>

Expand All @@ -19,7 +15,6 @@ namespace tvision
// Request IDs
const char
f2lNoAnswer = '\0',
f2lPing = '\x04',
f2lClipGetData = '\xA0';

static char f2lClientIdData[32 + 1];
Expand Down Expand Up @@ -157,12 +152,6 @@ ParseResult parseFar2lAnswer(GetChBuf &buf, TEvent &ev, InputState &state) noexc
state.putPaste(text);
}
}
else if (decoded.size() > 0 && decoded.back() == f2lPing)
{
ev.what = evNothing;
ev.message.infoPtr = &state.far2l;
res = Accepted;
}
free(pDecoded);
}
free(s);
Expand Down Expand Up @@ -295,19 +284,4 @@ bool requestFar2lClipboard(StdioCtl &io, InputState &state) noexcept
return false;
}

void waitFar2lPing(EventSource &source, InputState &state) noexcept
{
if (state.far2l.enabled)
{
TEvent ev {};
auto begin = steady_clock::now();
do
{
source.getEvent(ev);
}
while ( (ev.what != evNothing || ev.message.infoPtr != &state.far2l) &&
steady_clock::now() - begin <= milliseconds(pingTimeout) );
}
}

} // namespace tvision
67 changes: 23 additions & 44 deletions source/platform/ncursinp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

#include <ncurses.h>
#include <internal/ncursinp.h>
#include <internal/terminal.h>
#include <internal/getenv.h>
#include <internal/utf8.h>
#include <internal/stdioctl.h>
Expand Down Expand Up @@ -256,19 +255,19 @@ static const auto fromCursesHighKey =
{ "kc2", {{kbDown}, 0} },
});

static class NcursesInputGetter : public InputGetter
int NcursesInputGetter::get() noexcept
{
int get() noexcept override
{
int k = wgetch(stdscr);
return k != ERR ? k : -1;
}
int k = wgetch(stdscr);
if (pendingCount > 0)
--pendingCount;
return k != ERR ? k : -1;
}

void unget(int k) noexcept override
{
ungetch(k);
}
} ncInputGetter;
void NcursesInputGetter::unget(int k) noexcept
{
if (ungetch(k) != ERR)
++pendingCount;
}

NcursesInput::NcursesInput( StdioCtl &aIo, NcursesDisplay &,
InputState &aState, bool mouse ) noexcept :
Expand All @@ -288,7 +287,7 @@ NcursesInput::NcursesInput( StdioCtl &aIo, NcursesDisplay &,
// Make getch practically non-blocking. Some terminals may feed input slowly.
// Note that we only risk blocking when reading multibyte characters
// or parsing escape sequences.
wtimeout(stdscr, readTimeout);
wtimeout(stdscr, readTimeoutMs);
/* Do not delay too much on ESC key presses, as the Alt modifier works well
* in most modern terminals. Still, this delay helps ncurses distinguish
* special key sequences, I believe. */
Expand All @@ -303,8 +302,8 @@ NcursesInput::~NcursesInput()
{
if (mouseEnabled)
TermIO::mouseOff(io);
TermIO::keyModsOff(io, *this, state);
consumeUnprocessedInput();
TermIO::keyModsOff(io);
TermIO::consumeUnprocessedInput(io, in, state);
}

int NcursesInput::getButtonCount() noexcept
Expand All @@ -314,36 +313,30 @@ int NcursesInput::getButtonCount() noexcept
return mouseEnabled ? 2 : 0;
}

int NcursesInput::getch_nb() noexcept
int NcursesInput::getChNb() noexcept
{
wtimeout(stdscr, 0);
int k = wgetch(stdscr);
wtimeout(stdscr, readTimeout);
int k = in.get();
wtimeout(stdscr, readTimeoutMs);
return k;
}

bool NcursesInput::hasPendingEvents() noexcept
{
int k = getch_nb();
if (k != ERR)
{
ungetch(k);
return true;
}
return false;
return in.pendingCount > 0;
}

bool NcursesInput::getEvent(TEvent &ev) noexcept
{
GetChBuf buf(ncInputGetter);
GetChBuf buf(in);
switch (TermIO::parseEvent(buf, ev, state))
{
case Rejected: buf.reject(); break;
case Accepted: return true;
case Ignored: return false;
}

int k = wgetch(stdscr);
int k = in.get();

if (k == KEY_RESIZE)
return false; // Handled by SigwinchHandler.
Expand Down Expand Up @@ -394,7 +387,7 @@ void NcursesInput::detectAlt(int keys[4], bool &Alt) noexcept
* we check if another character has been received. If it has, we consider this
* an Alt+Key combination. Of course, many other things sent by the terminal
* begin with ESC, but ncurses already identifies most of them. */
int k = getch_nb();
int k = getChNb();
if (k != ERR)
{
keys[0] = k;
Expand Down Expand Up @@ -423,7 +416,7 @@ void NcursesInput::readUtf8Char(int keys[4], int &num_keys) noexcept
* have to predict the number of bytes it is composed of, then read as many. */
num_keys += Utf8BytesLeft((char) keys[0]);
for (int i = 1; i < num_keys; ++i)
if ( ERR == (keys[i] = wgetch(stdscr)) )
if ((keys[i] = in.get()) == -1)
{
num_keys = i;
break;
Expand Down Expand Up @@ -472,7 +465,7 @@ bool NcursesInput::parseCursesMouse(TEvent &ev) noexcept
for (auto &parseMouse : {TermIO::parseSGRMouse,
TermIO::parseX10Mouse})
{
GetChBuf buf(ncInputGetter);
GetChBuf buf(in);
switch (parseMouse(buf, ev, state))
{
case Rejected: buf.reject(); break;
Expand All @@ -484,20 +477,6 @@ bool NcursesInput::parseCursesMouse(TEvent &ev) noexcept
}
}

void NcursesInput::consumeUnprocessedInput() noexcept
{
// This may be useful if the terminal has sent us events right before we
// disabled keyboard and mouse extensions, or if we have been killed by
// a signal.
TEvent ev;
wtimeout(stdscr, 0);
auto begin = steady_clock::now();
while ( getEvent(ev) &&
steady_clock::now() - begin <= milliseconds(readTimeout) )
;
wtimeout(stdscr, readTimeout);
}

} // namespace tvision

#endif // HAVE_NCURSES
Loading

0 comments on commit 4df1e3c

Please sign in to comment.