From 925f10684628ab0c4776ed47f7344a74da40fe51 Mon Sep 17 00:00:00 2001 From: Markus Gans Date: Thu, 5 Mar 2020 21:30:54 +0100 Subject: [PATCH] Unbuffered reading of keystrokes for better latency --- ChangeLog | 4 ++++ examples/transparent.cpp | 1 + examples/ui.cpp | 1 + src/fapplication.cpp | 10 +++++++--- src/fkeyboard.cpp | 21 ++++++++++---------- src/fmouse.cpp | 2 +- src/fvterm.cpp | 7 +++++++ src/fwidget.cpp | 36 +++++++++++++++++------------------ src/fwidget_functions.cpp | 2 +- src/include/final/fkeyboard.h | 21 +++++++++++++++++--- src/include/final/fvterm.h | 12 ++++++++++++ src/include/final/fwidget.h | 2 +- test/fkeyboard-test.cpp | 28 ++++++++++++++++++++++++--- 13 files changed, 107 insertions(+), 40 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7919ed7c..a06c45e1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2020-03-05 Markus Gans + * Unbuffered reading of keystrokes for better latency + * Mouse adjustments when resizing an rxvt terminal + 2020-02-25 Markus Gans * New command line switch "--no-terminal-data-request" to disable font and title determination diff --git a/examples/transparent.cpp b/examples/transparent.cpp index 8245edae..ab40bca5 100644 --- a/examples/transparent.cpp +++ b/examples/transparent.cpp @@ -274,6 +274,7 @@ int main (int argc, char* argv[]) { // Create the application object finalcut::FApplication app (argc, argv); + app.setNonBlockingRead(); // Create main dialog object MainWindow main_dlg (&app); diff --git a/examples/ui.cpp b/examples/ui.cpp index 8bb5e7cc..0863b07c 100644 --- a/examples/ui.cpp +++ b/examples/ui.cpp @@ -1004,6 +1004,7 @@ int main (int argc, char* argv[]) // Create the application object app finalcut::FApplication app(argc, argv); + app.setNonBlockingRead(); app.redefineDefaultColors(true); app.setTermTitle (title); diff --git a/src/fapplication.cpp b/src/fapplication.cpp index 0765114e..d6665800 100644 --- a/src/fapplication.cpp +++ b/src/fapplication.cpp @@ -684,13 +684,11 @@ void FApplication::processKeyboardEvent() findKeyboardWidget(); flush(); + keyboard->escapeKeyHandling(); // special case: Esc key keyboard->clearKeyBufferOnTimeout(); if ( isKeyPressed() ) keyboard->fetchKeyCode(); - - // special case: Esc key - keyboard->escapeKeyHandling(); } //---------------------------------------------------------------------- @@ -1121,6 +1119,12 @@ void FApplication::processResizeEvent() if ( ! hasChangedTermSize() ) return; + if ( mouse ) + { + mouse->setMaxWidth (uInt16(getDesktopWidth())); + mouse->setMaxHeight (uInt16(getDesktopHeight())); + } + FResizeEvent r_ev(fc::Resize_Event); sendEvent(app_object, &r_ev); diff --git a/src/fkeyboard.cpp b/src/fkeyboard.cpp index 87230f63..54cee144 100644 --- a/src/fkeyboard.cpp +++ b/src/fkeyboard.cpp @@ -42,7 +42,8 @@ namespace finalcut { // static class attributes -uInt64 FKeyboard::key_timeout{100000}; // 100 ms (default timeout for keypress) +uInt64 FKeyboard::read_blocking_time{100000}; // preset to 100 ms +uInt64 FKeyboard::key_timeout{100000}; // preset to 100 ms struct timeval FKeyboard::time_keypressed{}; #if defined(__linux__) @@ -123,7 +124,7 @@ bool FKeyboard::isKeyPressed() FD_ZERO(&ifds); FD_SET(stdin_no, &ifds); tv.tv_sec = 0; - tv.tv_usec = 100000; // 100 ms + tv.tv_usec = FKeyboard::read_blocking_time; // preset to 100 ms const int result = select (stdin_no + 1, &ifds, nullptr, nullptr, &tv); if ( result > 0 && FD_ISSET(stdin_no, &ifds) ) @@ -290,6 +291,7 @@ inline FKey FKeyboard::getSingleKey() if ( utf8_input && (firstchar & 0xc0) == 0xc0 ) { char utf8char[5]{}; // Init array with '\0' + const std::size_t buf_len = std::strlen(fifo_buf); if ( (firstchar & 0xe0) == 0xc0 ) len = 2; @@ -298,6 +300,9 @@ inline FKey FKeyboard::getSingleKey() else if ( (firstchar & 0xf8) == 0xf0 ) len = 4; + if ( buf_len < len && ! isKeypressTimeout() ) + return fc::need_more_data; + for (std::size_t i{0}; i < len ; i++) utf8char[i] = char(fifo_buf[i] & 0xff); @@ -403,7 +408,7 @@ FKey FKeyboard::UTF8decode (const char utf8[]) inline ssize_t FKeyboard::readKey() { setNonBlockingInput(); - const ssize_t bytes = read(FTermios::getStdIn(), &read_buf, READ_BUF_SIZE - 1); + const ssize_t bytes = read(FTermios::getStdIn(), &read_character, 1); unsetNonBlockingInput(); return bytes; } @@ -418,12 +423,8 @@ void FKeyboard::parseKeyBuffer() { if ( bytesread + fifo_offset <= int(FIFO_BUF_SIZE) ) { - for (std::size_t i{0}; i < std::size_t(bytesread); i++) - { - fifo_buf[fifo_offset] = read_buf[i]; - fifo_offset++; - } - + fifo_buf[fifo_offset] = char(read_character); + fifo_offset++; fifo_in_use = true; } @@ -453,7 +454,7 @@ void FKeyboard::parseKeyBuffer() key = 0; } - std::fill_n (read_buf, READ_BUF_SIZE, '\0'); + read_character = 0; } //---------------------------------------------------------------------- diff --git a/src/fmouse.cpp b/src/fmouse.cpp index 73ed15bf..7f135a43 100644 --- a/src/fmouse.cpp +++ b/src/fmouse.cpp @@ -527,7 +527,7 @@ int FMouseGPM::gpmEvent (bool clear) FD_SET(stdin_no, &ifds); FD_SET(gpm_fd, &ifds); tv.tv_sec = 0; - tv.tv_usec = 100000; // 100 ms + tv.tv_usec = FKeyboard::getReadBlockingTime(); // preset to 100 ms const int result = select (max + 1, &ifds, nullptr, nullptr, &tv); if ( result > 0 && FD_ISSET(stdin_no, &ifds) ) diff --git a/src/fvterm.cpp b/src/fvterm.cpp index ec2db5d4..009b5b39 100644 --- a/src/fvterm.cpp +++ b/src/fvterm.cpp @@ -182,6 +182,13 @@ FColor FVTerm::rgb2ColorIndex (uInt8 r, uInt8 g, uInt8 b) return 16 + ri + gi + bi; } +//---------------------------------------------------------------------- +void FVTerm::setNonBlockingRead (bool enable) +{ + uInt64 blocking_time = (enable) ? 0 : 100000; // 0 or 100 ms + FKeyboard::setReadBlockingTime (blocking_time); +} + //---------------------------------------------------------------------- void FVTerm::clearArea (int fillchar) { diff --git a/src/fwidget.cpp b/src/fwidget.cpp index 33b4c75d..0af10ef4 100644 --- a/src/fwidget.cpp +++ b/src/fwidget.cpp @@ -36,7 +36,7 @@ namespace finalcut { // global FWidget object -static FWidget* rootObject{nullptr}; +static FWidget* root_widget{nullptr}; // static class attributes FStatusBar* FWidget::statusbar{nullptr}; @@ -59,7 +59,7 @@ uInt FWidget::modal_dialog_counter{}; // constructors and destructor //---------------------------------------------------------------------- FWidget::FWidget (FWidget* parent, bool disable_alt_screen) - : FVTerm( ! (bool(parent) || rootObject), disable_alt_screen) + : FVTerm( ! (bool(parent) || root_widget), disable_alt_screen) , FObject(parent) { // init bit field with 0 @@ -73,7 +73,7 @@ FWidget::FWidget (FWidget* parent, bool disable_alt_screen) if ( ! parent ) { - if ( rootObject ) + if ( root_widget ) { auto ftermdata = getFTerm().getFTermData(); ftermdata->setExitMessage("FWidget: No parent defined! " @@ -82,12 +82,12 @@ FWidget::FWidget (FWidget* parent, bool disable_alt_screen) return; } - rootObject = this; + root_widget = this; show_root_widget = nullptr; redraw_root_widget = nullptr; modal_dialog_counter = 0; statusbar = nullptr; - init(); + initRootWidget(); } else { @@ -133,7 +133,7 @@ FWidget::~FWidget() // destructor accelerator_list.clear(); // finish the program - if ( rootObject == this ) + if ( root_widget == this ) finish(); } @@ -454,7 +454,7 @@ void FWidget::setTopPadding (int top, bool adjust) { if ( isRootWidget() ) { - auto r = rootObject; + auto r = root_widget; r->wclient_offset.setY1 (r->padding.top); adjustSizeGlobal(); } @@ -475,7 +475,7 @@ void FWidget::setLeftPadding (int left, bool adjust) { if ( isRootWidget() ) { - auto r = rootObject; + auto r = root_widget; r->wclient_offset.setX1 (r->padding.left); adjustSizeGlobal(); } @@ -496,7 +496,7 @@ void FWidget::setBottomPadding (int bottom, bool adjust) { if ( isRootWidget() ) { - auto r = rootObject; + auto r = root_widget; r->wclient_offset.setY2 (int(r->getHeight()) - 1 - r->padding.bottom); adjustSizeGlobal(); } @@ -517,7 +517,7 @@ void FWidget::setRightPadding (int right, bool adjust) { if ( isRootWidget() ) { - auto r = rootObject; + auto r = root_widget; r->wclient_offset.setX2 (int(r->getWidth()) - 1 - r->padding.right); adjustSizeGlobal(); } @@ -533,8 +533,8 @@ void FWidget::setTermSize (const FSize& size) if ( isXTerminal() ) { - rootObject->wsize.setRect(FPoint(1, 1), size); - rootObject->adjust_wsize = rootObject->wsize; + root_widget->wsize.setRect(FPoint(1, 1), size); + root_widget->adjust_wsize = root_widget->wsize; FTerm::setTermSize(size); // width = columns / height = lines detectTermSize(); } @@ -995,7 +995,7 @@ void FWidget::show() { // Sets the initial screen settings initScreenSettings(); - // Draw the vdesktop + // Initializing vdesktop const auto& r = getRootWidget(); setColor(r->getForegroundColor(), r->getBackgroundColor()); clearArea (getVirtualDesktop()); @@ -1292,7 +1292,7 @@ void FWidget::adjustSize() if ( ignore_padding && ! isDialogWidget() ) setTermOffset(); else - woffset = rootObject->wclient_offset; + woffset = root_widget->wclient_offset; } else if ( ignore_padding && p ) { @@ -1654,8 +1654,8 @@ void FWidget::onAccel (FAccelEvent*) void FWidget::onResize (FResizeEvent* ev) { // The terminal was resized - rootObject->resize(); - rootObject->redraw(); + root_widget->resize(); + root_widget->redraw(); ev->accept(); } @@ -1676,7 +1676,7 @@ void FWidget::onClose (FCloseEvent* ev) // private methods of FWidget //---------------------------------------------------------------------- -void FWidget::init() +void FWidget::initRootWidget() { try { @@ -2012,7 +2012,7 @@ void FWidget::setStatusbarText (bool enable) //---------------------------------------------------------------------- void detectTermSize() { - const auto& r = rootObject; + const auto& r = root_widget; FTerm::detectTermSize(); r->adjust_wsize.setRect (1, 1, r->getDesktopWidth(), r->getDesktopHeight()); r->woffset.setRect (0, 0, r->getDesktopWidth(), r->getDesktopHeight()); diff --git a/src/fwidget_functions.cpp b/src/fwidget_functions.cpp index 258a0476..37304af9 100644 --- a/src/fwidget_functions.cpp +++ b/src/fwidget_functions.cpp @@ -166,7 +166,7 @@ void drawTransparentShadow (FWidget* w) w->print() << FPoint(int(width) + 1, int(y) + 1) << " "; } - w->print() << FStyle (fc::Transparent) + w->print() << FStyle (fc::Reset) << FStyle (fc::Transparent) << FPoint (1, int(height) + 1) << " " << FStyle (fc::Reset) diff --git a/src/include/final/fkeyboard.h b/src/include/final/fkeyboard.h index 1f88a559..4405af2c 100644 --- a/src/include/final/fkeyboard.h +++ b/src/include/final/fkeyboard.h @@ -104,10 +104,13 @@ class FKeyboard final const FString getKeyName (const FKey); keybuffer& getKeyBuffer(); timeval* getKeyPressedTime(); + static uInt64 getKeypressTimeout(); + static uInt64 getReadBlockingTime(); // Mutators void setTermcapMap (fc::FKeyMap*); - void setKeypressTimeout (const uInt64); + static void setKeypressTimeout (const uInt64); + static void setReadBlockingTime (const uInt64); void enableUTF8(); void disableUTF8(); void enableMouseSequences(); @@ -130,7 +133,6 @@ class FKeyboard final private: // Constants - static constexpr std::size_t READ_BUF_SIZE{1024}; static constexpr FKey NOT_SET = static_cast(-1); // Accessors @@ -169,10 +171,11 @@ class FKeyboard final #endif static timeval time_keypressed; + static uInt64 read_blocking_time; static uInt64 key_timeout; fc::FKeyMap* key_map{nullptr}; FKey key{0}; - char read_buf[READ_BUF_SIZE]{'\0'}; + uChar read_character{}; char fifo_buf[FIFO_BUF_SIZE]{'\0'}; int fifo_offset{0}; int stdin_status_flags{0}; @@ -200,10 +203,22 @@ inline FKeyboard::keybuffer& FKeyboard::getKeyBuffer() inline timeval* FKeyboard::getKeyPressedTime() { return &time_keypressed; } +//---------------------------------------------------------------------- +inline uInt64 FKeyboard::getKeypressTimeout() +{ return key_timeout; } + +//---------------------------------------------------------------------- +inline uInt64 FKeyboard::getReadBlockingTime() +{ return read_blocking_time; } + //---------------------------------------------------------------------- inline void FKeyboard::setKeypressTimeout (const uInt64 timeout) { key_timeout = timeout; } +//---------------------------------------------------------------------- +inline void FKeyboard::setReadBlockingTime (const uInt64 blocking_time) +{ read_blocking_time = blocking_time; } + //---------------------------------------------------------------------- inline void FKeyboard::enableUTF8() { utf8_input = true; } diff --git a/src/include/final/fvterm.h b/src/include/final/fvterm.h index bc2bd52a..2d7ad947 100644 --- a/src/include/final/fvterm.h +++ b/src/include/final/fvterm.h @@ -236,6 +236,10 @@ class FVTerm static bool setInheritBackground(); static bool unsetInheritBackground(); + static void setNonBlockingRead (bool); + static void setNonBlockingRead(); + static void unsetNonBlockingRead(); + static void setTermTitle (const FString&); static void setEncoding (fc::encoding); static bool setVGAFont(); @@ -871,6 +875,14 @@ inline bool FVTerm::setInheritBackground() inline bool FVTerm::unsetInheritBackground() { return setInheritBackground(false); } +//---------------------------------------------------------------------- +inline void FVTerm::setNonBlockingRead() +{ setNonBlockingRead(true); } + +//---------------------------------------------------------------------- +inline void FVTerm::unsetNonBlockingRead() +{ setNonBlockingRead(false); } + //---------------------------------------------------------------------- inline void FVTerm::setTermTitle (const FString& title) { FTerm::setTermTitle(title); } diff --git a/src/include/final/fwidget.h b/src/include/final/fwidget.h index 6049bb62..ae9252f2 100644 --- a/src/include/final/fwidget.h +++ b/src/include/final/fwidget.h @@ -401,7 +401,7 @@ class FWidget : public FVTerm, public FObject private: // Methods - void init(); + void initRootWidget(); void finish(); void insufficientSpaceAdjust(); void KeyPressEvent (FKeyEvent*); diff --git a/test/fkeyboard-test.cpp b/test/fkeyboard-test.cpp index 124e8ab2..03c81540 100644 --- a/test/fkeyboard-test.cpp +++ b/test/fkeyboard-test.cpp @@ -330,6 +330,30 @@ void FKeyboardTest::noArgumentTest() keyboard->escapeKeyHandling(); CPPUNIT_ASSERT ( keyboard->getKey() == 0 ); + + // Keypress timeout + CPPUNIT_ASSERT ( keyboard->getKeypressTimeout() == 100 * 1000 ); + + keyboard->setKeypressTimeout(0); // 0 ms + CPPUNIT_ASSERT ( keyboard->getKeypressTimeout() == 0 ); + + keyboard->setKeypressTimeout(100000); // 100 ms + CPPUNIT_ASSERT ( keyboard->getKeypressTimeout() == 100 * 1000 ); + + // Read blocking time + CPPUNIT_ASSERT ( keyboard->getReadBlockingTime() == 100 * 1000 ); + + keyboard->setReadBlockingTime(1000000); // 1000 ms + CPPUNIT_ASSERT ( keyboard->getReadBlockingTime() == 1000 * 1000 ); + + keyboard->setReadBlockingTime(0); // 0 ms + CPPUNIT_ASSERT ( keyboard->getReadBlockingTime() == 0 ); + + keyboard->setReadBlockingTime(50000); // 50 ms + CPPUNIT_ASSERT ( keyboard->getReadBlockingTime() == 50 * 1000 ); + + keyboard->setReadBlockingTime(100000); // 100 ms + CPPUNIT_ASSERT ( keyboard->getReadBlockingTime() == 100 * 1000 ); } //---------------------------------------------------------------------- @@ -2823,13 +2847,11 @@ void FKeyboardTest::input (std::string s) //---------------------------------------------------------------------- void FKeyboardTest::processInput() { + keyboard->escapeKeyHandling(); // special case: Esc key keyboard->clearKeyBufferOnTimeout(); if ( keyboard->isKeyPressed() ) keyboard->fetchKeyCode(); - - // special case: Esc key - keyboard->escapeKeyHandling(); } //----------------------------------------------------------------------