1+ import contextlib
12import os
23import signal
34import socket
@@ -106,6 +107,13 @@ def resize(self, width, height):
106107 ResizeEvent ("resize_event" , self )._process ()
107108 self .draw_idle ()
108109
110+ def start_event_loop (self , timeout = 0 ):
111+ # 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 )
116+
109117
110118class NavigationToolbar2Mac (_macosx .NavigationToolbar2 , NavigationToolbar2 ):
111119
@@ -171,34 +179,8 @@ def start_main_loop(cls):
171179 # Set up a SIGINT handler to allow terminating a plot via CTRL-C.
172180 # The logic is largely copied from qt_compat._maybe_allow_interrupt; see its
173181 # 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 :
182+ with _maybe_allow_interrupt ():
194183 _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 )
202184
203185 def show (self ):
204186 if not self ._shown :
@@ -208,6 +190,45 @@ def show(self):
208190 self ._raise ()
209191
210192
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+
211232@_Backend .export
212233class _BackendMac (_Backend ):
213234 FigureCanvas = FigureCanvasMac
0 commit comments