Skip to content

Commit

Permalink
Backport PR matplotlib#28981: FIX: macos: Use standard NSApp run loop…
Browse files Browse the repository at this point in the history
… in our input hook
  • Loading branch information
tacaswell authored and meeseeksmachine committed Oct 30, 2024
1 parent f4e9226 commit 985e8f4
Showing 1 changed file with 56 additions and 54 deletions.
110 changes: 56 additions & 54 deletions src/_macosx.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,60 +40,84 @@
static bool keyChangeCapsLock = false;
/* Keep track of the current mouse up/down state for open/closed cursor hand */
static bool leftMouseGrabbing = false;
/* Keep track of whether stdin has been received */
static bool stdin_received = false;
static bool stdin_sigint = false;
// Global variable to store the original SIGINT handler
static PyOS_sighandler_t originalSigintAction = NULL;

// Signal handler for SIGINT, only sets a flag to exit the run loop
// Stop the current app's run loop, sending an event to ensure it actually stops
static void stopWithEvent() {
[NSApp stop: nil];
// Post an event to trigger the actual stopping.
[NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0]
atStart: YES];
}

// Signal handler for SIGINT, only argument matching for stopWithEvent
static void handleSigint(int signal) {
stdin_sigint = true;
stopWithEvent();
}

// Helper function to flush all events.
// This is needed in some instances to ensure e.g. that windows are properly closed.
// It is used in the input hook as well as wrapped in a version callable from Python.
static void flushEvents() {
while (true) {
NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: [NSDate distantPast]
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (!event) {
break;
}
[NSApp sendEvent:event];
}
}

static int wait_for_stdin() {
@autoreleasepool {
stdin_received = false;
stdin_sigint = false;
// Short circuit if no windows are active
// Rely on Python's input handling to manage CPU usage
// This queries the NSApp, rather than using our FigureWindowCount because that is decremented when events still
// need to be processed to properly close the windows.
if (![[NSApp windows] count]) {
flushEvents();
return 1;
}

@autoreleasepool {
// Set up a SIGINT handler to interrupt the event loop if ctrl+c comes in too
originalSigintAction = PyOS_setsig(SIGINT, handleSigint);

// Create an NSFileHandle for standard input
NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput];


// Register for data available notifications on standard input
[[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification
object: stdinHandle
queue: [NSOperationQueue mainQueue] // Use the main queue
usingBlock: ^(NSNotification *notification) {
// Mark that input has been received
stdin_received = true;
}
id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification
object: stdinHandle
queue: [NSOperationQueue mainQueue] // Use the main queue
usingBlock: ^(NSNotification *notification) {stopWithEvent();}
];

// Wait in the background for anything that happens to stdin
[stdinHandle waitForDataInBackgroundAndNotify];

// continuously run an event loop until the stdin_received flag is set to exit
while (!stdin_received && !stdin_sigint) {
// This loop is similar to the main event loop and flush_events which have
// Py_[BEGIN|END]_ALLOW_THREADS surrounding the loop.
// This should not be necessary here because PyOS_InputHook releases the GIL for us.
while (true) {
NSEvent *event = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: [NSDate distantPast]
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (!event) { break; }
[NSApp sendEvent: event];
}
}
// Run the application's event loop, which will be interrupted on stdin or SIGINT
[NSApp run];

// Remove the input handler as an observer
[[NSNotificationCenter defaultCenter] removeObserver: stdinHandle];
[[NSNotificationCenter defaultCenter] removeObserver: notificationID];


// Restore the original SIGINT handler upon exiting the function
PyOS_setsig(SIGINT, originalSigintAction);

return 1;
}
}
Expand Down Expand Up @@ -236,18 +260,7 @@ static void lazy_init(void) {
static PyObject*
stop(PyObject* self)
{
[NSApp stop: nil];
// Post an event to trigger the actual stopping.
[NSApp postEvent: [NSEvent otherEventWithType: NSEventTypeApplicationDefined
location: NSZeroPoint
modifierFlags: 0
timestamp: 0
windowNumber: 0
context: nil
subtype: 0
data1: 0
data2: 0]
atStart: YES];
stopWithEvent();
Py_RETURN_NONE;
}

Expand Down Expand Up @@ -382,20 +395,9 @@ static CGFloat _get_device_scale(CGContextRef cr)
// We run the app, matching any events that are waiting in the queue
// to process, breaking out of the loop when no events remain and
// displaying the canvas if needed.
NSEvent *event;

Py_BEGIN_ALLOW_THREADS

while (true) {
event = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: [NSDate distantPast]
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (!event) {
break;
}
[NSApp sendEvent:event];
}
flushEvents();

Py_END_ALLOW_THREADS

Expand Down

0 comments on commit 985e8f4

Please sign in to comment.