From c2e7868a05e574ef804592b62a5c4958616d6e30 Mon Sep 17 00:00:00 2001 From: senaer <2799280+sena-neuro@users.noreply.github.com> Date: Tue, 9 Jun 2026 22:30:04 +0200 Subject: [PATCH 1/2] Add remote stream selection command --- README.md | 3 +- src/mainwindow.cpp | 131 +++++++++++++++++++++++++++++++------------ src/mainwindow.h | 42 ++++++++++++-- src/tcpinterface.cpp | 21 +++---- src/tcpinterface.h | 3 +- 5 files changed, 146 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 6a4574d..21f1a25 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,8 @@ If you check the box to EnableRCS then LabRecorder exposes some rudimentary cont Currently supported commands include: * `select all` * `select none` -* `start` +* `select ` - checks streams matching an LSL resolver predicate such as `name='BioSemi'`, `type='EEG'`, or `name='BioSemi' and hostname='LabPC1'`. +* `start` - starts recording the current stream selection; returns an error if no streams are selected. * `stop` * `update` * `filename ...` diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index be82724..1a260f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -287,6 +287,41 @@ QString info_to_listName(const lsl::stream_info& info) { return QString::fromStdString(info.name() + " (" + info.hostname() + ")"); } +void MainWindow::updateKnownStreamSelectionFromUi() { + for (int i = 0; i < ui->streamList->count(); i++) { + QListWidgetItem *item = ui->streamList->item(i); + bool ok = false; + int knownIndex = item->data(Qt::UserRole).toInt(&ok); + if (ok && knownIndex >= 0 && knownIndex < knownStreams.count()) + knownStreams[knownIndex].checked = item->checkState() == Qt::Checked; + } +} + +void MainWindow::rebuildStreamList() { + const QBrush good_brush(QColor(0, 128, 0)), bad_brush(QColor(255, 0, 0)); + ui->streamList->clear(); + for (auto& m : std::as_const(missingStreams)) { + auto *item = new QListWidgetItem(m, ui->streamList); + item->setCheckState(Qt::Checked); + item->setForeground(bad_brush); + ui->streamList->addItem(item); + } + for (int i = 0; i < knownStreams.count(); i++) { + const auto &k = knownStreams[i]; + auto *item = new QListWidgetItem(k.listName(), ui->streamList); + item->setData(Qt::UserRole, i); + item->setCheckState(k.checked ? Qt::Checked : Qt::Unchecked); + item->setForeground(good_brush); + item->setToolTip(QString("Name: %1\nType: %2\nSource ID: %3\nHostname: %4\nUID: %5") + .arg(QString::fromStdString(k.name), + QString::fromStdString(k.type), + QString::fromStdString(k.id), + QString::fromStdString(k.host), + QString::fromStdString(k.uid))); + ui->streamList->addItem(item); + } +} + /** * @brief MainWindow::refreshStreams Find streams, generate a list of missing streams * and fill the UI streamlist. @@ -294,30 +329,25 @@ QString info_to_listName(const lsl::stream_info& info) { */ std::vector MainWindow::refreshStreams() { const std::vector resolvedStreams = lsl::resolve_streams(1.0); + updateKnownStreamSelectionFromUi(); // For each item in resolvedStreams, ignore if already in knownStreams, otherwise add to knownStreams. // if in missingStreams then also mark it as required (--> checked by default) and remove from missingStreams. for (const auto& s : resolvedStreams) { bool known = false; for (auto &k : knownStreams) { - known |= s.name() == k.name && s.type() == k.type && s.source_id() == k.id; + if (k.matches(s)) { + k.updateInfo(s); + known = true; + break; + } } if (!known) { bool found = missingStreams.contains(info_to_listName(s)); - knownStreams << StreamItem(s.name(), s.type(), s.source_id(), s.hostname(), found); + knownStreams << StreamItem(s, found); if (found) { missingStreams.remove(info_to_listName(s)); } } } - // For each item in knownStreams, update its checked status from GUI. (only works for streams found on a previous refresh) - // Because we search by name + host, entries aren't guaranteed to be unique, so checking one entry with matching name and host checks them all. - for (auto &k : knownStreams) { - QList foundItems = ui->streamList->findItems(k.listName(), Qt::MatchCaseSensitive); - if (foundItems.count() > 0) { - bool checked = false; - for (auto &fi : foundItems) { checked |= fi->checkState() == Qt::Checked; } - k.checked = checked; - } - } // For each item in knownStreams; if it is not resolved then drop it. If it was checked then add back to missingStreams. int k_ind = 0; while (k_ind < knownStreams.count()) { @@ -326,7 +356,7 @@ std::vector MainWindow::refreshStreams() { size_t r_ind = 0; while (!resolved && r_ind < resolvedStreams.size()) { const lsl::stream_info r = resolvedStreams[r_ind]; - resolved |= (r.name() == k.name) && (r.type() == k.type) && (r.source_id() == k.id); + resolved |= k.matches(r); r_ind++; } if (!resolved) { @@ -339,31 +369,13 @@ std::vector MainWindow::refreshStreams() { // Clear the streamList // Add missing items first. // Then add knownStreams (only in list if resolved). - const QBrush good_brush(QColor(0, 128, 0)), bad_brush(QColor(255, 0, 0)); - ui->streamList->clear(); - for (auto& m : std::as_const(missingStreams)) { - auto *item = new QListWidgetItem(m, ui->streamList); - item->setCheckState(Qt::Checked); - item->setForeground(bad_brush); - ui->streamList->addItem(item); - } - for (auto& k : knownStreams) { - auto *item = new QListWidgetItem(k.listName(), ui->streamList); - item->setCheckState(k.checked ? Qt::Checked : Qt::Unchecked); - item->setForeground(good_brush); - item->setToolTip(QString("Name: %1\nType: %2\nSource ID: %3\nHostname: %4") - .arg(QString::fromStdString(k.name), - QString::fromStdString(k.type), - QString::fromStdString(k.id), - QString::fromStdString(k.host))); - ui->streamList->addItem(item); - } + rebuildStreamList(); // return a std::vector of streams of checked and not missing streams. std::vector requestedAndAvailableStreams; for (const auto &r : resolvedStreams) { for (auto &k : knownStreams) { - if ((r.name() == k.name) && (r.type() == k.type) && (r.source_id() == k.id)) { + if (k.matches(r)) { if (k.checked) { requestedAndAvailableStreams.push_back(r); } break; } @@ -507,6 +519,40 @@ void MainWindow::selectNoStreams() { } } +bool MainWindow::hasSelectedStreams() const { + for (int i = 0; i < ui->streamList->count(); i++) { + const QListWidgetItem *item = ui->streamList->item(i); + if (item->checkState() == Qt::Checked) return true; + } + return false; +} + +void MainWindow::selectStreams(const QString &query) { + updateKnownStreamSelectionFromUi(); + std::vector matchedStreams; + try { + matchedStreams = lsl::resolve_stream(query.toStdString(), 0, 1.0); + } catch (std::exception &e) { + qWarning() << "Invalid stream selection query" << query << ":" << e.what(); + return; + } + + for (const auto &stream : matchedStreams) { + bool known = false; + for (auto &k : knownStreams) { + if (k.matches(stream)) { + k.updateInfo(stream); + k.checked = true; + known = true; + break; + } + } + if (!known) knownStreams << StreamItem(stream, true); + missingStreams.remove(info_to_listName(stream)); + } + rebuildStreamList(); +} + void MainWindow::buildBidsTemplate() { // path/to/CurrentStudy/sub-%p/ses-%s/eeg/sub-%p_ses-%s_task-%b[_acq-%a]_run-%r_eeg.xdf @@ -647,6 +693,7 @@ void MainWindow::enableRcs(bool bEnable) { connect(rcs.get(), &RemoteControlSocket::filename, this, &MainWindow::rcsUpdateFilename); connect(rcs.get(), &RemoteControlSocket::select_all, this, &MainWindow::selectAllStreams); connect(rcs.get(), &RemoteControlSocket::select_none, this, &MainWindow::selectNoStreams); + connect(rcs.get(), &RemoteControlSocket::select_stream, this, &MainWindow::selectStreams); } bool oldState = ui->rcsCheckBox->blockSignals(true); ui->rcsCheckBox->setChecked(bEnable); @@ -660,17 +707,27 @@ void MainWindow::rcsportValueChangedInt(int value) { } } -void MainWindow::rcsStartRecording() { - // since we want to avoid a pop-up window when streams are missing or unchecked, - // we'll check all the streams and start recording +void MainWindow::rcsStartRecording(QTcpSocket *sock) { + // Remote start should record the current stream selection. Do not call + // selectAllStreams() here; doing so would override TCP `select ` commands. + // hideWarnings suppresses non-critical confirmation dialogs for remote control. + if (!hasSelectedStreams()) { + qWarning() << "Remote start rejected: no streams selected"; + if (sock) sock->write("ERROR no streams selected"); + return; + } + const bool oldHideWarnings = hideWarnings; hideWarnings = true; - selectAllStreams(); startRecording(); + hideWarnings = oldHideWarnings; + if (sock) sock->write("OK"); } void MainWindow::rcsStopRecording() { + const bool oldHideWarnings = hideWarnings; hideWarnings = true; stopRecording(); + hideWarnings = oldHideWarnings; } void MainWindow::rcsUpdateFilename(QString s) { diff --git a/src/mainwindow.h b/src/mainwindow.h index abeaad4..2d8497b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -17,19 +17,47 @@ class MainWindow; class recording; class RemoteControlSocket; +class QTcpSocket; class StreamItem { public: - StreamItem(std::string stream_name, std::string stream_type, std::string source_id, - std::string hostname, bool required) - : name(stream_name), type(stream_type), id(source_id), host(hostname), checked(required) {} + StreamItem(const lsl::stream_info &info, bool required) + : name(info.name()), type(info.type()), id(info.source_id()), host(info.hostname()), + uid(info.uid()), sessionId(info.session_id()), channelCount(info.channel_count()), + nominalSrate(info.nominal_srate()), channelFormat(info.channel_format()), + createdAt(info.created_at()), checked(required) {} - QString listName() { return QString::fromStdString(name + " (" + host + ")"); } + QString listName() const { return QString::fromStdString(name + " (" + host + ")"); } + bool matches(const lsl::stream_info &info) const { + if (!id.empty() || !info.source_id().empty()) + return name == info.name() && type == info.type() && id == info.source_id(); + return name == info.name() && type == info.type() && host == info.hostname() && + sessionId == info.session_id() && channelCount == info.channel_count() && + nominalSrate == info.nominal_srate() && channelFormat == info.channel_format(); + } + void updateInfo(const lsl::stream_info &info) { + name = info.name(); + type = info.type(); + id = info.source_id(); + host = info.hostname(); + uid = info.uid(); + sessionId = info.session_id(); + channelCount = info.channel_count(); + nominalSrate = info.nominal_srate(); + channelFormat = info.channel_format(); + createdAt = info.created_at(); + } std::string name; std::string type; std::string id; std::string host; + std::string uid; + std::string sessionId; + int32_t channelCount; + double nominalSrate; + lsl::channel_format_t channelFormat; + double createdAt; bool checked; }; @@ -50,13 +78,14 @@ private slots: void stopRecording(void); void selectAllStreams(); void selectNoStreams(); + void selectStreams(const QString &query); void buildFilename(); void buildBidsTemplate(); void printReplacedFilename(); void enableRcs(bool bEnable); void rcsCheckBoxChanged(bool checked); void rcsUpdateFilename(QString s); - void rcsStartRecording(); + void rcsStartRecording(QTcpSocket *sock); void rcsStopRecording(); void rcsportValueChangedInt(int value); @@ -65,6 +94,9 @@ private slots: // function for loading / saving the config file QString find_config_file(const char *filename); QString counterPlaceholder() const; + bool hasSelectedStreams() const; + void updateKnownStreamSelectionFromUi(); + void rebuildStreamList(); void load_config(QString filename); void save_config(QString filename); diff --git a/src/tcpinterface.cpp b/src/tcpinterface.cpp index 044486b..eaaa480 100644 --- a/src/tcpinterface.cpp +++ b/src/tcpinterface.cpp @@ -17,20 +17,21 @@ void RemoteControlSocket::addClient() { void RemoteControlSocket::handleLine(QString s, QTcpSocket *sock) { qInfo() << s; - if (s == "start") - emit start(); - else if (s == "stop") + if (s == "start") { + emit start(sock); + return; + } else if (s == "stop") emit stop(); else if (s == "update") emit refresh_streams(); - else if (s.contains("filename")) { + else if (s == "filename" || s.startsWith("filename ")) { emit filename(s); - } else if (s.contains("select")) { - if (s.contains("all")) { - emit select_all(); - } else if (s.contains("none")) { - emit select_none(); - } + } else if (s == "select all") { + emit select_all(); + } else if (s == "select none") { + emit select_none(); + } else if (s.startsWith("select ")) { + emit select_stream(s.mid(QStringLiteral("select ").size())); } sock->write("OK"); // TODO: select /deselect streams diff --git a/src/tcpinterface.h b/src/tcpinterface.h index 3eeef1e..dfec692 100644 --- a/src/tcpinterface.h +++ b/src/tcpinterface.h @@ -16,11 +16,12 @@ class RemoteControlSocket : public QObject { signals: void refresh_streams(); - void start(); + void start(QTcpSocket *sock); void stop(); void filename(QString s); void select_all(); void select_none(); + void select_stream(QString query); public slots: void addClient(); From 0ae59bb79fc0098c1cd9ef416ee808f8d3f4d66b Mon Sep 17 00:00:00 2001 From: senaer <2799280+sena-neuro@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:11:42 +0200 Subject: [PATCH 2/2] Tighten remote stream selection handling --- README.md | 2 ++ src/mainwindow.cpp | 59 ++++++++++++++++++++++++++++++-------------- src/mainwindow.h | 29 ++++++++-------------- src/tcpinterface.cpp | 6 ++++- src/tcpinterface.h | 2 +- 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 21f1a25..2342216 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ Currently supported commands include: * `update` * `filename ...` +Commands respond with `OK`, `WARNING ...`, or `ERROR ...`. + `filename` is followed by a series of space-delimited options enclosed in curly braces. e.g. {root:C:\root_data_dir} * `root` - Sets the root data directory. * `template` - sets the File Name / Template. Will unselect BIDS option. May contain wildcards. diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1a260f8..53ee375 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -40,7 +40,7 @@ MainWindow::MainWindow(QWidget *parent, const char *config_file) connect(ui->refreshButton, &QPushButton::clicked, this, &MainWindow::refreshStreams); connect(ui->selectAllButton, &QPushButton::clicked, this, &MainWindow::selectAllStreams); connect(ui->selectNoneButton, &QPushButton::clicked, this, &MainWindow::selectNoStreams); - connect(ui->startButton, &QPushButton::clicked, this, &MainWindow::startRecording); + connect(ui->startButton, &QPushButton::clicked, this, [this]() { startRecording(); }); connect(ui->stopButton, &QPushButton::clicked, this, &MainWindow::stopRecording); connect(ui->actionAbout, &QAction::triggered, this, [this]() { QString infostr = QStringLiteral("LSL library version: ") + @@ -312,12 +312,11 @@ void MainWindow::rebuildStreamList() { item->setData(Qt::UserRole, i); item->setCheckState(k.checked ? Qt::Checked : Qt::Unchecked); item->setForeground(good_brush); - item->setToolTip(QString("Name: %1\nType: %2\nSource ID: %3\nHostname: %4\nUID: %5") + item->setToolTip(QString("Name: %1\nType: %2\nSource ID: %3\nHostname: %4") .arg(QString::fromStdString(k.name), QString::fromStdString(k.type), QString::fromStdString(k.id), - QString::fromStdString(k.host), - QString::fromStdString(k.uid))); + QString::fromStdString(k.host))); ui->streamList->addItem(item); } } @@ -384,9 +383,8 @@ std::vector MainWindow::refreshStreams() { return requestedAndAvailableStreams; } -void MainWindow::startRecording() { +MainWindow::StartResult MainWindow::startRecording() { if (!currentRecording) { - // automatically refresh streams const std::vector requestedAndAvailableStreams = refreshStreams(); @@ -399,7 +397,7 @@ void MainWindow::startRecording() { QMessageBox::Yes | QMessageBox::No, this); msgBox.setInformativeText("Do you want to start recording anyway?"); msgBox.setDefaultButton(QMessageBox::No); - if (msgBox.exec() != QMessageBox::Yes) return; + if (msgBox.exec() != QMessageBox::Yes) return StartResult::Failed; } if (requestedAndAvailableStreams.size() == 0) { @@ -407,7 +405,7 @@ void MainWindow::startRecording() { "You have selected no streams", QMessageBox::Yes | QMessageBox::No, this); msgBox.setInformativeText("Do you want to start recording anyway?"); msgBox.setDefaultButton(QMessageBox::No); - if (msgBox.exec() != QMessageBox::Yes) return; + if (msgBox.exec() != QMessageBox::Yes) return StartResult::Failed; } } @@ -415,13 +413,13 @@ void MainWindow::startRecording() { QString recFilename = replaceFilename(QDir::cleanPath(ui->lineEdit_template->text())); if (recFilename.isEmpty()) { QMessageBox::critical(this, "Filename empty", "Can not record without a file name"); - return; + return StartResult::Failed; } if (ui->rootEdit->text().trimmed().isEmpty()) { QMessageBox::critical(this, "Study Root empty", "Can not record without a Study Root folder. " "Please set a Study Root before recording."); - return; + return StartResult::Failed; } recFilename.prepend(QDir::cleanPath(ui->rootEdit->text()) + '/'); @@ -430,7 +428,7 @@ void MainWindow::startRecording() { if (recFileInfo.isDir()) { QMessageBox::warning( this, "Error", "Recording path already exists and is a directory"); - return; + return StartResult::Failed; } QString rename_to = recFileInfo.absolutePath() + '/' + recFileInfo.baseName() + "_old%1." + recFileInfo.suffix(); @@ -441,7 +439,7 @@ void MainWindow::startRecording() { if (!QFile::rename(recFileInfo.absoluteFilePath(), newname)) { QMessageBox::warning(this, "Permissions issue", "Cannot rename the file " + recFilename + " to " + newname); - return; + return StartResult::Failed; } qInfo() << "Moved existing file to " << newname; recFileInfo.refresh(); @@ -452,7 +450,7 @@ void MainWindow::startRecording() { QMessageBox::warning(this, "Permissions issue", "Can not create the directory " + recFileInfo.dir().path() + ". Please check your permissions."); - return; + return StartResult::Failed; } std::vector watchfor; @@ -483,11 +481,13 @@ void MainWindow::startRecording() { ui->stopButton->setEnabled(true); ui->startButton->setEnabled(false); startTime = (int)lsl::local_clock(); + return StartResult::Started; } else if (!hideWarnings) { QMessageBox::information( this, "Already recording", "The recording is already running", QMessageBox::Ok); } + return StartResult::AlreadyRecording; } void MainWindow::stopRecording() { @@ -527,15 +527,16 @@ bool MainWindow::hasSelectedStreams() const { return false; } -void MainWindow::selectStreams(const QString &query) { +MainWindow::SelectResult MainWindow::selectStreams(const QString &query) { updateKnownStreamSelectionFromUi(); std::vector matchedStreams; try { matchedStreams = lsl::resolve_stream(query.toStdString(), 0, 1.0); } catch (std::exception &e) { qWarning() << "Invalid stream selection query" << query << ":" << e.what(); - return; + return SelectResult::InvalidQuery; } + if (matchedStreams.empty()) return SelectResult::NoMatches; for (const auto &stream : matchedStreams) { bool known = false; @@ -551,6 +552,7 @@ void MainWindow::selectStreams(const QString &query) { missingStreams.remove(info_to_listName(stream)); } rebuildStreamList(); + return SelectResult::Selected; } void MainWindow::buildBidsTemplate() { @@ -693,7 +695,7 @@ void MainWindow::enableRcs(bool bEnable) { connect(rcs.get(), &RemoteControlSocket::filename, this, &MainWindow::rcsUpdateFilename); connect(rcs.get(), &RemoteControlSocket::select_all, this, &MainWindow::selectAllStreams); connect(rcs.get(), &RemoteControlSocket::select_none, this, &MainWindow::selectNoStreams); - connect(rcs.get(), &RemoteControlSocket::select_stream, this, &MainWindow::selectStreams); + connect(rcs.get(), &RemoteControlSocket::select_stream, this, &MainWindow::rcsSelectStreams); } bool oldState = ui->rcsCheckBox->blockSignals(true); ui->rcsCheckBox->setChecked(bEnable); @@ -711,6 +713,10 @@ void MainWindow::rcsStartRecording(QTcpSocket *sock) { // Remote start should record the current stream selection. Do not call // selectAllStreams() here; doing so would override TCP `select ` commands. // hideWarnings suppresses non-critical confirmation dialogs for remote control. + if (currentRecording) { + if (sock) sock->write("WARNING already recording"); + return; + } if (!hasSelectedStreams()) { qWarning() << "Remote start rejected: no streams selected"; if (sock) sock->write("ERROR no streams selected"); @@ -718,9 +724,26 @@ void MainWindow::rcsStartRecording(QTcpSocket *sock) { } const bool oldHideWarnings = hideWarnings; hideWarnings = true; - startRecording(); + const StartResult result = startRecording(); hideWarnings = oldHideWarnings; - if (sock) sock->write("OK"); + if (!sock) return; + if (result == StartResult::Started) + sock->write("OK"); + else if (result == StartResult::AlreadyRecording) + sock->write("WARNING already recording"); + else + sock->write("ERROR failed to start recording"); +} + +void MainWindow::rcsSelectStreams(const QString &query, QTcpSocket *sock) { + const SelectResult result = selectStreams(query); + if (!sock) return; + if (result == SelectResult::Selected) + sock->write("OK"); + else if (result == SelectResult::NoMatches) + sock->write("WARNING no streams matched"); + else + sock->write("ERROR invalid select query"); } void MainWindow::rcsStopRecording() { diff --git a/src/mainwindow.h b/src/mainwindow.h index 2d8497b..477b5ee 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -24,40 +24,27 @@ class StreamItem { public: StreamItem(const lsl::stream_info &info, bool required) : name(info.name()), type(info.type()), id(info.source_id()), host(info.hostname()), - uid(info.uid()), sessionId(info.session_id()), channelCount(info.channel_count()), - nominalSrate(info.nominal_srate()), channelFormat(info.channel_format()), - createdAt(info.created_at()), checked(required) {} + sessionId(info.session_id()), checked(required) {} QString listName() const { return QString::fromStdString(name + " (" + host + ")"); } bool matches(const lsl::stream_info &info) const { - if (!id.empty() || !info.source_id().empty()) - return name == info.name() && type == info.type() && id == info.source_id(); + if (!id.empty() && !info.source_id().empty()) + return id == info.source_id() && name == info.name() && type == info.type(); return name == info.name() && type == info.type() && host == info.hostname() && - sessionId == info.session_id() && channelCount == info.channel_count() && - nominalSrate == info.nominal_srate() && channelFormat == info.channel_format(); + sessionId == info.session_id(); } void updateInfo(const lsl::stream_info &info) { name = info.name(); type = info.type(); id = info.source_id(); host = info.hostname(); - uid = info.uid(); sessionId = info.session_id(); - channelCount = info.channel_count(); - nominalSrate = info.nominal_srate(); - channelFormat = info.channel_format(); - createdAt = info.created_at(); } std::string name; std::string type; std::string id; std::string host; - std::string uid; std::string sessionId; - int32_t channelCount; - double nominalSrate; - lsl::channel_format_t channelFormat; - double createdAt; bool checked; }; @@ -74,11 +61,9 @@ private slots: void closeEvent(QCloseEvent *ev) override; void blockSelected(const QString &block); std::vector refreshStreams(void); - void startRecording(void); void stopRecording(void); void selectAllStreams(); void selectNoStreams(); - void selectStreams(const QString &query); void buildFilename(); void buildBidsTemplate(); void printReplacedFilename(); @@ -86,14 +71,20 @@ private slots: void rcsCheckBoxChanged(bool checked); void rcsUpdateFilename(QString s); void rcsStartRecording(QTcpSocket *sock); + void rcsSelectStreams(const QString &query, QTcpSocket *sock); void rcsStopRecording(); void rcsportValueChangedInt(int value); private: + enum class StartResult { Started, AlreadyRecording, Failed }; + enum class SelectResult { Selected, NoMatches, InvalidQuery }; + QString replaceFilename(QString fullfile) const; // function for loading / saving the config file QString find_config_file(const char *filename); QString counterPlaceholder() const; + StartResult startRecording(); + SelectResult selectStreams(const QString &query); bool hasSelectedStreams() const; void updateKnownStreamSelectionFromUi(); void rebuildStreamList(); diff --git a/src/tcpinterface.cpp b/src/tcpinterface.cpp index eaaa480..6ff3c37 100644 --- a/src/tcpinterface.cpp +++ b/src/tcpinterface.cpp @@ -31,7 +31,11 @@ void RemoteControlSocket::handleLine(QString s, QTcpSocket *sock) { } else if (s == "select none") { emit select_none(); } else if (s.startsWith("select ")) { - emit select_stream(s.mid(QStringLiteral("select ").size())); + emit select_stream(s.mid(QStringLiteral("select ").size()), sock); + return; + } else { + sock->write("ERROR unknown command"); + return; } sock->write("OK"); // TODO: select /deselect streams diff --git a/src/tcpinterface.h b/src/tcpinterface.h index dfec692..53a2f36 100644 --- a/src/tcpinterface.h +++ b/src/tcpinterface.h @@ -21,7 +21,7 @@ class RemoteControlSocket : public QObject { void filename(QString s); void select_all(); void select_none(); - void select_stream(QString query); + void select_stream(QString query, QTcpSocket *sock); public slots: void addClient();