From d02ea095292b0f2c134cad471c51068b0ff5f102 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 17 Jul 2025 20:13:27 +0200 Subject: [PATCH] fix: Many issues and code style of the TTY console --- .../include/views/view_tty_console.hpp | 102 ++++--- .../windows/source/views/view_tty_console.cpp | 263 +++++++++--------- 2 files changed, 175 insertions(+), 190 deletions(-) diff --git a/plugins/windows/include/views/view_tty_console.hpp b/plugins/windows/include/views/view_tty_console.hpp index 010a36a05..f3815c327 100644 --- a/plugins/windows/include/views/view_tty_console.hpp +++ b/plugins/windows/include/views/view_tty_console.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include namespace hex::plugin::windows { @@ -17,68 +19,64 @@ namespace hex::plugin::windows { void drawContent() override; private: - std::vector> m_comPorts; + void drawConsole(); - std::vector> getAvailablePorts() const; - bool connect(); - bool disconnect(); - - void transmitData(std::vector &data); - - void* m_portHandle = reinterpret_cast(-1); - std::jthread m_receiveThread; - - int m_selectedPort = 0; - int m_selectedBaudRate = 11; // 115200 - int m_selectedNumBits = 3; // 8 - int m_selectedStopBits = 0; // 1 - int m_selectedParityBits = 0; // None - bool m_hasCTSFlowControl = false; // No - - bool m_shouldAutoScroll = true; - - std::mutex m_receiveBufferMutex; - std::vector m_receiveDataBuffer, m_transmitDataBuffer; - std::vector m_wrapPositions; - bool m_transmitting = false; + private: + struct Port { + std::string name; + std::wstring path; + }; constexpr static std::array BaudRates = { - "110", - "300", - "600", - "1200", - "2400", - "4800", - "9600", - "14400", - "19200", - "38400", - "57600", - "115200", - "128000", - "256000" + 110, 150, 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 }; constexpr static std::array NumBits = { - "5", - "6", - "7", - "8" + 5, + 6, + 7, + 8 }; - constexpr static std::array StopBits = { - "1", - "1.5", - "2.0" + enum class StopBits : u8 { + _1_0 = ONESTOPBIT, + _1_5 = ONE5STOPBITS, + _2_0 = TWOSTOPBITS }; - constexpr static std::array ParityBits = { - "None", - "Odd", - "Even", - "Mark", - "Space" + enum class ParityBits { + None = NOPARITY, + Odd = ODDPARITY, + Even = EVENPARITY, + Mark = MARKPARITY, + Space = SPACEPARITY }; + + std::vector getAvailablePorts() const; + bool connect(); + bool disconnect(); + + void transmitData(const std::string &data); + + void* m_portHandle = INVALID_HANDLE_VALUE; + std::jthread m_receiveThread; + + u32 m_selectedPortIndex = 0; + i32 m_selectedBaudRate = 115200; + i32 m_selectedNumBits = 8; + StopBits m_selectedStopBits = StopBits::_1_0; + ParityBits m_selectedParityBits = ParityBits::None; + bool m_hasCTSFlowControl = false; + + bool m_shouldAutoScroll = true; + i64 m_scrollPosition = 0; + + std::mutex m_receiveBufferMutex; + std::vector m_receiveLines; + std::vector m_receiveLinesColor; + std::string m_transmitDataBuffer; + bool m_transmitting = false; + std::vector m_comPorts; }; -} \ No newline at end of file +} diff --git a/plugins/windows/source/views/view_tty_console.cpp b/plugins/windows/source/views/view_tty_console.cpp index 16f7305e2..c2d294292 100644 --- a/plugins/windows/source/views/view_tty_console.cpp +++ b/plugins/windows/source/views/view_tty_console.cpp @@ -9,15 +9,12 @@ #include -#include - namespace hex::plugin::windows { ViewTTYConsole::ViewTTYConsole() : View::Window("hex.windows.view.tty_console.name", ICON_VS_TERMINAL) { m_comPorts = getAvailablePorts(); + m_selectedPortIndex = 0; m_transmitDataBuffer.resize(0xFFF, 0x00); - m_receiveDataBuffer.reserve(0xFFF); - m_receiveDataBuffer.push_back(0x00); } void ViewTTYConsole::drawContent() { @@ -28,54 +25,58 @@ namespace hex::plugin::windows { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, connected); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, connected ? 0.5F : 1.0F); - ImGui::Combo( - "hex.windows.view.tty_console.port"_lang, &m_selectedPort, [](void *data, int idx) { - auto &ports = *static_cast> *>(data); - - return ports[idx].first.c_str(); - }, - &m_comPorts, - m_comPorts.size()); + if (ImGui::BeginCombo("hex.windows.view.tty_console.port"_lang, m_comPorts.empty() ? "" : m_comPorts[m_selectedPortIndex].name.c_str())) { + for (u32 i = 0; i < m_comPorts.size(); i++) { + const auto &port = m_comPorts[i]; + if (ImGui::Selectable(port.name.c_str(), m_selectedPortIndex == i)) { + m_selectedPortIndex = i; + } + } + ImGui::EndCombo(); + } ImGui::SameLine(); - if (ImGui::Button("hex.windows.view.tty_console.reload"_lang)) + if (ImGui::Button("hex.windows.view.tty_console.reload"_lang)) { m_comPorts = getAvailablePorts(); + m_selectedPortIndex = 0; + } - ImGui::Combo( - "hex.windows.view.tty_console.baud"_lang, &m_selectedBaudRate, [](void *data, int idx) { - std::ignore = data; + if (ImGui::BeginCombo("hex.windows.view.tty_console.baud"_lang, std::to_string(m_selectedBaudRate).c_str())) { + for (auto baudRate : BaudRates) { + if (ImGui::Selectable(std::to_string(baudRate).c_str(), m_selectedBaudRate == baudRate)) { + m_selectedBaudRate = baudRate; + } + } + ImGui::EndCombo(); + } - return ViewTTYConsole::BaudRates[idx]; - }, - nullptr, - ViewTTYConsole::BaudRates.size()); + if (ImGui::BeginCombo("hex.windows.view.tty_console.num_bits"_lang, std::to_string(m_selectedNumBits).c_str())) { + for (auto numBits : NumBits) { + if (ImGui::Selectable(std::to_string(numBits).c_str(), m_selectedBaudRate == numBits)) { + m_selectedBaudRate = numBits; + } + } + ImGui::EndCombo(); + } - ImGui::Combo( - "hex.windows.view.tty_console.num_bits"_lang, &m_selectedNumBits, [](void *data, int idx) { - std::ignore = data; - - return ViewTTYConsole::NumBits[idx]; - }, - nullptr, - ViewTTYConsole::NumBits.size()); - - ImGui::Combo( - "hex.windows.view.tty_console.stop_bits"_lang, &m_selectedStopBits, [](void *data, int idx) { - std::ignore = data; - - return ViewTTYConsole::StopBits[idx]; - }, - nullptr, - ViewTTYConsole::StopBits.size()); - - ImGui::Combo( - "hex.windows.view.tty_console.parity_bits"_lang, &m_selectedParityBits, [](void *data, int idx) { - std::ignore = data; - - return ViewTTYConsole::ParityBits[idx]; - }, - nullptr, - ViewTTYConsole::ParityBits.size()); + constexpr static std::array StopBitsStrings = { "1", "1.5", "2" }; + if (ImGui::BeginCombo( "hex.windows.view.tty_console.stop_bits"_lang, StopBitsStrings[u32(m_selectedStopBits)])) { + for (u32 i = 0; i < StopBitsStrings.size(); i++) { + if (ImGui::Selectable(StopBitsStrings[i], m_selectedStopBits == StopBits(i))) { + m_selectedStopBits = StopBits(i); + } + } + ImGui::EndCombo(); + } + constexpr static std::array ParityBitsStrings = { "None", "Odd", "Even", "Mark", "Space" }; + if (ImGui::BeginCombo( "hex.windows.view.tty_console.parity_bits"_lang, ParityBitsStrings[u32(m_selectedParityBits)])) { + for (u32 i = 0; i < ParityBitsStrings.size(); i++) { + if (ImGui::Selectable(ParityBitsStrings[i], m_selectedParityBits == ParityBits(i))) { + m_selectedParityBits = ParityBits(i); + } + } + ImGui::EndCombo(); + } ImGui::Checkbox("hex.windows.view.tty_console.cts"_lang, &m_hasCTSFlowControl); @@ -98,50 +99,19 @@ namespace hex::plugin::windows { if (ImGui::Button("hex.windows.view.tty_console.clear"_lang)) { std::scoped_lock lock(m_receiveBufferMutex); - m_receiveDataBuffer.clear(); - m_wrapPositions.clear(); + m_receiveLines.clear(); } ImGui::SameLine(); ImGui::Checkbox("hex.windows.view.tty_console.auto_scroll"_lang, &m_shouldAutoScroll); - ImGuiExt::Header("hex.windows.view.tty_console.console"_lang); - - auto consoleSize = ImMax(ImGui::GetContentRegionAvail(), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)); - consoleSize.y -= ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 4; - if (ImGui::BeginChild("##scrolling", consoleSize, true, ImGuiWindowFlags_HorizontalScrollbar)) { - ImGuiListClipper clipper; - clipper.Begin(m_wrapPositions.size(), ImGui::GetTextLineHeight()); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); - while (clipper.Step()) { - std::scoped_lock lock(m_receiveBufferMutex); - - for (int i = clipper.DisplayStart + 1; i < clipper.DisplayEnd; i++) { - ImGui::TextUnformatted(m_receiveDataBuffer.data() + m_wrapPositions[i - 1], m_receiveDataBuffer.data() + m_wrapPositions[i]); - } - - if (!m_receiveDataBuffer.empty() && !m_wrapPositions.empty()) - if (static_cast(clipper.DisplayEnd) >= m_wrapPositions.size() - 1) - ImGui::TextUnformatted(m_receiveDataBuffer.data() + m_wrapPositions.back()); - - if (m_shouldAutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { - ImGui::SetScrollHereY(0.0F); - } - } - ImGui::PopStyleVar(); - } - ImGui::EndChild(); + this->drawConsole(); ImGui::PushItemWidth(-1); - if (ImGui::InputText("##transmit", m_transmitDataBuffer.data(), m_transmitDataBuffer.size() - 2, ImGuiInputTextFlags_EnterReturnsTrue)) { - auto size = strlen(m_transmitDataBuffer.data()); - - m_transmitDataBuffer[size + 0] = '\n'; - m_transmitDataBuffer[size + 1] = 0x00; - - this->transmitData(m_transmitDataBuffer); + if (ImGui::InputText("##transmit", m_transmitDataBuffer, ImGuiInputTextFlags_EnterReturnsTrue)) { + this->transmitData(m_transmitDataBuffer + "\r\n"); + m_transmitDataBuffer.clear(); ImGui::SetKeyboardFocusHere(0); } ImGui::PopItemWidth(); @@ -151,33 +121,56 @@ namespace hex::plugin::windows { if (ImGui::BeginPopup("ConsoleMenu")) { - static std::vector buffer(2, 0x00); if (ImGui::MenuItem("hex.windows.view.tty_console.send_etx"_lang, "CTRL + C")) { - buffer[0] = 0x03; - this->transmitData(buffer); + this->transmitData({ 0x03 }); } if (ImGui::MenuItem("hex.windows.view.tty_console.send_eot"_lang, "CTRL + D")) { - buffer[0] = 0x04; - this->transmitData(buffer); + this->transmitData({ 0x04 }); } if (ImGui::MenuItem("hex.windows.view.tty_console.send_sub"_lang, "CTRL + Z")) { - buffer[0] = 0x1A; - this->transmitData(buffer); + this->transmitData({ 0x1A }); } ImGui::EndPopup(); } } - std::vector> ViewTTYConsole::getAvailablePorts() const { - std::vector> result; + void ViewTTYConsole::drawConsole() { + ImGuiExt::Header("hex.windows.view.tty_console.console"_lang); + + auto consoleSize = ImMax(ImGui::GetContentRegionAvail(), ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 5)); + consoleSize.y -= ImGui::GetTextLineHeight() + ImGui::GetStyle().FramePadding.y * 4; + if (ImGui::BeginChild("##scrolling", consoleSize, true, ImGuiWindowFlags_HorizontalScrollbar)) { + ImGuiListClipper clipper; + clipper.Begin(m_receiveLines.size(), ImGui::GetTextLineHeight()); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); + while (clipper.Step()) { + std::scoped_lock lock(m_receiveBufferMutex); + + for (int i = clipper.DisplayStart + 1; i < clipper.DisplayEnd; i++) { + ImGui::TextUnformatted(m_receiveLines[i].c_str()); + } + + if (m_shouldAutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(0.0F); + } + } + ImGui::PopStyleVar(); + } + ImGui::EndChild(); + } + + + std::vector ViewTTYConsole::getAvailablePorts() const { + std::vector result; std::vector buffer(0xFFF, 0x00); for (u16 portNumber = 0; portNumber <= 255; portNumber++) { std::wstring port = L"COM" + std::to_wstring(portNumber); if (::QueryDosDeviceW(port.c_str(), buffer.data(), buffer.size()) != 0) { - result.emplace_back(port, buffer.data()); + result.emplace_back(utf16ToUtf8(port), L"\\\\.\\" + port); } } @@ -185,22 +178,25 @@ namespace hex::plugin::windows { } bool ViewTTYConsole::connect() { - if (m_comPorts.empty() || static_cast(m_selectedPort) >= m_comPorts.size()) { + if (m_comPorts.empty() || static_cast(m_selectedPortIndex) >= m_comPorts.size()) { ui::ToastError::open("hex.windows.view.tty_console.no_available_port"_lang); - return true; // If false, connect_error error popup will override this error popup + return true; } - m_portHandle = ::CreateFileW((LR"(\\.\)" + m_comPorts[m_selectedPort].first).c_str(), + m_portHandle = ::CreateFileW(m_comPorts[m_selectedPortIndex].path.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, + FILE_ATTRIBUTE_NORMAL, nullptr); if (m_portHandle == INVALID_HANDLE_VALUE) return false; - auto closeHandle = SCOPE_GUARD { CloseHandle(m_portHandle); }; + auto closeHandle = SCOPE_GUARD { + CloseHandle(m_portHandle); + m_portHandle = INVALID_HANDLE_VALUE; + }; if (!::SetupComm(m_portHandle, 10000, 10000)) return false; @@ -211,10 +207,10 @@ namespace hex::plugin::windows { if (!::GetCommState(m_portHandle, &serialParams)) return false; - serialParams.BaudRate = std::stoi(ViewTTYConsole::BaudRates[m_selectedBaudRate]); - serialParams.ByteSize = std::stoi(ViewTTYConsole::NumBits[m_selectedNumBits]); - serialParams.StopBits = m_selectedStopBits; - serialParams.Parity = m_selectedParityBits; + serialParams.BaudRate = m_selectedBaudRate; + serialParams.ByteSize = m_selectedNumBits; + serialParams.StopBits = u32(m_selectedStopBits); + serialParams.Parity = u32(m_selectedParityBits); serialParams.fOutxCtsFlow = m_hasCTSFlowControl; if (!::SetCommState(m_portHandle, &serialParams)) @@ -237,23 +233,23 @@ namespace hex::plugin::windows { OVERLAPPED overlapped = { }; overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr); - ON_SCOPE_EXIT { ::CloseHandle(&overlapped); }; + ON_SCOPE_EXIT { ::CloseHandle(overlapped.hEvent); }; - auto addByte = [this](char byte) { + auto addByte = [&, this](char byte) { std::scoped_lock lock(m_receiveBufferMutex); - if (byte >= 0x20 && byte <= 0x7E) { - m_receiveDataBuffer.back() = byte; - m_receiveDataBuffer.push_back(0x00); - } else if (byte == '\n' || byte == '\r') { - if (m_receiveDataBuffer.empty()) - return; - - u32 wrapPos = m_receiveDataBuffer.size() - 1; - - if (m_wrapPositions.empty() || m_wrapPositions.back() != wrapPos) - m_wrapPositions.push_back(wrapPos); + if (m_receiveLines.empty()) + m_receiveLines.emplace_back(); + if (std::isprint(byte)) { + m_receiveLines.back().push_back(byte); + } else if (byte == '\n') { + m_receiveLines.emplace_back(); + } else if (byte == '\r') { + // Ignore carriage return + } else { + m_receiveLines.back() += hex::format("<{:02X}>", static_cast(byte)); } + }; while (!token.stop_requested()) { @@ -261,16 +257,16 @@ namespace hex::plugin::windows { char byte = 0; if (!waitingOnRead) { - if (::ReadFile(m_portHandle, &byte, sizeof(char), &bytesRead, &overlapped)) { + if (::ReadFile(m_portHandle, &byte, sizeof(char), &bytesRead, &overlapped) && bytesRead > 0) { addByte(byte); } else if (::GetLastError() == ERROR_IO_PENDING) { waitingOnRead = true; } } else { - byte = 0; + byte = 0; switch (::WaitForSingleObject(overlapped.hEvent, 500)) { case WAIT_OBJECT_0: - if (::GetOverlappedResult(m_portHandle, &overlapped, &bytesRead, false)) { + if (::GetOverlappedResult(m_portHandle, &overlapped, &bytesRead, false) && bytesRead > 0) { addByte(byte); waitingOnRead = false; } @@ -286,39 +282,30 @@ namespace hex::plugin::windows { bool ViewTTYConsole::disconnect() { ::SetCommMask(m_portHandle, EV_TXEMPTY); - m_receiveThread.request_stop(); - m_receiveThread.join(); ::CloseHandle(m_portHandle); m_portHandle = INVALID_HANDLE_VALUE; + if (m_receiveThread.joinable()) { + m_receiveThread.request_stop(); + m_receiveThread.join(); + } + return true; } - void ViewTTYConsole::transmitData(std::vector &data) { + void ViewTTYConsole::transmitData(const std::string &data) { if (m_transmitting) return; - TaskManager::createBackgroundTask("hex.windows.view.tty_console.task.transmitting", [&, this](auto&) { - OVERLAPPED overlapped = { }; + m_transmitting = true; - overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr); - ON_SCOPE_EXIT { ::CloseHandle(&overlapped); }; + DWORD bytesWritten = 0; + if (!::WriteFile(m_portHandle, data.data(), data.size(), &bytesWritten, nullptr)) { + log::error("Failed to write data to serial port: {}", formatSystemError(::GetLastError())); + } - m_transmitting = true; - - DWORD bytesWritten = 0; - if (!::WriteFile(m_portHandle, data.data(), strlen(data.data()), &bytesWritten, &overlapped)) { - if (::GetLastError() == ERROR_IO_PENDING) { - ::GetOverlappedResult(m_portHandle, &overlapped, &bytesWritten, true); - } - } - - if (bytesWritten > 0) - data[0] = 0x00; - - m_transmitting = false; - }); + m_transmitting = false; } } \ No newline at end of file