@@ -59,21 +59,47 @@ static void stop_with_event() {
5959 atStart: YES ];
6060}
6161
62- // Signal handler for SIGINT, only sets a flag to exit the run loop
62+ // Signal handler for SIGINT, only argument matching for stop_with_event
6363static void handleSigint (int signal) {
64- stop_with_event ();
64+ stop_with_event ();
65+ }
66+
67+ // Helper function to flush all events.
68+ // This is needed in some instances to ensure e.g. that windows are properly closed.
69+ // It is used in the input hook as well as wrapped in a version callable from Python.
70+ static void flushEvents () {
71+ while (true ) {
72+ NSEvent * event = [NSApp nextEventMatchingMask: NSEventMaskAny
73+ untilDate: [NSDate distantPast ]
74+ inMode: NSDefaultRunLoopMode
75+ dequeue: YES ];
76+ if (!event) {
77+ break ;
78+ }
79+ [NSApp sendEvent: event];
80+ }
6581}
6682
6783static int wait_for_stdin () {
84+ // Short circuit if no windows are active
85+ // Rely on Python's input handling to manage CPU usage
86+ // This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still
87+ // need to be processed to properly close the windows.
88+ if (![[NSApp windows ] count ]) {
89+ flushEvents ();
90+ return 1 ;
91+ }
92+
6893 @autoreleasepool {
6994 // Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too
7095 originalSigintAction = PyOS_setsig (SIGINT, handleSigint);
7196
7297 // Create an NSFileHandle for standard input
7398 NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput ];
7499
100+
75101 // Register for data available notifications on standard input
76- [[NSNotificationCenter defaultCenter ] addObserverForName: NSFileHandleDataAvailableNotification
102+ id notificationID = [[NSNotificationCenter defaultCenter ] addObserverForName: NSFileHandleDataAvailableNotification
77103 object: stdinHandle
78104 queue: [NSOperationQueue mainQueue ] // Use the main queue
79105 usingBlock: ^(NSNotification *notification) {stop_with_event ();}
@@ -82,13 +108,16 @@ static int wait_for_stdin() {
82108 // Wait in the background for anything that happens to stdin
83109 [stdinHandle waitForDataInBackgroundAndNotify ];
84110
111+ // Run the application's event loop, which will be interrupted on stdin or SIGINT
85112 [NSApp run ];
86113
87114 // Remove the input handler as an observer
88- [[NSNotificationCenter defaultCenter ] removeObserver: stdinHandle];
115+ [[NSNotificationCenter defaultCenter ] removeObserver: notificationID];
116+
89117
90118 // Restore the original SIGINT handler upon exiting the function
91119 PyOS_setsig (SIGINT, originalSigintAction);
120+
92121 return 1 ;
93122 }
94123}
@@ -366,20 +395,9 @@ static CGFloat _get_device_scale(CGContextRef cr)
366395 // We run the app, matching any events that are waiting in the queue
367396 // to process, breaking out of the loop when no events remain and
368397 // displaying the canvas if needed.
369- NSEvent *event;
370-
371398 Py_BEGIN_ALLOW_THREADS
372399
373- while (true ) {
374- event = [NSApp nextEventMatchingMask: NSEventMaskAny
375- untilDate: [NSDate distantPast ]
376- inMode: NSDefaultRunLoopMode
377- dequeue: YES ];
378- if (!event) {
379- break ;
380- }
381- [NSApp sendEvent: event];
382- }
400+ flushEvents ();
383401
384402 Py_END_ALLOW_THREADS
385403
@@ -1106,13 +1124,10 @@ - (void)close
11061124{
11071125 [super close ];
11081126 --FigureWindowCount;
1109- if (!FigureWindowCount) {
1110- /* This is needed for show(), which should exit from [NSApp run]
1111- * after all windows are closed.
1112- */
1113- PyObject* x = stop (NULL );
1114- Py_DECREF (x);
1115- }
1127+ if (!FigureWindowCount) [NSApp stop: self ];
1128+ /* This is needed for show(), which should exit from [NSApp run]
1129+ * after all windows are closed.
1130+ */
11161131 // For each new window, we have incremented the manager reference, so
11171132 // we need to bring that down during close and not just dealloc.
11181133 Py_DECREF (manager);
0 commit comments