From 46a79c6bb5968e3d7f6add67d2cb6410da6ed8e2 Mon Sep 17 00:00:00 2001 From: Dmitry K Date: Wed, 16 Jul 2025 10:29:16 +0400 Subject: [PATCH] Serial port field is now drop-down list --- .../DMXSettingsEntry/DMXSettingsEntry.cpp | 28 +++++++- .../DMXSettingsEntry/DMXSettingsEntry.ui | 11 ++- .../SerialSettingsEntry.cpp | 28 +++++++- .../SerialSettingsEntry.ui | 46 +++++++------ serial_port/serial_port.cpp | 67 +++++++++++++++++++ serial_port/serial_port.h | 4 ++ 6 files changed, 158 insertions(+), 26 deletions(-) diff --git a/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.cpp b/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.cpp index 85eecb64..078000bb 100644 --- a/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.cpp +++ b/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.cpp @@ -10,11 +10,35 @@ #include "DMXSettingsEntry.h" #include "ui_DMXSettingsEntry.h" +#include "serial_port.h" +#include + DMXSettingsEntry::DMXSettingsEntry(QWidget *parent) : BaseManualDeviceEntry(parent), ui(new Ui::DMXSettingsEntry) { ui->setupUi(this); + + std::vector serialPorts = serial_port::getSerialPorts(); + for(size_t i = 0; i < serialPorts.size(); ++i) + { + ui->PortComboBox->addItem(QString::fromStdString(serialPorts[i])); + } + if(serialPorts.empty()) + { + /*---------------------------------------------------*\ + | When no ports were found, add an unselectable entry | + | denoting this fact istead | + \*---------------------------------------------------*/ + QStandardItemModel* comboBoxModel = qobject_cast(ui->PortComboBox->model()); + if(comboBoxModel != nullptr) + { + ui->PortComboBox->addItem(tr("No serial ports found")); + QStandardItem *item = comboBoxModel->item(0); + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + } + } + ui->PortComboBox->clearEditText(); } DMXSettingsEntry::~DMXSettingsEntry() @@ -39,7 +63,7 @@ void DMXSettingsEntry::loadFromSettings(const json& data) if(data.contains("port")) { - ui->PortEdit->setText(QString::fromStdString(data["port"])); + ui->PortComboBox->setCurrentText(QString::fromStdString(data["port"])); } if(data.contains("red_channel")) @@ -75,7 +99,7 @@ json DMXSettingsEntry::saveSettings() | Required parameters | \*-------------------------------------------------*/ result["name"] = ui->NameEdit->text().toStdString(); - result["port"] = ui->PortEdit->text().toStdString(); + result["port"] = ui->PortComboBox->currentText().toStdString(); result["red_channel"] = ui->RedEdit->text().toUInt(); result["green_channel"] = ui->GreenEdit->text().toUInt(); result["blue_channel"] = ui->BlueEdit->text().toUInt(); diff --git a/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.ui b/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.ui index ba0b0080..7938b054 100644 --- a/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.ui +++ b/qt/ManualDevicesSettingsPage/DMXSettingsEntry/DMXSettingsEntry.ui @@ -7,7 +7,7 @@ 0 0 531 - 199 + 206 @@ -94,7 +94,14 @@ - + + + true + + + QComboBox::NoInsert + + diff --git a/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.cpp b/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.cpp index 1f2c09ed..1e0765a3 100644 --- a/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.cpp +++ b/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.cpp @@ -10,6 +10,9 @@ #include "SerialSettingsEntry.h" #include "ui_SerialSettingsEntry.h" +#include "serial_port.h" +#include + SerialSettingsEntry::SerialSettingsEntry(QWidget *parent) : BaseManualDeviceEntry(parent), ui(new Ui::SerialSettingsEntry) @@ -20,6 +23,27 @@ SerialSettingsEntry::SerialSettingsEntry(QWidget *parent) : ui->ProtocolComboBox->addItem("Adalight"); ui->ProtocolComboBox->addItem("TPM2"); ui->ProtocolComboBox->addItem("Basic I2C"); + + std::vector serialPorts = serial_port::getSerialPorts(); + for(size_t i = 0; i < serialPorts.size(); ++i) + { + ui->PortComboBox->addItem(QString::fromStdString(serialPorts[i])); + } + if(serialPorts.empty()) + { + /*---------------------------------------------------*\ + | When no ports were found, add an unselectable entry | + | denoting this fact istead | + \*---------------------------------------------------*/ + QStandardItemModel* comboBoxModel = qobject_cast(ui->PortComboBox->model()); + if(comboBoxModel != nullptr) + { + ui->PortComboBox->addItem(tr("No serial ports found")); + QStandardItem *item = comboBoxModel->item(0); + item->setFlags(item->flags() & ~Qt::ItemIsEnabled); + } + } + ui->PortComboBox->clearEditText(); } SerialSettingsEntry::~SerialSettingsEntry() @@ -56,7 +80,7 @@ void SerialSettingsEntry::loadFromSettings(const json& data) if(data.contains("port")) { - ui->PortEdit->setText(QString::fromStdString(data["port"])); + ui->PortComboBox->setCurrentText(QString::fromStdString(data["port"])); } if(data.contains("baud")) @@ -99,7 +123,7 @@ json SerialSettingsEntry::saveSettings() | Required parameters | \*-------------------------------------------------*/ result["name"] = ui->NameEdit->text().toStdString(); - result["port"] = ui->PortEdit->text().toStdString(); + result["port"] = ui->PortComboBox->currentText().toStdString(); result["num_leds"] = ui->NumLEDsEdit->text().toUInt(); result["baud"] = ui->BaudEdit->text().toUInt(); diff --git a/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.ui b/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.ui index 52a43157..2d2dcadd 100644 --- a/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.ui +++ b/qt/ManualDevicesSettingsPage/SerialSettingsEntry/SerialSettingsEntry.ui @@ -26,11 +26,12 @@ Serial Device - - - - - + + + + Number of LEDs: + + @@ -39,6 +40,9 @@ + + + @@ -46,17 +50,13 @@ - - - - Number of LEDs: - - + + - - + + - Port: + Protocol: @@ -66,15 +66,22 @@ - - + + - Protocol: + Port: - - + + + + true + + + QComboBox::NoInsert + + @@ -83,7 +90,6 @@ NameEdit - PortEdit BaudEdit NumLEDsEdit ProtocolComboBox diff --git a/serial_port/serial_port.cpp b/serial_port/serial_port.cpp index 72ca7509..c3b43550 100644 --- a/serial_port/serial_port.cpp +++ b/serial_port/serial_port.cpp @@ -13,6 +13,73 @@ #include "serial_port.h" +#include // For std::sort only + +/*---------------------------------------------------------*\ +| getSerialPorts(): returns the list of available serial | +| ports in the system | +\*---------------------------------------------------------*/ + +std::vector serial_port::getSerialPorts() +{ + /*-----------------------------------------------------------------------------------*\ + | Ported from https://github.com/nkinar/GetComPortList/blob/master/GetComPortList.cpp | + \*-----------------------------------------------------------------------------------*/ + std::vector port_list; +#if defined (_WIN32) || defined( _WIN64) + const uint32_t CHAR_NUM = 1024; + const uint32_t MAX_PORTS = 255; + const std::string COM_STR = "COM"; + char path[CHAR_NUM]; + for (uint32_t k = 0; k < MAX_PORTS; k++) + { + std::string port_name = COM_STR + std::to_string(k); + DWORD test = QueryDosDevice(port_name.c_str(), path, CHAR_NUM); + if (test == 0) continue; + port_list.push_back(port_name); + } +#endif +#if defined (__linux__) + namespace fs = std::filesystem; + const std::string DEV_PATH = "/dev/serial/by-id"; + try + { + fs::path p(DEV_PATH); + if (!fs::exists(DEV_PATH)) return port_list; + for (fs::directory_entry de: fs::directory_iterator(p)) + { + if (fs::is_symlink(de.symlink_status())) + { + fs::path symlink_points_at = fs::read_symlink(de); + port_list.push_back(std::string("/dev/")+symlink_points_at.filename().c_str()); + } + } + } + catch (const fs::filesystem_error &ex) {} +#endif +#if defined(__APPLE__) + namespace fs = std::filesystem; + const std::string DEV_PATH = "/dev"; + const std::regex base_regex(R"(\/dev\/(tty|cu)\..*)"); + try + { + fs::path p(DEV_PATH); + if (!fs::exists(DEV_PATH)) return port_list; + for (fs::directory_entry de: fs::directory_iterator(p)) { + fs::path canonical_path = fs::canonical(de); + std::string name = canonical_path.generic_string(); + std::smatch res; + std::regex_search(name, res, base_regex); + if (res.empty()) continue; + port_list.push_back(canonical_path.generic_string()); + } + } + catch (const fs::filesystem_error &ex) {} +#endif + std::sort(port_list.begin(), port_list.end()); + return port_list; +} + /*---------------------------------------------------------*\ | serial_port (constructor) | | The default constructor does not initialize the serial | diff --git a/serial_port/serial_port.h b/serial_port/serial_port.h index 879f63cf..bfc3f636 100644 --- a/serial_port/serial_port.h +++ b/serial_port/serial_port.h @@ -16,6 +16,8 @@ #include #include +#include +#include #ifdef _WIN32 /*---------------------------------------------------------*\ @@ -103,6 +105,8 @@ enum class serial_port { public: + static std::vector getSerialPorts(); + serial_port(); serial_port(const char * name, unsigned int baud); serial_port(const char * name,