1- import contextlib
21import os
3- import signal
4- import socket
52
63import matplotlib as mpl
74from matplotlib import _api , cbook
107from .backend_agg import FigureCanvasAgg
118from matplotlib .backend_bases import (
129 _Backend , FigureCanvasBase , FigureManagerBase , NavigationToolbar2 ,
13- ResizeEvent , TimerBase )
10+ ResizeEvent , TimerBase , _allow_interrupt )
1411
1512
1613class TimerMac (_macosx .Timer , TimerBase ):
1714 """Subclass of `.TimerBase` using CFRunLoop timer events."""
1815 # completely implemented at the C-level (in _macosx.Timer)
1916
2017
18+ def _allow_interrupt_macos ():
19+ """A context manager that allows terminating a plot by sending a SIGINT."""
20+ return _allow_interrupt (
21+ lambda rsock : _macosx .wake_on_fd_write (rsock .fileno ()), _macosx .stop )
22+
23+
2124class FigureCanvasMac (FigureCanvasAgg , _macosx .FigureCanvas , FigureCanvasBase ):
2225 # docstring inherited
2326
@@ -109,10 +112,9 @@ def resize(self, width, height):
109112
110113 def start_event_loop (self , timeout = 0 ):
111114 # docstring inherited
112- with _maybe_allow_interrupt ():
113- # Call the objc implementation of the event loop after
114- # setting up the interrupt handling
115- self ._start_event_loop (timeout = timeout )
115+ # Set up a SIGINT handler to allow terminating a plot via CTRL-C.
116+ with _allow_interrupt_macos ():
117+ self ._start_event_loop (timeout = timeout ) # Forward to ObjC implementation.
116118
117119
118120class NavigationToolbar2Mac (_macosx .NavigationToolbar2 , NavigationToolbar2 ):
@@ -177,9 +179,7 @@ def destroy(self):
177179 @classmethod
178180 def start_main_loop (cls ):
179181 # Set up a SIGINT handler to allow terminating a plot via CTRL-C.
180- # The logic is largely copied from qt_compat._maybe_allow_interrupt; see its
181- # docstring for details. Parts are implemented by wake_on_fd_write in ObjC.
182- with _maybe_allow_interrupt ():
182+ with _allow_interrupt_macos ():
183183 _macosx .show ()
184184
185185 def show (self ):
@@ -190,45 +190,6 @@ def show(self):
190190 self ._raise ()
191191
192192
193- @contextlib .contextmanager
194- def _maybe_allow_interrupt ():
195- """
196- This manager allows to terminate a plot by sending a SIGINT. It is
197- necessary because the running backend prevents Python interpreter to
198- run and process signals (i.e., to raise KeyboardInterrupt exception). To
199- solve this one needs to somehow wake up the interpreter and make it close
200- the plot window. The implementation is taken from qt_compat, see that
201- docstring for a more detailed description.
202- """
203- old_sigint_handler = signal .getsignal (signal .SIGINT )
204- if old_sigint_handler in (None , signal .SIG_IGN , signal .SIG_DFL ):
205- yield
206- return
207-
208- handler_args = None
209- wsock , rsock = socket .socketpair ()
210- wsock .setblocking (False )
211- rsock .setblocking (False )
212- old_wakeup_fd = signal .set_wakeup_fd (wsock .fileno ())
213- _macosx .wake_on_fd_write (rsock .fileno ())
214-
215- def handle (* args ):
216- nonlocal handler_args
217- handler_args = args
218- _macosx .stop ()
219-
220- signal .signal (signal .SIGINT , handle )
221- try :
222- yield
223- finally :
224- wsock .close ()
225- rsock .close ()
226- signal .set_wakeup_fd (old_wakeup_fd )
227- signal .signal (signal .SIGINT , old_sigint_handler )
228- if handler_args is not None :
229- old_sigint_handler (* handler_args )
230-
231-
232193@_Backend .export
233194class _BackendMac (_Backend ):
234195 FigureCanvas = FigureCanvasMac
0 commit comments