Skip to content

can't stop macosx mainloop #10002

Closed

Description

@minrk

Bug report

Bug summary

I can't figure out how to stop the cocoa mainloop once it's going with the macosx backend. This could be a missing API, or just one that I don't know about.

I suspect an API that exposes sending [NSApp stop] is all that's needed, but I'm not sure.

Code for reproduction

import matplotlib
matplotlib.use('macosx')
import matplotlib.pyplot as plt
from matplotlib.backends.backend_macosx import TimerMac, show

def stop_mainloop():
    print("I don't know how to quit you")
    t.stop()

t = TimerMac(2000)
t.add_callback(stop_mainloop)
t.start()

show.mainloop()

Actual outcome

Loop runs forever because I don't know how to stop it.

Matplotlib version

  • Operating system: macOS 10.13.2
  • Matplotlib version: 2.0.2 (conda-forge)
  • Matplotlib backend (print(matplotlib.get_backend())): MacOSX
  • Python version: 3.6.1

Activity

  1. added this to the milestone on Dec 14, 2017
  2. tacaswell commented on Dec 14, 2017

    @tacaswell
    Member

    can you try with 2.1? There was some recent work on this.

  3. jklymak commented on Dec 14, 2017

    @jklymak
    Member

    Yeah on master this gives:

    Traceback (most recent call last):
      File "testLoop.py", line 14, in <module>
        show.mainloop()
    AttributeError: 'function' object has no attribute 'mainloop'
    (matplotlibdev)
    
  4. jklymak commented on Dec 14, 2017

    @jklymak
    Member

    #8773 (@anntzer) changed this behaviour so that show no longer has a mainloop. I'm not sure if this was an oversight or on purpose. I don't understand how events work in the backends well enough to guess about how to get the event loop rolling in master.

  5. anntzer commented on Dec 14, 2017

    @anntzer
    Contributor

    Show().mainloop() should still work (the Show class has no instance attributes and effectively serves as a namespace). This should also work with old versions of matplotlib.

    I could restore a mainloop attribute on show itself if needed; I just didn't realize that there would be demand for it (but frankly in that case I'd rather just make mainloop a public function, instead of keeping it half-hidden-as-an-attribute-but-public).

    In the case of the given example, you can also use show() which does exactly the same thing (it first shows the pyplot windows, but there's none of them in the example).

  6. jklymak commented on Dec 14, 2017

    @jklymak
    Member

    OK, with Show().mainloop() the callback works, and the GUI window comes up in the toolbar. It can be quit by right-clicking the icon and selecting quit. The terminal window is non responsive to ^C. No idea if thats all desirable or not, but at least there is a graceful way to kill the loop.

  7. minrk commented on Dec 15, 2017

    @minrk
    ContributorAuthor

    No idea if thats all desirable or not, but at least there is a graceful way to kill the loop.

    There's still not a programmatic way to do it, right? That's what I need right now.

    I figured it out with ctypes, cribbing heavily from some forgotten work in IPython.

    I need a way to call [NSApp stop] followed by sending a wake event to trigger the teardown. Here's a script that shows everything working:

    import ctypes
    import ctypes.util
    import time
    
    import matplotlib
    matplotlib.use('macosx')
    
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_macosx import TimerMac, Show
    
    
    objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc'))
    
    void_p = ctypes.c_void_p
    
    objc.objc_getClass.restype = void_p
    objc.sel_registerName.restype = void_p
    objc.objc_msgSend.restype = void_p
    objc.objc_msgSend.argtypes = [void_p, void_p]
    
    msg = objc.objc_msgSend
    
    def _utf8(s):
        if not isinstance(s, bytes):
            s = s.encode('utf8')
        return s
    
    def n(name):
        """create a selector name (for methods)"""
        return objc.sel_registerName(_utf8(name))
    
    def C(classname):
        """get an ObjC Class by name"""
        return objc.objc_getClass(_utf8(classname))
    
    def stop_mainloop():
        time.sleep(2)
        print("Do I know how to quit you?")
        NSApp = msg(C('NSApplication'), n('sharedApplication'))
        running = msg(NSApp, n('isRunning'))
        if not running:
            print("not running")
            return
        r = msg(NSApp, n('stop:'), void_p(NSApp))
        # need to send an event to wake the eventloop to handle stop
        event = msg(C('NSEvent'),
            n('otherEventWithType:location:modifierFlags:'
              'timestamp:windowNumber:context:subtype:data1:data2:'),
            15, # Type
            0, # location
            0, # flags
            0, # timestamp
            0, # window
            None, # context
            0, # subtype
            0, # data1
            0, # data2
        )
        msg(NSApp, n(b'postEvent:atStart:'), void_p(event), True)
    
    from threading import Thread
    Thread(target=stop_mainloop).start()
    
    Show().mainloop()
    print("I do!")
  8. minrk commented on Dec 15, 2017

    @minrk
    ContributorAuthor

    If there were a stop_mainloop function somewhere, it should be a pretty simple two calls: [NSApp stop] and sending the wake event.

  9. removed their assignment
    on Nov 7, 2018
  10. github-actions commented on Apr 28, 2023

    @github-actions

    This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

  11. oscargus commented on May 28, 2023

    @oscargus
    Member

    Reopening this since there is a PR proposed that solves it.

  12. modified the milestones: future releases, v3.8.0 on Aug 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions