@@ -287,37 +287,67 @@ QString info_to_listName(const lsl::stream_info& info) {
287287 return QString::fromStdString (info.name () + " (" + info.hostname () + " )" );
288288}
289289
290+ void MainWindow::updateKnownStreamSelectionFromUi () {
291+ for (int i = 0 ; i < ui->streamList ->count (); i++) {
292+ QListWidgetItem *item = ui->streamList ->item (i);
293+ bool ok = false ;
294+ int knownIndex = item->data (Qt::UserRole).toInt (&ok);
295+ if (ok && knownIndex >= 0 && knownIndex < knownStreams.count ())
296+ knownStreams[knownIndex].checked = item->checkState () == Qt::Checked;
297+ }
298+ }
299+
300+ void MainWindow::rebuildStreamList () {
301+ const QBrush good_brush (QColor (0 , 128 , 0 )), bad_brush (QColor (255 , 0 , 0 ));
302+ ui->streamList ->clear ();
303+ for (auto & m : std::as_const (missingStreams)) {
304+ auto *item = new QListWidgetItem (m, ui->streamList );
305+ item->setCheckState (Qt::Checked);
306+ item->setForeground (bad_brush);
307+ ui->streamList ->addItem (item);
308+ }
309+ for (int i = 0 ; i < knownStreams.count (); i++) {
310+ const auto &k = knownStreams[i];
311+ auto *item = new QListWidgetItem (k.listName (), ui->streamList );
312+ item->setData (Qt::UserRole, i);
313+ item->setCheckState (k.checked ? Qt::Checked : Qt::Unchecked);
314+ item->setForeground (good_brush);
315+ item->setToolTip (QString (" Name: %1\n Type: %2\n Source ID: %3\n Hostname: %4\n UID: %5" )
316+ .arg (QString::fromStdString (k.name ),
317+ QString::fromStdString (k.type ),
318+ QString::fromStdString (k.id ),
319+ QString::fromStdString (k.host ),
320+ QString::fromStdString (k.uid )));
321+ ui->streamList ->addItem (item);
322+ }
323+ }
324+
290325/* *
291326 * @brief MainWindow::refreshStreams Find streams, generate a list of missing streams
292327 * and fill the UI streamlist.
293328 * @return A vector of found stream_infos
294329 */
295330std::vector<lsl::stream_info> MainWindow::refreshStreams () {
296331 const std::vector<lsl::stream_info> resolvedStreams = lsl::resolve_streams (1.0 );
332+ updateKnownStreamSelectionFromUi ();
297333
298334 // For each item in resolvedStreams, ignore if already in knownStreams, otherwise add to knownStreams.
299335 // if in missingStreams then also mark it as required (--> checked by default) and remove from missingStreams.
300336 for (const auto & s : resolvedStreams) {
301337 bool known = false ;
302338 for (auto &k : knownStreams) {
303- known |= s.name () == k.name && s.type () == k.type && s.source_id () == k.id ;
339+ if (k.matches (s)) {
340+ k.updateInfo (s);
341+ known = true ;
342+ break ;
343+ }
304344 }
305345 if (!known) {
306346 bool found = missingStreams.contains (info_to_listName (s));
307- knownStreams << StreamItem (s. name (), s. type (), s. source_id (), s. hostname () , found);
347+ knownStreams << StreamItem (s, found);
308348 if (found) { missingStreams.remove (info_to_listName (s)); }
309349 }
310350 }
311- // For each item in knownStreams, update its checked status from GUI. (only works for streams found on a previous refresh)
312- // 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.
313- for (auto &k : knownStreams) {
314- QList<QListWidgetItem *> foundItems = ui->streamList ->findItems (k.listName (), Qt::MatchCaseSensitive);
315- if (foundItems.count () > 0 ) {
316- bool checked = false ;
317- for (auto &fi : foundItems) { checked |= fi->checkState () == Qt::Checked; }
318- k.checked = checked;
319- }
320- }
321351 // For each item in knownStreams; if it is not resolved then drop it. If it was checked then add back to missingStreams.
322352 int k_ind = 0 ;
323353 while (k_ind < knownStreams.count ()) {
@@ -326,7 +356,7 @@ std::vector<lsl::stream_info> MainWindow::refreshStreams() {
326356 size_t r_ind = 0 ;
327357 while (!resolved && r_ind < resolvedStreams.size ()) {
328358 const lsl::stream_info r = resolvedStreams[r_ind];
329- resolved |= (r. name () == k. name ) && (r. type () == k. type ) && (r. source_id () == k. id );
359+ resolved |= k. matches (r );
330360 r_ind++;
331361 }
332362 if (!resolved) {
@@ -339,31 +369,13 @@ std::vector<lsl::stream_info> MainWindow::refreshStreams() {
339369 // Clear the streamList
340370 // Add missing items first.
341371 // Then add knownStreams (only in list if resolved).
342- const QBrush good_brush (QColor (0 , 128 , 0 )), bad_brush (QColor (255 , 0 , 0 ));
343- ui->streamList ->clear ();
344- for (auto & m : std::as_const (missingStreams)) {
345- auto *item = new QListWidgetItem (m, ui->streamList );
346- item->setCheckState (Qt::Checked);
347- item->setForeground (bad_brush);
348- ui->streamList ->addItem (item);
349- }
350- for (auto & k : knownStreams) {
351- auto *item = new QListWidgetItem (k.listName (), ui->streamList );
352- item->setCheckState (k.checked ? Qt::Checked : Qt::Unchecked);
353- item->setForeground (good_brush);
354- item->setToolTip (QString (" Name: %1\n Type: %2\n Source ID: %3\n Hostname: %4" )
355- .arg (QString::fromStdString (k.name ),
356- QString::fromStdString (k.type ),
357- QString::fromStdString (k.id ),
358- QString::fromStdString (k.host )));
359- ui->streamList ->addItem (item);
360- }
372+ rebuildStreamList ();
361373
362374 // return a std::vector of streams of checked and not missing streams.
363375 std::vector<lsl::stream_info> requestedAndAvailableStreams;
364376 for (const auto &r : resolvedStreams) {
365377 for (auto &k : knownStreams) {
366- if ((r. name () == k. name ) && (r. type () == k. type ) && (r. source_id () == k. id )) {
378+ if (k. matches (r )) {
367379 if (k.checked ) { requestedAndAvailableStreams.push_back (r); }
368380 break ;
369381 }
@@ -507,6 +519,40 @@ void MainWindow::selectNoStreams() {
507519 }
508520}
509521
522+ bool MainWindow::hasSelectedStreams () const {
523+ for (int i = 0 ; i < ui->streamList ->count (); i++) {
524+ const QListWidgetItem *item = ui->streamList ->item (i);
525+ if (item->checkState () == Qt::Checked) return true ;
526+ }
527+ return false ;
528+ }
529+
530+ void MainWindow::selectStreams (const QString &query) {
531+ updateKnownStreamSelectionFromUi ();
532+ std::vector<lsl::stream_info> matchedStreams;
533+ try {
534+ matchedStreams = lsl::resolve_stream (query.toStdString (), 0 , 1.0 );
535+ } catch (std::exception &e) {
536+ qWarning () << " Invalid stream selection query" << query << " :" << e.what ();
537+ return ;
538+ }
539+
540+ for (const auto &stream : matchedStreams) {
541+ bool known = false ;
542+ for (auto &k : knownStreams) {
543+ if (k.matches (stream)) {
544+ k.updateInfo (stream);
545+ k.checked = true ;
546+ known = true ;
547+ break ;
548+ }
549+ }
550+ if (!known) knownStreams << StreamItem (stream, true );
551+ missingStreams.remove (info_to_listName (stream));
552+ }
553+ rebuildStreamList ();
554+ }
555+
510556void MainWindow::buildBidsTemplate () {
511557 // path/to/CurrentStudy/sub-%p/ses-%s/eeg/sub-%p_ses-%s_task-%b[_acq-%a]_run-%r_eeg.xdf
512558
@@ -647,6 +693,7 @@ void MainWindow::enableRcs(bool bEnable) {
647693 connect (rcs.get (), &RemoteControlSocket::filename, this , &MainWindow::rcsUpdateFilename);
648694 connect (rcs.get (), &RemoteControlSocket::select_all, this , &MainWindow::selectAllStreams);
649695 connect (rcs.get (), &RemoteControlSocket::select_none, this , &MainWindow::selectNoStreams);
696+ connect (rcs.get (), &RemoteControlSocket::select_stream, this , &MainWindow::selectStreams);
650697 }
651698 bool oldState = ui->rcsCheckBox ->blockSignals (true );
652699 ui->rcsCheckBox ->setChecked (bEnable);
@@ -660,17 +707,27 @@ void MainWindow::rcsportValueChangedInt(int value) {
660707 }
661708}
662709
663- void MainWindow::rcsStartRecording () {
664- // since we want to avoid a pop-up window when streams are missing or unchecked,
665- // we'll check all the streams and start recording
710+ void MainWindow::rcsStartRecording (QTcpSocket *sock) {
711+ // Remote start should record the current stream selection. Do not call
712+ // selectAllStreams() here; doing so would override TCP `select <query>` commands.
713+ // hideWarnings suppresses non-critical confirmation dialogs for remote control.
714+ if (!hasSelectedStreams ()) {
715+ qWarning () << " Remote start rejected: no streams selected" ;
716+ if (sock) sock->write (" ERROR no streams selected" );
717+ return ;
718+ }
719+ const bool oldHideWarnings = hideWarnings;
666720 hideWarnings = true ;
667- selectAllStreams ();
668721 startRecording ();
722+ hideWarnings = oldHideWarnings;
723+ if (sock) sock->write (" OK" );
669724}
670725
671726void MainWindow::rcsStopRecording () {
727+ const bool oldHideWarnings = hideWarnings;
672728 hideWarnings = true ;
673729 stopRecording ();
730+ hideWarnings = oldHideWarnings;
674731}
675732
676733void MainWindow::rcsUpdateFilename (QString s) {
0 commit comments