#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GamesPanel.h" #include "LogPanel.h" #include "PowerPanel.h" #include "SettingsTree.h" #include "VersionsPanel.h" #include "WebSocketController.h" static QWidget *makeGamesTab(WebSocketController *ctrl, QWidget *parent) { auto *panel = new GamesPanel(parent); ctrl->setGamesPanel(panel); // Route GAM add commands back through the controller QObject::connect(panel, &GamesPanel::commandRequested, ctrl, &WebSocketController::sendCommand); return panel; } static QWidget *makeVersionsTab(WebSocketController *ctrl, QWidget *parent) { auto *panel = new VersionsPanel(parent); ctrl->setVersionsPanel(panel); return panel; } static QWidget *makeSettingsTab(WebSocketController *ctrl, QWidget *parent) { auto *page = new QWidget(parent); auto *layout = new QVBoxLayout(page); layout->setContentsMargins(2, 2, 2, 2); layout->setSpacing(4); // --- Sliders row: brightness and volume side by side (top) --- auto *slidersRow = new QHBoxLayout(); auto *brightnessLabel = new QLabel("Brightness:", page); auto *brightnessSlider = new QSlider(Qt::Horizontal, page); brightnessSlider->setMinimum(1); brightnessSlider->setMaximum(12); brightnessSlider->setValue(1); brightnessSlider->setEnabled(false); auto *brightnessVal = new QLabel("–", page); brightnessVal->setFixedWidth(28); auto *volumeLabel = new QLabel("Volume:", page); auto *volumeSlider = new QSlider(Qt::Horizontal, page); volumeSlider->setMinimum(1); volumeSlider->setMaximum(6); volumeSlider->setValue(1); volumeSlider->setEnabled(false); auto *volumeVal = new QLabel("–", page); volumeVal->setFixedWidth(28); slidersRow->addWidget(brightnessLabel); slidersRow->addWidget(brightnessSlider, 1); slidersRow->addWidget(brightnessVal); slidersRow->addSpacing(16); slidersRow->addWidget(volumeLabel); slidersRow->addWidget(volumeSlider, 1); slidersRow->addWidget(volumeVal); layout->addLayout(slidersRow); // --- Tree (takes all spare vertical space) --- auto *tree = new SettingsTree(page); auto *ph = new QTreeWidgetItem(tree); ph->setText(0, "Connect to load settings..."); layout->addWidget(tree, 1); ctrl->setSettingsTree(tree); // --- Single compact toolbar row: name input + 3 GBL buttons + checkbox (below tree) --- auto *toolbar = new QHBoxLayout(); auto *nameEdit = new QLineEdit(page); nameEdit->setPlaceholderText("Config name..."); nameEdit->setMaximumWidth(160); auto *saveBtn = new QPushButton("Save", page); auto *loadBtn = new QPushButton("Restore From File", page); auto *resetBtn = new QPushButton("Restore Defaults", page); resetBtn->setStyleSheet( "QPushButton { color: white; background-color: #c62828; border-radius: 4px; padding: 3px 8px; }" "QPushButton:hover { background-color: #b71c1c; }" "QPushButton:disabled { background-color: #888888; color: #cccccc; }"); auto *autoPowerOff = new QCheckBox("Auto Power Off", page); toolbar->addWidget(nameEdit); toolbar->addWidget(saveBtn); toolbar->addWidget(loadBtn); toolbar->addWidget(resetBtn); toolbar->addStretch(1); toolbar->addWidget(autoPowerOff); layout->addLayout(toolbar); // --- Reset to defaults (inline confirm row — no QMessageBox::exec on WASM) --- auto *confirmRow = new QHBoxLayout(); auto *confirmLabel = new QLabel(page); confirmLabel->setText("Click again to confirm reset:"); confirmLabel->setStyleSheet("color: #c62828;"); confirmLabel->setVisible(false); auto *confirmYesBtn = new QPushButton("Yes, Reset", page); confirmYesBtn->setStyleSheet( "QPushButton { color: white; background-color: #c62828; border-radius: 4px; padding: 2px 8px; }"); confirmYesBtn->setVisible(false); auto *confirmNoBtn = new QPushButton("Cancel", page); confirmNoBtn->setVisible(false); confirmRow->addWidget(confirmLabel); confirmRow->addWidget(confirmYesBtn); confirmRow->addWidget(confirmNoBtn); confirmRow->addStretch(1); layout->addLayout(confirmRow); // ---------------------------------------------------------------- // Connections // ---------------------------------------------------------------- // Save config auto doSave = [ctrl, nameEdit]() { QString name = nameEdit->text().trimmed(); name.remove(QRegularExpression("[^A-Za-z0-9]")); if (name.isEmpty()) return; ctrl->sendCommand(QString("GBL SaveToFile %1").arg(name)); nameEdit->clear(); }; QObject::connect(saveBtn, &QPushButton::clicked, page, doSave); QObject::connect(nameEdit, &QLineEdit::returnPressed, page, doSave); // Restore from file QObject::connect(loadBtn, &QPushButton::clicked, page, [ctrl, nameEdit]() { QString name = nameEdit->text().trimmed(); name.remove(QRegularExpression("[^A-Za-z0-9]")); if (name.isEmpty()) return; ctrl->sendCommand(QString("GBL LoadFromFile %1").arg(name)); nameEdit->clear(); }); // Reset confirm QObject::connect(resetBtn, &QPushButton::clicked, page, [confirmLabel, confirmYesBtn, confirmNoBtn]() { confirmLabel->setVisible(true); confirmYesBtn->setVisible(true); confirmNoBtn->setVisible(true); }); QObject::connect(confirmYesBtn, &QPushButton::clicked, page, [ctrl, confirmLabel, confirmYesBtn, confirmNoBtn]() { ctrl->sendCommand(QStringLiteral("GBL ResetAllToDefaults")); confirmLabel->setVisible(false); confirmYesBtn->setVisible(false); confirmNoBtn->setVisible(false); }); QObject::connect(confirmNoBtn, &QPushButton::clicked, page, [confirmLabel, confirmYesBtn, confirmNoBtn]() { confirmLabel->setVisible(false); confirmYesBtn->setVisible(false); confirmNoBtn->setVisible(false); }); // Auto power off QObject::connect(autoPowerOff, &QCheckBox::toggled, page, [ctrl](bool checked) { ctrl->sendCommand(checked ? QStringLiteral("POW EnableAutoOff") : QStringLiteral("POW KeepOn")); }); // Query brightness/volume ranges+values on connect; disable on disconnect QObject::connect(ctrl->socket(), &QWebSocket::connected, page, [ctrl]() { ctrl->sendCommand(QStringLiteral("GBL brightnessMin")); ctrl->sendCommand(QStringLiteral("GBL brightnessMax")); ctrl->sendCommand(QStringLiteral("GBL brightness")); ctrl->sendCommand(QStringLiteral("GBL sound/volumeMin")); ctrl->sendCommand(QStringLiteral("GBL sound/volumeMax")); ctrl->sendCommand(QStringLiteral("GBL sound/volume")); }); QObject::connect(ctrl->socket(), &QWebSocket::disconnected, page, [brightnessSlider, brightnessVal, volumeSlider, volumeVal]() { brightnessSlider->setEnabled(false); brightnessVal->setText(QStringLiteral("–")); volumeSlider->setEnabled(false); volumeVal->setText(QStringLiteral("–")); }); // Parse incoming GBL brightness / sound/volume responses QObject::connect(ctrl->socket(), &QWebSocket::textMessageReceived, page, [brightnessSlider, brightnessVal, volumeSlider, volumeVal](const QString &msg) { // helper: parse "GBL =" and return {ok, value} auto parseGbl = [&](const QString &key) -> std::pair { const QString prefix = QString("GBL %1=").arg(key); if (!msg.startsWith(prefix)) return {false, 0}; bool ok = false; int v = msg.mid(prefix.length()).toInt(&ok); return {ok, v}; }; if (auto [ok, v] = parseGbl("brightness"); ok) { brightnessSlider->setValue(v); brightnessVal->setText(QString::number(v)); brightnessSlider->setEnabled(true); } else if (auto [ok, v] = parseGbl("brightnessMin"); ok) { brightnessSlider->setMinimum(v); } else if (auto [ok, v] = parseGbl("brightnessMax"); ok) { brightnessSlider->setMaximum(v); } else if (auto [ok, v] = parseGbl("sound/volume"); ok) { volumeSlider->setValue(v); volumeVal->setText(QString::number(v)); volumeSlider->setEnabled(true); } else if (auto [ok, v] = parseGbl("sound/volumeMin"); ok) { volumeSlider->setMinimum(v); } else if (auto [ok, v] = parseGbl("sound/volumeMax"); ok) { volumeSlider->setMaximum(v); } }); // Live label update while dragging; send command only on release QObject::connect(brightnessSlider, &QSlider::valueChanged, page, [brightnessVal](int v) { brightnessVal->setText(QString::number(v)); }); QObject::connect(brightnessSlider, &QSlider::sliderReleased, page, [ctrl, brightnessSlider]() { ctrl->sendCommand(QString("GBL brightness=%1").arg(brightnessSlider->value())); }); QObject::connect(volumeSlider, &QSlider::valueChanged, page, [volumeVal](int v) { volumeVal->setText(QString::number(v)); }); QObject::connect(volumeSlider, &QSlider::sliderReleased, page, [ctrl, volumeSlider]() { ctrl->sendCommand(QString("GBL sound/volume=%1").arg(volumeSlider->value())); }); return page; } static QWidget *makeManualTab(WebSocketController *ctrl, QWidget *parent) { auto *page = new QWidget(parent); auto *layout = new QVBoxLayout(page); auto *row = new QHBoxLayout(); auto *cmdEdit = new QLineEdit(page); cmdEdit->setPlaceholderText("Enter command to send..."); auto *sendBtn = new QPushButton("Send", page); auto *clearBtn = new QPushButton("Clear", page); row->addWidget(new QLabel("Command:", page)); row->addWidget(cmdEdit, 1); row->addWidget(sendBtn); row->addWidget(clearBtn); auto *log = new QTextEdit(page); log->setReadOnly(true); layout->addLayout(row); layout->addWidget(log, 1); ctrl->addLogView(log); auto send = [cmdEdit, ctrl]() { const QString cmd = cmdEdit->text().trimmed(); if (!cmd.isEmpty()) { ctrl->sendCommand(cmd); cmdEdit->clear(); } }; QObject::connect(sendBtn, &QPushButton::clicked, page, send); QObject::connect(cmdEdit, &QLineEdit::returnPressed, page, send); QObject::connect(clearBtn, &QPushButton::clicked, log, &QTextEdit::clear); return page; } static QWidget *makePowerTab(WebSocketController *ctrl, QWidget *parent) { auto *panel = new PowerPanel(parent); ctrl->setPowerPanel(panel); return panel; } static QWidget *makeLogsTab(WebSocketController *ctrl, QWidget *parent) { auto *panel = new LogPanel(ctrl, parent); ctrl->setLogPanel(panel); return panel; } static QWidget *makePanelsTab(WebSocketController *ctrl, QWidget *parent) { auto *page = new QWidget(parent); auto *layout = new QVBoxLayout(page); auto *log = new QTextEdit(page); log->setReadOnly(true); layout->addWidget(log, 1); ctrl->addLogView(log); return page; } int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget window; window.setWindowTitle("ESA WebSocket Client"); auto *mainLayout = new QVBoxLayout(&window); mainLayout->setContentsMargins(6, 6, 6, 6); mainLayout->setSpacing(4); // --- ICON → IP helper (formula + hardcoded overrides) --- // Returns the IP string for a given ICON number. static const auto iconToIp = [](int icon) -> QString { static const QHash overrides = { {280, QStringLiteral("10.10.1.30")}, }; if (overrides.contains(icon)) return overrides[icon]; const int subnet = (icon - 1) / 254; const int host = ((icon - 1) % 254) + 1; return QString("10.10.%1.%2").arg(subnet).arg(host); }; // --- Hidden urlEdit still used by WebSocketController::startConnection() --- auto *urlEdit = new QLineEdit(&window); urlEdit->setVisible(false); auto *statusLabel = new QLabel("Disconnected", &window); // --- Network connection row --- auto *headerRow = new QHBoxLayout(); headerRow->addWidget(new QLabel("Network connection:", &window)); // Option 1 – fixed IP auto *radioFixed = new QRadioButton("10.110.110.10", &window); // Option 2 – ICON number → derived IP auto *radioIcon = new QRadioButton(&window); auto *iconSpin = new QSpinBox(&window); iconSpin->setRange(1, 9999); iconSpin->setValue(280); iconSpin->setFixedWidth(68); iconSpin->setAlignment(Qt::AlignRight); auto *iconIpLabel = new QLabel(iconToIp(280), &window); iconIpLabel->setStyleSheet("color: #555; font-style: italic;"); // Option 3 – free text (hostname or raw IP) auto *radioOther = new QRadioButton("other:", &window); auto *otherEdit = new QLineEdit(&window); otherEdit->setText("ftsDevkit4.local"); otherEdit->setFixedWidth(160); otherEdit->setEnabled(false); // only active when radioOther selected auto *btnGroup = new QButtonGroup(&window); btnGroup->addButton(radioFixed, 0); btnGroup->addButton(radioIcon, 1); btnGroup->addButton(radioOther, 2); radioIcon->setChecked(true); // default selection headerRow->addWidget(radioFixed); headerRow->addWidget(radioIcon); headerRow->addWidget(iconSpin); headerRow->addWidget(iconIpLabel); headerRow->addSpacing(8); headerRow->addWidget(radioOther); headerRow->addWidget(otherEdit); headerRow->addStretch(1); // Buttons auto *connectBtn = new QPushButton("Connect", &window); auto *disconnectBtn = new QPushButton("Disconnect", &window); auto *shutdownBtn = new QPushButton("Shutdown", &window); disconnectBtn->setEnabled(false); shutdownBtn->setEnabled(false); shutdownBtn->setStyleSheet( "QPushButton { color: white; background-color: #c62828;" " border-radius: 4px; padding: 3px 10px; }" "QPushButton:hover { background-color: #b71c1c; }" "QPushButton:disabled { background-color: #888888; color: #cccccc; }"); headerRow->addWidget(connectBtn); headerRow->addWidget(disconnectBtn); headerRow->addWidget(shutdownBtn); headerRow->addWidget(statusLabel); mainLayout->addLayout(headerRow); // --- Build URL from current selection into the hidden urlEdit --- auto updateUrl = [=]() { const int port = 5424; QString host; if (radioFixed->isChecked()) { host = QStringLiteral("10.110.110.10"); } else if (radioIcon->isChecked()) { host = iconToIp(iconSpin->value()); } else { host = otherEdit->text().trimmed(); if (host.isEmpty()) host = QStringLiteral("127.0.0.1"); } urlEdit->setText(QString("ws://%1:%2/").arg(host).arg(port)); }; updateUrl(); // set initial value // Keep iconIpLabel in sync and rebuild URL whenever ICON changes QObject::connect(iconSpin, QOverload::of(&QSpinBox::valueChanged), &window, [=](int v) { iconIpLabel->setText(iconToIp(v)); updateUrl(); }); QObject::connect(btnGroup, QOverload::of(&QButtonGroup::idClicked), &window, [=](int) { otherEdit->setEnabled(radioOther->isChecked()); updateUrl(); }); QObject::connect(otherEdit, &QLineEdit::textChanged, &window, [=]() { if (radioOther->isChecked()) updateUrl(); }); // --- Controller --- auto *ctrl = new WebSocketController(urlEdit, statusLabel, &window); // --- Tabs --- auto *tabs = new QTabWidget(&window); tabs->addTab(makeGamesTab (ctrl, &window), "Games"); tabs->addTab(makeVersionsTab(ctrl, &window), "Versions"); tabs->addTab(makeManualTab (ctrl, &window), "Manual"); tabs->addTab(makeSettingsTab(ctrl, &window), "Settings"); tabs->addTab(makePowerTab (ctrl, &window), "Power"); tabs->addTab(makeLogsTab (ctrl, &window), "Logs"); tabs->addTab(makePanelsTab (ctrl, &window), "Panels"); mainLayout->addWidget(tabs, 1); // --- Shutdown: inline confirm (no QMessageBox::exec on WASM) --- auto *shutdownConfirmLabel = new QLabel("Confirm shutdown:", &window); auto *shutdownConfirmYes = new QPushButton("Yes, Shutdown", &window); auto *shutdownConfirmNo = new QPushButton("Cancel", &window); shutdownConfirmLabel->setStyleSheet("color: #c62828; font-weight: bold;"); shutdownConfirmYes->setStyleSheet( "QPushButton { color: white; background-color: #c62828; border-radius: 4px; padding: 2px 8px; }"); shutdownConfirmLabel->setVisible(false); shutdownConfirmYes->setVisible(false); shutdownConfirmNo->setVisible(false); headerRow->addWidget(shutdownConfirmLabel); headerRow->addWidget(shutdownConfirmYes); headerRow->addWidget(shutdownConfirmNo); QObject::connect(shutdownBtn, &QPushButton::clicked, &window, [shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo]() { shutdownConfirmLabel->setVisible(true); shutdownConfirmYes->setVisible(true); shutdownConfirmNo->setVisible(true); }); QObject::connect(shutdownConfirmYes, &QPushButton::clicked, &window, [ctrl, shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo]() { ctrl->sendCommand(QStringLiteral("POW ShutDown")); shutdownConfirmLabel->setVisible(false); shutdownConfirmYes->setVisible(false); shutdownConfirmNo->setVisible(false); }); QObject::connect(shutdownConfirmNo, &QPushButton::clicked, &window, [shutdownConfirmLabel, shutdownConfirmYes, shutdownConfirmNo]() { shutdownConfirmLabel->setVisible(false); shutdownConfirmYes->setVisible(false); shutdownConfirmNo->setVisible(false); }); // --- Wire up buttons --- QObject::connect(connectBtn, &QPushButton::clicked, ctrl, &WebSocketController::startConnection); QObject::connect(disconnectBtn, &QPushButton::clicked, ctrl, &WebSocketController::closeConnection); QObject::connect(ctrl->socket(), &QWebSocket::connected, ctrl, &WebSocketController::onConnected); QObject::connect(ctrl->socket(), &QWebSocket::disconnected, ctrl, &WebSocketController::onDisconnected); QObject::connect(ctrl->socket(), &QWebSocket::textMessageReceived, ctrl, &WebSocketController::onTextMessageReceived); QObject::connect(ctrl->socket(), &QWebSocket::errorOccurred, ctrl, &WebSocketController::onErrorOccurred); QObject::connect(ctrl->socket(), &QWebSocket::connected, &window, [connectBtn, disconnectBtn, shutdownBtn]() { connectBtn->setEnabled(false); disconnectBtn->setEnabled(true); shutdownBtn->setEnabled(true); }); QObject::connect(ctrl->socket(), &QWebSocket::disconnected, &window, [connectBtn, disconnectBtn, shutdownBtn]() { connectBtn->setEnabled(true); disconnectBtn->setEnabled(false); shutdownBtn->setEnabled(false); }); window.resize(1050, 620); window.show(); return app.exec(); }