diff options
| author | Calvin Morrison <calvin@pobox.com> | 2026-05-15 16:03:08 -0400 |
|---|---|---|
| committer | Calvin Morrison <calvin@pobox.com> | 2026-05-15 16:03:08 -0400 |
| commit | 4a8a83f223bbc2b9d18ef91423c1ee807b297b35 (patch) | |
| tree | 03b89f3eac9f0796ccdcbad5fa90915587f8e3ff /src/model/pulsemodel.cpp | |
| parent | e0c8fb0cdcb9c95e3efa60322c1733df0a965650 (diff) | |
PA reconnect: recover cleanly when PulseAudio restarts
- On PA_CONTEXT_FAILED/TERMINATED, emit deviceRemoved for all devices
so UI clears itself, then reconnect after 2s
- Skip pa_context_disconnect and stream detach locks when PA is already
dead to avoid hang in pa_threaded_mainloop_stop
- Emit defaultOutputChanged(0)/defaultInputChanged(0) before deleting
devices to prevent dangling pointer crash in TmixTray::setDevice
- Disconnect all device signals before close() to avoid use-after-free
from cross-device wiring (source→recording levelChanged bridge)
- Icon: generate proper 22x22 (and 16/32/48) from source, install all sizes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/model/pulsemodel.cpp')
| -rw-r--r-- | src/model/pulsemodel.cpp | 55 |
1 files changed, 50 insertions, 5 deletions
diff --git a/src/model/pulsemodel.cpp b/src/model/pulsemodel.cpp index 640b918..49a17c2 100644 --- a/src/model/pulsemodel.cpp +++ b/src/model/pulsemodel.cpp @@ -2,11 +2,13 @@ #include "pulsedevice.h" #include <tqapplication.h> +#include <tqtimer.h> // 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; +static const int PA_DIED_EVENT = TQEvent::User + 5; struct PAServerEvent : public TQCustomEvent { TQString defaultSinkName; @@ -183,18 +185,27 @@ bool PulseModel::open() void PulseModel::close() { - // Detach monitor streams on all devices before tearing down the mainloop. TQPtrList<PulseDevice> *allLists[4] = { &m_sinks, &m_sources, &m_sinkInputs, &m_sourceOutputs }; + + bool alreadyDead = false; + if ( m_context ) { + pa_context_state_t state = pa_context_get_state( m_context ); + alreadyDead = ( state == PA_CONTEXT_FAILED || + state == PA_CONTEXT_TERMINATED ); + } + for ( int i = 0; i < 4; i++ ) for ( TQPtrListIterator<PulseDevice> it(*allLists[i]); *it; ++it ) - (*it)->detach(); + (*it)->detach( alreadyDead ); if ( m_context ) { - pa_threaded_mainloop_lock( m_mainloop ); - pa_context_disconnect( m_context ); + if ( !alreadyDead ) { + pa_threaded_mainloop_lock( m_mainloop ); + pa_context_disconnect( m_context ); + pa_threaded_mainloop_unlock( m_mainloop ); + } pa_context_unref( m_context ); m_context = 0; - pa_threaded_mainloop_unlock( m_mainloop ); } if ( m_mainloop ) { pa_threaded_mainloop_stop( m_mainloop ); @@ -247,6 +258,7 @@ void PulseModel::contextStateCb( pa_context *c, void *userdata ) break; case PA_CONTEXT_FAILED: case PA_CONTEXT_TERMINATED: + TQApplication::postEvent( self, new TQCustomEvent(PA_DIED_EVENT) ); break; default: break; @@ -437,8 +449,41 @@ void PulseModel::cardInfoCb( pa_context *, const pa_card_info *info, int eol, vo // ---- main thread event handler ---------------------------------------------- +void PulseModel::reconnect() +{ + open(); +} + void PulseModel::customEvent( TQCustomEvent *e ) { + if ( e->type() == PA_DIED_EVENT ) { + if ( !m_mainloop ) return; // already handling a previous die event + // Notify UI that all devices are gone, then tear down and retry. + TQPtrList<PulseDevice> *allLists[4] = { &m_sinks, &m_sources, &m_sinkInputs, &m_sourceOutputs }; + for ( int i = 0; i < 4; i++ ) { + TQPtrListIterator<PulseDevice> it( *allLists[i] ); + while ( *it ) { + PulseDevice *dev = *it; + ++it; + emit deviceRemoved( dev ); + } + } + // Zero out default-device pointers in connected UI before devices are deleted. + emit defaultOutputChanged( 0 ); + emit defaultInputChanged( 0 ); + // Sever all remaining signal connections on every device so that + // close() can delete them safely regardless of cross-device wiring. + for ( int i = 0; i < 4; i++ ) + for ( TQPtrListIterator<PulseDevice> it( *allLists[i] ); *it; ++it ) + disconnect( *it, 0, 0, 0 ); + m_sinksByName.clear(); + m_sourcesByName.clear(); + m_sourceOutputToSource.clear(); + m_cards.clear(); + close(); + TQTimer::singleShot( 2000, this, TQ_SLOT(reconnect()) ); + return; + } if ( e->type() == PA_CARD_EVENT ) { PACardEvent *ev = static_cast<PACardEvent *>(e); if ( ev->removed ) { |
