40
40
static bool keyChangeCapsLock = false ;
41
41
/* Keep track of the current mouse up/down state for open/closed cursor hand */
42
42
static bool leftMouseGrabbing = false ;
43
- /* Keep track of whether stdin has been received */
44
- static bool stdin_received = false ;
45
- static bool stdin_sigint = false ;
46
43
// Global variable to store the original SIGINT handler
47
44
static PyOS_sighandler_t originalSigintAction = NULL ;
48
45
49
- // Signal handler for SIGINT, only sets a flag to exit the run loop
46
+ // Stop the current app's run loop, sending an event to ensure it actually stops
47
+ static void stopWithEvent () {
48
+ [NSApp stop: nil ];
49
+ // Post an event to trigger the actual stopping.
50
+ [NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined
51
+ location: NSZeroPoint
52
+ modifierFlags: 0
53
+ timestamp: 0
54
+ windowNumber: 0
55
+ context: nil
56
+ subtype: 0
57
+ data1: 0
58
+ data2: 0 ]
59
+ atStart: YES ];
60
+ }
61
+
62
+ // Signal handler for SIGINT, only argument matching for stopWithEvent
50
63
static void handleSigint (int signal) {
51
- stdin_sigint = true ;
64
+ stopWithEvent ();
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
+ }
52
81
}
53
82
54
83
static int wait_for_stdin () {
55
- @autoreleasepool {
56
- stdin_received = false ;
57
- stdin_sigint = false ;
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
+ }
58
92
93
+ @autoreleasepool {
59
94
// Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too
60
95
originalSigintAction = PyOS_setsig (SIGINT, handleSigint);
61
96
62
97
// Create an NSFileHandle for standard input
63
98
NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput ];
64
99
100
+
65
101
// Register for data available notifications on standard input
66
- [[NSNotificationCenter defaultCenter ] addObserverForName: NSFileHandleDataAvailableNotification
67
- object: stdinHandle
68
- queue: [NSOperationQueue mainQueue ] // Use the main queue
69
- usingBlock: ^(NSNotification *notification) {
70
- // Mark that input has been received
71
- stdin_received = true ;
72
- }
102
+ id notificationID = [[NSNotificationCenter defaultCenter ] addObserverForName: NSFileHandleDataAvailableNotification
103
+ object: stdinHandle
104
+ queue: [NSOperationQueue mainQueue ] // Use the main queue
105
+ usingBlock: ^(NSNotification *notification) {stopWithEvent ();}
73
106
];
74
107
75
108
// Wait in the background for anything that happens to stdin
76
109
[stdinHandle waitForDataInBackgroundAndNotify ];
77
110
78
- // continuously run an event loop until the stdin_received flag is set to exit
79
- while (!stdin_received && !stdin_sigint) {
80
- // This loop is similar to the main event loop and flush_events which have
81
- // Py_[BEGIN|END]_ALLOW_THREADS surrounding the loop.
82
- // This should not be necessary here because PyOS_InputHook releases the GIL for us.
83
- while (true ) {
84
- NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny
85
- untilDate: [NSDate distantPast ]
86
- inMode: NSDefaultRunLoopMode
87
- dequeue: YES ];
88
- if (!event) { break ; }
89
- [NSApp sendEvent: event];
90
- }
91
- }
111
+ // Run the application's event loop, which will be interrupted on stdin or SIGINT
112
+ [NSApp run ];
113
+
92
114
// Remove the input handler as an observer
93
- [[NSNotificationCenter defaultCenter ] removeObserver: stdinHandle];
115
+ [[NSNotificationCenter defaultCenter ] removeObserver: notificationID];
116
+
94
117
95
118
// Restore the original SIGINT handler upon exiting the function
96
119
PyOS_setsig (SIGINT, originalSigintAction);
120
+
97
121
return 1 ;
98
122
}
99
123
}
@@ -236,18 +260,7 @@ static void lazy_init(void) {
236
260
static PyObject*
237
261
stop (PyObject* self)
238
262
{
239
- [NSApp stop: nil ];
240
- // Post an event to trigger the actual stopping.
241
- [NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined
242
- location: NSZeroPoint
243
- modifierFlags: 0
244
- timestamp: 0
245
- windowNumber: 0
246
- context: nil
247
- subtype: 0
248
- data1: 0
249
- data2: 0 ]
250
- atStart: YES ];
263
+ stopWithEvent ();
251
264
Py_RETURN_NONE;
252
265
}
253
266
@@ -402,20 +415,9 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name)
402
415
// We run the app, matching any events that are waiting in the queue
403
416
// to process, breaking out of the loop when no events remain and
404
417
// displaying the canvas if needed.
405
- NSEvent *event;
406
-
407
418
Py_BEGIN_ALLOW_THREADS
408
419
409
- while (true ) {
410
- event = [NSApp nextEventMatchingMask: NSEventMaskAny
411
- untilDate: [NSDate distantPast ]
412
- inMode: NSDefaultRunLoopMode
413
- dequeue: YES ];
414
- if (!event) {
415
- break ;
416
- }
417
- [NSApp sendEvent: event];
418
- }
420
+ flushEvents ();
419
421
420
422
Py_END_ALLOW_THREADS
421
423
0 commit comments