diff --git a/include/boost/process/v2/experimental/basic_pty.hpp b/include/boost/process/v2/experimental/basic_pty.hpp index 43f3555b..8bfbc1ac 100644 --- a/include/boost/process/v2/experimental/basic_pty.hpp +++ b/include/boost/process/v2/experimental/basic_pty.hpp @@ -58,7 +58,7 @@ class basic_pty template handle_t_(Arg && arg) : rm{arg} {} - bool is_open() {return rm.is_open();} + bool is_open() const {return rm.is_open();} }; handle_t_ handle_; @@ -600,10 +600,10 @@ class basic_pty this->close(ec); } #else - net::connect_pipe(handle_.rm, handle_.ws, ec); + net::connect_pipe(handle_.rm, handle_.ws, ec); // output if (!ec) { - net::connect_pipe(handle_.wm, handle_.rs, ec); + net::connect_pipe(handle_.rs, handle_.wm, ec); // input if (ec) handle_.rm.close(ec); } @@ -698,10 +698,14 @@ class basic_pty { error_code ec; if (!is_open()) - open(ec); + open(console_size_t{80, 24}, ec); auto &si = launcher.startup_info; - + launcher.startup_info.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + // https://github.com/microsoft/terminal/issues/11276 + launcher.startup_info.StartupInfo.hStdOutput = NULL; + launcher.startup_info.StartupInfo.hStdError = NULL; + launcher.startup_info.StartupInfo.hStdInput = NULL; size_t bytes_required; InitializeProcThreadAttributeList(NULL, 1, 0, &bytes_required); si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, bytes_required); diff --git a/include/boost/process/v2/experimental/basic_stream.hpp b/include/boost/process/v2/experimental/basic_stream.hpp index f552f39c..01e16de4 100644 --- a/include/boost/process/v2/experimental/basic_stream.hpp +++ b/include/boost/process/v2/experimental/basic_stream.hpp @@ -207,7 +207,7 @@ struct basic_stream } /// Get the executor associated with the object. - const executor_type& get_executor() noexcept + executor_type get_executor() noexcept { return handle_.get_executor(); } @@ -390,7 +390,7 @@ struct basic_stream struct ::termios t; return !tcgetattr(handle_.native_handle(), &t); #else - return handle_.index() == 0u; + return handle_.object.is_open(); #endif } @@ -421,7 +421,7 @@ struct basic_stream else mode &= ~ENABLE_ECHO_INPUT; - if (!ec && SetConsoleMode(handle_.native_handle(), mode)) + if (!ec && !SetConsoleMode(handle_.native_handle(), mode)) BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); #endif } @@ -484,11 +484,11 @@ struct basic_stream BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); if (enable) - mode |= ENABLE_LINE_INPUT; + mode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT ; else - mode &= ~ENABLE_LINE_INPUT; + mode &= ~(ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT ); - if (!ec && SetConsoleMode(handle_.native_handle(), mode)) + if (!ec && !SetConsoleMode(handle_.native_handle(), mode)) BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); #endif } @@ -561,24 +561,26 @@ struct basic_stream { if (!this_->is_pty()) { - auto e = net::get_associated_immediate_executor(self, this_->sig_winch_.get_executor()); + auto e = net::get_associated_immediate_executor(self, this_->handle_.object.get_executor()); return net::dispatch(e, net::append(std::move(self), net::error::operation_not_supported)); } - buf = this_->handle_.cs_buf_; - this_->handle_.trigger_size_.async_wait(std::move(self)); + this_->async_wait(std::move(self)); } template void operator()(Self && self, error_code ec) { - if (ec == net::error::operation_aborted - && !self.get_cancellation_state().cancelled() - && buf != this_->handle_.cs_buf_) - ec.clear(); - - if (this_->handle_.trigger_size_.expiry() == std::chrono::steady_clock::time_point::min()) - BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::broken_pipe); - + if (!ec) + { + CONSOLE_SCREEN_BUFFER_INFO bi; + if (::GetConsoleScreenBufferInfo(this_->handle_.object.native_handle(), &bi)) + { + this_->handle_.cs_buf_.columns = bi.dwSize.X; + this_->handle_.cs_buf_.rows = bi.dwSize.Y; + } + else + BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); + } self.complete(ec, this_->handle_.cs_buf_); } @@ -600,7 +602,7 @@ struct basic_stream async_wait_for_size_change_op_{this}, handler, handle_)) { return net::async_compose( - async_wait_for_size_change_op_{this}, handler, get_executor()); + async_wait_for_size_change_op_{this}, handler, handle_); } /// Wait for the stream to become ready to read. @@ -710,9 +712,9 @@ struct basic_stream auto async_wait(WaitToken&& token = net::default_completion_token_t()) -> decltype(this->handle_.async_wait( #if defined(BOOST_PROCESS_V2_POSIX) - net::posix::descriptor_base::wait_read, std::declval() + net::posix::descriptor_base::wait_read, #endif - )) + std::declval())) { return this->handle_.async_wait( #if defined(BOOST_PROCESS_V2_POSIX) @@ -836,11 +838,11 @@ struct basic_stream * @li @c cancellation_type::total */ template > auto async_read_some(const MutableBufferSequence& buffers, ReadToken&& token = net::default_completion_token_t()) - -> decltype(this->handle_.async_read_some(buffers, std::forward(token))) + -> decltype(this->handle_.async_read_some(buffers, std::forward(token))) { return this->handle_.async_read_some(buffers, std::forward(token)); } @@ -975,11 +977,11 @@ basic_stream open_stdin(Executor exec, bool duplicate, error_code & ec typename basic_stream::native_handle_type handle; #if defined(BOOST_WINDOWS_API) if (!duplicate) - handle = GetStdHandle(STDIN_FILENO); + handle = GetStdHandle(STD_INPUT_HANDLE); else if ( !DuplicateHandle( GetCurrentProcess(), - GetStdHandle(STDIN_FILENO), + GetStdHandle(STD_INPUT_HANDLE), GetCurrentProcess(), &handle, 0u, @@ -1000,11 +1002,11 @@ basic_stream open_stdout(Executor exec, bool duplicate, error_code & e typename basic_stream::native_handle_type handle; #if defined(BOOST_WINDOWS_API) if (!duplicate) - handle = GetStdHandle(STDOUT_FILENO); + handle = GetStdHandle(STD_OUTPUT_HANDLE); else if ( !DuplicateHandle( GetCurrentProcess(), - GetStdHandle(STDOUT_FILENO), + GetStdHandle(STD_OUTPUT_HANDLE), GetCurrentProcess(), &handle, 0u, @@ -1025,11 +1027,11 @@ basic_stream open_stderr(Executor exec, bool duplicate, error_code & e typename basic_stream::native_handle_type handle; #if defined(BOOST_WINDOWS_API) if (!duplicate) - handle = GetStdHandle(STDERR_FILENO); + handle = GetStdHandle(STD_ERROR_HANDLE); else if ( !DuplicateHandle( GetCurrentProcess(), - GetStdHandle(STDERR_FILENO), + GetStdHandle(STD_ERROR_HANDLE), GetCurrentProcess(), &handle, 0u, diff --git a/include/boost/process/v2/experimental/detail/basic_stream_handle.hpp b/include/boost/process/v2/experimental/detail/basic_stream_handle.hpp index 0fb59cfe..6f600184 100644 --- a/include/boost/process/v2/experimental/detail/basic_stream_handle.hpp +++ b/include/boost/process/v2/experimental/detail/basic_stream_handle.hpp @@ -43,7 +43,6 @@ struct basic_stream_handle { net::windows::basic_object_handle object; net::windows::basic_stream_handle stream; - net::steady_timer trigger_size_{get_executor(), std::chrono::steady_clock::time_point::max()}; v2::experimental::console_size_t cs_buf_{0u, 0u}; template @@ -51,15 +50,17 @@ struct basic_stream_handle template basic_stream_handle(basic_stream_handle &&lhs) - : object(std::move(lhs.object)), stream(std::move(lhs.stream)), trigger_size_(std::move(lhs.trigger_size_)), - cs_buf_(lhs.cs_buf_) {} + : object(std::move(lhs.object)), stream(std::move(lhs.stream)), cs_buf_(lhs.cs_buf_) {} - Executor get_executor() noexcept { return object.get_executor(); } + using executor_type = Executor; + executor_type get_executor() noexcept { return object.get_executor(); } void assign(HANDLE h, error_code &ec) { - if (GetFileType(h) == FILE_TYPE_CHAR) { + assert(GetFileType(h) == FILE_TYPE_CHAR); + if (GetFileType(h) == FILE_TYPE_CHAR) + { stream.close(ec); object.assign(h, ec); DWORD flags; @@ -68,15 +69,25 @@ struct basic_stream_handle return; } - flags |= ENABLE_VIRTUAL_TERMINAL_INPUT; - - if (!::SetConsoleMode(h, flags) || - !::SetConsoleCP(CP_UTF8) || - !::SetConsoleOutputCP(CP_UTF8)) + if (!::SetConsoleMode(h, flags | ENABLE_VIRTUAL_TERMINAL_INPUT)) // probably output! + if (!::SetConsoleMode(h, flags | ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING )) + BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); + if (!::SetConsoleCP(CP_UTF8)) + BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); + if (!::SetConsoleOutputCP(CP_UTF8)) BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); - trigger_size_.expires_at(std::chrono::steady_clock::time_point::max()); - } else { + + CONSOLE_SCREEN_BUFFER_INFO bi; + if (::GetConsoleScreenBufferInfo(h, &bi)) + { + cs_buf_.columns = bi.dwSize.X; + cs_buf_.rows = bi.dwSize.Y; + } + + } + else + { object.close(ec); stream.assign(h, ec); } @@ -94,14 +105,12 @@ struct basic_stream_handle { object.close(ec); stream.close(ec); - trigger_size_.expires_at(std::chrono::steady_clock::time_point::min()); } void close() { object.close(); stream.close(); - trigger_size_.expires_at(std::chrono::steady_clock::time_point::min()); } HANDLE native_handle() @@ -178,10 +187,13 @@ struct basic_stream_handle if (stream.is_open()) return stream.read_some(mbs, ec); - net::mutable_buffer buf; + + asio::mutable_buffer buf; + for (auto itr = net::buffer_sequence_begin(mbs); itr != net::buffer_sequence_end(mbs); itr++) - if (itr->size() > 0) { + if (itr->size() > 0) + { buf = *itr; break; } @@ -189,43 +201,16 @@ struct basic_stream_handle if (buf.size() == 0u) return 0u; - try_again: - INPUT_RECORD in_buffer[8092]; - DWORD sz = buf.size(), read_size = 0u; // - if (!PeekConsoleInputW(object.native_handle(), in_buffer, sz, &read_size)) { - BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); - return 0u; - } - - auto begin = in_buffer, - end = in_buffer + read_size; - - DWORD keys = 0u; - for (auto itr = begin; itr != end; itr++) { - if (itr->EventType == KEY_EVENT - && itr->Event.KeyEvent.bKeyDown) - keys += itr->Event.KeyEvent.wRepeatCount; - // not handling resize here: don't mix sync & async apis. - } - - if (keys == 0u) { - // read all we just peeked - if (!::ReadConsoleW(object.native_handle(), in_buffer, read_size, sz)) { - BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); - return 0u; - } - - goto try_again; // never read zero - } - - read_size = 0u; - if (!ReadFile(object.native_handle(), buf.data(), keys, &read_size, NULL)) { + DWORD read_size = 0u; + if (!::ReadFile(object.native_handle(), buf.data(), buf.size(), &read_size, NULL)) + { const DWORD last_error = ::GetLastError(); - if (ERROR_MORE_DATA == last_error) + if (ERROR_END_OF_MEDIA == last_error) BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::eof); else BOOST_PROCESS_V2_ASSIGN_EC(ec, last_error, system_category()); } + return static_cast(read_size); } @@ -239,7 +224,7 @@ struct basic_stream_handle void operator()(Self &&self) { if (this_->stream.is_open()) - return this_->stream.async_reasd_some(buffer, std::move(self)); + return this_->stream.async_read_some(buffer, std::move(self)); this_->async_wait(std::move(self)); } @@ -247,10 +232,12 @@ struct basic_stream_handle template void operator()(Self &&self, error_code ec) { - net::mutable_buffer buf; + asio::mutable_buffer buf; + for (auto itr = net::buffer_sequence_begin(buffer); itr != net::buffer_sequence_end(buffer); itr++) - if (itr->size() > 0) { + if (itr->size() > 0) + { buf = *itr; break; } @@ -258,50 +245,56 @@ struct basic_stream_handle if (buf.size() == 0u) return self.complete(error_code{}, 0u); - WINDOW_BUFFER_SIZE_RECORD *window_change = nullptr; - try_again: - INPUT_RECORD in_buffer[8092]; - DWORD sz = buf.size(), read_size = 0u; // - if (!PeekConsoleInputW(this_->object.native_handle(), in_buffer, sz, &read_size)) { + DWORD read_size = 0u; + if (!::PeekConsoleInputW(this_->object.native_handle(), in_buffer, 8092, &read_size)) + { BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); return self.complete(ec, 0u); } - auto begin = in_buffer, - end = in_buffer + read_size; + end = in_buffer + read_size; - DWORD keys = 0u; - - for (auto itr = begin; itr != end; itr++) { - if (itr->EventType == KEY_EVENT - && itr->Event.KeyEvent.bKeyDown) + std::size_t keys = 0u; + bool has_cr = false; + for (auto itr = begin; itr != end; itr++) + { + if (itr->EventType == KEY_EVENT) + { keys += itr->Event.KeyEvent.wRepeatCount; - else if (itr->EventType == WINDOW_BUFFER_SIZE_EVENT) - window_change = &itr->Event.WindowBufferSizeEvent; + has_cr |= itr->Event.KeyEvent.uChar.AsciiChar == '\r'; + } } - if (keys == 0u) { - // read all we just peeked - if (!::ReadConsoleW(this_->object.native_handle(), in_buffer, read_size, sz)) { + if (keys == 0u) + { + if (!::FlushConsoleInputBuffer(this_->object.native_handle())) + { BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); return self.complete(ec, 0u); } - - goto try_again; // never read zero + else + return this_->async_wait(std::move(self)); } - if (window_change) // + if (!has_cr) { - this_->cs_buf_.columns = window_change->dwSize.X; - this_->cs_buf_.rows = window_change->dwSize.Y; - this_->trigger_size_.cancel(); + DWORD mode = 0; + if (!::GetConsoleMode(this_->object.native_handle(), &mode)) + BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); + else if ((mode & ENABLE_LINE_INPUT) != 0) + BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::would_block); // line mode would block on windows } + if (ec) + return self.complete(ec, 0u); + + DWORD to_read = (std::min)(keys, buf.size()); read_size = 0u; - if (!ReadFile(this_->object.native_handle(), buf.data(), keys, &read_size, NULL)) { + if (!ReadFile(this_->object.native_handle(), buf.data(), to_read, &read_size, NULL)) + { const DWORD last_error = ::GetLastError(); - if (ERROR_MORE_DATA == last_error) + if (ERROR_END_OF_MEDIA == last_error) BOOST_PROCESS_V2_ASSIGN_EC(ec, net::error::eof); else BOOST_PROCESS_V2_ASSIGN_EC(ec, last_error, system_category()); @@ -311,22 +304,22 @@ struct basic_stream_handle } template - void operator()(Self &&self, error_code ec, std::size_t n) + void operator()(Self &&self, error_code ec, std::size_t n_) { - self.complete(ec, n); + self.complete(ec, n_); } }; template auto async_read_some(const MutableBuffer &buffer, CompletionToken &&token) - -> decltype(boost::asio::async_initiate( - boost::asio::composed(async_read_some_op{this, buffer}, socket), token)) - { - return boost::asio::async_initiate decltype(net::async_initiate( - boost::asio::composed(async_read_some_op{this, buffer}, socket), token); + net::composed(async_read_some_op{this, buffer}, stream), token)) + { + return net::async_initiate( + net::composed(async_read_some_op{this, buffer}, stream), token); } template @@ -335,10 +328,11 @@ struct basic_stream_handle if (stream.is_open()) return stream.write_some(cbs, ec); - net::mutable_buffer buf; + net::const_buffer buf; for (auto itr = net::buffer_sequence_begin(cbs); itr != net::buffer_sequence_end(cbs); itr++) - if (itr->size() > 0) { + if (itr->size() > 0) + { buf = *itr; break; } @@ -347,9 +341,8 @@ struct basic_stream_handle return 0u; // get one screen size, that's how much we'll write - - CONSOLE_SCREEN_BUFFER_INFOEX bi; - if (!GetConsoleScreenBufferInfoEx(object.native_handle(), &bi)) { + CONSOLE_SCREEN_BUFFER_INFO bi; + if (!GetConsoleScreenBufferInfo(object.native_handle(), &bi)) { BOOST_PROCESS_V2_ASSIGN_LAST_ERROR(ec); return 0u; } @@ -378,11 +371,23 @@ struct basic_stream_handle error_code ec; auto n = this_->write_some(buffer, ec); auto exec = net::get_associated_immediate_executor( - handler, this_->sig_winch_.get_executor()); + handler, this_->object.get_executor()); net::dispatch(exec, net::append(std::move(handler), ec, n)); } }; + + template + auto async_write_some(const ConstBufferSequence & buffer, CompletionToken && token) + -> decltype(net::async_initiate( + async_write_op{this, buffer}, token)) + { + return net::async_initiate( + async_write_op{this, buffer}, token); + } + }; } diff --git a/test/v2/experimental/pty.cpp b/test/v2/experimental/pty.cpp index 3d9b5724..ea638f88 100644 --- a/test/v2/experimental/pty.cpp +++ b/test/v2/experimental/pty.cpp @@ -6,6 +6,8 @@ // + + #include #include @@ -37,12 +39,12 @@ BOOST_AUTO_TEST_CASE(sync_plain) std::string l1 = "Hello", l2 = ", ", l3 = "World!", l4 = "\n"; std::string read_buffer; - read_buffer.resize(64); + read_buffer.resize(128); auto rb = net::buffer(read_buffer); BOOST_CHECK_EQUAL(pt.write_some(net::buffer(l1)), 5); auto n = pt.read_some(rb); - BOOST_CHECK_EQUAL(n, 5); rb += n; + BOOST_CHECK_MESSAGE(n == 5, read_buffer.substr(0, n)); rb += n; BOOST_CHECK_EQUAL(pt.write_some(net::buffer(l2)), 2); BOOST_CHECK_EQUAL(n = pt.read_some(rb), 2); rb += n; @@ -77,6 +79,8 @@ BOOST_AUTO_TEST_CASE(async_plain) bp2::experimental::pty &pt; bp2::process &proc; + op(bp2::experimental::pty &pt, bp2::process &proc) : pt(pt), proc(proc) {} + std::string read_buffer = std::string(64, ' '); net::mutable_buffer rb = net::buffer(read_buffer); std::string l1 = "Hello", l2 = ", ", l3 = "World!", l4 = "\n"; @@ -115,7 +119,7 @@ BOOST_AUTO_TEST_CASE(async_plain) } }; - op{{}, pt, proc}({}, 0u); + op{pt, proc}({}, 0u); ctx.run(); } @@ -169,7 +173,6 @@ BOOST_AUTO_TEST_CASE(async_echo) bp2::experimental::pty pt{ctx}; bp2::process proc(ctx, pth, {"--async", "--echo"}, pt); - char buf[4]; net::read(pt, net::buffer(buf)); @@ -179,6 +182,8 @@ BOOST_AUTO_TEST_CASE(async_echo) bp2::experimental::pty &pt; bp2::process &proc; + op(bp2::experimental::pty &pt, bp2::process &proc) : pt(pt), proc(proc) {} + std::string read_buffer = std::string(64, ' '); net::mutable_buffer rb = net::buffer(read_buffer); std::string l1 = "Hello", l2 = ", ", l3 = "World!", l4 = "\n"; @@ -217,7 +222,7 @@ BOOST_AUTO_TEST_CASE(async_echo) } }; - op{{}, pt, proc}({}, 0u); + op{pt, proc}({}, 0u); ctx.run(); } @@ -252,6 +257,8 @@ BOOST_AUTO_TEST_CASE(async_line) bp2::experimental::pty &pt; std::size_t & read; + op(bp2::experimental::pty &pt, std::size_t & read) : pt(pt), read(read) {} + std::string l1 = "Hello", l2 = ", ", l3 = "World!", l4 = "\n"; @@ -276,7 +283,7 @@ BOOST_AUTO_TEST_CASE(async_line) } }; - op{{}, pt, read}({}, 0u); + op{pt, read}({}, 0u); ctx.run(); } diff --git a/test/v2/experimental/pty_target.cpp b/test/v2/experimental/pty_target.cpp index 5fcb6a06..b9137bdf 100644 --- a/test/v2/experimental/pty_target.cpp +++ b/test/v2/experimental/pty_target.cpp @@ -26,6 +26,8 @@ struct async_op : net::coroutine { bp::experimental::basic_stream &in, &out; + async_op(bp::experimental::basic_stream &in, + bp::experimental::basic_stream &out) : in(in), out(out) {} void operator()(bp::error_code ec = {}, std::size_t n = {}) { if (ec) @@ -46,13 +48,14 @@ struct async_op : net::coroutine }; int main(int argc, char * argv[]) +try { net::io_context ctx; auto in = bp::experimental::open_stdin(ctx.get_executor()); assert(in.is_pty()); - assert(in.echo()); - assert(in.line()); + //assert(in.echo()); + //assert(in.line()); std::vector args{argv + 1, argv + argc}; @@ -64,8 +67,6 @@ int main(int argc, char * argv[]) auto out = stder ? bp::experimental::open_stderr(ctx.get_executor()) : bp::experimental::open_stdout(ctx.get_executor()); - - in.set_echo(echo); in.set_line(line); @@ -92,7 +93,7 @@ int main(int argc, char * argv[]) } else if (async) { - net::post(ctx, async_op{{}, in, out}); + net::post(ctx, async_op{in, out}); ctx.run(); } else @@ -110,4 +111,9 @@ int main(int argc, char * argv[]) } return 0u; +} +catch(boost::system::system_error & se) +{ + fprintf(stderr, "Pty-Target exception: %s(%d): %s\n", se.code().location().file_name(), se.code().location().line(), se.what()); + return 1; } \ No newline at end of file