Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 059c8ee

Browse files
committed
Fix support for Ctrl-C on the macosx backend.
Support is largely copy-pasted from, and tests are shared with, the qt implementation (qt_compat._maybe_allow_interrupt), the main difference being that what we need from QSocketNotifier, as well as the equivalent for QApplication.quit(), are reimplemented in ObjC. qt_compat._maybe_allow_interrupt is also slightly cleaned up by moving out the "do-nothing" case (`old_sigint_handler in (None, SIG_IGN, SIG_DFL)`) and dedenting the rest, instead of keeping track of whether signals were actually manipulated via a `skip` variable. Factoring out the common parts of _maybe_allow_interrupt is left as a follow-up. (Test e.g. with `MPLBACKEND=macosx python -c "from pylab import *; plot(); show()"` followed by Ctrl-C.)
1 parent 5140177 commit 059c8ee

File tree

5 files changed

+312
-237
lines changed

5 files changed

+312
-237
lines changed

lib/matplotlib/backends/backend_macosx.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
import signal
3+
import socket
24

35
import matplotlib as mpl
46
from matplotlib import _api, cbook
@@ -166,7 +168,37 @@ def destroy(self):
166168

167169
@classmethod
168170
def start_main_loop(cls):
169-
_macosx.show()
171+
# Set up a SIGINT handler to allow terminating a plot via CTRL-C.
172+
# The logic is largely copied from qt_compat._maybe_allow_interrupt; see its
173+
# docstring for details. Parts are implemented by wake_on_fd_write in ObjC.
174+
175+
old_sigint_handler = signal.getsignal(signal.SIGINT)
176+
if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL):
177+
_macosx.show()
178+
return
179+
180+
handler_args = None
181+
wsock, rsock = socket.socketpair()
182+
wsock.setblocking(False)
183+
rsock.setblocking(False)
184+
old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno())
185+
_macosx.wake_on_fd_write(rsock.fileno())
186+
187+
def handle(*args):
188+
nonlocal handler_args
189+
handler_args = args
190+
_macosx.stop()
191+
192+
signal.signal(signal.SIGINT, handle)
193+
try:
194+
_macosx.show()
195+
finally:
196+
wsock.close()
197+
rsock.close()
198+
signal.set_wakeup_fd(old_wakeup_fd)
199+
signal.signal(signal.SIGINT, old_sigint_handler)
200+
if handler_args is not None:
201+
old_sigint_handler(*handler_args)
170202

171203
def show(self):
172204
if not self._shown:

lib/matplotlib/backends/qt_compat.py

Lines changed: 36 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -186,48 +186,45 @@ def _maybe_allow_interrupt(qapp):
186186
that a non-python handler was installed, i.e. in Julia, and not SIG_IGN
187187
which means we should ignore the interrupts.
188188
"""
189+
189190
old_sigint_handler = signal.getsignal(signal.SIGINT)
190-
handler_args = None
191-
skip = False
192191
if old_sigint_handler in (None, signal.SIG_IGN, signal.SIG_DFL):
193-
skip = True
194-
else:
195-
wsock, rsock = socket.socketpair()
196-
wsock.setblocking(False)
197-
old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno())
198-
sn = QtCore.QSocketNotifier(
199-
rsock.fileno(), QtCore.QSocketNotifier.Type.Read
200-
)
192+
yield
193+
return
194+
195+
handler_args = None
196+
wsock, rsock = socket.socketpair()
197+
wsock.setblocking(False)
198+
rsock.setblocking(False)
199+
old_wakeup_fd = signal.set_wakeup_fd(wsock.fileno())
200+
sn = QtCore.QSocketNotifier(rsock.fileno(), QtCore.QSocketNotifier.Type.Read)
201+
202+
# We do not actually care about this value other than running some Python code to
203+
# ensure that the interpreter has a chance to handle the signal in Python land. We
204+
# also need to drain the socket because it will be written to as part of the wakeup!
205+
# There are some cases where this may fire too soon / more than once on Windows so
206+
# we should be forgiving about reading an empty socket.
207+
# Clear the socket to re-arm the notifier.
208+
@sn.activated.connect
209+
def _may_clear_sock(*args):
210+
try:
211+
rsock.recv(1)
212+
except BlockingIOError:
213+
pass
214+
215+
def handle(*args):
216+
nonlocal handler_args
217+
handler_args = args
218+
qapp.quit()
201219

202-
# We do not actually care about this value other than running some
203-
# Python code to ensure that the interpreter has a chance to handle the
204-
# signal in Python land. We also need to drain the socket because it
205-
# will be written to as part of the wakeup! There are some cases where
206-
# this may fire too soon / more than once on Windows so we should be
207-
# forgiving about reading an empty socket.
208-
rsock.setblocking(False)
209-
# Clear the socket to re-arm the notifier.
210-
@sn.activated.connect
211-
def _may_clear_sock(*args):
212-
try:
213-
rsock.recv(1)
214-
except BlockingIOError:
215-
pass
216-
217-
def handle(*args):
218-
nonlocal handler_args
219-
handler_args = args
220-
qapp.quit()
221-
222-
signal.signal(signal.SIGINT, handle)
220+
signal.signal(signal.SIGINT, handle)
223221
try:
224222
yield
225223
finally:
226-
if not skip:
227-
wsock.close()
228-
rsock.close()
229-
sn.setEnabled(False)
230-
signal.set_wakeup_fd(old_wakeup_fd)
231-
signal.signal(signal.SIGINT, old_sigint_handler)
232-
if handler_args is not None:
233-
old_sigint_handler(*handler_args)
224+
wsock.close()
225+
rsock.close()
226+
sn.setEnabled(False)
227+
signal.set_wakeup_fd(old_wakeup_fd)
228+
signal.signal(signal.SIGINT, old_sigint_handler)
229+
if handler_args is not None:
230+
old_sigint_handler(*handler_args)

0 commit comments

Comments
 (0)