273 lines
8.9 KiB
Python
273 lines
8.9 KiB
Python
|
"""
|
||
|
sys.argv emulation
|
||
|
|
||
|
This module starts a basic event loop to collect file- and url-open AppleEvents. Those get
|
||
|
converted to strings and stuffed into sys.argv. When that is done we continue starting
|
||
|
the application.
|
||
|
|
||
|
This is a workaround to convert scripts that expect filenames on the command-line to work
|
||
|
in a GUI environment. GUI applications should not use this feature.
|
||
|
|
||
|
NOTE: This module uses ctypes and not the Carbon modules in the stdlib because the latter
|
||
|
don't work in 64-bit mode and are also not available with python 3.x.
|
||
|
"""
|
||
|
|
||
|
import sys
|
||
|
import os
|
||
|
import time
|
||
|
|
||
|
import ctypes
|
||
|
import struct
|
||
|
|
||
|
class AEDesc (ctypes.Structure):
|
||
|
_fields_ = [
|
||
|
('descKey', ctypes.c_int),
|
||
|
('descContent', ctypes.c_void_p),
|
||
|
]
|
||
|
|
||
|
class EventTypeSpec (ctypes.Structure):
|
||
|
_fields_ = [
|
||
|
('eventClass', ctypes.c_int),
|
||
|
('eventKind', ctypes.c_uint),
|
||
|
]
|
||
|
|
||
|
def _ctypes_setup():
|
||
|
carbon = ctypes.CDLL('/System/Library/Carbon.framework/Carbon')
|
||
|
|
||
|
timer_func = ctypes.CFUNCTYPE(
|
||
|
None, ctypes.c_void_p, ctypes.c_long)
|
||
|
|
||
|
ae_callback = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p,
|
||
|
ctypes.c_void_p, ctypes.c_void_p)
|
||
|
carbon.AEInstallEventHandler.argtypes = [
|
||
|
ctypes.c_int, ctypes.c_int, ae_callback,
|
||
|
ctypes.c_void_p, ctypes.c_char ]
|
||
|
carbon.AERemoveEventHandler.argtypes = [
|
||
|
ctypes.c_int, ctypes.c_int, ae_callback,
|
||
|
ctypes.c_char ]
|
||
|
|
||
|
carbon.AEProcessEvent.restype = ctypes.c_int
|
||
|
carbon.AEProcessEvent.argtypes = [ctypes.c_void_p]
|
||
|
|
||
|
|
||
|
carbon.ReceiveNextEvent.restype = ctypes.c_int
|
||
|
carbon.ReceiveNextEvent.argtypes = [
|
||
|
ctypes.c_long, ctypes.POINTER(EventTypeSpec),
|
||
|
ctypes.c_double, ctypes.c_char,
|
||
|
ctypes.POINTER(ctypes.c_void_p)
|
||
|
]
|
||
|
|
||
|
|
||
|
carbon.AEGetParamDesc.restype = ctypes.c_int
|
||
|
carbon.AEGetParamDesc.argtypes = [
|
||
|
ctypes.c_void_p, ctypes.c_int, ctypes.c_int,
|
||
|
ctypes.POINTER(AEDesc)]
|
||
|
|
||
|
carbon.AECountItems.restype = ctypes.c_int
|
||
|
carbon.AECountItems.argtypes = [ ctypes.POINTER(AEDesc),
|
||
|
ctypes.POINTER(ctypes.c_long) ]
|
||
|
|
||
|
carbon.AEGetNthDesc.restype = ctypes.c_int
|
||
|
carbon.AEGetNthDesc.argtypes = [
|
||
|
ctypes.c_void_p, ctypes.c_long, ctypes.c_int,
|
||
|
ctypes.c_void_p, ctypes.c_void_p ]
|
||
|
|
||
|
carbon.AEGetDescDataSize.restype = ctypes.c_int
|
||
|
carbon.AEGetDescDataSize.argtypes = [ ctypes.POINTER(AEDesc) ]
|
||
|
|
||
|
carbon.AEGetDescData.restype = ctypes.c_int
|
||
|
carbon.AEGetDescData.argtypes = [
|
||
|
ctypes.POINTER(AEDesc),
|
||
|
ctypes.c_void_p,
|
||
|
ctypes.c_int,
|
||
|
]
|
||
|
|
||
|
|
||
|
carbon.FSRefMakePath.restype = ctypes.c_int
|
||
|
carbon.FSRefMakePath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint]
|
||
|
|
||
|
return carbon
|
||
|
|
||
|
def _run_argvemulator(timeout = 60):
|
||
|
|
||
|
# Configure ctypes
|
||
|
carbon = _ctypes_setup()
|
||
|
|
||
|
# Is the emulator running?
|
||
|
running = [True]
|
||
|
|
||
|
timeout = [timeout]
|
||
|
|
||
|
# Configure AppleEvent handlers
|
||
|
ae_callback = carbon.AEInstallEventHandler.argtypes[2]
|
||
|
|
||
|
kAEInternetSuite, = struct.unpack('>i', b'GURL')
|
||
|
kAEISGetURL, = struct.unpack('>i', b'GURL')
|
||
|
kCoreEventClass, = struct.unpack('>i', b'aevt')
|
||
|
kAEOpenApplication, = struct.unpack('>i', b'oapp')
|
||
|
kAEOpenDocuments, = struct.unpack('>i', b'odoc')
|
||
|
keyDirectObject, = struct.unpack('>i', b'----')
|
||
|
typeAEList, = struct.unpack('>i', b'list')
|
||
|
typeChar, = struct.unpack('>i', b'TEXT')
|
||
|
typeFSRef, = struct.unpack('>i', b'fsrf')
|
||
|
FALSE = b'\0'
|
||
|
TRUE = b'\1'
|
||
|
eventLoopTimedOutErr = -9875
|
||
|
|
||
|
kEventClassAppleEvent, = struct.unpack('>i', b'eppc')
|
||
|
kEventAppleEvent = 1
|
||
|
|
||
|
|
||
|
@ae_callback
|
||
|
def open_app_handler(message, reply, refcon):
|
||
|
# Got a kAEOpenApplication event, which means we can
|
||
|
# start up. On some OSX versions this event is even
|
||
|
# sent when an kAEOpenDocuments or kAEOpenURLs event
|
||
|
# is sent later on.
|
||
|
#
|
||
|
# Therefore don't set running to false, but reduce the
|
||
|
# timeout to at most two seconds beyond the current time.
|
||
|
timeout[0] = min(timeout[0], time.time() - start + 2)
|
||
|
#running[0] = False
|
||
|
return 0
|
||
|
|
||
|
carbon.AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
|
||
|
open_app_handler, 0, FALSE)
|
||
|
|
||
|
@ae_callback
|
||
|
def open_file_handler(message, reply, refcon):
|
||
|
listdesc = AEDesc()
|
||
|
sts = carbon.AEGetParamDesc(message, keyDirectObject, typeAEList,
|
||
|
ctypes.byref(listdesc))
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot unpack open document event")
|
||
|
running[0] = False
|
||
|
return
|
||
|
|
||
|
item_count = ctypes.c_long()
|
||
|
sts = carbon.AECountItems(ctypes.byref(listdesc), ctypes.byref(item_count))
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot unpack open document event")
|
||
|
running[0] = False
|
||
|
return
|
||
|
|
||
|
desc = AEDesc()
|
||
|
for i in range(item_count.value):
|
||
|
sts = carbon.AEGetNthDesc(ctypes.byref(listdesc), i+1, typeFSRef, 0, ctypes.byref(desc))
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot unpack open document event")
|
||
|
running[0] = False
|
||
|
return
|
||
|
|
||
|
sz = carbon.AEGetDescDataSize(ctypes.byref(desc))
|
||
|
buf = ctypes.create_string_buffer(sz)
|
||
|
sts = carbon.AEGetDescData(ctypes.byref(desc), buf, sz)
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot extract open document event")
|
||
|
continue
|
||
|
|
||
|
fsref = buf
|
||
|
|
||
|
buf = ctypes.create_string_buffer(1024)
|
||
|
sts = carbon.FSRefMakePath(ctypes.byref(fsref), buf, 1023)
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot extract open document event")
|
||
|
continue
|
||
|
|
||
|
if sys.version_info[0] > 2:
|
||
|
sys.argv.append(buf.value.decode('utf-8'))
|
||
|
else:
|
||
|
sys.argv.append(buf.value)
|
||
|
|
||
|
running[0] = False
|
||
|
return 0
|
||
|
|
||
|
carbon.AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
|
||
|
open_file_handler, 0, FALSE)
|
||
|
|
||
|
@ae_callback
|
||
|
def open_url_handler(message, reply, refcon):
|
||
|
listdesc = AEDesc()
|
||
|
ok = carbon.AEGetParamDesc(message, keyDirectObject, typeAEList,
|
||
|
ctypes.byref(listdesc))
|
||
|
if ok != 0:
|
||
|
print("argvemulator warning: cannot unpack open document event")
|
||
|
running[0] = False
|
||
|
return
|
||
|
|
||
|
item_count = ctypes.c_long()
|
||
|
sts = carbon.AECountItems(ctypes.byref(listdesc), ctypes.byref(item_count))
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot unpack open url event")
|
||
|
running[0] = False
|
||
|
return
|
||
|
|
||
|
desc = AEDesc()
|
||
|
for i in range(item_count.value):
|
||
|
sts = carbon.AEGetNthDesc(ctypes.byref(listdesc), i+1, typeChar, 0, ctypes.byref(desc))
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot unpack open URL event")
|
||
|
running[0] = False
|
||
|
return
|
||
|
|
||
|
sz = carbon.AEGetDescDataSize(ctypes.byref(desc))
|
||
|
buf = ctypes.create_string_buffer(sz)
|
||
|
sts = carbon.AEGetDescData(ctypes.byref(desc), buf, sz)
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: cannot extract open URL event")
|
||
|
|
||
|
else:
|
||
|
if sys.version_info[0] > 2:
|
||
|
sys.argv.append(buf.value.decode('utf-8'))
|
||
|
else:
|
||
|
sys.argv.append(buf.value)
|
||
|
|
||
|
running[0] = False
|
||
|
return 0
|
||
|
|
||
|
carbon.AEInstallEventHandler(kAEInternetSuite, kAEISGetURL,
|
||
|
open_url_handler, 0, FALSE)
|
||
|
|
||
|
# Remove the funny -psn_xxx_xxx argument
|
||
|
if len(sys.argv) > 1 and sys.argv[1].startswith('-psn_'):
|
||
|
del sys.argv[1]
|
||
|
|
||
|
start = time.time()
|
||
|
now = time.time()
|
||
|
eventType = EventTypeSpec()
|
||
|
eventType.eventClass = kEventClassAppleEvent
|
||
|
eventType.eventKind = kEventAppleEvent
|
||
|
|
||
|
while running[0] and now - start < timeout[0]:
|
||
|
event = ctypes.c_void_p()
|
||
|
|
||
|
sts = carbon.ReceiveNextEvent(1, ctypes.byref(eventType),
|
||
|
start + timeout[0] - now, TRUE, ctypes.byref(event))
|
||
|
|
||
|
if sts == eventLoopTimedOutErr:
|
||
|
break
|
||
|
|
||
|
elif sts != 0:
|
||
|
print("argvemulator warning: fetching events failed")
|
||
|
break
|
||
|
|
||
|
sts = carbon.AEProcessEvent(event)
|
||
|
if sts != 0:
|
||
|
print("argvemulator warning: processing events failed")
|
||
|
break
|
||
|
|
||
|
|
||
|
carbon.AERemoveEventHandler(kCoreEventClass, kAEOpenApplication,
|
||
|
open_app_handler, FALSE)
|
||
|
carbon.AERemoveEventHandler(kCoreEventClass, kAEOpenDocuments,
|
||
|
open_file_handler, FALSE)
|
||
|
carbon.AERemoveEventHandler(kAEInternetSuite, kAEISGetURL,
|
||
|
open_url_handler, FALSE)
|
||
|
|
||
|
def _argv_emulation():
|
||
|
import sys, os
|
||
|
# only use if started by LaunchServices
|
||
|
if os.environ.get('_PY2APP_LAUNCHED_'):
|
||
|
_run_argvemulator()
|
||
|
_argv_emulation()
|