#include "pulsemodel.h" #include "pulsedevice.h" #include // Custom events posted from the PA thread to the main thread. static const int PA_EVENT = TQEvent::User + 1; static const int PA_SERVER_EVENT = TQEvent::User + 3; static const int PA_CARD_EVENT = TQEvent::User + 4; struct PAServerEvent : public TQCustomEvent { TQString defaultSinkName; TQString defaultSourceName; PAServerEvent( const TQString &sink, const TQString &source ) : TQCustomEvent(PA_SERVER_EVENT), defaultSinkName(sink), defaultSourceName(source) {} }; struct PAEvent : public TQCustomEvent { enum Kind { DeviceAdded, DeviceRemoved, DeviceUpdated }; Kind kind; AudioDevice::Category cat; uint32_t paIndex; TQString name; int volume; bool muted; uint8_t channels; int pan; TQString monitorName; TQString iconName; TQString paName; // raw PA name (sinks/sources only, for default tracking) uint32_t parentIndex; // for Playback: parent sink PA index PAEvent( Kind k, AudioDevice::Category c, uint32_t idx, const TQString &n = TQString(), int vol = 0, bool m = false, uint8_t ch = 2, int p = 0, const TQString &mon = TQString(), const TQString &icon = TQString(), uint32_t parent = PA_INVALID_INDEX, const TQString &pname = TQString() ) : TQCustomEvent(PA_EVENT), kind(k), cat(c), paIndex(idx), name(n), volume(vol), muted(m), channels(ch), pan(p), monitorName(mon), iconName(icon), paName(pname), parentIndex(parent) {} }; struct PACardEvent : public TQCustomEvent { struct Profile { TQString name; TQString description; bool available; }; struct Port { TQString name; TQString description; int available; int direction; uint32_t type; TQString availGroup; }; bool removed; uint32_t index; TQString name; TQString description; TQString activeProfile; TQString vendor; TQString product; TQString formFactor; TQString busType; TQValueList profiles; TQValueList ports; explicit PACardEvent( uint32_t idx ) : TQCustomEvent(PA_CARD_EVENT), removed(true), index(idx) {} PACardEvent( uint32_t idx, const TQString &n, const TQString &d, const TQString &ap ) : TQCustomEvent(PA_CARD_EVENT), removed(false), index(idx), name(n), description(d), activeProfile(ap) {} }; // ---- helpers ---------------------------------------------------------------- static bool isGenericMediaName( const TQString &s ) { TQString l = s.lower().stripWhiteSpace(); return l == "audio stream" || l == "alsa playback" || l == "playback" || l == "audio output" || l == "output" || l.isEmpty(); } static TQString paCleanAppName( const char *raw ) { if ( !raw || !*raw ) return TQString(); TQString s = TQString::fromUtf8( raw ); // Strip "ALSA plug-in [Foo]" wrapper → "Foo" int lb = s.find('['), rb = s.findRev(']'); if ( lb != -1 && rb > lb ) s = s.mid( lb + 1, rb - lb - 1 ).stripWhiteSpace(); // Strip trailing "app" (amarokapp → amarok), then capitalise first letter if ( s.endsWith("app") && s.length() > 3 ) s = s.left( s.length() - 3 ); if ( !s.isEmpty() ) s[0] = s[0].upper(); return s; } static TQString paStreamName( pa_proplist *pl, const char *fallback ) { TQString appName = paCleanAppName( pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME) ); const char *mn = pa_proplist_gets(pl, PA_PROP_MEDIA_NAME); TQString mediaName = mn ? TQString::fromUtf8(mn).stripWhiteSpace() : TQString(); if ( !appName.isEmpty() && !isGenericMediaName(mediaName) && !mediaName.isEmpty() ) return appName + " - " + mediaName; if ( !appName.isEmpty() ) return appName; if ( !isGenericMediaName(mediaName) && !mediaName.isEmpty() ) return mediaName; return fallback ? TQString::fromUtf8(fallback) : TQString(); } static TQString paAppIcon( pa_proplist *pl ) { const char *s; if ( (s = pa_proplist_gets(pl, PA_PROP_APPLICATION_ICON_NAME)) && *s ) return TQString::fromUtf8(s); if ( (s = pa_proplist_gets(pl, "application.process.binary")) && *s ) { TQString bin = TQString::fromUtf8(s).lower(); // Some TDE apps register binary as "fooapp" but icon as "foo" if ( bin.endsWith("app") ) return bin.left( bin.length() - 3 ); return bin; } if ( (s = pa_proplist_gets(pl, PA_PROP_APPLICATION_NAME)) && *s ) return TQString::fromUtf8(s).lower(); return TQString(); } static int paVolumeToPercent( const pa_cvolume &cv ) { pa_volume_t mx = pa_cvolume_max( &cv ); return (int)( (double)mx / PA_VOLUME_NORM * 100.0 + 0.5 ); } // Derive pan -50..+50 from the first two channels of a cvolume. static int cvolumeToPan( const pa_cvolume &cv ) { if ( cv.channels < 2 ) return 0; double l = cv.values[0]; double r = cv.values[1]; double mx = ( l > r ) ? l : r; if ( mx < 1.0 ) return 0; return (int)( ( r - l ) / mx * 50.0 ); } // ---- ctor / dtor ------------------------------------------------------------ PulseModel::PulseModel( TQObject *parent ) : TQObject(parent), m_mainloop(0), m_context(0) { m_sinks.setAutoDelete( true ); m_sources.setAutoDelete( true ); m_sinkInputs.setAutoDelete( true ); m_sourceOutputs.setAutoDelete( true ); } PulseModel::~PulseModel() { close(); } // ---- connect / disconnect --------------------------------------------------- bool PulseModel::open() { m_mainloop = pa_threaded_mainloop_new(); if ( !m_mainloop ) return false; pa_mainloop_api *api = pa_threaded_mainloop_get_api( m_mainloop ); m_context = pa_context_new( api, "tmix" ); if ( !m_context ) { pa_threaded_mainloop_free( m_mainloop ); m_mainloop = 0; return false; } pa_context_set_state_callback( m_context, contextStateCb, this ); pa_threaded_mainloop_lock( m_mainloop ); pa_threaded_mainloop_start( m_mainloop ); pa_context_connect( m_context, 0, PA_CONTEXT_NOFLAGS, 0 ); pa_threaded_mainloop_unlock( m_mainloop ); return true; } void PulseModel::close() { // Detach monitor streams on all devices before tearing down the mainloop. TQPtrList *allLists[4] = { &m_sinks, &m_sources, &m_sinkInputs, &m_sourceOutputs }; for ( int i = 0; i < 4; i++ ) for ( TQPtrListIterator it(*allLists[i]); *it; ++it ) (*it)->detach(); if ( m_context ) { pa_threaded_mainloop_lock( m_mainloop ); pa_context_disconnect( m_context ); pa_context_unref( m_context ); m_context = 0; pa_threaded_mainloop_unlock( m_mainloop ); } if ( m_mainloop ) { pa_threaded_mainloop_stop( m_mainloop ); pa_threaded_mainloop_free( m_mainloop ); m_mainloop = 0; } m_sinks.clear(); m_sources.clear(); m_sinkInputs.clear(); m_sourceOutputs.clear(); } // ---- public query ----------------------------------------------------------- TQPtrList PulseModel::devices( AudioDevice::Category cat ) const { TQPtrList result; const TQPtrList *src = 0; switch ( cat ) { case AudioDevice::Output: src = &m_sinks; break; case AudioDevice::Input: src = &m_sources; break; case AudioDevice::Playback: src = &m_sinkInputs; break; case AudioDevice::Recording: src = &m_sourceOutputs; break; } if ( src ) for ( TQPtrListIterator it(*src); *it; ++it ) result.append( *it ); return result; } // ---- PA thread callbacks (called from PA thread — only post events) --------- void PulseModel::contextStateCb( pa_context *c, void *userdata ) { PulseModel *self = static_cast(userdata); switch ( pa_context_get_state(c) ) { case PA_CONTEXT_READY: pa_context_set_subscribe_callback( c, subscribeCb, self ); pa_context_subscribe( c, (pa_subscription_mask_t)( PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT | PA_SUBSCRIPTION_MASK_SERVER | PA_SUBSCRIPTION_MASK_CARD ), 0, 0 ); pa_operation_unref( pa_context_get_server_info( c, serverInfoCb, self ) ); self->enumerateAll(); break; case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: break; default: break; } } void PulseModel::enumerateAll() { // Called from PA thread (inside context state callback at READY). pa_operation_unref( pa_context_get_sink_info_list( m_context, sinkInfoCb, this ) ); pa_operation_unref( pa_context_get_source_info_list( m_context, sourceInfoCb, this ) ); pa_operation_unref( pa_context_get_sink_input_info_list( m_context, sinkInputInfoCb, this ) ); pa_operation_unref( pa_context_get_source_output_info_list( m_context, sourceOutputInfoCb, this ) ); pa_operation_unref( pa_context_get_card_info_list( m_context, cardInfoCb, this ) ); } void PulseModel::serverInfoCb( pa_context *, const pa_server_info *info, void *userdata ) { if ( !info ) return; PulseModel *self = static_cast(userdata); TQApplication::postEvent( self, new PAServerEvent( TQString::fromUtf8( info->default_sink_name ? info->default_sink_name : "" ), TQString::fromUtf8( info->default_source_name ? info->default_source_name : "" ) ) ); } void PulseModel::sinkInfoCb( pa_context *, const pa_sink_info *info, int eol, void *userdata ) { if ( eol || !info ) return; PulseModel *self = static_cast(userdata); PAEvent *ev = new PAEvent( PAEvent::DeviceAdded, AudioDevice::Output, info->index, TQString::fromUtf8( info->description ), paVolumeToPercent( info->volume ), info->mute != 0, info->volume.channels, cvolumeToPan( info->volume ), TQString::fromUtf8( info->monitor_source_name ), TQString::fromUtf8( pa_proplist_gets( info->proplist, PA_PROP_DEVICE_ICON_NAME ) ? : "" ), PA_INVALID_INDEX, TQString::fromUtf8( info->name ) ); TQApplication::postEvent( self, ev ); } void PulseModel::sourceInfoCb( pa_context *, const pa_source_info *info, int eol, void *userdata ) { if ( eol || !info ) return; // Skip monitor sources — they're PA internals, not real inputs. if ( info->monitor_of_sink != PA_INVALID_INDEX ) return; PulseModel *self = static_cast(userdata); TQApplication::postEvent( self, new PAEvent( PAEvent::DeviceAdded, AudioDevice::Input, info->index, TQString::fromUtf8( info->description ), paVolumeToPercent( info->volume ), info->mute != 0, info->volume.channels, cvolumeToPan( info->volume ), TQString::fromUtf8( info->name ), TQString::fromUtf8( pa_proplist_gets( info->proplist, PA_PROP_DEVICE_ICON_NAME ) ? : "" ), PA_INVALID_INDEX, TQString::fromUtf8( info->name ) ) ); } void PulseModel::sinkInputInfoCb( pa_context *, const pa_sink_input_info *info, int eol, void *userdata ) { if ( eol || !info ) return; PulseModel *self = static_cast(userdata); TQApplication::postEvent( self, new PAEvent( PAEvent::DeviceAdded, AudioDevice::Playback, info->index, paStreamName( info->proplist, info->name ), paVolumeToPercent( info->volume ), info->mute != 0, info->volume.channels, cvolumeToPan( info->volume ), TQString(), // monitor name filled in by customEvent from parent sink paAppIcon( info->proplist ), info->sink ) ); // parent sink index for peak monitoring } void PulseModel::sourceOutputInfoCb( pa_context *, const pa_source_output_info *info, int eol, void *userdata ) { if ( eol || !info ) return; // Filter out our own peak-monitor streams. if ( info->name && strcmp(info->name, "tmix-peak") == 0 ) return; PulseModel *self = static_cast(userdata); TQApplication::postEvent( self, new PAEvent( PAEvent::DeviceAdded, AudioDevice::Recording, info->index, paStreamName( info->proplist, info->name ), paVolumeToPercent( info->volume ), info->mute != 0, info->volume.channels, cvolumeToPan( info->volume ), TQString(), paAppIcon( info->proplist ), info->source ) ); // parentIndex = source being recorded from } void PulseModel::subscribeCb( pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata ) { PulseModel *self = static_cast(userdata); pa_subscription_event_type_t facility = (pa_subscription_event_type_t)( t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK ); pa_subscription_event_type_t evtype = (pa_subscription_event_type_t)( t & PA_SUBSCRIPTION_EVENT_TYPE_MASK ); if ( facility == PA_SUBSCRIPTION_EVENT_SERVER ) { pa_operation_unref( pa_context_get_server_info( c, serverInfoCb, self ) ); return; } if ( facility == PA_SUBSCRIPTION_EVENT_CARD ) { if ( evtype == PA_SUBSCRIPTION_EVENT_REMOVE ) { TQApplication::postEvent( self, new PACardEvent( idx ) ); } else { pa_operation *op = pa_context_get_card_info_by_index( c, idx, cardInfoCb, self ); if ( op ) pa_operation_unref( op ); } return; } AudioDevice::Category cat; switch ( facility ) { case PA_SUBSCRIPTION_EVENT_SINK: cat = AudioDevice::Output; break; case PA_SUBSCRIPTION_EVENT_SOURCE: cat = AudioDevice::Input; break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: cat = AudioDevice::Playback; break; case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: cat = AudioDevice::Recording; break; default: return; } if ( evtype == PA_SUBSCRIPTION_EVENT_REMOVE ) { TQApplication::postEvent( self, new PAEvent( PAEvent::DeviceRemoved, cat, idx ) ); return; } // NEW or CHANGE — re-query to get current state. pa_operation *op = 0; switch ( facility ) { case PA_SUBSCRIPTION_EVENT_SINK: op = pa_context_get_sink_info_by_index( c, idx, sinkInfoCb, self ); break; case PA_SUBSCRIPTION_EVENT_SOURCE: op = pa_context_get_source_info_by_index( c, idx, sourceInfoCb, self ); break; case PA_SUBSCRIPTION_EVENT_SINK_INPUT: op = pa_context_get_sink_input_info( c, idx, sinkInputInfoCb, self ); break; case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT: op = pa_context_get_source_output_info( c, idx, sourceOutputInfoCb, self ); break; default: break; } if ( op ) pa_operation_unref( op ); } void PulseModel::cardInfoCb( pa_context *, const pa_card_info *info, int eol, void *userdata ) { if ( eol || !info ) return; PulseModel *self = static_cast(userdata); const char *desc = pa_proplist_gets( info->proplist, PA_PROP_DEVICE_DESCRIPTION ); const char *vendor = pa_proplist_gets( info->proplist, PA_PROP_DEVICE_VENDOR_NAME ); const char *product = pa_proplist_gets( info->proplist, PA_PROP_DEVICE_PRODUCT_NAME ); const char *ff = pa_proplist_gets( info->proplist, PA_PROP_DEVICE_FORM_FACTOR ); const char *bus = pa_proplist_gets( info->proplist, "device.bus" ); PACardEvent *ev = new PACardEvent( info->index, TQString::fromUtf8( info->name ), TQString::fromUtf8( desc ? desc : info->name ), info->active_profile2 ? TQString::fromUtf8( info->active_profile2->name ) : TQString() ); ev->vendor = TQString::fromUtf8( vendor ? vendor : "" ); ev->product = TQString::fromUtf8( product ? product : "" ); ev->formFactor = TQString::fromUtf8( ff ? ff : "" ); ev->busType = TQString::fromUtf8( bus ? bus : "" ); for ( uint32_t i = 0; info->profiles2 && info->profiles2[i]; ++i ) { PACardEvent::Profile p; p.name = TQString::fromUtf8( info->profiles2[i]->name ); p.description = TQString::fromUtf8( info->profiles2[i]->description ); p.available = info->profiles2[i]->available != 0; ev->profiles.append( p ); } for ( uint32_t i = 0; i < info->n_ports; ++i ) { PACardEvent::Port port; port.name = TQString::fromUtf8( info->ports[i]->name ); port.description= TQString::fromUtf8( info->ports[i]->description ); port.available = info->ports[i]->available; port.direction = info->ports[i]->direction; port.type = info->ports[i]->type; if ( info->ports[i]->availability_group ) port.availGroup = TQString::fromUtf8( info->ports[i]->availability_group ); ev->ports.append( port ); } TQApplication::postEvent( self, ev ); } // ---- main thread event handler ---------------------------------------------- void PulseModel::customEvent( TQCustomEvent *e ) { if ( e->type() == PA_CARD_EVENT ) { PACardEvent *ev = static_cast(e); if ( ev->removed ) { for ( TQValueList::Iterator it = m_cards.begin(); it != m_cards.end(); ++it ) { if ( it->index == ev->index ) { m_cards.remove( it ); emit cardRemoved( ev->index ); return; } } return; } PulseCardInfo *existing = findCard( ev->index ); if ( existing ) { existing->name = ev->name; existing->description = ev->description; existing->activeProfile = ev->activeProfile; existing->vendor = ev->vendor; existing->product = ev->product; existing->formFactor = ev->formFactor; existing->busType = ev->busType; existing->profiles.clear(); existing->ports.clear(); for ( TQValueList::Iterator it = ev->profiles.begin(); it != ev->profiles.end(); ++it ) { PulseCardProfile p; p.name = it->name; p.description = it->description; p.available = it->available; existing->profiles.append( p ); } for ( TQValueList::Iterator it = ev->ports.begin(); it != ev->ports.end(); ++it ) { PulseCardPort port; port.name = it->name; port.description = it->description; port.available = it->available; port.direction = it->direction; port.type = it->type; port.availabilityGroup = it->availGroup; existing->ports.append( port ); } emit cardUpdated( ev->index ); } else { PulseCardInfo info; info.index = ev->index; info.name = ev->name; info.description = ev->description; info.activeProfile = ev->activeProfile; info.vendor = ev->vendor; info.product = ev->product; info.formFactor = ev->formFactor; info.busType = ev->busType; for ( TQValueList::Iterator it = ev->profiles.begin(); it != ev->profiles.end(); ++it ) { PulseCardProfile p; p.name = it->name; p.description = it->description; p.available = it->available; info.profiles.append( p ); } for ( TQValueList::Iterator it = ev->ports.begin(); it != ev->ports.end(); ++it ) { PulseCardPort port; port.name = it->name; port.description = it->description; port.available = it->available; port.direction = it->direction; port.type = it->type; port.availabilityGroup = it->availGroup; info.ports.append( port ); } m_cards.append( info ); emit cardAdded( ev->index ); } return; } if ( e->type() == PA_SERVER_EVENT ) { PAServerEvent *ev = static_cast(e); if ( ev->defaultSinkName != m_defaultSinkName ) { m_defaultSinkName = ev->defaultSinkName; emit defaultOutputChanged( defaultOutput() ); } if ( ev->defaultSourceName != m_defaultSourceName ) { m_defaultSourceName = ev->defaultSourceName; emit defaultInputChanged( defaultInput() ); } return; } if ( e->type() != PA_EVENT ) return; PAEvent *ev = static_cast(e); TQPtrList *list = 0; switch ( ev->cat ) { case AudioDevice::Output: list = &m_sinks; break; case AudioDevice::Input: list = &m_sources; break; case AudioDevice::Playback: list = &m_sinkInputs; break; case AudioDevice::Recording: list = &m_sourceOutputs; break; } if ( ev->kind == PAEvent::DeviceRemoved ) { PulseDevice *dev = findDevice( *list, ev->paIndex ); if ( dev ) { // Remove from name maps if present. TQMap::Iterator it; for ( it = m_sinksByName.begin(); it != m_sinksByName.end(); ++it ) { if ( it.data() == dev ) { m_sinksByName.remove(it); break; } } for ( it = m_sourcesByName.begin(); it != m_sourcesByName.end(); ++it ) { if ( it.data() == dev ) { m_sourcesByName.remove(it); break; } } // If a recording stream ended, decrement its parent source's count. if ( ev->cat == AudioDevice::Recording ) { TQMap::Iterator si = m_sourceOutputToSource.find( ev->paIndex ); if ( si != m_sourceOutputToSource.end() ) { PulseDevice *src = findDevice( m_sources, si.data() ); if ( src ) src->adjustRecordingCount( -1 ); m_sourceOutputToSource.remove( si ); } } emit deviceRemoved( dev ); list->remove( dev ); } return; } // DeviceAdded or DeviceUpdated (both come through the same info callbacks). // For Playback streams, resolve the parent sink's monitor source name so the // level meter can attach a peak-detect stream to just this sink input. TQString monitorName = ev->monitorName; if ( ev->cat == AudioDevice::Playback && monitorName.isEmpty() && ev->parentIndex != PA_INVALID_INDEX ) { PulseDevice *sink = findDevice( m_sinks, ev->parentIndex ); if ( sink ) monitorName = sink->monitorName(); } PulseDevice *dev = findDevice( *list, ev->paIndex ); if ( dev ) { dev->update( ev->name, ev->volume, ev->muted, ev->channels, ev->pan, monitorName, ev->iconName ); if ( ev->cat == AudioDevice::Playback && ev->parentIndex != PA_INVALID_INDEX ) dev->setSinkIndex( ev->parentIndex ); } else { dev = new PulseDevice( ev->cat, ev->paIndex, this ); dev->setModel( this ); dev->setPAContext( m_context, m_mainloop ); dev->update( ev->name, ev->volume, ev->muted, ev->channels, ev->pan, monitorName, ev->iconName ); if ( ev->cat == AudioDevice::Playback && ev->parentIndex != PA_INVALID_INDEX ) dev->setSinkIndex( ev->parentIndex ); if ( !ev->paName.isEmpty() ) { dev->setPaName( ev->paName ); } list->append( dev ); if ( !ev->paName.isEmpty() ) { if ( ev->cat == AudioDevice::Output ) m_sinksByName.insert( ev->paName, dev ); else if ( ev->cat == AudioDevice::Input ) m_sourcesByName.insert( ev->paName, dev ); } emit deviceAdded( dev ); if ( ev->cat == AudioDevice::Output && ev->paName == m_defaultSinkName ) emit defaultOutputChanged( dev ); if ( ev->cat == AudioDevice::Input && ev->paName == m_defaultSourceName ) emit defaultInputChanged( dev ); // Track which source a new recording stream is attached to. if ( ev->cat == AudioDevice::Recording && ev->parentIndex != PA_INVALID_INDEX ) { m_sourceOutputToSource.insert( ev->paIndex, ev->parentIndex ); PulseDevice *src = findDevice( m_sources, ev->parentIndex ); if ( src ) { src->adjustRecordingCount( +1 ); connect( src, TQ_SIGNAL(levelChanged(float)), dev, TQ_SIGNAL(levelChanged(float)) ); } } } } AudioDevice *PulseModel::defaultOutput() const { if ( m_sinksByName.contains(m_defaultSinkName) ) return m_sinksByName[m_defaultSinkName]; return 0; } AudioDevice *PulseModel::defaultInput() const { if ( m_sourcesByName.contains(m_defaultSourceName) ) return m_sourcesByName[m_defaultSourceName]; return 0; } PulseDevice *PulseModel::findDevice( TQPtrList &list, uint32_t paIndex ) { for ( TQPtrListIterator it(list); *it; ++it ) if ( (*it)->paIndex() == paIndex ) return *it; return 0; } // ---- operations called from UI (main thread) --------------------------------- void PulseModel::setDefaultOutput( AudioDevice *dev ) { PulseDevice *pd = dynamic_cast(dev); if ( !pd || pd->paName().isEmpty() || !m_context || !m_mainloop ) return; pa_threaded_mainloop_lock( m_mainloop ); pa_operation *op = pa_context_set_default_sink( m_context, pd->paName().utf8().data(), 0, 0 ); if ( op ) pa_operation_unref( op ); pa_threaded_mainloop_unlock( m_mainloop ); } void PulseModel::setDefaultInput( AudioDevice *dev ) { PulseDevice *pd = dynamic_cast(dev); if ( !pd || pd->paName().isEmpty() || !m_context || !m_mainloop ) return; pa_threaded_mainloop_lock( m_mainloop ); pa_operation *op = pa_context_set_default_source( m_context, pd->paName().utf8().data(), 0, 0 ); if ( op ) pa_operation_unref( op ); pa_threaded_mainloop_unlock( m_mainloop ); } void PulseModel::moveSinkInputToSink( AudioDevice *playbackDev, AudioDevice *outputDev ) { PulseDevice *si = dynamic_cast(playbackDev); PulseDevice *sink = dynamic_cast(outputDev); if ( !si || !sink || !m_context || !m_mainloop ) return; pa_threaded_mainloop_lock( m_mainloop ); pa_operation *op = pa_context_move_sink_input_by_index( m_context, si->paIndex(), sink->paIndex(), 0, 0 ); if ( op ) pa_operation_unref( op ); pa_threaded_mainloop_unlock( m_mainloop ); } const PulseCardInfo *PulseModel::card( uint32_t index ) const { for ( TQValueList::ConstIterator it = m_cards.begin(); it != m_cards.end(); ++it ) if ( (*it).index == index ) return &(*it); return 0; } PulseCardInfo *PulseModel::findCard( uint32_t index ) { for ( TQValueList::Iterator it = m_cards.begin(); it != m_cards.end(); ++it ) if ( it->index == index ) return &(*it); return 0; } void PulseModel::setCardProfile( uint32_t cardIndex, const TQString &profileName ) { if ( !m_context || !m_mainloop || profileName.isEmpty() ) return; pa_threaded_mainloop_lock( m_mainloop ); pa_operation *op = pa_context_set_card_profile_by_index( m_context, cardIndex, profileName.utf8().data(), 0, 0 ); if ( op ) pa_operation_unref( op ); pa_threaded_mainloop_unlock( m_mainloop ); } #include "pulsemodel.moc"