diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b882be8..49667a8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,11 @@ foreach(lib screen dom component) target_compile_options(${lib} PRIVATE "-Werror") target_compile_options(${lib} PRIVATE "-Wno-sign-compare") endif() + + # Force Win32 to UNICODE + if(MSVC) + target_compile_definitions(${lib} PRIVATE UNICODE _UNICODE) + endif() endforeach() if(FTXUI_ENABLE_INSTALL) diff --git a/include/ftxui/component/event_input_listener.hpp b/include/ftxui/component/event_input_listener.hpp new file mode 100644 index 00000000..f90fdd7e --- /dev/null +++ b/include/ftxui/component/event_input_listener.hpp @@ -0,0 +1,55 @@ +#ifndef FTXUI_COMPONENT_EVENT_INPUT_LISTENER_HPP +#define FTXUI_COMPONENT_EVENT_INPUT_LISTENER_HPP + +#include +#include +#include + +#include "event.hpp" + +#ifdef WIN32 + #include + #include + #include +#endif + +namespace ftxui { + +// Receives input events from the OS and turns them into +// Event objects and sends to a consumer +// +// On NIX systems: +// - uses SIGWINCH for resize +// - uses getchar() for keypresses +// +// On Windows systems: +// - Uses ReadConsoleInput for resize and keypresses +class EventInputListener { + public: + EventInputListener(std::function consumer); + ~EventInputListener(); + + void stop(); + + private: + char readchar(); + void readchar_thread_func(std::function consumer); + + std::atomic quit_{false}; + std::thread readchar_thread_; +#ifndef _WIN32 + using signal_handler_t = void (*)(int); + signal_handler_t old_sigwinch_handler_; +#else + void input_thread_func(std::function consumer); + + std::mutex input_queue_mutex_; + std::condition_variable input_queue_condvar_; + std::deque input_queue_; + std::thread input_event_thread_; +#endif +}; + +} // namespace ftxui + +#endif /* end of include guard: FTXUI_COMPONENT_EVENT_INPUT_LISTENER_HPP */ diff --git a/src/ftxui/component/screen_interactive.cpp b/src/ftxui/component/screen_interactive.cpp index f7c87841..a7afa644 100644 --- a/src/ftxui/component/screen_interactive.cpp +++ b/src/ftxui/component/screen_interactive.cpp @@ -9,13 +9,17 @@ #include #include "ftxui/component/component.hpp" +#include "ftxui/component/event_input_listener.hpp" #include "ftxui/screen/string.hpp" #include "ftxui/screen/terminal.hpp" -#ifdef WIN32 +#if defined(WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include + #ifndef UNICODE + #error Must be compiled in UNICODE mode + #endif #else #include #include @@ -35,6 +39,56 @@ void CharToEventStream(Receiver receiver, Sender sender) { Event::Convert(receiver, sender, c); } +#if defined(WIN32) + +void Win32EventListener(std::atomic* quit, + Sender char_sender, + Sender event_sender) { + auto console = GetStdHandle(STD_INPUT_HANDLE); + while (!*quit) { + // Throttle ReadConsoleInput by waiting 250ms, this wait function will + // return if there is input in the console. + auto wait_result = WaitForSingleObject(console, 250); + if (wait_result == WAIT_TIMEOUT) + continue; + + DWORD number_of_events = 0; + if (!GetNumberOfConsoleInputEvents(console, &number_of_events)) + continue; + if (number_of_events <= 0) + continue; + + std::vector records{number_of_events}; + DWORD number_of_events_read = 0; + ReadConsoleInput(console, records.data(), + (DWORD)(records.size() * sizeof(INPUT_RECORD)), + &number_of_events_read); + records.resize(number_of_events_read); + + for (const auto& r : records) { + switch (r.EventType) { + case KEY_EVENT: { + auto key_event = r.Event.KeyEvent; + // ignore UP key events + if (key_event.bKeyDown == FALSE) + continue; + char_sender->Send((char)key_event.uChar.UnicodeChar); + } break; + case WINDOW_BUFFER_SIZE_EVENT: + event_sender->Send(Event::Special({0})); + break; + case MENU_EVENT: + case FOCUS_EVENT: + case MOUSE_EVENT: + // TODO(mauve): Implement later. + break; + } + } + } +} + +#else + // Read char from the terminal. void UnixEventListener(std::atomic* quit, Sender sender) { // TODO(arthursonzogni): Use a timeout so that it doesn't block even if the @@ -43,6 +97,8 @@ void UnixEventListener(std::atomic* quit, Sender sender) { sender->Send((char)getchar()); } +#endif + static const char* HIDE_CURSOR = "\x1B[?25l"; static const char* SHOW_CURSOR = "\x1B[?25h"; @@ -105,11 +161,8 @@ void ScreenInteractive::PostEvent(Event event) { } void ScreenInteractive::Loop(Component* component) { - // Install a SIGINT handler and restore the old handler on exit. - install_signal_handler(SIGINT, OnExit); - // Save the old terminal configuration and restore it on exit. -#ifdef WIN32 +#if defined(WIN32) // Enable VT processing on stdout and stdin auto stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); auto stdin_handle = GetStdHandle(STD_INPUT_HANDLE); @@ -141,7 +194,7 @@ void ScreenInteractive::Loop(Component* component) { tcsetattr(STDIN_FILENO, TCSANOW, &terminal); // Handle resize. - on_resize = [&] { PostEvent(Event::Special({0})); }; + on_resize = [&] { event_sender_->Send(Event::Special({0})); }; install_signal_handler(SIGWINCH, OnResize); #endif @@ -159,15 +212,18 @@ void ScreenInteractive::Loop(Component* component) { // Produce a stream of Event from a stream of char. auto char_receiver = MakeReceiver(); auto char_sender = char_receiver->MakeSender(); - auto event_sender = event_receiver_->MakeSender(); + auto event_sender_1 = event_receiver_->MakeSender(); auto char_to_event_stream = std::thread( - CharToEventStream, std::move(char_receiver), std::move(event_sender)); + CharToEventStream, std::move(char_receiver), std::move(event_sender_1)); // Depending on the OS, start a thread that will produce events and/or chars. #if defined(WIN32) - // TODO(arthursonzogni) implement here. + auto event_sender_2 = event_receiver_->MakeSender(); + auto event_listener = + std::thread(&Win32EventListener, &quit_, std::move(char_sender), + std::move(event_sender_2)); #else - auto unix_event_listener = + auto event_listener = std::thread(&UnixEventListener, &quit_, std::move(char_sender)); #endif @@ -183,9 +239,7 @@ void ScreenInteractive::Loop(Component* component) { } char_to_event_stream.join(); -#if !defined(WIN32) - unix_event_listener.join(); -#endif + event_listener.join(); OnExit(0); } diff --git a/src/ftxui/screen/screen.cpp b/src/ftxui/screen/screen.cpp index c5cf8ffe..85dd6700 100644 --- a/src/ftxui/screen/screen.cpp +++ b/src/ftxui/screen/screen.cpp @@ -7,7 +7,7 @@ #include "ftxui/screen/string.hpp" #include "ftxui/screen/terminal.hpp" -#ifdef WIN32 +#if defined(WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include diff --git a/src/ftxui/screen/terminal.cpp b/src/ftxui/screen/terminal.cpp index d214f93f..2a8b2355 100644 --- a/src/ftxui/screen/terminal.cpp +++ b/src/ftxui/screen/terminal.cpp @@ -2,7 +2,7 @@ #include -#ifdef WIN32 +#if defined(WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include