You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

wxreactor.py 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. This module provides wxPython event loop support for Twisted.
  5. In order to use this support, simply do the following::
  6. | from twisted.internet import wxreactor
  7. | wxreactor.install()
  8. Then, when your root wxApp has been created::
  9. | from twisted.internet import reactor
  10. | reactor.registerWxApp(yourApp)
  11. | reactor.run()
  12. Then use twisted.internet APIs as usual. Stop the event loop using
  13. reactor.stop(), not yourApp.ExitMainLoop().
  14. IMPORTANT: tests will fail when run under this reactor. This is
  15. expected and probably does not reflect on the reactor's ability to run
  16. real applications.
  17. """
  18. try:
  19. from queue import Empty, Queue
  20. except ImportError:
  21. from Queue import Empty, Queue
  22. try:
  23. from wx import PySimpleApp as wxPySimpleApp, CallAfter as wxCallAfter, \
  24. Timer as wxTimer
  25. except ImportError:
  26. # older version of wxPython:
  27. from wxPython.wx import wxPySimpleApp, wxCallAfter, wxTimer
  28. from twisted.python import log, runtime
  29. from twisted.internet import _threadedselect
  30. class ProcessEventsTimer(wxTimer):
  31. """
  32. Timer that tells wx to process pending events.
  33. This is necessary on macOS, probably due to a bug in wx, if we want
  34. wxCallAfters to be handled when modal dialogs, menus, etc. are open.
  35. """
  36. def __init__(self, wxapp):
  37. wxTimer.__init__(self)
  38. self.wxapp = wxapp
  39. def Notify(self):
  40. """
  41. Called repeatedly by wx event loop.
  42. """
  43. self.wxapp.ProcessPendingEvents()
  44. class WxReactor(_threadedselect.ThreadedSelectReactor):
  45. """
  46. wxPython reactor.
  47. wxPython drives the event loop, select() runs in a thread.
  48. """
  49. _stopping = False
  50. def registerWxApp(self, wxapp):
  51. """
  52. Register wxApp instance with the reactor.
  53. """
  54. self.wxapp = wxapp
  55. def _installSignalHandlersAgain(self):
  56. """
  57. wx sometimes removes our own signal handlers, so re-add them.
  58. """
  59. try:
  60. # make _handleSignals happy:
  61. import signal
  62. signal.signal(signal.SIGINT, signal.default_int_handler)
  63. except ImportError:
  64. return
  65. self._handleSignals()
  66. def stop(self):
  67. """
  68. Stop the reactor.
  69. """
  70. if self._stopping:
  71. return
  72. self._stopping = True
  73. _threadedselect.ThreadedSelectReactor.stop(self)
  74. def _runInMainThread(self, f):
  75. """
  76. Schedule function to run in main wx/Twisted thread.
  77. Called by the select() thread.
  78. """
  79. if hasattr(self, "wxapp"):
  80. wxCallAfter(f)
  81. else:
  82. # wx shutdown but twisted hasn't
  83. self._postQueue.put(f)
  84. def _stopWx(self):
  85. """
  86. Stop the wx event loop if it hasn't already been stopped.
  87. Called during Twisted event loop shutdown.
  88. """
  89. if hasattr(self, "wxapp"):
  90. self.wxapp.ExitMainLoop()
  91. def run(self, installSignalHandlers=True):
  92. """
  93. Start the reactor.
  94. """
  95. self._postQueue = Queue()
  96. if not hasattr(self, "wxapp"):
  97. log.msg("registerWxApp() was not called on reactor, "
  98. "registering my own wxApp instance.")
  99. self.registerWxApp(wxPySimpleApp())
  100. # start select() thread:
  101. self.interleave(self._runInMainThread,
  102. installSignalHandlers=installSignalHandlers)
  103. if installSignalHandlers:
  104. self.callLater(0, self._installSignalHandlersAgain)
  105. # add cleanup events:
  106. self.addSystemEventTrigger("after", "shutdown", self._stopWx)
  107. self.addSystemEventTrigger("after", "shutdown",
  108. lambda: self._postQueue.put(None))
  109. # On macOS, work around wx bug by starting timer to ensure
  110. # wxCallAfter calls are always processed. We don't wake up as
  111. # often as we could since that uses too much CPU.
  112. if runtime.platform.isMacOSX():
  113. t = ProcessEventsTimer(self.wxapp)
  114. t.Start(2) # wake up every 2ms
  115. self.wxapp.MainLoop()
  116. wxapp = self.wxapp
  117. del self.wxapp
  118. if not self._stopping:
  119. # wx event loop exited without reactor.stop() being
  120. # called. At this point events from select() thread will
  121. # be added to _postQueue, but some may still be waiting
  122. # unprocessed in wx, thus the ProcessPendingEvents()
  123. # below.
  124. self.stop()
  125. wxapp.ProcessPendingEvents() # deal with any queued wxCallAfters
  126. while 1:
  127. try:
  128. f = self._postQueue.get(timeout=0.01)
  129. except Empty:
  130. continue
  131. else:
  132. if f is None:
  133. break
  134. try:
  135. f()
  136. except:
  137. log.err()
  138. def install():
  139. """
  140. Configure the twisted mainloop to be run inside the wxPython mainloop.
  141. """
  142. reactor = WxReactor()
  143. from twisted.internet.main import installReactor
  144. installReactor(reactor)
  145. return reactor
  146. __all__ = ['install']