Frage

I'm trying to send signals from my pyobjc gui (a menu in the osx status bar) to the main process of my app. Specifically, I'm running the gui wrapped in a class and this inside a process, and I'm trying to send messages from the gui to the main process via a pipe.

When I use a simple method to put data into the pipe, my code works. The message gets passed to the main process, yielding main process... recv(): foo When I start the gui in a subprocess and attempt to put data into the pipe, say when I click on the menu option 'start', nothing happens. The main process line never gets printed, and as far as I can tell the main process is blocked.

I'm assuming this has something to do with the event loop in pyobjc. What can I do to make this work? How can I run the pyobjc code as a subprocess?


main.py

import sys
from multiprocessing import Process, Pipe
from userinterface import OSXstatusbaritem

def f2(pipe):
    print "starting subprocess f2"
    print pipe.send("foo")
    pipe.close()

def main():
    pipeUI, pipeServer = Pipe()

    # p = Process(target=f2, args=(pipeUI,)) # <---------------------- This works
    p = Process(target=OSXstatusbaritem.start(pipeUI), args=()) # <----This doesn't

    p.start()
    print "main process... recv():", pipeServer.recv()
    p.join()

if __name__ == "__main__": sys.exit(main())

userinterface.py

import objc, re, os
from Foundation import *
from AppKit import *
from PyObjCTools import AppHelper
from multiprocessing import Pipe

status_images = {'idle':'./ghost.png'}

class OSXstatusbaritem(NSObject):
    images = {}
    statusbar = None
    state = 'idle'

    @classmethod
    def start(self, pipe):
        self.pipe = pipe
        self.start_time = NSDate.date()
        app = NSApplication.sharedApplication()
        delegate = self.alloc().init()
        app.setDelegate_(delegate)
        AppHelper.runEventLoop()

    def applicationDidFinishLaunching_(self, notification):
        statusbar = NSStatusBar.systemStatusBar()
        # Create the statusbar item
        self.statusitem = statusbar.statusItemWithLength_(NSVariableStatusItemLength)
        # Load all images
        for i in status_images.keys():
            self.images[i] = NSImage.alloc().initByReferencingFile_(status_images[i])
        # Set initial image
        self.statusitem.setImage_(self.images['idle'])
        # self.statusitem.setAlternateImage_(self.images['highlight'])
        # Let it highlight upon clicking
        self.statusitem.setHighlightMode_(1)
        # Set a tooltip
        self.statusitem.setToolTip_('Sample app')

        # Build a very simple menu
        self.menu = NSMenu.alloc().init()
        # Start and stop service
        menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Start Service', 'startService:', '')
        self.menu.addItem_(menuitem)
        menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Stop Service', 'stopService:', '')
        self.menu.addItem_(menuitem)
        # Add a separator
        menuitem = NSMenuItem.separatorItem()
        self.menu.addItem_(menuitem)
        # Terminate event
        menuitem = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '')
        self.menu.addItem_(menuitem)
        # Bind it to the status item
        self.statusitem.setMenu_(self.menu)

        # Get the timer going
        self.timer = NSTimer.alloc().initWithFireDate_interval_target_selector_userInfo_repeats_(self.start_time, 5.0, self, 'tick:', None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(self.timer, NSDefaultRunLoopMode)
        self.timer.fire()

    def tick_(self, notification):
        print self.state

    def startService_(self, notification):
        self.pipe.send(["foobar", None])
        print "starting service"

    def stopService_(self, notification):

        print "stopping service"
War es hilfreich?

Lösung

Your code creates the GUI objects in the main process (main.py) then uses that object in a subprocess created using fork. This in not supported by most of Apple's frameworks.

Furthermore the call to OSXstatusbaritem.start creates and runs the eventloop in the main process.

You might have more success by creating the GUI object in the child process, but even that is not guaranteed to work (if you're unlucky the GUI framework has already initialised and causes a crash when using it in the child process):

p = Process(target=OSXstatusbaritem.start, args=(pipeUI,))

The safest way to start the status bar item process it to use subprocess, to avoid creating a new process without calling execv(2). There was some talk on Python's tracker to add an option to the multiprocessing module to start new processes using fork+exec but that hasn't led to a commit yet.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top