|
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