A context manager that allows terminating a plot by sending a SIGINT.
(qapp_or_eventloop)
| 148 | |
| 149 | |
| 150 | def _allow_interrupt_qt(qapp_or_eventloop): |
| 151 | """A context manager that allows terminating a plot by sending a SIGINT.""" |
| 152 | |
| 153 | # Use QSocketNotifier to read the socketpair while the Qt event loop runs. |
| 154 | |
| 155 | def prepare_notifier(rsock): |
| 156 | sn = QtCore.QSocketNotifier(rsock.fileno(), QtCore.QSocketNotifier.Type.Read) |
| 157 | |
| 158 | @sn.activated.connect |
| 159 | def _may_clear_sock(): |
| 160 | # Running a Python function on socket activation gives the interpreter a |
| 161 | # chance to handle the signal in Python land. We also need to drain the |
| 162 | # socket with recv() to re-arm it, because it will be written to as part of |
| 163 | # the wakeup. (We need this in case set_wakeup_fd catches a signal other |
| 164 | # than SIGINT and we shall continue waiting.) |
| 165 | try: |
| 166 | rsock.recv(1) |
| 167 | except BlockingIOError: |
| 168 | # This may occasionally fire too soon or more than once on Windows, so |
| 169 | # be forgiving about reading an empty socket. |
| 170 | pass |
| 171 | |
| 172 | # We return the QSocketNotifier so that the caller holds a reference, and we |
| 173 | # also explicitly clean it up in handle_sigint(). Without doing both, deletion |
| 174 | # of the socket notifier can happen prematurely or not at all. |
| 175 | return sn |
| 176 | |
| 177 | def handle_sigint(sn): |
| 178 | sn.deleteLater() |
| 179 | QtCore.QCoreApplication.sendPostedEvents(sn, QtCore.QEvent.Type.DeferredDelete) |
| 180 | if hasattr(qapp_or_eventloop, 'closeAllWindows'): |
| 181 | qapp_or_eventloop.closeAllWindows() |
| 182 | qapp_or_eventloop.quit() |
| 183 | |
| 184 | return _allow_interrupt(prepare_notifier, handle_sigint) |
| 185 | |
| 186 | |
| 187 | class TimerQT(TimerBase): |
no test coverage detected
searching dependent graphs…