@@ -34,6 +34,7 @@ type portalBackend struct {
3434 sessionPath dbus.ObjectPath
3535 preferredTrigger string
3636 keyChan chan struct {}
37+ done chan struct {}
3738 mu sync.Mutex
3839 closed bool
3940}
@@ -81,6 +82,7 @@ func newPortalBackend(modStrs []string, keyStr string) (*portalBackend, error) {
8182 conn : conn ,
8283 preferredTrigger : buildTriggerString (modStrs , keyStr ),
8384 keyChan : make (chan struct {}, 1 ),
85+ done : make (chan struct {}),
8486 }, nil
8587}
8688
@@ -169,6 +171,7 @@ func (p *portalBackend) Register() error {
169171 call = obj .Call (portalShortcutIf + ".BindShortcuts" , 0 ,
170172 p .sessionPath , shortcuts , "" , bindOpts )
171173 if call .Err != nil {
174+ p .closeSession ()
172175 return fmt .Errorf ("BindShortcuts call failed: %w" , call .Err )
173176 }
174177
@@ -178,10 +181,12 @@ func (p *portalBackend) Register() error {
178181
179182 responseCode , _ , err = waitResponse (sigChan , expectedBindPath )
180183 if err != nil {
184+ p .closeSession ()
181185 return fmt .Errorf ("BindShortcuts response failed: %w" , err )
182186 }
183187
184188 if responseCode != 0 {
189+ p .closeSession ()
185190 return fmt .Errorf ("BindShortcuts denied (response code: %d)" , responseCode )
186191 }
187192
@@ -218,31 +223,58 @@ func (p *portalBackend) listenActivated() {
218223
219224 log .Println ("Hotkey portal: Listening for Activated signals" )
220225
221- for sig := range sigChan {
222- if sig .Name != portalShortcutIf + ".Activated" {
223- continue
224- }
226+ for {
227+ select {
228+ case <- p .done :
229+ log .Println ("Hotkey portal: Listener stopped" )
230+ return
231+ case sig , ok := <- sigChan :
232+ if ! ok {
233+ log .Println ("Hotkey portal: Signal channel closed" )
234+ return
235+ }
225236
226- // Activated signal body: (session_handle, shortcut_id, timestamp, options)
227- if len (sig .Body ) < minResponseBodyLen {
228- continue
229- }
237+ if sig .Name != portalShortcutIf + ".Activated" {
238+ continue
239+ }
230240
231- id , ok := sig .Body [1 ].(string )
232- if ! ok || id != shortcutID {
233- continue
234- }
241+ // Activated signal body: (session_handle, shortcut_id, timestamp, options)
242+ if len (sig .Body ) < minResponseBodyLen {
243+ continue
244+ }
245+
246+ id , ok := sig .Body [1 ].(string )
247+ if ! ok || id != shortcutID {
248+ continue
249+ }
235250
236- log .Println ("Hotkey portal: Shortcut activated" )
251+ log .Println ("Hotkey portal: Shortcut activated" )
237252
238- select {
239- case p .keyChan <- struct {}{}:
240- default :
241- // Channel full, skip duplicate
253+ select {
254+ case p .keyChan <- struct {}{}:
255+ default :
256+ // Channel full, skip duplicate
257+ }
242258 }
243259 }
244260}
245261
262+ // closeSession closes the portal session via D-Bus.
263+ // Safe to call even if no session was created.
264+ func (p * portalBackend ) closeSession () {
265+ if p .sessionPath == "" {
266+ return
267+ }
268+
269+ sessionObj := p .conn .Object (portalDest , p .sessionPath )
270+
271+ if err := sessionObj .Call (portalSessionIf + ".Close" , 0 ).Err ; err != nil {
272+ log .Printf ("Hotkey portal: warning: failed to close session: %v" , err )
273+ }
274+
275+ p .sessionPath = ""
276+ }
277+
246278// Unregister closes the portal session and D-Bus connection.
247279func (p * portalBackend ) Unregister () error {
248280 p .mu .Lock ()
@@ -253,15 +285,9 @@ func (p *portalBackend) Unregister() error {
253285 }
254286
255287 p .closed = true
288+ close (p .done )
256289
257- // Close the portal session
258- if p .sessionPath != "" {
259- sessionObj := p .conn .Object (portalDest , p .sessionPath )
260-
261- if err := sessionObj .Call (portalSessionIf + ".Close" , 0 ).Err ; err != nil {
262- log .Printf ("Hotkey portal: warning: failed to close session: %v" , err )
263- }
264- }
290+ p .closeSession ()
265291
266292 if err := p .conn .Close (); err != nil {
267293 return fmt .Errorf ("failed to close D-Bus connection: %w" , err )
0 commit comments