diff --git a/src/_macosx.m b/src/_macosx.m index 62daf43abe1b..0de0540018a7 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -21,6 +21,37 @@ #define WINDOW_CLOSING 3 +/* When calling into Objective-C from Python, wrap the calls with + BEGIN_OBJC_ENTRY and END_OBJC_ENTRY. This will set up an autorelease + pool as well as catch any Obj-C exceptions thrown. These macros + should be used for any call exposed to Python via the external module + interface. + + To avoid undefined behavior, each END_OBJC_ENTRY should be followed + by a return statement which handles the rare case when an Objective-C + exception was thrown. + + As a convenience, the RETURN_NULL_OR_NONE macro can be used for functions + that return a PyObject* */ +#define BEGIN_OBJC_ENTRY \ + @autoreleasepool { @try { + +#define END_OBJC_ENTRY \ + } @catch (NSException *e) { errSetException(e); } } + +#define RETURN_NULL_OR_NONE \ + if (PyErr_Occurred()) { \ + return NULL; \ + } else { \ + Py_RETURN_NONE; \ + } + + +/* Variable for our delegate since it has a +1 reference count. + Not needed under manual reference count, but standard practice + under ARC. */ +static id appDelegate = nil; + /* Keep track of number of windows present Needed to know when to stop the NSApp */ static long FigureWindowCount = 0; @@ -43,6 +74,11 @@ // Global variable to store the original SIGINT handler static PyOS_sighandler_t originalSigintAction = NULL; +// Convert an Objective-C exception into a Python RuntimeError +static void errSetException(NSException *exception) { + PyErr_SetString(PyExc_RuntimeError, [[exception reason] UTF8String]); +} + // Stop the current app's run loop, sending an event to ensure it actually stops static void stopWithEvent() { [NSApp stop: nil]; @@ -73,57 +109,64 @@ static void handleSigint(int signal) { // 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; + @autoreleasepool { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: [NSDate distantPast] + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event) { + break; + } + [NSApp sendEvent:event]; } - [NSApp sendEvent:event]; } } static int wait_for_stdin() { + BEGIN_OBJC_ENTRY + // 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 { + 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); + // 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]; + // Create an NSFileHandle for standard input + NSFileHandle *stdinHandle = [NSFileHandle fileHandleWithStandardInput]; - // Register for data available notifications on standard input - id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification - object: stdinHandle - queue: [NSOperationQueue mainQueue] // Use the main queue - usingBlock: ^(NSNotification *notification) {stopWithEvent();} - ]; + // Register for data available notifications on standard input + 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]; + // Wait in the background for anything that happens to stdin + [stdinHandle waitForDataInBackgroundAndNotify]; - // Run the application's event loop, which will be interrupted on stdin or SIGINT - [NSApp run]; + // 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: notificationID]; + // Remove the input handler as an observer + [[NSNotificationCenter defaultCenter] removeObserver: notificationID]; - // Restore the original SIGINT handler upon exiting the function - PyOS_setsig(SIGINT, originalSigintAction); + // Restore the original SIGINT handler upon exiting the function + PyOS_setsig(SIGINT, originalSigintAction); - return 1; - } + return 1; + + END_OBJC_ENTRY + return 0; } /* ---------------------------- Cocoa classes ---------------------------- */ @@ -225,7 +268,8 @@ static void lazy_init(void) { NSApp = [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - [NSApp setDelegate: [[[MatplotlibAppDelegate alloc] init] autorelease]]; + appDelegate = [[MatplotlibAppDelegate alloc] init]; + [NSApp setDelegate:appDelegate]; // Run our own event loop while waiting for stdin on the Python side // this is needed to keep the application responsive while waiting for input @@ -235,21 +279,26 @@ static void lazy_init(void) { static PyObject* event_loop_is_running(PyObject* self) { + BEGIN_OBJC_ENTRY + if (backend_inited) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* wake_on_fd_write(PyObject* unused, PyObject* args) { + BEGIN_OBJC_ENTRY int fd; if (!PyArg_ParseTuple(args, "i", &fd)) { return NULL; } NSFileHandle* fh = [[NSFileHandle alloc] initWithFileDescriptor: fd]; - [fh waitForDataInBackgroundAndNotify]; - [[NSNotificationCenter defaultCenter] + __block id notificationID = [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleDataAvailableNotification object: fh queue: nil @@ -257,15 +306,21 @@ static void lazy_init(void) { PyGILState_STATE gstate = PyGILState_Ensure(); PyErr_CheckSignals(); PyGILState_Release(gstate); + [fh release]; + [[NSNotificationCenter defaultCenter] removeObserver:notificationID]; }]; - Py_RETURN_NONE; + [fh waitForDataInBackgroundAndNotify]; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* stop(PyObject* self, PyObject* _ /* ignored */) { + BEGIN_OBJC_ENTRY stopWithEvent(); - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static CGFloat _get_device_scale(CGContextRef cr) @@ -343,16 +398,22 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static PyObject* FigureCanvas_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY + lazy_init(); FigureCanvas *self = (FigureCanvas*)type->tp_alloc(type, 0); if (!self) { return NULL; } self->view = [View alloc]; return (PyObject*)self; + + END_OBJC_ENTRY + return NULL; } static int FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY if (!self->view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); return -1; @@ -392,14 +453,18 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Py_XDECREF(super_init); Py_XDECREF(init_res); Py_XDECREF(wh); + + END_OBJC_ENTRY return PyErr_Occurred() ? -1 : 0; } static void FigureCanvas_dealloc(FigureCanvas* self) { + BEGIN_OBJC_ENTRY [self->view setCanvas: NULL]; [self->view release]; + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -413,13 +478,16 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static PyObject* FigureCanvas_update(FigureCanvas* self) { + BEGIN_OBJC_ENTRY [self->view setNeedsDisplay: YES]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE; } static PyObject* FigureCanvas_flush_events(FigureCanvas* self) { + BEGIN_OBJC_ENTRY // 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. @@ -430,12 +498,14 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Py_END_ALLOW_THREADS [self->view displayIfNeeded]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_set_cursor(PyObject* unused, PyObject* args) { + BEGIN_OBJC_ENTRY int i; if (!PyArg_ParseTuple(args, "i", &i)) { return NULL; } switch (i) { @@ -455,12 +525,14 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) case 7: [[NSCursor resizeUpDownCursor] set]; break; default: return NULL; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_set_rubberband(FigureCanvas* self, PyObject *args) { + BEGIN_OBJC_ENTRY View* view = self->view; if (!view) { PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); @@ -477,19 +549,23 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) NSRect rubberband = NSMakeRect(x0 < x1 ? x0 : x1, y0 < y1 ? y0 : y1, abs(x1 - x0), abs(y1 - y0)); [view setRubberband: rubberband]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_remove_rubberband(FigureCanvas* self) { + BEGIN_OBJC_ENTRY [self->view removeRubberband]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas__start_event_loop(FigureCanvas* self, PyObject* args, PyObject* keywords) { + BEGIN_OBJC_ENTRY float timeout = 0.0; static char* kwlist[] = {"timeout", NULL}; @@ -502,23 +578,27 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) NSDate* date = (timeout > 0.0) ? [NSDate dateWithTimeIntervalSinceNow: timeout] : [NSDate distantFuture]; - while (true) - { NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny - untilDate: date - inMode: NSDefaultRunLoopMode - dequeue: YES]; - if (!event || [event type]==NSEventTypeApplicationDefined) { break; } - [NSApp sendEvent: event]; + while (true) { + @autoreleasepool { + NSEvent* event = [NSApp nextEventMatchingMask: NSEventMaskAny + untilDate: date + inMode: NSDefaultRunLoopMode + dequeue: YES]; + if (!event || [event type]==NSEventTypeApplicationDefined) { break; } + [NSApp sendEvent: event]; + } } Py_END_ALLOW_THREADS - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureCanvas_stop_event_loop(FigureCanvas* self) { + BEGIN_OBJC_ENTRY // +[NSEvent otherEventWithType:...] is declared nullable but will not return // nil for these constant, valid arguments; guard defensively anyway. NSEvent* event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined @@ -533,7 +613,8 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) if (event) { [NSApp postEvent: event atStart: true]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyTypeObject FigureCanvasType = { @@ -591,6 +672,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static PyObject* FigureManager_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY if (![NSThread isMainThread]) { PyErr_SetString( PyExc_RuntimeError, @@ -612,11 +694,14 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) self->window = window; ++FigureWindowCount; return (PyObject*)self; + END_OBJC_ENTRY + return NULL; } static int FigureManager_init(FigureManager *self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY PyObject* canvas; if (!PyArg_ParseTuple(args, "O", &canvas)) { return -1; @@ -649,15 +734,18 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Window* window = self->window; [window setDelegate: view]; [window makeFirstResponder: view]; + [window setReleasedWhenClosed:NO]; [[window contentView] addSubview: view]; [view updateDevicePixelRatio: [window backingScaleFactor]]; + END_OBJC_ENTRY return 0; } static PyObject* FigureManager__set_window_mode(FigureManager* self, PyObject* args) { + BEGIN_OBJC_ENTRY const char* window_mode; if (!PyArg_ParseTuple(args, "s", &window_mode) || !self->window) { return NULL; @@ -671,7 +759,8 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) } else { // system settings [self->window setTabbingMode: NSWindowTabbingModeAutomatic]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* @@ -684,28 +773,39 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) static void FigureManager_dealloc(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window close]; + [self->window setDelegate:nil]; + [self->window release]; + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } static PyObject* FigureManager__show(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window makeKeyAndOrderFront: nil]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager__raise(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window orderFrontRegardless]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_destroy(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window close]; + [self->window setDelegate:nil]; + [self->window release]; self->window = NULL; // call super(self, FigureManager).destroy() - it seems we need the @@ -726,11 +826,13 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) } Py_DECREF(result); - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_set_icon(PyObject* null, PyObject* args) { + BEGIN_OBJC_ENTRY PyObject* icon_path; if (!PyArg_ParseTuple(args, "O&", &PyUnicode_FSDecoder, &icon_path)) { return NULL; @@ -740,38 +842,35 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) Py_DECREF(icon_path); return NULL; } - @autoreleasepool { - NSString* ns_icon_path = [NSString stringWithUTF8String: icon_path_ptr]; - Py_DECREF(icon_path); - if (!ns_icon_path) { - PyErr_SetString(PyExc_RuntimeError, "Could not convert to NSString*"); - return NULL; - } - NSImage* image = [[[NSImage alloc] initByReferencingFile: ns_icon_path] autorelease]; - if (!image) { - PyErr_SetString(PyExc_RuntimeError, "Could not create NSImage*"); - return NULL; - } - if (!image.valid) { - PyErr_SetString(PyExc_RuntimeError, "Image is not valid"); - return NULL; - } - @try { - NSApplication* app = [NSApplication sharedApplication]; - app.applicationIconImage = image; - } - @catch (NSException* exception) { - PyErr_SetString(PyExc_RuntimeError, exception.reason.UTF8String); - return NULL; - } + + NSString* ns_icon_path = [NSString stringWithUTF8String: icon_path_ptr]; + Py_DECREF(icon_path); + if (!ns_icon_path) { + PyErr_SetString(PyExc_RuntimeError, "Could not convert to NSString*"); + return NULL; + } + NSImage* image = [[[NSImage alloc] initByReferencingFile: ns_icon_path] autorelease]; + if (!image) { + PyErr_SetString(PyExc_RuntimeError, "Could not create NSImage*"); + return NULL; + } + if (!image.valid) { + PyErr_SetString(PyExc_RuntimeError, "Image is not valid"); + return NULL; } - Py_RETURN_NONE; + + NSApplication* app = [NSApplication sharedApplication]; + app.applicationIconImage = image; + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_set_window_title(FigureManager* self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY const char* title; if (!PyArg_ParseTuple(args, "s", &title)) { return NULL; @@ -780,23 +879,26 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) // not return nil here; the nullable annotation is a false positive. // NOLINTNEXTLINE(clang-analyzer-nullability.NullablePassedToNonnull) [self->window setTitle: [NSString stringWithUTF8String: title]]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_get_window_title(FigureManager* self) { + BEGIN_OBJC_ENTRY NSString* title = [self->window title]; if (title) { return PyUnicode_FromString([title UTF8String]); - } else { - Py_RETURN_NONE; } + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_resize(FigureManager* self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY int width, height; if (!PyArg_ParseTuple(args, "ii", &width, &height)) { return NULL; @@ -809,14 +911,17 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) // 36 comes from hard-coded size of toolbar later in code [window setContentSize: NSMakeSize(width, height + 36.)]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyObject* FigureManager_full_screen_toggle(FigureManager* self) { + BEGIN_OBJC_ENTRY [self->window toggleFullScreen: nil]; - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyTypeObject FigureManagerType = { @@ -938,6 +1043,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } static PyObject* NavigationToolbar2_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY lazy_init(); NavigationToolbar2Handler* handler = [NavigationToolbar2Handler alloc]; if (!handler) { return NULL; } @@ -948,11 +1054,14 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } } self->handler = handler; return (PyObject*)self; + END_OBJC_ENTRY + return NULL; } static int NavigationToolbar2_init(NavigationToolbar2 *self, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY FigureCanvas* canvas; const char* images[7]; const char* tooltips[7]; @@ -1062,14 +1171,17 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } [[window contentView] display]; self->messagebox = messagebox; + END_OBJC_ENTRY return 0; } static void NavigationToolbar2_dealloc(NavigationToolbar2 *self) { + BEGIN_OBJC_ENTRY [self->handler release]; [self->messagebox release]; + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -1082,6 +1194,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } static PyObject* NavigationToolbar2_set_message(NavigationToolbar2 *self, PyObject* args) { + BEGIN_OBJC_ENTRY const char* message; if (!PyArg_ParseTuple(args, "s", &message)) { return NULL; } @@ -1113,7 +1226,8 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } [[messagebox.superview window] disableCursorRects]; } - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static PyTypeObject NavigationToolbar2Type = { @@ -1139,6 +1253,8 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } static PyObject* choose_save_file(PyObject* unused, PyObject* args) { + BEGIN_OBJC_ENTRY + int result; const char* title; const char* directory; @@ -1162,7 +1278,9 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } } return PyUnicode_FromString([filename UTF8String]); } - Py_RETURN_NONE; + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } @implementation Window @@ -1775,17 +1893,26 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* show(PyObject* self) { - [NSApp activateIgnoringOtherApps: YES]; - NSArray *windowsArray = [NSApp windows]; - NSEnumerator *enumerator = [windowsArray objectEnumerator]; - NSWindow *window; - while ((window = [enumerator nextObject])) { - [window orderFront:nil]; + BEGIN_OBJC_ENTRY + + // Iterating over -[NSApp windows] will add the windows to the topmost + // autorelease pool, wrap in @autoreleasepool as -[NSApp run] is long-running. + @autoreleasepool { + [NSApp activateIgnoringOtherApps: YES]; + NSArray *windowsArray = [NSApp windows]; + NSEnumerator *enumerator = [windowsArray objectEnumerator]; + NSWindow *window; + while ((window = [enumerator nextObject])) { + [window orderFront:nil]; + } } + Py_BEGIN_ALLOW_THREADS [NSApp run]; Py_END_ALLOW_THREADS - Py_RETURN_NONE; + + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } typedef struct { @@ -1797,6 +1924,7 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* Timer_new(PyTypeObject* type, PyObject *args, PyObject *kwds) { + BEGIN_OBJC_ENTRY lazy_init(); Timer* self = (Timer*)type->tp_alloc(type, 0); if (!self) { @@ -1804,6 +1932,8 @@ - (void)flagsChanged:(NSEvent *)event } self->timer = NULL; return (PyObject*) self; + END_OBJC_ENTRY + return NULL; } static PyObject* @@ -1816,6 +1946,7 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* Timer__timer_start(Timer* self, PyObject* args) { + BEGIN_OBJC_ENTRY NSTimeInterval interval; PyObject* py_interval = NULL, * py_single = NULL, * py_on_timer = NULL; int single; @@ -1850,11 +1981,8 @@ - (void)flagsChanged:(NSEvent *)event Py_XDECREF(py_interval); Py_XDECREF(py_single); Py_XDECREF(py_on_timer); - if (PyErr_Occurred()) { - return NULL; - } else { - Py_RETURN_NONE; - } + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static void @@ -1869,14 +1997,18 @@ - (void)flagsChanged:(NSEvent *)event static PyObject* Timer__timer_stop(Timer* self) { + BEGIN_OBJC_ENTRY Timer__timer_stop_impl(self); - Py_RETURN_NONE; + END_OBJC_ENTRY + RETURN_NULL_OR_NONE } static void Timer_dealloc(Timer* self) { + BEGIN_OBJC_ENTRY Timer__timer_stop_impl(self); + END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); }