Skip to content

Commit 91d0012

Browse files
authored
Merge 244ef55 into 49a8578
2 parents 49a8578 + 244ef55 commit 91d0012

1 file changed

Lines changed: 18 additions & 24 deletions

File tree

source/gui/__init__.py

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -358,22 +358,36 @@ def onConfigProfilesCommand(self, evt):
358358

359359
def safeAppExit():
360360
"""
361-
Ensures the app is exited by all the top windows being destroyed
361+
Ensures the app is exited by all the top windows being destroyed.
362+
wx objects that don't inherit from wx.Window (eg sysTrayIcon, Menu) need to be manually destroyed.
362363
"""
363364

365+
import brailleViewer
366+
brailleViewer.destroyBrailleViewer()
367+
368+
# wx.Windows destroy child Windows automatically but wx.Menu and TaskBarIcon don't inherit from wx.Window.
369+
# They must be manually destroyed when exiting the app.
370+
# Note: this doesn't consistently clean them from the tray and appears to be a wx issue. (#12238)
371+
log.debug(f"destroying system tray icon and menu")
372+
if mainFrame.sysTrayIcon.menu:
373+
mainFrame.sysTrayIcon.menu.Destroy()
374+
if mainFrame.sysTrayIcon:
375+
mainFrame.sysTrayIcon.Destroy()
376+
364377
for window in wx.GetTopLevelWindows():
365378
if isinstance(window, wx.Dialog) and window.IsModal():
366-
log.info(f"ending modal {window} during exit process")
379+
log.debug(f"ending modal {window} during exit process")
367380
wx.CallAfter(window.EndModal, wx.ID_CLOSE_ALL)
368381
if isinstance(window, MainFrame):
369-
log.info(f"destroying main frame during exit process")
382+
log.debug(f"destroying main frame during exit process")
370383
# the MainFrame has EVT_CLOSE bound to the ExitDialog
371384
# which calls this function on exit, so destroy this window
372385
wx.CallAfter(window.Destroy)
373386
else:
374-
log.info(f"closing window {window} during exit process")
387+
log.debug(f"closing window {window} during exit process")
375388
wx.CallAfter(window.Close)
376389

390+
377391
class SysTrayIcon(wx.adv.TaskBarIcon):
378392

379393
def __init__(self, frame):
@@ -582,27 +596,7 @@ def wx_CallAfter_wrapper(func, *args, **kwargs):
582596
wx.CallAfter = wx_CallAfter_wrapper
583597

584598
def terminate():
585-
import brailleViewer
586-
brailleViewer.destroyBrailleViewer()
587-
588-
for instance, state in gui.SettingsDialog._instances.items():
589-
if state is gui.SettingsDialog._DIALOG_DESTROYED_STATE:
590-
log.error(
591-
"Destroyed but not deleted instance of settings dialog exists: {!r}".format(instance)
592-
)
593-
else:
594-
log.debug("Exiting NVDA with an open settings dialog: {!r}".format(instance))
595599
global mainFrame
596-
# This is called after the main loop exits because WM_QUIT exits the main loop
597-
# without destroying all objects correctly and we need to support WM_QUIT.
598-
# Therefore, any request to exit should exit the main loop.
599-
safeAppExit()
600-
# #4460: We need another iteration of the main loop
601-
# so that everything (especially the TaskBarIcon) is cleaned up properly.
602-
# ProcessPendingEvents doesn't seem to work, but MainLoop does.
603-
# Because the top window gets destroyed,
604-
# MainLoop thankfully returns pretty quickly.
605-
wx.GetApp().MainLoop()
606600
mainFrame = None
607601

608602
def showGui():

0 commit comments

Comments
 (0)