Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0e2eff0

Browse filesBrowse 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 0e2eff0
Copy full SHA for 0e2eff0

File tree

Expand file treeCollapse file tree

5 files changed

+312
-237
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+312
-237
lines changed

‎lib/matplotlib/backends/backend_macosx.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/backend_macosx.py
+33-1Lines 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

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/qt_compat.py
+36-39Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.