Version

Quick search

Table Of Contents

Source code for kivy.input.providers.mactouch

'''
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)