code: def wait_for_frame(frame):
      
      if not frame:
          # It's already closed or maybe we're in batch mode
          return
  
      @dispatcher
      def close():
          ROOT.gSystem.ExitLoop()
  
      if not getattr(frame, "_py_close_dispatcher_attached", False):
          frame._py_close_dispatcher_attached = True
          frame.Connect("CloseWindow()", "TPyDispatcher", close, "Dispatch()")
  
      @dispatcher
      def exit_application_loop():
          """
          Signal handler for CTRL-c to cause gApplication.Run() to finish.
  
          incpy.ignore
          """
          ROOT.gSystem.ExitLoop()
  
      # Handle CTRL-c
      sh = ROOT.TSignalHandler(ROOT.kSigInterrupt, True)
      sh.Add()
      sh.Connect("Notified()", "TPyDispatcher",
                 exit_application_loop, "Dispatch()")
  
      if not ROOT.gROOT.IsBatch():
          run_application_until_done()
          # Need to disconnect to prevent close handler from running when python
          # teardown has already commenced.
          frame.Disconnect("CloseWindow()", close, "Dispatch()")
docs: wait until a TGMainFrame is closed or ctrl-c