Question

I am creating a little script which check the number of mail in my gmail account and print them in the status bar. The function gmail() returns the number of new emails. I have few questions, but first this is the code I wrote so far (clearly I am a novice):

class MyApplicationAppDelegate(NSObject):

var = 1

def applicationDidFinishLaunching_(self, sender):
    NSLog("Application did finish launching.")

    global ngmail

    self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)

    while var == 1 :  
        ngmail2 = gmail();
        if  ngmail2 !=ngmail:
            self.statusItem.setTitle_("loading")
            self.statusItem.setTitle_(ngmail2)
            ngmail = ngmail2
        time.sleep(6)

1) Why do I need the line "self.statusItem.setTitle_("loading")" ? without that line it wouldn't update itself. I really do not know why.

2) it runs as it should, but whenever I get close to the number in the status bar, the spinning wheel appear.
I guess the reason is because I am using while, and instead I should be using something like nsrunloop or something like that. Can anyone advice on this?

3) If I put my mac to sleep and I wake it up, the script stops working. Any solution? maybe this is related to question 2) above.

Thanks!

Était-ce utile?

La solution

All of your problems come from the fact that you're blocking the main thread.

In Cocoa, or almost any other GUI framework, the main thread runs a loop that waits for the next event, calls an event handler, and repeats until quit.

Your event handler, applicationDidFinishLaunching_, never returns. This means Cocoa can never handle the next event. Eventually, the OS will notice that you're not responding and put up the beachball.

With Cocoa, sometimes it sneaks in some other events each time you give it a chance, like on the setTitle_ calls, and there are some things the OS can fake even if you're not responding, like keeping the window redrawing, so it isn't always obvious that your app is not responsive. But that doesn't mean you don't need to solve the problem.

There are a number ways to do this, but is the easiest is probably to use a background thread. Then, applicationDidFinishLaunching_ can kick off the background thread and then return immediately, allowing the main thread to get back to its job handling events.

The only tricky bit is that code running on background threads can't make calls to UI objects. So, what do you do about that?

That's what performSelectorOnMainThread_withObject_waitUntilDone_ is for.


Here's an example:

class MyApplicationAppDelegate(NSObject):

    var = 1

    def background_work(self):
        global ngmail

        while var == 1 :  
            ngmail2 = gmail();
            if  ngmail2 !=ngmail:
                self.statusItem.setTitle_("loading")
                self.statusItem.performSelectorOnMainThread_withObject_waitUntilDone_('setTitle:', ngmail2, False)
            time.sleep(6)

    def applicationDidFinishLaunching_(self, sender):
        NSLog("Application did finish launching.")
        self.statusItem = NSStatusBar.systemStatusBar().statusItemWithLength_(NSVariableStatusItemLength)
        self.background_worker = threading.Thread(target=self.background_work)
        self.background_worker.start()    

The only tricky bit is that you have to use the ObjC name for the selector (setTitle:), not the Python name (setTitle_).


However, your code has another subtle bug: var isn't actually synchronized, so it's possible for you to change its value in the main thread, without the background thread ever noticing.

On top of that, doing a sleep(6) means that it will take up to 6 seconds to quit your app, because the background thread won't get to the code that checks var until it finishes sleeping.

You can fix both of these by using a Condition.

class MyApplicationAppDelegate(NSObject):

    var = 1
    condition = threading.Condition()

    def background_work(self):
        global ngmail

        with condition:
            while var == 1:
                ngmail2 = gmail();
                if ngmail2 != ngmail:
                    self.statusItem.performSelectorOnMainThread_withObject_waitUntilDone_('setTitle:', ngmail2, False)
                condition.wait(6)

    @classmethod
    def shutdown_background_threads(cls):
        with condition:
            var = 0
            condition.notify_all()

(I assume you used a class attribute for var instead of an instance attribute on purpose, so I likewise made the condition a class attribute and the shutdown method a class method.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top