mirror of
https://github.com/arsenetar/send2trash.git
synced 2025-05-08 09:49:52 +00:00
initial attempt with ctypes
This commit is contained in:
parent
1dded4f572
commit
3eb9fa0eda
@ -6,18 +6,21 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from ctypes import cdll, byref, Structure, c_char, c_char_p
|
from ctypes import cdll, byref, Structure, c_char, c_char_p, c_void_p, c_int64, sizeof
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
|
|
||||||
from .compat import binary_type
|
from .compat import binary_type
|
||||||
|
|
||||||
Foundation = cdll.LoadLibrary(find_library('Foundation'))
|
Foundation = cdll.LoadLibrary(find_library('Foundation'))
|
||||||
CoreServices = cdll.LoadLibrary(find_library('CoreServices'))
|
CoreServices = cdll.LoadLibrary(find_library('CoreServices'))
|
||||||
|
objc = cdll.LoadLibrary(find_library('objc'))
|
||||||
|
|
||||||
GetMacOSStatusCommentString = Foundation.GetMacOSStatusCommentString
|
GetMacOSStatusCommentString = Foundation.GetMacOSStatusCommentString
|
||||||
GetMacOSStatusCommentString.restype = c_char_p
|
GetMacOSStatusCommentString.restype = c_char_p
|
||||||
FSPathMakeRefWithOptions = CoreServices.FSPathMakeRefWithOptions
|
FSPathMakeRefWithOptions = CoreServices.FSPathMakeRefWithOptions
|
||||||
FSMoveObjectToTrashSync = CoreServices.FSMoveObjectToTrashSync
|
FSMoveObjectToTrashSync = CoreServices.FSMoveObjectToTrashSync
|
||||||
|
AEGetParamDesc = CoreServices.AEGetParamDesc
|
||||||
|
AESendMessage = CoreServices.AESendMessage
|
||||||
|
|
||||||
kFSPathMakeRefDefaultOptions = 0
|
kFSPathMakeRefDefaultOptions = 0
|
||||||
kFSPathMakeRefDoNotFollowLeafSymlink = 0x01
|
kFSPathMakeRefDoNotFollowLeafSymlink = 0x01
|
||||||
@ -36,9 +39,12 @@ def check_op_result(op_result):
|
|||||||
msg = GetMacOSStatusCommentString(op_result).decode('utf-8')
|
msg = GetMacOSStatusCommentString(op_result).decode('utf-8')
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
def send2trash(path):
|
def send2trash(path, with_put_back=False):
|
||||||
if not isinstance(path, binary_type):
|
if not isinstance(path, binary_type):
|
||||||
path = path.encode('utf-8')
|
path = path.encode('utf-8')
|
||||||
|
_with_put_back(path) if with_put_back else _normally(path)
|
||||||
|
|
||||||
|
def _normally(path):
|
||||||
fp = FSRef()
|
fp = FSRef()
|
||||||
opts = kFSPathMakeRefDoNotFollowLeafSymlink
|
opts = kFSPathMakeRefDoNotFollowLeafSymlink
|
||||||
op_result = FSPathMakeRefWithOptions(path, opts, byref(fp), None)
|
op_result = FSPathMakeRefWithOptions(path, opts, byref(fp), None)
|
||||||
@ -46,3 +52,106 @@ def send2trash(path):
|
|||||||
opts = kFSFileOperationDefaultOptions
|
opts = kFSFileOperationDefaultOptions
|
||||||
op_result = FSMoveObjectToTrashSync(byref(fp), None, opts)
|
op_result = FSMoveObjectToTrashSync(byref(fp), None, opts)
|
||||||
check_op_result(op_result)
|
check_op_result(op_result)
|
||||||
|
|
||||||
|
# Everything below here is required for getting the "put back" feature
|
||||||
|
# in the macOS trash - we attempt to ask Finder the trash the file for us.
|
||||||
|
|
||||||
|
objc.objc_getClass.restype = c_void_p
|
||||||
|
objc.sel_registerName.restype = c_void_p
|
||||||
|
objc.objc_msgSend.restype = c_void_p
|
||||||
|
objc.objc_msgSend.argtypes = [c_void_p, c_void_p]
|
||||||
|
|
||||||
|
_msg = objc.objc_msgSend
|
||||||
|
_cls = objc.objc_getClass
|
||||||
|
_sel = objc.sel_registerName
|
||||||
|
|
||||||
|
kAEWaitReply = 0x3
|
||||||
|
kAnyTransactionID = 0
|
||||||
|
kAEDefaultTimeout = -1
|
||||||
|
kAutoGenerateReturnID = -1
|
||||||
|
NSUTF8StringEncoding = 0x4
|
||||||
|
|
||||||
|
# TODO: this is probably incorrect/or changes depending on arch, etc
|
||||||
|
typeKernelProcessID = 0x6b706964
|
||||||
|
|
||||||
|
# TODO: not sure exactly what these should be
|
||||||
|
keyDirectObject = '----'
|
||||||
|
typeWildCard = '****'
|
||||||
|
|
||||||
|
class AEDataStorage(Structure):
|
||||||
|
_fields_ = []
|
||||||
|
|
||||||
|
# AppleEvent seems to be an alias of AEDesc
|
||||||
|
class AEDesc(Structure):
|
||||||
|
# DescType is an unsigned int ?
|
||||||
|
# AEDataStorage is something else ?
|
||||||
|
_fields_ = [('descriptorType', c_int64), ('dataHandle', AEDataStorage)]
|
||||||
|
|
||||||
|
# This was inspired by https://github.com/ali-rantakari/trash.
|
||||||
|
# See https://github.com/ali-rantakari/trash/blob/master/trash.m#L263-L341
|
||||||
|
# and also https://stackoverflow.com/a/1490644/5552584.
|
||||||
|
def _with_put_back(path):
|
||||||
|
# Create an autorelease pool.
|
||||||
|
NSAutoreleasePool = _cls('NSAutoreleasePool')
|
||||||
|
pool = _msg(NSAutoreleasePool, _sel('alloc'))
|
||||||
|
pool = _msg(pool, _sel('init'))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Generate list descriptor containing the file URL
|
||||||
|
NSAppleEventDescriptor = _cls('NSAppleEventDescriptor')
|
||||||
|
url_list_descr = _msg(NSAppleEventDescriptor, _sel('listDescriptor'))
|
||||||
|
url = _msg(_cls('NSURL'), _sel('fileURLWithPath'), path)
|
||||||
|
url_str = _msg(url, _sel('absoluteString'))
|
||||||
|
data = _msg(url_str, _sel('dataUsingEncoding:'), NSUTF8StringEncoding)
|
||||||
|
descr = _msg(NSAppleEventDescriptor,
|
||||||
|
_sel('descriptorWithDescriptorType:'), 'furl',
|
||||||
|
_sel('data:'), data)
|
||||||
|
_msg(url_list_descr,
|
||||||
|
_sel('insertDescriptor:'), descr,
|
||||||
|
_sel('atIndex:'), 1)
|
||||||
|
|
||||||
|
# Generate the 'top level' "delete" descriptor
|
||||||
|
finder_pid = _get_finder_pid()
|
||||||
|
target_descr = _msg(NSAppleEventDescriptor,
|
||||||
|
_sel('descriptorWithDescriptorType:'), typeKernelProcessID,
|
||||||
|
_sel('bytes:'), byref(finder_pid),
|
||||||
|
_sel('length:'), sizeof(finder_pid))
|
||||||
|
descriptor = _msg(NSAppleEventDescriptor,
|
||||||
|
_sel('appleEventWithEventClass:'), 'core',
|
||||||
|
_sel('eventID:'), 'delo',
|
||||||
|
_sel('targetDescriptor:'), target_descr,
|
||||||
|
_sel('returnID:'), kAutoGenerateReturnID,
|
||||||
|
_sel('transactionID:'), kAnyTransactionID)
|
||||||
|
|
||||||
|
# add the list of file URLs as argument
|
||||||
|
_msg(descriptor,
|
||||||
|
_sel('setDescriptor:'), url_list_descr,
|
||||||
|
_sel('forKeyword:'), '----')
|
||||||
|
|
||||||
|
# send the Apple Event synchronously
|
||||||
|
reply_event = AEDesc()
|
||||||
|
op_result = AESendMessage(_msg(descriptor, _sel('asDesc')),
|
||||||
|
byref(reply_event), kAEWaitReply, kAEDefaultTimeout)
|
||||||
|
check_op_result(op_result)
|
||||||
|
|
||||||
|
# check reply in order to determine return value
|
||||||
|
reply_ae_descr = AEDesc()
|
||||||
|
op_result = AEGetParamDesc(byref(reply_event), keyDirectObject,
|
||||||
|
typeWildCard, byref(reply_ae_descr))
|
||||||
|
check_op_result(op_result)
|
||||||
|
|
||||||
|
reply_descr = _msg(_msg(_msg(NSAppleEventDescriptor, _sel('alloc')),
|
||||||
|
_sel('initWithAEDescNoCopy:'), byref(reply_ae_descr)), _sel('autorelease'))
|
||||||
|
|
||||||
|
if _msg(reply_descr, _sel('numberOfItems')) == 0:
|
||||||
|
raise Exception('file could not be trashed')
|
||||||
|
finally:
|
||||||
|
_msg(pool, _sel('release'))
|
||||||
|
|
||||||
|
def _get_finder_pid():
|
||||||
|
import subprocess
|
||||||
|
child = subprocess.Popen(['pgrep', '-f', 'Finder'],
|
||||||
|
stdout=subprocess.PIPE, shell=False)
|
||||||
|
response = child.communicate()[0]
|
||||||
|
# TODO: assumed that pid_t is a c_int64 (should depend on arch)
|
||||||
|
return c_int64(int(response.split()[0]))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user