From f8fa47123887452f48d0b523289ca6990c8a2e25 Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Fri, 15 May 2026 19:22:41 -0400 Subject: Devices tab: per-sink/source port switching Add Output port / Input port dropdowns in the Devices tab for any sink or source that has 2+ ports. Selecting a port calls pa_context_set_sink/source_port_by_index live. Active port selection updates in-place when PA reports a port change externally. Co-Authored-By: Claude Sonnet 4.6 --- src/ui/devicespage.cpp | 217 ++++++++++++++++++++++++++++++++++++++++--------- src/ui/devicespage.h | 24 +++++- 2 files changed, 197 insertions(+), 44 deletions(-) (limited to 'src/ui') diff --git a/src/ui/devicespage.cpp b/src/ui/devicespage.cpp index 28cce07..0b39ece 100644 --- a/src/ui/devicespage.cpp +++ b/src/ui/devicespage.cpp @@ -64,9 +64,11 @@ DevicesPage::DevicesPage( PulseModel *model, TQWidget *parent ) rebuild(); - connect( model, TQ_SIGNAL(cardAdded(uint32_t)), this, TQ_SLOT(onCardAdded(uint32_t)) ); - connect( model, TQ_SIGNAL(cardRemoved(uint32_t)), this, TQ_SLOT(onCardRemoved(uint32_t)) ); - connect( model, TQ_SIGNAL(cardUpdated(uint32_t)), this, TQ_SLOT(onCardUpdated(uint32_t)) ); + connect( model, TQ_SIGNAL(cardAdded(uint32_t)), this, TQ_SLOT(onCardAdded(uint32_t)) ); + connect( model, TQ_SIGNAL(cardRemoved(uint32_t)), this, TQ_SLOT(onCardRemoved(uint32_t)) ); + connect( model, TQ_SIGNAL(cardUpdated(uint32_t)), this, TQ_SLOT(onCardUpdated(uint32_t)) ); + connect( model, TQ_SIGNAL(sinkUpdated(uint32_t)), this, TQ_SLOT(onSinkUpdated(uint32_t)) ); + connect( model, TQ_SIGNAL(sourceUpdated(uint32_t)),this, TQ_SLOT(onSourceUpdated(uint32_t)) ); } void DevicesPage::rebuild() @@ -75,6 +77,12 @@ void DevicesPage::rebuild() m_profileNames.clear(); m_cardCombo.clear(); m_cardPorts.clear(); + m_sinkPortComboSink.clear(); + m_sinkPortComboNames.clear(); + m_sinkPortCombos.clear(); + m_sourcePortComboSource.clear(); + m_sourcePortComboNames.clear(); + m_sourcePortCombos.clear(); delete m_container; m_container = new TQWidget( m_scroll->viewport() ); @@ -117,6 +125,7 @@ void DevicesPage::rebuild() if ( !typeparts.isEmpty() ) addTextRow( i18n("Type:"), typeparts.join(" / ") ); + // Profile dropdown TQComboBox *combo = new TQComboBox( false, grp ); combo->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Fixed ); addRow( i18n("Profile:"), combo ); @@ -139,6 +148,78 @@ void DevicesPage::rebuild() connect( combo, TQ_SIGNAL(activated(int)), this, TQ_SLOT(onProfileActivated(int)) ); + // Sink port dropdowns — only when a sink has 2+ ports + TQValueList sinks = m_model->sinksForCard( info.index ); + bool multiSink = false; + for ( TQValueList::Iterator sit = sinks.begin(); + sit != sinks.end(); ++sit ) + if ( (int)sit->ports.count() >= 2 ) { multiSink = ( sinks.count() > 1 ); break; } + + for ( TQValueList::Iterator sit = sinks.begin(); + sit != sinks.end(); ++sit ) { + if ( (int)sit->ports.count() < 2 ) continue; + + TQComboBox *pc = new TQComboBox( false, grp ); + pc->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Fixed ); + TQString label = multiSink + ? sit->description + i18n(" port:") + : i18n("Output port:"); + addRow( label, pc ); + + TQValueList portNames; + int activePort = 0, pi = 0; + for ( TQValueList::ConstIterator pit = sit->ports.begin(); + pit != sit->ports.end(); ++pit, ++pi ) { + pc->insertItem( (*pit).description ); + portNames.append( (*pit).name ); + if ( (*pit).name == sit->activePort ) + activePort = pi; + } + pc->setCurrentItem( activePort ); + + m_sinkPortComboNames[pc] = portNames; + m_sinkPortComboSink[pc] = sit->paIndex; + m_sinkPortCombos[sit->paIndex] = pc; + + connect( pc, TQ_SIGNAL(activated(int)), this, TQ_SLOT(onSinkPortActivated(int)) ); + } + + // Source port dropdowns — only when a source has 2+ ports + TQValueList sources = m_model->sourcesForCard( info.index ); + bool multiSource = false; + for ( TQValueList::Iterator sit = sources.begin(); + sit != sources.end(); ++sit ) + if ( (int)sit->ports.count() >= 2 ) { multiSource = ( sources.count() > 1 ); break; } + + for ( TQValueList::Iterator sit = sources.begin(); + sit != sources.end(); ++sit ) { + if ( (int)sit->ports.count() < 2 ) continue; + + TQComboBox *pc = new TQComboBox( false, grp ); + pc->setSizePolicy( TQSizePolicy::Preferred, TQSizePolicy::Fixed ); + TQString label = multiSource + ? sit->description + i18n(" port:") + : i18n("Input port:"); + addRow( label, pc ); + + TQValueList portNames; + int activePort = 0, pi = 0; + for ( TQValueList::ConstIterator pit = sit->ports.begin(); + pit != sit->ports.end(); ++pit, ++pi ) { + pc->insertItem( (*pit).description ); + portNames.append( (*pit).name ); + if ( (*pit).name == sit->activePort ) + activePort = pi; + } + pc->setCurrentItem( activePort ); + + m_sourcePortComboNames[pc] = portNames; + m_sourcePortComboSource[pc] = sit->paIndex; + m_sourcePortCombos[sit->paIndex] = pc; + + connect( pc, TQ_SIGNAL(activated(int)), this, TQ_SLOT(onSourcePortActivated(int)) ); + } + // Ports — 2-column grid to keep tall cards compact if ( !info.ports.isEmpty() ) { TQLabel *portsHdr = new TQLabel( i18n("Ports:"), grp ); @@ -196,42 +277,7 @@ void DevicesPage::rebuild() void DevicesPage::onCardAdded( uint32_t ) { rebuild(); } void DevicesPage::onCardRemoved( uint32_t ) { rebuild(); } - -void DevicesPage::onCardUpdated( uint32_t index ) -{ - TQMap::Iterator cit = m_cardCombo.find( index ); - if ( cit == m_cardCombo.end() ) return; - TQComboBox *combo = cit.data(); - - const PulseCardInfo *info = m_model->card( index ); - if ( !info ) return; - - // Update active profile in combo - TQMap >::Iterator nit = m_profileNames.find( combo ); - if ( nit != m_profileNames.end() ) { - TQValueList &names = nit.data(); - for ( int i = 0; i < (int)names.count(); ++i ) { - if ( names[i] == info->activeProfile ) { - combo->blockSignals( true ); - combo->setCurrentItem( i ); - combo->blockSignals( false ); - break; - } - } - } - - // Update port availability indicators in-place - TQMap >::Iterator pit = m_cardPorts.find( index ); - if ( pit == m_cardPorts.end() ) return; - TQValueList &portWidgets = pit.data(); - - int i = 0; - for ( TQValueList::ConstIterator p = info->ports.begin(); - p != info->ports.end() && i < (int)portWidgets.count(); ++p, ++i ) { - bool plugged = ( (*p).available != PA_PORT_AVAILABLE_NO ); - portWidgets[i].dot->setPixmap( availDot( plugged, 10 ) ); - } -} +void DevicesPage::onCardUpdated( uint32_t ) { rebuild(); } void DevicesPage::onProfileActivated( int idx ) { @@ -242,10 +288,101 @@ void DevicesPage::onProfileActivated( int idx ) if ( cit == m_comboCard.end() ) return; uint32_t cardIndex = cit.data(); - TQMap >::Iterator nit = m_profileNames.find( combo ); + TQMap>::Iterator nit = m_profileNames.find( combo ); if ( nit == m_profileNames.end() || idx >= (int)nit.data().count() ) return; m_model->setCardProfile( cardIndex, nit.data()[idx] ); } +void DevicesPage::onSinkPortActivated( int idx ) +{ + TQComboBox *combo = dynamic_cast( const_cast( sender() ) ); + if ( !combo ) return; + + TQMap::Iterator cit = m_sinkPortComboSink.find( combo ); + if ( cit == m_sinkPortComboSink.end() ) return; + + TQMap>::Iterator nit = m_sinkPortComboNames.find( combo ); + if ( nit == m_sinkPortComboNames.end() || idx >= (int)nit.data().count() ) return; + + m_model->setSinkPort( cit.data(), nit.data()[idx] ); +} + +void DevicesPage::onSourcePortActivated( int idx ) +{ + TQComboBox *combo = dynamic_cast( const_cast( sender() ) ); + if ( !combo ) return; + + TQMap::Iterator cit = m_sourcePortComboSource.find( combo ); + if ( cit == m_sourcePortComboSource.end() ) return; + + TQMap>::Iterator nit = m_sourcePortComboNames.find( combo ); + if ( nit == m_sourcePortComboNames.end() || idx >= (int)nit.data().count() ) return; + + m_model->setSourcePort( cit.data(), nit.data()[idx] ); +} + +void DevicesPage::onSinkUpdated( uint32_t paIndex ) +{ + TQMap::Iterator cit = m_sinkPortCombos.find( paIndex ); + if ( cit == m_sinkPortCombos.end() ) { + // Sink with ports appeared after last rebuild — refresh. + rebuild(); + return; + } + TQComboBox *combo = cit.data(); + TQMap>::Iterator nit = m_sinkPortComboNames.find( combo ); + if ( nit == m_sinkPortComboNames.end() ) return; + + // Find current active port from the model's sinksForCard lookup. + // Walk all cards to find this sink. + TQValueList cards = m_model->cards(); + for ( TQValueList::ConstIterator it = cards.begin(); it != cards.end(); ++it ) { + TQValueList sinks = m_model->sinksForCard( (*it).index ); + for ( TQValueList::ConstIterator sit = sinks.begin(); + sit != sinks.end(); ++sit ) { + if ( (*sit).paIndex != paIndex ) continue; + TQValueList &names = nit.data(); + for ( int i = 0; i < (int)names.count(); ++i ) { + if ( names[i] == (*sit).activePort ) { + combo->blockSignals( true ); + combo->setCurrentItem( i ); + combo->blockSignals( false ); + return; + } + } + } + } +} + +void DevicesPage::onSourceUpdated( uint32_t paIndex ) +{ + TQMap::Iterator cit = m_sourcePortCombos.find( paIndex ); + if ( cit == m_sourcePortCombos.end() ) { + rebuild(); + return; + } + TQComboBox *combo = cit.data(); + TQMap>::Iterator nit = m_sourcePortComboNames.find( combo ); + if ( nit == m_sourcePortComboNames.end() ) return; + + TQValueList cards = m_model->cards(); + for ( TQValueList::ConstIterator it = cards.begin(); it != cards.end(); ++it ) { + TQValueList sources = m_model->sourcesForCard( (*it).index ); + for ( TQValueList::ConstIterator sit = sources.begin(); + sit != sources.end(); ++sit ) { + if ( (*sit).paIndex != paIndex ) continue; + TQValueList &names = nit.data(); + for ( int i = 0; i < (int)names.count(); ++i ) { + if ( names[i] == (*sit).activePort ) { + combo->blockSignals( true ); + combo->setCurrentItem( i ); + combo->blockSignals( false ); + return; + } + } + } + } +} + #include "devicespage.moc" diff --git a/src/ui/devicespage.h b/src/ui/devicespage.h index 49081f4..046b224 100644 --- a/src/ui/devicespage.h +++ b/src/ui/devicespage.h @@ -10,6 +10,7 @@ class TQScrollView; class TQComboBox; class TQLabel; class TQVBoxLayout; +class AudioDevice; struct PortWidgets { TQLabel *dot; @@ -27,6 +28,10 @@ private slots: void onCardRemoved( uint32_t index ); void onCardUpdated( uint32_t index ); void onProfileActivated( int comboIndex ); + void onSinkPortActivated( int comboIndex ); + void onSourcePortActivated( int comboIndex ); + void onSinkUpdated( uint32_t paIndex ); + void onSourceUpdated( uint32_t paIndex ); private: void rebuild(); @@ -35,8 +40,19 @@ private: TQScrollView *m_scroll; TQWidget *m_container; - TQMap m_comboCard; - TQMap > m_profileNames; - TQMap m_cardCombo; - TQMap > m_cardPorts; + // Profile combos + TQMap m_comboCard; + TQMap> m_profileNames; + TQMap m_cardCombo; + TQMap> m_cardPorts; + + // Sink port combos + TQMap m_sinkPortComboSink; + TQMap> m_sinkPortComboNames; + TQMap m_sinkPortCombos; + + // Source port combos + TQMap m_sourcePortComboSource; + TQMap> m_sourcePortComboNames; + TQMap m_sourcePortCombos; }; -- cgit v1.2.3