'''
Native support of MultitouchSupport framework for MacBook (MaxOSX platform)
===========================================================================
'''
__all__ = ('MacMotionEventProvider', )
import ctypes
import threading
import collections
import os
from kivy.input.provider import MotionEventProvider
from kivy.input.factory import MotionEventFactory
from kivy.input.motionevent import MotionEvent
from kivy.input.shape import ShapeRect
if 'KIVY_DOC' not in os.environ:
CFArrayRef = ctypes.c_void_p
CFMutableArrayRef = ctypes.c_void_p
CFIndex = ctypes.c_long
dll = '/System/Library/PrivateFrameworks/' + \
'MultitouchSupport.framework/MultitouchSupport'
MultitouchSupport = ctypes.CDLL(dll)
CFArrayGetCount = MultitouchSupport.CFArrayGetCount
CFArrayGetCount.argtypes = [CFArrayRef]
CFArrayGetCount.restype = CFIndex
CFArrayGetValueAtIndex = MultitouchSupport.CFArrayGetValueAtIndex
CFArrayGetValueAtIndex.argtypes = [CFArrayRef, CFIndex]
CFArrayGetValueAtIndex.restype = ctypes.c_void_p
MTDeviceCreateList = MultitouchSupport.MTDeviceCreateList
MTDeviceCreateList.argtypes = []
MTDeviceCreateList.restype = CFMutableArrayRef
class MTPoint(ctypes.Structure):
_fields_ = [('x', ctypes.c_float),
('y', ctypes.c_float)]
class MTVector(ctypes.Structure):
_fields_ = [('position', MTPoint),
('velocity', MTPoint)]
class MTData(ctypes.Structure):
_fields_ = [
('frame', ctypes.c_int),
('timestamp', ctypes.c_double),
('identifier', ctypes.c_int),
# Current state (of unknown meaning).
('state', ctypes.c_int),
('unknown1', ctypes.c_int),
('unknown2', ctypes.c_int),
# Normalized position and vector of the touch (0 to 1)
('normalized', MTVector),
# The area of the touch.
('size', ctypes.c_float),
('unknown3', ctypes.c_int),
# The following three define the ellipsoid of a finger.
('angle', ctypes.c_float),
('major_axis', ctypes.c_float),
('minor_axis', ctypes.c_float),
('unknown4', MTVector),
('unknown5_1', ctypes.c_int),
('unknown5_2', ctypes.c_int),
('unknown6', ctypes.c_float), ]
MTDataRef = ctypes.POINTER(MTData)
MTContactCallbackFunction = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int,
MTDataRef, ctypes.c_int,
ctypes.c_double, ctypes.c_int)
MTDeviceRef = ctypes.c_void_p
MTRegisterContactFrameCallback = \
MultitouchSupport.MTRegisterContactFrameCallback
MTRegisterContactFrameCallback.argtypes = \
[MTDeviceRef, MTContactCallbackFunction]
MTRegisterContactFrameCallback.restype = None
MTDeviceStart = MultitouchSupport.MTDeviceStart
MTDeviceStart.argtypes = [MTDeviceRef, ctypes.c_int]
MTDeviceStart.restype = None
else:
MTContactCallbackFunction = lambda x: None
class MacMotionEvent(MotionEvent):
'''MotionEvent representing a contact point on the touchpad. Supports pos
and shape profiles.
'''
def __init__(self, *args, **kwargs):
kwargs.setdefault('is_touch', True)
kwargs.setdefault('type_id', 'touch')
super().__init__(*args, **kwargs)
self.profile = ('pos', 'shape')
def depack(self, args):
self.shape = ShapeRect()
self.sx, self.sy = args[0], args[1]
self.shape.width = args[2]
self.shape.height = args[2]
super().depack(args)
def __str__(self):
return '<MacMotionEvent id=%d pos=(%f, %f) device=%s>' \
% (self.id, self.sx, self.sy, self.device)
_instance = None
[docs]class MacMotionEventProvider(MotionEventProvider):
def __init__(self, *largs, **kwargs):
global _instance
if _instance is not None:
raise Exception('Only one MacMotionEvent provider is allowed.')
_instance = self
super(MacMotionEventProvider, self).__init__(*largs, **kwargs)
[docs] def start(self):
# global uid
self.uid = 0
# touches will be per devices
self.touches = {}
# lock needed to access on uid
self.lock = threading.Lock()
# event queue to dispatch in main thread
self.queue = collections.deque()
# ok, listing devices, and attach !
devices = MultitouchSupport.MTDeviceCreateList()
num_devices = CFArrayGetCount(devices)
for i in range(num_devices):
device = CFArrayGetValueAtIndex(devices, i)
# create touch dict for this device
data_id = str(device)
self.touches[data_id] = {}
# start !
MTRegisterContactFrameCallback(device, self._mts_callback)
MTDeviceStart(device, 0)
[docs] def update(self, dispatch_fn):
# dispatch all event from threads
try:
while True:
event_type, touch = self.queue.popleft()
dispatch_fn(event_type, touch)
except:
pass
[docs] def stop(self):
# i don't known how to stop it...
pass
@MTContactCallbackFunction
def _mts_callback(device, data_ptr, n_fingers, timestamp, frame):
global _instance
devid = str(device)
# XXX create live touch, we get one case that
# the device announced by macosx don't match the device
# in _mts_callback....
if devid not in _instance.touches:
_instance.touches[devid] = {}
touches = _instance.touches[devid]
actives = []
for i in range(n_fingers):
# get pointer on data
data = data_ptr[i]
# add this touch as an active touch
actives.append(data.identifier)
# extract identifier
data_id = data.identifier
# prepare argument position
norm_pos = data.normalized.position
args = (norm_pos.x, norm_pos.y, data.size)
if data_id not in touches:
# increment uid
_instance.lock.acquire()
_instance.uid += 1
# create a touch
touch = MacMotionEvent(_instance.device, _instance.uid, args)
_instance.lock.release()
# create event
_instance.queue.append(('begin', touch))
# store touch
touches[data_id] = touch
else:
touch = touches[data_id]
# check if he really moved
if data.normalized.position.x == touch.sx and \
data.normalized.position.y == touch.sy:
continue
touch.move(args)
_instance.queue.append(('update', touch))
# delete old touchs
for tid in list(touches.keys())[:]:
if tid not in actives:
touch = touches[tid]
touch.update_time_end()
_instance.queue.append(('end', touch))
del touches[tid]
return 0
MotionEventFactory.register('mactouch', MacMotionEventProvider)