diff --git a/.github/workflows/cibuildwheel.yml b/.github/workflows/cibuildwheel.yml index 36ec0e404f1f..4ab10fdf96b0 100644 --- a/.github/workflows/cibuildwheel.yml +++ b/.github/workflows/cibuildwheel.yml @@ -121,7 +121,7 @@ jobs: CIBW_SKIP: "*-musllinux_aarch64" CIBW_TEST_COMMAND: >- python {package}/ci/check_version_number.py - MACOSX_DEPLOYMENT_TARGET: "10.12" + MACOSX_DEPLOYMENT_TARGET: "10.14" strategy: matrix: include: diff --git a/src/_macosx.m b/src/_macosx.m index 0de0540018a7..b41b89948afa 100755 --- a/src/_macosx.m +++ b/src/_macosx.m @@ -3,18 +3,10 @@ #include #include -/* Proper way to check for the OS X version we are compiling for, from - * https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/cross_development/Using/using.html - - * Renamed symbols cause deprecation warnings, so define macros for the new - * names if we are compiling on an older SDK */ -#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101400 -#define NSButtonTypeMomentaryLight NSMomentaryLightButton -#define NSButtonTypePushOnPushOff NSPushOnPushOffButton -#define NSBezelStyleShadowlessSquare NSShadowlessSquareBezelStyle -#define CGContext graphicsPort -#endif +#if !__has_feature(objc_arc_fields) +#error "The macOS backend requires ARC C struct fields support (objc_arc_fields)." +#endif /* Various NSApplicationDefined event subtypes */ #define STOP_EVENT_LOOP 2 @@ -47,9 +39,7 @@ } -/* Variable for our delegate since it has a +1 reference count. - Not needed under manual reference count, but standard practice - under ARC. */ +/* Variable for our delegate since it needs a +1 reference count. */ static id appDelegate = nil; /* Keep track of number of windows present @@ -175,25 +165,20 @@ - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app; @end @interface Window : NSWindow -{ PyObject* manager; -} -- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager; - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen; - (BOOL)closeButtonPressed; +@property (nonatomic, assign) PyObject* manager; @end @interface View : NSView -{ PyObject* canvas; - NSRect rubberband; +{ NSRect rubberband; @public double device_scale; } -- (void)dealloc; - (void)drawRect:(NSRect)rect; - (void)updateDevicePixelRatio:(double)scale; - (void)windowDidChangeBackingProperties:(NSNotification*)notification; - (void)windowDidResize:(NSNotification*)notification; -- (View*)initWithFrame:(NSRect)rect; -- (void)setCanvas: (PyObject*)newCanvas; +- (instancetype)initWithFrame:(NSRect)rect; - (void)windowWillClose:(NSNotification*)notification; - (BOOL)windowShouldClose:(NSNotification*)notification; - (void)mouseEntered:(NSEvent*)event; @@ -216,6 +201,7 @@ - (void)keyUp:(NSEvent*)event; - (void)scrollWheel:(NSEvent *)event; - (BOOL)acceptsFirstResponder; - (void)flagsChanged:(NSEvent*)event; +@property (nonatomic, assign) PyObject* canvas; @end /* ---------------------------- Python classes ---------------------------- */ @@ -303,10 +289,10 @@ static void lazy_init(void) { object: fh queue: nil usingBlock: ^(NSNotification* note) { + NSFileHandle* strongFileHandle __attribute__((unused)) = fh; PyGILState_STATE gstate = PyGILState_Ensure(); PyErr_CheckSignals(); PyGILState_Release(gstate); - [fh release]; [[NSNotificationCenter defaultCenter] removeObserver:notificationID]; }]; [fh waitForDataInBackgroundAndNotify]; @@ -390,7 +376,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) typedef struct { PyObject_HEAD - View* view; + __strong View* view; } FigureCanvas; static PyTypeObject FigureCanvasType; @@ -402,8 +388,6 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) lazy_init(); FigureCanvas *self = (FigureCanvas*)type->tp_alloc(type, 0); - if (!self) { return NULL; } - self->view = [View alloc]; return (PyObject*)self; END_OBJC_ENTRY @@ -414,10 +398,8 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) FigureCanvas_init(FigureCanvas *self, PyObject *args, PyObject *kwds) { BEGIN_OBJC_ENTRY - if (!self->view) { - PyErr_SetString(PyExc_RuntimeError, "NSView* is NULL"); - return -1; - } + View *view; + NSTrackingArea *trackingArea; PyObject *builtins = NULL, *super_obj = NULL, *super_init = NULL, @@ -436,17 +418,17 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) goto exit; } NSRect rect = NSMakeRect(0.0, 0.0, width, height); - self->view = [self->view initWithFrame: rect]; - self->view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; + view = [[View alloc] initWithFrame: rect]; + view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; int opts = (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect); - NSTrackingArea* area = [[NSTrackingArea alloc] initWithRect: rect - options: opts - owner: self->view - userInfo: nil]; - [self->view addTrackingArea: area]; - [area release]; - [self->view setCanvas: (PyObject*)self]; + trackingArea = [[NSTrackingArea alloc] initWithRect: rect + options: opts + owner: view + userInfo: nil]; + [view addTrackingArea:trackingArea]; + [view setCanvas: (PyObject*)self]; + self->view = view; exit: Py_XDECREF(super_obj); @@ -462,8 +444,9 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) FigureCanvas_dealloc(FigureCanvas* self) { BEGIN_OBJC_ENTRY + NSLog(@"FigureCanvas_dealloc"); [self->view setCanvas: NULL]; - [self->view release]; + self->view = nil; END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -472,7 +455,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) FigureCanvas_repr(FigureCanvas* self) { return PyUnicode_FromFormat("FigureCanvas object %p wrapping NSView %p", - (void*)self, (void*)(self->view)); + (void*)self, (__bridge void*)(self->view)); } static PyObject* @@ -666,7 +649,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) typedef struct { PyObject_HEAD - Window* window; + __strong Window* window; } FigureManager; static PyObject* @@ -684,14 +667,10 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) } lazy_init(); - Window* window = [Window alloc]; - if (!window) { return NULL; } FigureManager *self = (FigureManager*)type->tp_alloc(type, 0); if (!self) { - [window release]; return NULL; } - self->window = window; ++FigureWindowCount; return (PyObject*)self; END_OBJC_ENTRY @@ -723,21 +702,22 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) NSRect rect = NSMakeRect( /* x */ 100, /* y */ 350, width, height); - self->window = [self->window initWithContentRect: rect - styleMask: NSWindowStyleMaskTitled - | NSWindowStyleMaskClosable - | NSWindowStyleMaskResizable - | NSWindowStyleMaskMiniaturizable - backing: NSBackingStoreBuffered - defer: YES - withManager: (PyObject*)self]; - Window* window = self->window; + Window* window = [[Window alloc] initWithContentRect: rect + styleMask: NSWindowStyleMaskTitled + | NSWindowStyleMaskClosable + | NSWindowStyleMaskResizable + | NSWindowStyleMaskMiniaturizable + backing: NSBackingStoreBuffered + defer: YES]; [window setDelegate: view]; + [window setManager: (PyObject*)self]; [window makeFirstResponder: view]; [window setReleasedWhenClosed:NO]; [[window contentView] addSubview: view]; [view updateDevicePixelRatio: [window backingScaleFactor]]; + self->window = window; + END_OBJC_ENTRY return 0; } @@ -767,16 +747,23 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) FigureManager_repr(FigureManager* self) { return PyUnicode_FromFormat("FigureManager object %p wrapping NSWindow %p", - (void*) self, (void*)(self->window)); + (void*) self, (__bridge void*)(self->window)); } static void -FigureManager_dealloc(FigureManager* self) +FigureManager__closeAndClearWindow(FigureManager* self) { - BEGIN_OBJC_ENTRY [self->window close]; [self->window setDelegate:nil]; - [self->window release]; + [self->window setManager:NULL]; + self->window = nil; +} + +static void +FigureManager_dealloc(FigureManager* self) +{ + BEGIN_OBJC_ENTRY + FigureManager__closeAndClearWindow(self); END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -803,10 +790,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) FigureManager_destroy(FigureManager* self) { BEGIN_OBJC_ENTRY - [self->window close]; - [self->window setDelegate:nil]; - [self->window release]; - self->window = NULL; + FigureManager__closeAndClearWindow(self); // call super(self, FigureManager).destroy() - it seems we need the // explicit arguments, and just super() doesn't work in the C API. @@ -849,7 +833,7 @@ bool mpl_check_modifier(bool present, PyObject* list, char const* name) PyErr_SetString(PyExc_RuntimeError, "Could not convert to NSString*"); return NULL; } - NSImage* image = [[[NSImage alloc] initByReferencingFile: ns_icon_path] autorelease]; + NSImage* image = [[NSImage alloc] initByReferencingFile: ns_icon_path]; if (!image) { PyErr_SetString(PyExc_RuntimeError, "Could not create NSImage*"); return NULL; @@ -977,12 +961,7 @@ - (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app { @end @interface NavigationToolbar2Handler : NSObject -{ PyObject* toolbar; - NSButton* panbutton; - NSButton* zoombutton; -} -- (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)toolbar; -- (void)installCallbacks:(SEL[7])actions forButtons:(NSButton*[7])buttons; +- (void)installCallbacks:(SEL[7])actions forButtons:(__strong NSButton*[7])buttons; - (void)home:(id)sender; - (void)back:(id)sender; - (void)forward:(id)sender; @@ -990,54 +969,50 @@ - (void)pan:(id)sender; - (void)zoom:(id)sender; - (void)configure_subplots:(id)sender; - (void)save_figure:(id)sender; +@property (nonatomic, assign) PyObject *toolbar; +@property (nonatomic, readonly) NSButton *panButton; +@property (nonatomic, readonly) NSButton *zoomButton; @end typedef struct { PyObject_HEAD - NSTextView* messagebox; - NavigationToolbar2Handler* handler; + __strong NSTextView* messagebox; + __strong NavigationToolbar2Handler* handler; int height; } NavigationToolbar2; @implementation NavigationToolbar2Handler -- (NavigationToolbar2Handler*)initWithToolbar:(PyObject*)theToolbar -{ - self = [self init]; - if (!self) { return nil; } - toolbar = theToolbar; - return self; -} -- (void)installCallbacks:(SEL[7])actions forButtons:(NSButton*[7])buttons +- (void)installCallbacks:(SEL[7])actions forButtons:(__strong NSButton*[7])buttons { for (int i = 0; i < 7; i++) { SEL action = actions[i]; NSButton* button = buttons[i]; [button setTarget: self]; [button setAction: action]; - if (action == @selector(pan:)) { panbutton = button; } - if (action == @selector(zoom:)) { zoombutton = button; } + if (action == @selector(pan:)) { _panButton = button; } + if (action == @selector(zoom:)) { _zoomButton = button; } } } --(void)home:(id)sender { gil_call_method(toolbar, "home"); } --(void)back:(id)sender { gil_call_method(toolbar, "back"); } --(void)forward:(id)sender { gil_call_method(toolbar, "forward"); } +-(void)home:(id)sender { gil_call_method(_toolbar, "home"); } +-(void)back:(id)sender { gil_call_method(_toolbar, "back"); } +-(void)forward:(id)sender { gil_call_method(_toolbar, "forward"); } -(void)pan:(id)sender { - if ([sender state]) { [zoombutton setState:NO]; } - gil_call_method(toolbar, "pan"); + if ([sender state]) { [_zoomButton setState:NO]; } + gil_call_method(_toolbar, "pan"); } -(void)zoom:(id)sender { - if ([sender state]) { [panbutton setState:NO]; } - gil_call_method(toolbar, "zoom"); + if ([sender state]) { [_panButton setState:NO]; } + gil_call_method(_toolbar, "zoom"); } --(void)configure_subplots:(id)sender { gil_call_method(toolbar, "configure_subplots"); } --(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } +-(void)configure_subplots:(id)sender { gil_call_method(_toolbar, "configure_subplots"); } +-(void)save_figure:(id)sender { gil_call_method(_toolbar, "save_figure"); } @end static PyObject* @@ -1045,14 +1020,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } { BEGIN_OBJC_ENTRY lazy_init(); - NavigationToolbar2Handler* handler = [NavigationToolbar2Handler alloc]; - if (!handler) { return NULL; } NavigationToolbar2 *self = (NavigationToolbar2*)type->tp_alloc(type, 0); - if (!self) { - [handler release]; - return NULL; - } - self->handler = handler; return (PyObject*)self; END_OBJC_ENTRY return NULL; @@ -1144,13 +1112,13 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } [buttons[i] setImagePosition: NSImageOnly]; [buttons[i] setToolTip: tooltip]; [[window contentView] addSubview: buttons[i]]; - [buttons[i] release]; - [image release]; rect.origin.x += rect.size.width + gap; } - self->handler = [self->handler initWithToolbar: (PyObject*)self]; - [self->handler installCallbacks: actions forButtons: buttons]; + NavigationToolbar2Handler *handler; + handler = [[NavigationToolbar2Handler alloc] init]; + [handler setToolbar:(PyObject*)self]; + [handler installCallbacks: actions forButtons: buttons]; NSFont* font = [NSFont systemFontOfSize: 0.0]; // rect.origin.x is now at the far right edge of the buttons @@ -1170,6 +1138,7 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } [[window contentView] addSubview: messagebox]; [[window contentView] display]; + self->handler = handler; self->messagebox = messagebox; END_OBJC_ENTRY return 0; @@ -1179,8 +1148,9 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } NavigationToolbar2_dealloc(NavigationToolbar2 *self) { BEGIN_OBJC_ENTRY - [self->handler release]; - [self->messagebox release]; + [self->handler setToolbar:NULL]; + self->handler = nil; + self->messagebox = nil; END_OBJC_ENTRY Py_TYPE(self)->tp_free((PyObject*)self); } @@ -1284,16 +1254,6 @@ -(void)save_figure:(id)sender { gil_call_method(toolbar, "save_figure"); } } @implementation Window -- (Window*)initWithContentRect:(NSRect)rect styleMask:(unsigned int)mask backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation withManager: (PyObject*)theManager -{ - self = [super initWithContentRect: rect - styleMask: mask - backing: bufferingType - defer: deferCreation]; - manager = theManager; - Py_INCREF(manager); - return self; -} - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen { @@ -1307,7 +1267,7 @@ - (NSRect)constrainFrameRect:(NSRect)rect toScreen:(NSScreen*)screen - (BOOL)closeButtonPressed { - gil_call_method(manager, "_close_button_pressed"); + gil_call_method(_manager, "_close_button_pressed"); return YES; } @@ -1319,33 +1279,19 @@ - (void)close /* This is needed for show(), which should exit from [NSApp run] * after all windows are closed. */ - // For each new window, we have incremented the manager reference, so - // we need to bring that down during close and not just dealloc. - Py_DECREF(manager); } @end @implementation View -- (View*)initWithFrame:(NSRect)rect +- (instancetype)initWithFrame:(NSRect)rect { - self = [super initWithFrame: rect]; - rubberband = NSZeroRect; - device_scale = 1; + if (self = [super initWithFrame: rect]) { + rubberband = NSZeroRect; + device_scale = 1; + } return self; } -- (void)dealloc -{ - FigureCanvas* fc = (FigureCanvas*)canvas; - if (fc) { fc->view = NULL; } - [super dealloc]; -} - -- (void)setCanvas: (PyObject*)newCanvas -{ - canvas = newCanvas; -} - static void _buffer_release(void* info, const void* data, size_t size) { PyGILState_STATE gstate = PyGILState_Ensure(); PyBuffer_Release((Py_buffer *)info); @@ -1428,7 +1374,7 @@ -(void)drawRect:(NSRect)rect CGContextRef cr = [[NSGraphicsContext currentContext] CGContext]; - if (!(renderer = PyObject_CallMethod(canvas, "get_renderer", "")) + if (!(renderer = PyObject_CallMethod(_canvas, "get_renderer", "")) || !(renderer_buffer = PyObject_CallMethod(renderer, "buffer_rgba", ""))) { PyErr_Print(); goto exit; @@ -1465,7 +1411,7 @@ - (void)updateDevicePixelRatio:(double)scale PyGILState_STATE gstate = PyGILState_Ensure(); device_scale = scale; - if (!(change = PyObject_CallMethod(canvas, "_set_device_pixel_ratio", "d", device_scale))) { + if (!(change = PyObject_CallMethod(_canvas, "_set_device_pixel_ratio", "d", device_scale))) { PyErr_Print(); goto exit; } @@ -1473,8 +1419,8 @@ - (void)updateDevicePixelRatio:(double)scale // Notify that there was a resize_event that took place process_event( "ResizeEvent", "{s:s, s:O}", - "name", "resize_event", "canvas", canvas); - gil_call_method(canvas, "draw_idle"); + "name", "resize_event", "canvas", _canvas); + gil_call_method(_canvas, "draw_idle"); [self setNeedsDisplay: YES]; } @@ -1504,7 +1450,7 @@ - (void)windowDidResize: (NSNotification*)notification PyGILState_STATE gstate = PyGILState_Ensure(); PyObject* result = PyObject_CallMethod( - canvas, "resize", "ii", width, height); + _canvas, "resize", "ii", width, height); if (result) Py_DECREF(result); else @@ -1517,7 +1463,7 @@ - (void)windowWillClose:(NSNotification*)notification { process_event( "CloseEvent", "{s:s, s:O}", - "name", "close_event", "canvas", canvas); + "name", "close_event", "canvas", _canvas); } - (BOOL)windowShouldClose:(NSNotification*)notification @@ -1554,7 +1500,7 @@ - (void)mouseEntered:(NSEvent *)event y = location.y * device_scale; process_event( "LocationEvent", "{s:s, s:O, s:i, s:i, s:N}", - "name", "figure_enter_event", "canvas", canvas, "x", x, "y", y, + "name", "figure_enter_event", "canvas", _canvas, "x", x, "y", y, "modifiers", mpl_modifiers(event)); } @@ -1567,7 +1513,7 @@ - (void)mouseExited:(NSEvent *)event y = location.y * device_scale; process_event( "LocationEvent", "{s:s, s:O, s:i, s:i, s:N}", - "name", "figure_leave_event", "canvas", canvas, "x", x, "y", y, + "name", "figure_leave_event", "canvas", _canvas, "x", x, "y", y, "modifiers", mpl_modifiers(event)); } @@ -1608,7 +1554,7 @@ - (void)mouseDown:(NSEvent *)event } process_event( "MouseEvent", "{s:s, s:O, s:i, s:i, s:i, s:i, s:N}", - "name", "button_press_event", "canvas", canvas, "x", x, "y", y, + "name", "button_press_event", "canvas", _canvas, "x", x, "y", y, "button", button, "dblclick", dblclick, "modifiers", mpl_modifiers(event)); } @@ -1633,7 +1579,7 @@ - (void)mouseUp:(NSEvent *)event } process_event( "MouseEvent", "{s:s, s:O, s:i, s:i, s:i, s:N}", - "name", "button_release_event", "canvas", canvas, "x", x, "y", y, + "name", "button_release_event", "canvas", _canvas, "x", x, "y", y, "button", button, "modifiers", mpl_modifiers(event)); } @@ -1646,7 +1592,7 @@ - (void)mouseMoved:(NSEvent *)event y = location.y * device_scale; process_event( "MouseEvent", "{s:s, s:O, s:i, s:i, s:N, s:N}", - "name", "motion_notify_event", "canvas", canvas, "x", x, "y", y, + "name", "motion_notify_event", "canvas", _canvas, "x", x, "y", y, "buttons", mpl_buttons(), "modifiers", mpl_modifiers(event)); } @@ -1659,7 +1605,7 @@ - (void)mouseDragged:(NSEvent *)event y = location.y * device_scale; process_event( "MouseEvent", "{s:s, s:O, s:i, s:i, s:N, s:N}", - "name", "motion_notify_event", "canvas", canvas, "x", x, "y", y, + "name", "motion_notify_event", "canvas", _canvas, "x", x, "y", y, "buttons", mpl_buttons(), "modifiers", mpl_modifiers(event)); } @@ -1786,11 +1732,11 @@ - (void)keyDown:(NSEvent*)event if (s) { process_event( "KeyEvent", "{s:s, s:O, s:s, s:i, s:i}", - "name", "key_press_event", "canvas", canvas, "key", s, "x", x, "y", y); + "name", "key_press_event", "canvas", _canvas, "key", s, "x", x, "y", y); } else { process_event( "KeyEvent", "{s:s, s:O, s:O, s:i, s:i}", - "name", "key_press_event", "canvas", canvas, "key", Py_None, "x", x, "y", y); + "name", "key_press_event", "canvas", _canvas, "key", Py_None, "x", x, "y", y); } } @@ -1804,11 +1750,11 @@ - (void)keyUp:(NSEvent*)event if (s) { process_event( "KeyEvent", "{s:s, s:O, s:s, s:i, s:i}", - "name", "key_release_event", "canvas", canvas, "key", s, "x", x, "y", y); + "name", "key_release_event", "canvas", _canvas, "key", s, "x", x, "y", y); } else { process_event( "KeyEvent", "{s:s, s:O, s:O, s:i, s:i}", - "name", "key_release_event", "canvas", canvas, "key", Py_None, "x", x, "y", y); + "name", "key_release_event", "canvas", _canvas, "key", Py_None, "x", x, "y", y); } } @@ -1825,7 +1771,7 @@ - (void)scrollWheel:(NSEvent*)event int y = (int)round(point.y * device_scale - 1); process_event( "MouseEvent", "{s:s, s:O, s:i, s:i, s:i, s:N}", - "name", "scroll_event", "canvas", canvas, + "name", "scroll_event", "canvas", _canvas, "x", x, "y", y, "step", step, "modifiers", mpl_modifiers(event)); } @@ -1917,8 +1863,8 @@ - (void)flagsChanged:(NSEvent *)event typedef struct { PyObject_HEAD - NSTimer* timer; - + __strong NSTimer* timer; + BOOL shouldInvalidate; } Timer; static PyObject* @@ -1927,10 +1873,6 @@ - (void)flagsChanged:(NSEvent *)event BEGIN_OBJC_ENTRY lazy_init(); Timer* self = (Timer*)type->tp_alloc(type, 0); - if (!self) { - return NULL; - } - self->timer = NULL; return (PyObject*) self; END_OBJC_ENTRY return NULL; @@ -1940,13 +1882,24 @@ - (void)flagsChanged:(NSEvent *)event Timer_repr(Timer* self) { return PyUnicode_FromFormat("Timer object %p wrapping NSTimer %p", - (void*) self, (void*)(self->timer)); + (void*) self, (__bridge void*)(self->timer)); +} + +static void +Timer__timer_stop_impl(Timer* self) +{ + if (self->shouldInvalidate) { + [self->timer invalidate]; + self->shouldInvalidate = NO; + } + self->timer = nil; } static PyObject* Timer__timer_start(Timer* self, PyObject* args) { BEGIN_OBJC_ENTRY + NSTimer *timer; NSTimeInterval interval; PyObject* py_interval = NULL, * py_single = NULL, * py_on_timer = NULL; int single; @@ -1962,20 +1915,27 @@ - (void)flagsChanged:(NSEvent *)event goto exit; } + // Stop any previous timers if start() was called multiple times + Timer__timer_stop_impl(self); + // hold a reference to the timer so we can invalidate/stop it later - self->timer = [NSTimer timerWithTimeInterval: interval - repeats: !single - block: ^(NSTimer *timer) { + timer = [NSTimer timerWithTimeInterval: interval + repeats: !single + block: ^(NSTimer *timer) { gil_call_method((PyObject*)self, "_on_timer"); if (single) { // A single-shot timer will be automatically invalidated when it fires, so // we shouldn't do it ourselves when the object is deleted. - self->timer = NULL; + self->shouldInvalidate = NO; } }]; + // Schedule the timer on the main run loop which is needed // when updating the UI from a background thread - [[NSRunLoop mainRunLoop] addTimer: self->timer forMode: NSRunLoopCommonModes]; + [[NSRunLoop mainRunLoop] addTimer: timer forMode: NSRunLoopCommonModes]; + + self->timer = timer; + self->shouldInvalidate = YES; exit: Py_XDECREF(py_interval); @@ -1985,15 +1945,6 @@ - (void)flagsChanged:(NSEvent *)event RETURN_NULL_OR_NONE } -static void -Timer__timer_stop_impl(Timer* self) -{ - if (self->timer) { - [self->timer invalidate]; - self->timer = NULL; - } -} - static PyObject* Timer__timer_stop(Timer* self) { diff --git a/src/meson.build b/src/meson.build index 8b52bf739c03..9465c54e9f18 100644 --- a/src/meson.build +++ b/src/meson.build @@ -145,6 +145,7 @@ if get_option('macosx') and host_machine.system() == 'darwin' ), dependencies: dependency('appleframeworks', modules: 'Cocoa'), override_options: ['werror=true'], + objc_args: ['-fobjc-arc'], install: true, ) endif