summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2026-05-15 16:03:08 -0400
committerCalvin Morrison <calvin@pobox.com>2026-05-15 16:03:08 -0400
commit4a8a83f223bbc2b9d18ef91423c1ee807b297b35 (patch)
tree03b89f3eac9f0796ccdcbad5fa90915587f8e3ff /src
parente0c8fb0cdcb9c95e3efa60322c1733df0a965650 (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')
-rw-r--r--src/model/pulsedevice.cpp19
-rw-r--r--src/model/pulsedevice.h2
-rw-r--r--src/model/pulsemodel.cpp55
-rw-r--r--src/model/pulsemodel.h3
-rw-r--r--src/ui/tmixtray.cpp2
5 files changed, 67 insertions, 14 deletions
diff --git a/src/model/pulsedevice.cpp b/src/model/pulsedevice.cpp
index 8bdea54..8fb8d48 100644
--- a/src/model/pulsedevice.cpp
+++ b/src/model/pulsedevice.cpp
@@ -45,15 +45,20 @@ void PulseDevice::setPAContext( pa_context *ctx, pa_threaded_mainloop *mainloop
startMonitoring();
}
-void PulseDevice::detach()
+void PulseDevice::detach( bool paAlreadyDead )
{
- if ( m_monitorStream && m_mainloop ) {
- pa_threaded_mainloop_lock( m_mainloop );
- pa_stream_set_read_callback( m_monitorStream, 0, 0 );
- pa_stream_disconnect( m_monitorStream );
- pa_stream_unref( m_monitorStream );
+ if ( m_monitorStream ) {
+ if ( paAlreadyDead ) {
+ pa_stream_set_read_callback( m_monitorStream, 0, 0 );
+ pa_stream_unref( m_monitorStream );
+ } else if ( m_mainloop ) {
+ pa_threaded_mainloop_lock( m_mainloop );
+ pa_stream_set_read_callback( m_monitorStream, 0, 0 );
+ pa_stream_disconnect( m_monitorStream );
+ pa_stream_unref( m_monitorStream );
+ pa_threaded_mainloop_unlock( m_mainloop );
+ }
m_monitorStream = 0;
- pa_threaded_mainloop_unlock( m_mainloop );
}
m_context = 0;
m_mainloop = 0;
diff --git a/src/model/pulsedevice.h b/src/model/pulsedevice.h
index e974508..27aca02 100644
--- a/src/model/pulsedevice.h
+++ b/src/model/pulsedevice.h
@@ -40,7 +40,7 @@ public:
TQWidget *createWidget( TQWidget *parent );
void setPAContext( pa_context *ctx, pa_threaded_mainloop *mainloop );
- void detach(); // called by PulseModel before tearing down the mainloop
+ void detach( bool paAlreadyDead = false );
protected:
void customEvent( TQCustomEvent *e );
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 ) {
diff --git a/src/model/pulsemodel.h b/src/model/pulsemodel.h
index 52e379f..c1d4561 100644
--- a/src/model/pulsemodel.h
+++ b/src/model/pulsemodel.h
@@ -80,6 +80,9 @@ signals:
void cardRemoved( uint32_t index );
void cardUpdated( uint32_t index );
+private slots:
+ void reconnect();
+
private:
static void contextStateCb( pa_context *c, void *userdata );
static void serverInfoCb( pa_context *c, const pa_server_info *info, void *userdata );
diff --git a/src/ui/tmixtray.cpp b/src/ui/tmixtray.cpp
index 4048270..e452d71 100644
--- a/src/ui/tmixtray.cpp
+++ b/src/ui/tmixtray.cpp
@@ -43,7 +43,7 @@ TmixTray::TmixTray( TQWidget *parent )
setCaption( i18n("Volume Control") );
TQPixmap titleIcon = TDEGlobal::iconLoader()->loadIcon(
- "kmix", TDEIcon::Small, 16, TDEIcon::DefaultState, 0, true );
+ "tmix", TDEIcon::Small, 16, TDEIcon::DefaultState, 0, true );
if ( titleIcon.isNull() )
titleIcon = TDEGlobal::iconLoader()->loadIcon(
"audio-volume-medium", TDEIcon::Small, 16, TDEIcon::DefaultState, 0, true );