1
0
mirror of synced 2024-12-22 20:45:53 +01:00
ImHex/plugins/windows/source/views/view_tty_console.cpp

324 lines
12 KiB
C++
Raw Normal View History

2021-05-29 21:52:52 +02:00
#include "views/view_tty_console.hpp"
#include <imgui_internal.h>
#include <hex/api/task_manager.hpp>
2021-08-29 22:15:18 +02:00
#include <hex/helpers/utils.hpp>
#include <toasts/toast_notification.hpp>
#include <wolv/utils/guards.hpp>
2023-11-30 10:22:15 +01:00
#include <windows.h>
namespace hex::plugin::windows {
2021-05-29 21:52:52 +02:00
2024-01-08 21:51:48 +01:00
ViewTTYConsole::ViewTTYConsole() : View::Window("hex.windows.view.tty_console.name", ICON_VS_TERMINAL) {
2023-12-19 13:10:25 +01:00
m_comPorts = getAvailablePorts();
m_transmitDataBuffer.resize(0xFFF, 0x00);
m_receiveDataBuffer.reserve(0xFFF);
m_receiveDataBuffer.push_back(0x00);
2021-05-29 21:52:52 +02:00
}
void ViewTTYConsole::drawContent() {
ImGuiExt::Header("hex.windows.view.tty_console.config"_lang, true);
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
bool connected = m_portHandle != INVALID_HANDLE_VALUE;
2021-05-29 21:52:52 +02:00
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, connected);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, connected ? 0.5F : 1.0F);
2021-05-29 21:52:52 +02:00
ImGui::Combo(
2023-12-19 13:10:25 +01:00
"hex.windows.view.tty_console.port"_lang, &m_selectedPort, [](void *data, int idx) {
auto &ports = *static_cast<std::vector<std::pair<std::string, std::string>> *>(data);
2021-05-29 21:52:52 +02:00
return ports[idx].first.c_str();
},
2023-12-19 13:10:25 +01:00
&m_comPorts,
m_comPorts.size());
2021-05-29 21:52:52 +02:00
ImGui::SameLine();
if (ImGui::Button("hex.windows.view.tty_console.reload"_lang))
2023-12-19 13:10:25 +01:00
m_comPorts = getAvailablePorts();
2021-05-29 21:52:52 +02:00
ImGui::Combo(
2023-12-19 13:10:25 +01:00
"hex.windows.view.tty_console.baud"_lang, &m_selectedBaudRate, [](void *data, int idx) {
hex::unused(data);
2021-05-29 21:52:52 +02:00
return ViewTTYConsole::BaudRates[idx];
},
nullptr,
ViewTTYConsole::BaudRates.size());
ImGui::Combo(
2023-12-19 13:10:25 +01:00
"hex.windows.view.tty_console.num_bits"_lang, &m_selectedNumBits, [](void *data, int idx) {
hex::unused(data);
return ViewTTYConsole::NumBits[idx];
},
nullptr,
ViewTTYConsole::NumBits.size());
2023-11-15 20:22:56 +01:00
ImGui::Combo(
2023-12-19 13:10:25 +01:00
"hex.windows.view.tty_console.stop_bits"_lang, &m_selectedStopBits, [](void *data, int idx) {
hex::unused(data);
return ViewTTYConsole::StopBits[idx];
},
nullptr,
ViewTTYConsole::StopBits.size());
2023-11-15 20:22:56 +01:00
ImGui::Combo(
2023-12-19 13:10:25 +01:00
"hex.windows.view.tty_console.parity_bits"_lang, &m_selectedParityBits, [](void *data, int idx) {
hex::unused(data);
return ViewTTYConsole::ParityBits[idx];
},
nullptr,
ViewTTYConsole::ParityBits.size());
2023-11-15 20:22:56 +01:00
2023-12-19 13:10:25 +01:00
ImGui::Checkbox("hex.windows.view.tty_console.cts"_lang, &m_hasCTSFlowControl);
2021-05-29 21:52:52 +02:00
ImGui::PopStyleVar();
ImGui::PopItemFlag();
2021-05-29 21:52:52 +02:00
ImGui::NewLine();
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
if (m_portHandle == INVALID_HANDLE_VALUE) {
if (ImGui::Button("hex.windows.view.tty_console.connect"_lang))
if (!this->connect())
ui::ToastError::open("hex.windows.view.tty_console.connect_error"_lang);
} else {
if (ImGui::Button("hex.windows.view.tty_console.disconnect"_lang))
this->disconnect();
}
2021-05-29 21:52:52 +02:00
ImGui::NewLine();
2021-05-29 21:52:52 +02:00
if (ImGui::Button("hex.windows.view.tty_console.clear"_lang)) {
2023-12-19 13:10:25 +01:00
std::scoped_lock lock(m_receiveBufferMutex);
2023-12-19 13:10:25 +01:00
m_receiveDataBuffer.clear();
m_wrapPositions.clear();
}
2021-05-29 21:52:52 +02:00
ImGui::SameLine();
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
ImGui::Checkbox("hex.windows.view.tty_console.auto_scroll"_lang, &m_shouldAutoScroll);
2021-05-29 21:52:52 +02:00
ImGuiExt::Header("hex.windows.view.tty_console.console"_lang);
2021-05-29 21:52:52 +02:00
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;
2023-12-19 13:10:25 +01:00
clipper.Begin(m_wrapPositions.size(), ImGui::GetTextLineHeight());
2021-05-29 21:52:52 +02:00
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1));
while (clipper.Step()) {
2023-12-19 13:10:25 +01:00
std::scoped_lock lock(m_receiveBufferMutex);
for (int i = clipper.DisplayStart + 1; i < clipper.DisplayEnd; i++) {
2023-12-19 13:10:25 +01:00
ImGui::TextUnformatted(m_receiveDataBuffer.data() + m_wrapPositions[i - 1], m_receiveDataBuffer.data() + m_wrapPositions[i]);
}
2023-12-19 13:10:25 +01:00
if (!m_receiveDataBuffer.empty() && !m_wrapPositions.empty())
if (static_cast<size_t>(clipper.DisplayEnd) >= m_wrapPositions.size() - 1)
ImGui::TextUnformatted(m_receiveDataBuffer.data() + m_wrapPositions.back());
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
if (m_shouldAutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
ImGui::SetScrollHereY(0.0F);
2021-05-29 21:52:52 +02:00
}
}
ImGui::PopStyleVar();
}
ImGui::EndChild();
2021-05-29 21:52:52 +02:00
ImGui::PushItemWidth(-1);
2023-12-19 13:10:25 +01:00
if (ImGui::InputText("##transmit", m_transmitDataBuffer.data(), m_transmitDataBuffer.size() - 2, ImGuiInputTextFlags_EnterReturnsTrue)) {
auto size = strlen(m_transmitDataBuffer.data());
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
m_transmitDataBuffer[size + 0] = '\n';
m_transmitDataBuffer[size + 1] = 0x00;
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
this->transmitData(m_transmitDataBuffer);
ImGui::SetKeyboardFocusHere(0);
}
ImGui::PopItemWidth();
if (ImGui::IsMouseDown(ImGuiMouseButton_Right) && ImGui::IsItemHovered() && m_portHandle != INVALID_HANDLE_VALUE && !m_transmitting)
ImGui::OpenPopup("ConsoleMenu");
if (ImGui::BeginPopup("ConsoleMenu")) {
static std::vector<char> buffer(2, 0x00);
if (ImGui::MenuItem("hex.windows.view.tty_console.send_etx"_lang, "CTRL + C")) {
buffer[0] = 0x03;
this->transmitData(buffer);
}
if (ImGui::MenuItem("hex.windows.view.tty_console.send_eot"_lang, "CTRL + D")) {
buffer[0] = 0x04;
this->transmitData(buffer);
}
if (ImGui::MenuItem("hex.windows.view.tty_console.send_sub"_lang, "CTRL + Z")) {
buffer[0] = 0x1A;
this->transmitData(buffer);
}
ImGui::EndPopup();
2021-05-29 21:52:52 +02:00
}
}
std::vector<std::pair<std::wstring, std::wstring>> ViewTTYConsole::getAvailablePorts() const {
std::vector<std::pair<std::wstring, std::wstring>> result;
std::vector<wchar_t> buffer(0xFFF, 0x00);
2021-05-29 21:52:52 +02:00
for (u16 portNumber = 0; portNumber <= 255; portNumber++) {
std::wstring port = L"COM" + std::to_wstring(portNumber);
2021-05-29 21:52:52 +02:00
if (::QueryDosDeviceW(port.c_str(), buffer.data(), buffer.size()) != 0) {
2021-05-29 21:52:52 +02:00
result.emplace_back(port, buffer.data());
}
}
return result;
}
bool ViewTTYConsole::connect() {
2023-12-19 13:10:25 +01:00
if (m_comPorts.empty() || static_cast<size_t>(m_selectedPort) >= 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
}
m_portHandle = ::CreateFileW((LR"(\\.\)" + m_comPorts[m_selectedPort].first).c_str(),
2022-02-01 22:09:44 +01:00
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
nullptr);
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
if (m_portHandle == INVALID_HANDLE_VALUE)
2021-05-29 21:52:52 +02:00
return false;
2023-12-19 13:10:25 +01:00
auto closeHandle = SCOPE_GUARD { CloseHandle(m_portHandle); };
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
if (!::SetupComm(m_portHandle, 10000, 10000))
2021-12-20 21:50:59 +01:00
return false;
2021-05-29 21:52:52 +02:00
DCB serialParams;
serialParams.DCBlength = sizeof(DCB);
2023-12-19 13:10:25 +01:00
if (!::GetCommState(m_portHandle, &serialParams))
2021-05-29 21:52:52 +02:00
return false;
2023-12-19 13:10:25 +01:00
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.fOutxCtsFlow = m_hasCTSFlowControl;
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
if (!::SetCommState(m_portHandle, &serialParams))
2021-05-29 21:52:52 +02:00
return false;
COMMTIMEOUTS timeouts;
2022-02-01 22:09:44 +01:00
timeouts.ReadIntervalTimeout = 500;
timeouts.ReadTotalTimeoutConstant = 500;
timeouts.ReadTotalTimeoutMultiplier = 100;
timeouts.WriteTotalTimeoutConstant = 500;
2021-05-29 21:52:52 +02:00
timeouts.WriteTotalTimeoutMultiplier = 100;
2023-12-19 13:10:25 +01:00
if (!::SetCommTimeouts(m_portHandle, &timeouts))
2021-05-29 21:52:52 +02:00
return false;
closeHandle.release();
2023-12-19 13:10:25 +01:00
m_receiveThread = std::jthread([this](const std::stop_token &token) {
2022-02-01 22:09:44 +01:00
bool waitingOnRead = false;
OVERLAPPED overlapped = { };
2021-05-29 21:52:52 +02:00
overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr);
ON_SCOPE_EXIT { ::CloseHandle(&overlapped); };
auto addByte = [this](char byte) {
2023-12-19 13:10:25 +01:00
std::scoped_lock lock(m_receiveBufferMutex);
if (byte >= 0x20 && byte <= 0x7E) {
2023-12-19 13:10:25 +01:00
m_receiveDataBuffer.back() = byte;
m_receiveDataBuffer.push_back(0x00);
} else if (byte == '\n' || byte == '\r') {
2023-12-19 13:10:25 +01:00
if (m_receiveDataBuffer.empty())
return;
2023-12-19 13:10:25 +01:00
u32 wrapPos = m_receiveDataBuffer.size() - 1;
2023-12-19 13:10:25 +01:00
if (m_wrapPositions.empty() || m_wrapPositions.back() != wrapPos)
m_wrapPositions.push_back(wrapPos);
}
};
2021-05-29 21:52:52 +02:00
while (!token.stop_requested()) {
DWORD bytesRead = 0;
char byte = 0;
if (!waitingOnRead) {
2023-12-19 13:10:25 +01:00
if (::ReadFile(m_portHandle, &byte, sizeof(char), &bytesRead, &overlapped)) {
addByte(byte);
} else if (::GetLastError() == ERROR_IO_PENDING) {
waitingOnRead = true;
}
} else {
2022-02-01 22:09:44 +01:00
byte = 0;
2023-11-10 20:47:08 +01:00
switch (::WaitForSingleObject(overlapped.hEvent, 500)) {
2022-02-01 22:09:44 +01:00
case WAIT_OBJECT_0:
2023-12-19 13:10:25 +01:00
if (::GetOverlappedResult(m_portHandle, &overlapped, &bytesRead, false)) {
2022-02-01 22:09:44 +01:00
addByte(byte);
waitingOnRead = false;
}
default:
break;
}
}
2021-05-29 21:52:52 +02:00
}
});
return true;
}
bool ViewTTYConsole::disconnect() {
2023-12-19 13:10:25 +01:00
::SetCommMask(m_portHandle, EV_TXEMPTY);
m_receiveThread.request_stop();
m_receiveThread.join();
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
::CloseHandle(m_portHandle);
m_portHandle = INVALID_HANDLE_VALUE;
2021-05-29 21:52:52 +02:00
return true;
}
void ViewTTYConsole::transmitData(std::vector<char> &data) {
2023-12-19 13:10:25 +01:00
if (m_transmitting)
2021-05-29 21:52:52 +02:00
return;
TaskManager::createBackgroundTask("hex.windows.view.tty_console.task.transmitting"_lang, [&, this](auto&) {
OVERLAPPED overlapped = { };
2021-05-29 21:52:52 +02:00
overlapped.hEvent = ::CreateEvent(nullptr, true, false, nullptr);
ON_SCOPE_EXIT { ::CloseHandle(&overlapped); };
2023-12-19 13:10:25 +01:00
m_transmitting = true;
2021-05-29 21:52:52 +02:00
DWORD bytesWritten = 0;
2023-12-19 13:10:25 +01:00
if (!::WriteFile(m_portHandle, data.data(), strlen(data.data()), &bytesWritten, &overlapped)) {
2021-05-29 21:52:52 +02:00
if (::GetLastError() == ERROR_IO_PENDING) {
2023-12-19 13:10:25 +01:00
::GetOverlappedResult(m_portHandle, &overlapped, &bytesWritten, true);
2021-05-29 21:52:52 +02:00
}
}
if (bytesWritten > 0)
data[0] = 0x00;
2021-05-29 21:52:52 +02:00
2023-12-19 13:10:25 +01:00
m_transmitting = false;
2021-05-29 21:52:52 +02:00
});
}
}