@@ -120,12 +120,20 @@ export default class HanabiExtension extends Extension {
120120 // handler would immediately pause it.)
121121 this . launchRenderer ( ) ;
122122
123+ // Handle monitor configuration changes (connect/disconnect)
124+ this . _monitorsChangedId = Main . layoutManager . connect ( 'monitors-changed' , ( ) => {
125+ this . _onMonitorsChanged ( ) ;
126+ } ) ;
127+
123128 this . playbackState . suppressMismatch = true ;
124129 this . _autoPauseDelayId = GLib . timeout_add ( GLib . PRIORITY_DEFAULT , 5000 , ( ) => {
125130 this . _autoPauseDelayId = null ;
126131 this . playbackState . suppressMismatch = false ;
132+
127133 if ( this . isEnabled )
128134 this . autoPause . enable ( ) ;
135+
136+ this . playbackState . sync ( ) ;
129137 return false ;
130138 } ) ;
131139
@@ -135,6 +143,56 @@ export default class HanabiExtension extends Extension {
135143 this . _setupSleepWatch ( ) ;
136144 }
137145
146+ _onMonitorsChanged ( ) {
147+ if ( ! this . isEnabled || this . _isSuspending )
148+ return ;
149+
150+ // Debounce rapid monitor changes
151+ if ( this . _monitorChangeTimeoutId ) {
152+ GLib . source_remove ( this . _monitorChangeTimeoutId ) ;
153+ this . _monitorChangeTimeoutId = null ;
154+ }
155+
156+ this . _monitorChangeTimeoutId = GLib . timeout_add ( GLib . PRIORITY_DEFAULT , 500 , ( ) => {
157+ this . _monitorChangeTimeoutId = null ;
158+
159+ if ( ! this . isEnabled || this . _isSuspending )
160+ return false ;
161+
162+ // Disable autoPause during transition
163+ this . autoPause . disable ( ) ;
164+
165+ // Cancel any pending autoPause delay
166+ if ( this . _autoPauseDelayId ) {
167+ GLib . source_remove ( this . _autoPauseDelayId ) ;
168+ this . _autoPauseDelayId = null ;
169+ }
170+
171+ // Reset playback state for fresh start
172+ this . playbackState . reset ( ) ;
173+
174+ // Restart renderer for new monitor configuration
175+ this . killCurrentProcess ( ) ;
176+
177+ // If no process was running and no relaunch is pending, launch directly
178+ if ( ! this . currentProcess && ! this . launchRendererId )
179+ this . launchRenderer ( ) ;
180+
181+ // Suppress mismatch and re-enable autoPause after renderer stabilizes
182+ this . playbackState . suppressMismatch = true ;
183+ this . _autoPauseDelayId = GLib . timeout_add ( GLib . PRIORITY_DEFAULT , 5000 , ( ) => {
184+ this . _autoPauseDelayId = null ;
185+ this . playbackState . suppressMismatch = false ;
186+ if ( this . isEnabled && ! this . _isSuspending )
187+ this . autoPause . enable ( ) ;
188+ this . playbackState . sync ( ) ;
189+ return false ;
190+ } ) ;
191+
192+ return false ;
193+ } ) ;
194+ }
195+
138196 _setupSleepWatch ( ) {
139197 if ( this . _sleepWatchId )
140198 return ;
@@ -175,19 +233,16 @@ export default class HanabiExtension extends Extension {
175233 }
176234 this . _cancelRendererWait ( ) ;
177235
178- // Suppress the mismatch handler so the renderer isn't
179- // force-paused while starting up.
180- this . playbackState . suppressMismatch = true ;
181-
182- // After 5 seconds, force-sync: directly tell renderer to
183- // play via D-Bus (bypassing state machine which has no
184- // transition from 'playing'), then re-enable autoPause.
185- this . _autoPauseDelayId = GLib . timeout_add ( GLib . PRIORITY_DEFAULT , 5000 , ( ) => {
236+ // Small grace period for the renderer process to come
237+ // up and export its D-Bus interface before we start
238+ // sending it commands. This is just a launch race
239+ // guard, not a "wait for GStreamer to stabilize" hack.
240+ // The actual post-wake play/pause loop is prevented
241+ // by the mismatch-handler and autoPause changes in
242+ // playbackState.js (see comments there).
243+ const POST_WAKE_GRACE_MS = 1500 ;
244+ this . _autoPauseDelayId = GLib . timeout_add ( GLib . PRIORITY_DEFAULT , POST_WAKE_GRACE_MS , ( ) => {
186245 this . _autoPauseDelayId = null ;
187- this . playbackState . suppressMismatch = false ;
188- // Force renderer to play - reset() set state to 'playing'
189- // but never sent a D-Bus command.
190- this . playbackState . _renderer . setPlay ( ) ;
191246 if ( this . isEnabled && ! this . _isSuspending )
192247 this . autoPause . enable ( ) ;
193248 return false ;
@@ -196,7 +251,7 @@ export default class HanabiExtension extends Extension {
196251 }
197252 } ) ;
198253 } catch ( e ) {
199- Logger . Logger . prototype . warn ?. call ( null , `Failed to setup sleep watch: ${ e } ` ) ;
254+ console . warn ( `Failed to setup sleep watch: ${ e } ` ) ;
200255 }
201256 }
202257
@@ -317,6 +372,21 @@ export default class HanabiExtension extends Extension {
317372 this . _autoPauseDelayId = null ;
318373 }
319374
375+ // Cancel any pending post-wake mismatch-resume timer.
376+ if ( this . _mismatchResumeId ) {
377+ GLib . source_remove ( this . _mismatchResumeId ) ;
378+ this . _mismatchResumeId = 0 ;
379+ }
380+
381+ if ( this . _monitorsChangedId ) {
382+ Main . layoutManager . disconnect ( this . _monitorsChangedId ) ;
383+ this . _monitorsChangedId = null ;
384+ }
385+ if ( this . _monitorChangeTimeoutId ) {
386+ GLib . source_remove ( this . _monitorChangeTimeoutId ) ;
387+ this . _monitorChangeTimeoutId = null ;
388+ }
389+
320390 this . _cancelRendererWait ( ) ;
321391 this . _teardownSleepWatch ( ) ;
322392 this . _isSuspending = false ;
0 commit comments