Ajout du GUI
This commit is contained in:
68
kivy/input/providers/__init__.py
Normal file
68
kivy/input/providers/__init__.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# pylint: disable=W0611
|
||||
'''
|
||||
Providers
|
||||
=========
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
from kivy.utils import platform as core_platform
|
||||
from kivy.logger import Logger
|
||||
from kivy.setupconfig import USE_SDL2
|
||||
|
||||
import kivy.input.providers.tuio
|
||||
import kivy.input.providers.mouse
|
||||
|
||||
platform = core_platform
|
||||
|
||||
if platform == 'win' or 'KIVY_DOC' in os.environ:
|
||||
try:
|
||||
import kivy.input.providers.wm_touch
|
||||
import kivy.input.providers.wm_pen
|
||||
except:
|
||||
err = 'Input: WM_Touch/WM_Pen not supported by your version of Windows'
|
||||
Logger.warning(err)
|
||||
|
||||
if platform == 'macosx' or 'KIVY_DOC' in os.environ:
|
||||
try:
|
||||
import kivy.input.providers.mactouch
|
||||
except:
|
||||
err = 'Input: MacMultitouchSupport is not supported by your system'
|
||||
Logger.exception(err)
|
||||
|
||||
if platform == 'linux' or 'KIVY_DOC' in os.environ:
|
||||
try:
|
||||
import kivy.input.providers.probesysfs
|
||||
except:
|
||||
err = 'Input: ProbeSysfs is not supported by your version of linux'
|
||||
Logger.exception(err)
|
||||
try:
|
||||
import kivy.input.providers.mtdev
|
||||
except:
|
||||
err = 'Input: MTDev is not supported by your version of linux'
|
||||
Logger.exception(err)
|
||||
try:
|
||||
import kivy.input.providers.hidinput
|
||||
except:
|
||||
err = 'Input: HIDInput is not supported by your version of linux'
|
||||
Logger.exception(err)
|
||||
try:
|
||||
import kivy.input.providers.linuxwacom
|
||||
except:
|
||||
err = 'Input: LinuxWacom is not supported by your version of linux'
|
||||
Logger.exception(err)
|
||||
|
||||
if (platform == 'android' and not USE_SDL2) or 'KIVY_DOC' in os.environ:
|
||||
try:
|
||||
import kivy.input.providers.androidjoystick
|
||||
except:
|
||||
err = 'Input: AndroidJoystick is not supported by your version ' \
|
||||
'of linux'
|
||||
Logger.exception(err)
|
||||
|
||||
try:
|
||||
import kivy.input.providers.leapfinger # NOQA
|
||||
except:
|
||||
err = 'Input: LeapFinger is not available on your system'
|
||||
Logger.exception(err)
|
||||
BIN
kivy/input/providers/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/androidjoystick.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/androidjoystick.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/hidinput.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/hidinput.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/leapfinger.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/leapfinger.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/linuxwacom.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/linuxwacom.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/mactouch.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/mactouch.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/mouse.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/mouse.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/mtdev.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/mtdev.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/probesysfs.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/probesysfs.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/tuio.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/tuio.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/wm_common.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/wm_common.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/wm_pen.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/wm_pen.cpython-310.pyc
Normal file
Binary file not shown.
BIN
kivy/input/providers/__pycache__/wm_touch.cpython-310.pyc
Normal file
BIN
kivy/input/providers/__pycache__/wm_touch.cpython-310.pyc
Normal file
Binary file not shown.
117
kivy/input/providers/androidjoystick.py
Normal file
117
kivy/input/providers/androidjoystick.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# pylint: disable=W0611
|
||||
'''
|
||||
Android Joystick Input Provider
|
||||
===============================
|
||||
|
||||
This module is based on the PyGame JoyStick Input Provider. For more
|
||||
information, please refer to
|
||||
`<http://www.pygame.org/docs/ref/joystick.html>`_
|
||||
|
||||
|
||||
'''
|
||||
__all__ = ('AndroidMotionEventProvider', )
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
import android # NOQA
|
||||
except ImportError:
|
||||
if 'KIVY_DOC' not in os.environ:
|
||||
raise Exception('android lib not found.')
|
||||
|
||||
from kivy.logger import Logger
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.input.shape import ShapeRect
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
if 'KIVY_DOC' not in os.environ:
|
||||
import pygame.joystick
|
||||
|
||||
|
||||
class AndroidMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.profile = ['pos', 'pressure', 'shape']
|
||||
|
||||
def depack(self, args):
|
||||
self.sx, self.sy, self.pressure, radius = args
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = radius
|
||||
self.shape.height = radius
|
||||
super().depack(args)
|
||||
|
||||
|
||||
class AndroidMotionEventProvider(MotionEventProvider):
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(AndroidMotionEventProvider, self).__init__(device, args)
|
||||
self.joysticks = []
|
||||
self.touches = {}
|
||||
self.uid = 0
|
||||
self.window = None
|
||||
|
||||
def create_joystick(self, index):
|
||||
Logger.info('Android: create joystick <%d>' % index)
|
||||
js = pygame.joystick.Joystick(index)
|
||||
js.init()
|
||||
if js.get_numbuttons() == 0:
|
||||
Logger.info('Android: discard joystick <%d> cause no button' %
|
||||
index)
|
||||
return
|
||||
self.joysticks.append(js)
|
||||
|
||||
def start(self):
|
||||
pygame.joystick.init()
|
||||
Logger.info('Android: found %d joystick' % pygame.joystick.get_count())
|
||||
for i in range(pygame.joystick.get_count()):
|
||||
self.create_joystick(i)
|
||||
|
||||
def stop(self):
|
||||
self.joysticks = []
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
if not self.window:
|
||||
from kivy.core.window import Window
|
||||
self.window = Window
|
||||
w, h = self.window.system_size
|
||||
touches = self.touches
|
||||
for joy in self.joysticks:
|
||||
jid = joy.get_id()
|
||||
pressed = joy.get_button(0)
|
||||
if pressed or jid in touches:
|
||||
x = joy.get_axis(0) * 32768. / w
|
||||
y = 1. - (joy.get_axis(1) * 32768. / h)
|
||||
|
||||
# python for android do * 1000.
|
||||
pressure = joy.get_axis(2) / 1000.
|
||||
radius = joy.get_axis(3) / 1000.
|
||||
|
||||
# new touch ?
|
||||
if pressed and jid not in touches:
|
||||
self.uid += 1
|
||||
touch = AndroidMotionEvent(self.device, self.uid,
|
||||
[x, y, pressure, radius])
|
||||
touches[jid] = touch
|
||||
dispatch_fn('begin', touch)
|
||||
# update touch
|
||||
elif pressed:
|
||||
touch = touches[jid]
|
||||
# avoid same touch position
|
||||
if (touch.sx == x and touch.sy == y and
|
||||
touch.pressure == pressure):
|
||||
continue
|
||||
touch.move([x, y, pressure, radius])
|
||||
dispatch_fn('update', touch)
|
||||
# disappear
|
||||
elif not pressed and jid in touches:
|
||||
touch = touches[jid]
|
||||
touch.move([x, y, pressure, radius])
|
||||
touch.update_time_end()
|
||||
dispatch_fn('end', touch)
|
||||
touches.pop(jid)
|
||||
|
||||
|
||||
MotionEventFactory.register('android', AndroidMotionEventProvider)
|
||||
778
kivy/input/providers/hidinput.py
Normal file
778
kivy/input/providers/hidinput.py
Normal file
@@ -0,0 +1,778 @@
|
||||
# coding utf-8
|
||||
'''
|
||||
Native support for HID input from the linux kernel
|
||||
==================================================
|
||||
|
||||
Support starts from 2.6.32-ubuntu, or 2.6.34.
|
||||
|
||||
To configure HIDInput, add this to your configuration::
|
||||
|
||||
[input]
|
||||
# devicename = hidinput,/dev/input/eventXX
|
||||
# example with Stantum MTP4.3" screen
|
||||
stantum = hidinput,/dev/input/event2
|
||||
|
||||
.. note::
|
||||
You must have read access to the input event.
|
||||
|
||||
You can use a custom range for the X, Y and pressure values.
|
||||
For some drivers, the range reported is invalid.
|
||||
To fix that, you can add these options to the argument line:
|
||||
|
||||
* invert_x : 1 to invert X axis
|
||||
* invert_y : 1 to invert Y axis
|
||||
* min_position_x : X relative minimum
|
||||
* max_position_x : X relative maximum
|
||||
* min_position_y : Y relative minimum
|
||||
* max_position_y : Y relative maximum
|
||||
* min_abs_x : X absolute minimum
|
||||
* min_abs_y : Y absolute minimum
|
||||
* max_abs_x : X absolute maximum
|
||||
* max_abs_y : Y absolute maximum
|
||||
* min_pressure : pressure minimum
|
||||
* max_pressure : pressure maximum
|
||||
* rotation : rotate the input coordinate (0, 90, 180, 270)
|
||||
|
||||
For example, on the Asus T101M, the touchscreen reports a range from 0-4095 for
|
||||
the X and Y values, but the real values are in a range from 0-32768. To correct
|
||||
this, you can add the following to the configuration::
|
||||
|
||||
[input]
|
||||
t101m = hidinput,/dev/input/event7,max_position_x=32768,\
|
||||
max_position_y=32768
|
||||
|
||||
.. versionadded:: 1.9.1
|
||||
|
||||
`rotation` configuration token added.
|
||||
|
||||
'''
|
||||
import os
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
from kivy.input.shape import ShapeRect
|
||||
|
||||
__all__ = ('HIDInputMotionEventProvider', 'HIDMotionEvent')
|
||||
|
||||
# late imports
|
||||
Window = None
|
||||
Keyboard = None
|
||||
|
||||
|
||||
class HIDMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def depack(self, args):
|
||||
self.sx = args['x']
|
||||
self.sy = args['y']
|
||||
self.profile = ['pos']
|
||||
if 'size_w' in args and 'size_h' in args:
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = args['size_w']
|
||||
self.shape.height = args['size_h']
|
||||
self.profile.append('shape')
|
||||
if 'pressure' in args:
|
||||
self.pressure = args['pressure']
|
||||
self.profile.append('pressure')
|
||||
if 'button' in args:
|
||||
self.button = args['button']
|
||||
self.profile.append('button')
|
||||
super().depack(args)
|
||||
|
||||
def __str__(self):
|
||||
return '<HIDMotionEvent id=%d pos=(%f, %f) device=%s>' \
|
||||
% (self.id, self.sx, self.sy, self.device)
|
||||
|
||||
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
# documentation hack
|
||||
HIDInputMotionEventProvider = None
|
||||
|
||||
else:
|
||||
import threading
|
||||
import collections
|
||||
import struct
|
||||
import fcntl
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.logger import Logger
|
||||
|
||||
#
|
||||
# This part is taken from linux-source-2.6.32/include/linux/input.h
|
||||
#
|
||||
|
||||
# Event types
|
||||
EV_SYN = 0x00
|
||||
EV_KEY = 0x01
|
||||
EV_REL = 0x02
|
||||
EV_ABS = 0x03
|
||||
EV_MSC = 0x04
|
||||
EV_SW = 0x05
|
||||
EV_LED = 0x11
|
||||
EV_SND = 0x12
|
||||
EV_REP = 0x14
|
||||
EV_FF = 0x15
|
||||
EV_PWR = 0x16
|
||||
EV_FF_STATUS = 0x17
|
||||
EV_MAX = 0x1f
|
||||
EV_CNT = (EV_MAX + 1)
|
||||
|
||||
KEY_MAX = 0x2ff
|
||||
|
||||
# Synchronization events
|
||||
SYN_REPORT = 0
|
||||
SYN_CONFIG = 1
|
||||
SYN_MT_REPORT = 2
|
||||
|
||||
# Misc events
|
||||
MSC_SERIAL = 0x00
|
||||
MSC_PULSELED = 0x01
|
||||
MSC_GESTURE = 0x02
|
||||
MSC_RAW = 0x03
|
||||
MSC_SCAN = 0x04
|
||||
MSC_MAX = 0x07
|
||||
MSC_CNT = (MSC_MAX + 1)
|
||||
|
||||
ABS_X = 0x00
|
||||
ABS_Y = 0x01
|
||||
ABS_PRESSURE = 0x18
|
||||
ABS_MT_TOUCH_MAJOR = 0x30 # Major axis of touching ellipse
|
||||
ABS_MT_TOUCH_MINOR = 0x31 # Minor axis (omit if circular)
|
||||
ABS_MT_WIDTH_MAJOR = 0x32 # Major axis of approaching ellipse
|
||||
ABS_MT_WIDTH_MINOR = 0x33 # Minor axis (omit if circular)
|
||||
ABS_MT_ORIENTATION = 0x34 # Ellipse orientation
|
||||
ABS_MT_POSITION_X = 0x35 # Center X ellipse position
|
||||
ABS_MT_POSITION_Y = 0x36 # Center Y ellipse position
|
||||
ABS_MT_TOOL_TYPE = 0x37 # Type of touching device
|
||||
ABS_MT_BLOB_ID = 0x38 # Group a set of packets as a blob
|
||||
ABS_MT_TRACKING_ID = 0x39 # Unique ID of initiated contact
|
||||
ABS_MT_PRESSURE = 0x3a # Pressure on contact area
|
||||
|
||||
# some ioctl base (with 0 value)
|
||||
EVIOCGNAME = 2147501318
|
||||
EVIOCGBIT = 2147501344
|
||||
EVIOCGABS = 2149074240
|
||||
|
||||
keyboard_keys = {
|
||||
0x29: ('`', '~'),
|
||||
0x02: ('1', '!'),
|
||||
0x03: ('2', '@'),
|
||||
0x04: ('3', '#'),
|
||||
0x05: ('4', '$'),
|
||||
0x06: ('5', '%'),
|
||||
0x07: ('6', '^'),
|
||||
0x08: ('7', '&'),
|
||||
0x09: ('8', '*'),
|
||||
0x0a: ('9', '('),
|
||||
0x0b: ('0', ')'),
|
||||
0x0c: ('-', '_'),
|
||||
0x0d: ('=', '+'),
|
||||
0x0e: ('backspace', ),
|
||||
0x0f: ('tab', ),
|
||||
0x10: ('q', 'Q'),
|
||||
0x11: ('w', 'W'),
|
||||
0x12: ('e', 'E'),
|
||||
0x13: ('r', 'R'),
|
||||
0x14: ('t', 'T'),
|
||||
0x15: ('y', 'Y'),
|
||||
0x16: ('u', 'U'),
|
||||
0x17: ('i', 'I'),
|
||||
0x18: ('o', 'O'),
|
||||
0x19: ('p', 'P'),
|
||||
0x1a: ('[', '{'),
|
||||
0x1b: (']', '}'),
|
||||
0x2b: ('\\', '|'),
|
||||
0x3a: ('capslock', ),
|
||||
0x1e: ('a', 'A'),
|
||||
0x1f: ('s', 'S'),
|
||||
0x20: ('d', 'D'),
|
||||
0x21: ('f', 'F'),
|
||||
0x22: ('g', 'G'),
|
||||
0x23: ('h', 'H'),
|
||||
0x24: ('j', 'J'),
|
||||
0x25: ('k', 'K'),
|
||||
0x26: ('l', 'L'),
|
||||
0x27: (';', ':'),
|
||||
0x28: ("'", '"'),
|
||||
0xff: ('non-US-1', ),
|
||||
0x1c: ('enter', ),
|
||||
0x2a: ('shift', ),
|
||||
0x2c: ('z', 'Z'),
|
||||
0x2d: ('x', 'X'),
|
||||
0x2e: ('c', 'C'),
|
||||
0x2f: ('v', 'V'),
|
||||
0x30: ('b', 'B'),
|
||||
0x31: ('n', 'N'),
|
||||
0x32: ('m', 'M'),
|
||||
0x33: (',', '<'),
|
||||
0x34: ('.', '>'),
|
||||
0x35: ('/', '?'),
|
||||
0x36: ('shift', ),
|
||||
0x56: ('pipe', ),
|
||||
0x1d: ('lctrl', ),
|
||||
0x7D: ('super', ),
|
||||
0x38: ('alt', ),
|
||||
0x39: ('spacebar', ),
|
||||
0x64: ('alt-gr', ),
|
||||
0x7e: ('super', ),
|
||||
0x7f: ('compose', ),
|
||||
0x61: ('rctrl', ),
|
||||
0x45: ('numlock', ),
|
||||
0x47: ('numpad7', 'home'),
|
||||
0x4b: ('numpad4', 'left'),
|
||||
0x4f: ('numpad1', 'end'),
|
||||
0x48: ('numpad8', 'up'),
|
||||
0x4c: ('numpad5', ),
|
||||
0x50: ('numpad2', 'down'),
|
||||
0x52: ('numpad0', 'insert'),
|
||||
0x37: ('numpadmul', ),
|
||||
0x62: ('numpaddivide', ),
|
||||
0x49: ('numpad9', 'pageup'),
|
||||
0x4d: ('numpad6', 'right'),
|
||||
0x51: ('numpad3', 'pagedown'),
|
||||
0x53: ('numpaddecimal', 'delete'),
|
||||
0x4a: ('numpadsubstract', ),
|
||||
0x4e: ('numpadadd', ),
|
||||
0x60: ('numpadenter', ),
|
||||
0x01: ('escape', ),
|
||||
0x3b: ('f1', ),
|
||||
0x3c: ('f2', ),
|
||||
0x3d: ('f3', ),
|
||||
0x3e: ('f4', ),
|
||||
0x3f: ('f5', ),
|
||||
0x40: ('f6', ),
|
||||
0x41: ('f7', ),
|
||||
0x42: ('f8', ),
|
||||
0x43: ('f9', ),
|
||||
0x44: ('f10', ),
|
||||
0x57: ('f11', ),
|
||||
0x58: ('f12', ),
|
||||
0x54: ('Alt+SysRq', ),
|
||||
0x46: ('Screenlock', ),
|
||||
0x67: ('up', ),
|
||||
0x6c: ('down', ),
|
||||
0x69: ('left', ),
|
||||
0x6a: ('right', ),
|
||||
0x6e: ('insert', ),
|
||||
0x6f: ('delete', ),
|
||||
0x66: ('home', ),
|
||||
0x6b: ('end', ),
|
||||
0x68: ('pageup', ),
|
||||
0x6d: ('pagedown', ),
|
||||
0x63: ('print', ),
|
||||
0x77: ('pause', ),
|
||||
|
||||
|
||||
# TODO combinations
|
||||
# e0-37 PrtScr
|
||||
# e0-46 Ctrl+Break
|
||||
# e0-5b LWin (USB: LGUI)
|
||||
# e0-5c RWin (USB: RGUI)
|
||||
# e0-5d Menu
|
||||
# e0-5f Sleep
|
||||
# e0-5e Power
|
||||
# e0-63 Wake
|
||||
# e0-38 RAlt
|
||||
# e0-1d RCtrl
|
||||
# e0-52 Insert
|
||||
# e0-53 Delete
|
||||
# e0-47 Home
|
||||
# e0-4f End
|
||||
# e0-49 PgUp
|
||||
# e0-51 PgDn
|
||||
# e0-4b Left
|
||||
# e0-48 Up
|
||||
# e0-50 Down
|
||||
# e0-4d Right
|
||||
# e0-35 KP-/
|
||||
# e0-1c KP-Enter
|
||||
# e1-1d-45 77 Pause
|
||||
}
|
||||
|
||||
keys_str = {
|
||||
'spacebar': ' ',
|
||||
'tab': ' ',
|
||||
'shift': '',
|
||||
'alt': '',
|
||||
'ctrl': '',
|
||||
'escape': '',
|
||||
'numpad1': '1',
|
||||
'numpad2': '2',
|
||||
'numpad3': '3',
|
||||
'numpad4': '4',
|
||||
'numpad5': '5',
|
||||
'numpad6': '6',
|
||||
'numpad7': '7',
|
||||
'numpad8': '8',
|
||||
'numpad9': '9',
|
||||
'numpad0': '0',
|
||||
'numpadmul': '*',
|
||||
'numpaddivide': '/',
|
||||
'numpadadd': '+',
|
||||
'numpaddecimal': '.',
|
||||
'numpadsubstract': '-',
|
||||
}
|
||||
|
||||
# sizeof(struct input_event)
|
||||
struct_input_event_sz = struct.calcsize('LLHHi')
|
||||
struct_input_absinfo_sz = struct.calcsize('iiiiii')
|
||||
sz_l = struct.calcsize('Q')
|
||||
|
||||
class HIDInputMotionEventProvider(MotionEventProvider):
|
||||
|
||||
options = ('min_position_x', 'max_position_x',
|
||||
'min_position_y', 'max_position_y',
|
||||
'min_pressure', 'max_pressure',
|
||||
'min_abs_x', 'max_abs_x',
|
||||
'min_abs_y', 'max_abs_y',
|
||||
'invert_x', 'invert_y', 'rotation')
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(HIDInputMotionEventProvider, self).__init__(device, args)
|
||||
global Window, Keyboard
|
||||
|
||||
if Window is None:
|
||||
from kivy.core.window import Window
|
||||
if Keyboard is None:
|
||||
from kivy.core.window import Keyboard
|
||||
|
||||
self.input_fn = None
|
||||
self.default_ranges = dict()
|
||||
|
||||
# split arguments
|
||||
args = args.split(',')
|
||||
if not args:
|
||||
Logger.error('HIDInput: Filename missing in configuration')
|
||||
Logger.error('HIDInput: Use /dev/input/event0 for example')
|
||||
return None
|
||||
|
||||
# read filename
|
||||
self.input_fn = args[0]
|
||||
Logger.info('HIDInput: Read event from <%s>' % self.input_fn)
|
||||
|
||||
# read parameters
|
||||
for arg in args[1:]:
|
||||
if arg == '':
|
||||
continue
|
||||
arg = arg.split('=')
|
||||
|
||||
# ensure it's a key = value
|
||||
if len(arg) != 2:
|
||||
Logger.error('HIDInput: invalid parameter '
|
||||
'%s, not in key=value format.' % arg)
|
||||
continue
|
||||
|
||||
# ensure the key exist
|
||||
key, value = arg
|
||||
if key not in HIDInputMotionEventProvider.options:
|
||||
Logger.error('HIDInput: unknown %s option' % key)
|
||||
continue
|
||||
|
||||
# ensure the value
|
||||
try:
|
||||
self.default_ranges[key] = int(value)
|
||||
except ValueError:
|
||||
err = 'HIDInput: invalid value "%s" for "%s"' % (
|
||||
key, value)
|
||||
Logger.error(err)
|
||||
continue
|
||||
|
||||
# all good!
|
||||
Logger.info('HIDInput: Set custom %s to %d' % (
|
||||
key, int(value)))
|
||||
|
||||
if 'rotation' not in self.default_ranges:
|
||||
self.default_ranges['rotation'] = 0
|
||||
elif self.default_ranges['rotation'] not in (0, 90, 180, 270):
|
||||
Logger.error('HIDInput: invalid rotation value ({})'.format(
|
||||
self.default_ranges['rotation']))
|
||||
self.default_ranges['rotation'] = 0
|
||||
|
||||
def start(self):
|
||||
if self.input_fn is None:
|
||||
return
|
||||
self.uid = 0
|
||||
self.queue = collections.deque()
|
||||
self.dispatch_queue = []
|
||||
self.thread = threading.Thread(
|
||||
name=self.__class__.__name__,
|
||||
target=self._thread_run,
|
||||
kwargs=dict(
|
||||
queue=self.queue,
|
||||
input_fn=self.input_fn,
|
||||
device=self.device,
|
||||
default_ranges=self.default_ranges))
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def _thread_run(self, **kwargs):
|
||||
input_fn = kwargs.get('input_fn')
|
||||
queue = self.queue
|
||||
dispatch_queue = self.dispatch_queue
|
||||
device = kwargs.get('device')
|
||||
drs = kwargs.get('default_ranges').get
|
||||
touches = {}
|
||||
touches_sent = []
|
||||
point = {}
|
||||
l_points = []
|
||||
|
||||
# prepare some vars to get limit of some component
|
||||
range_min_position_x = 0
|
||||
range_max_position_x = 2048
|
||||
range_min_position_y = 0
|
||||
range_max_position_y = 2048
|
||||
range_min_pressure = 0
|
||||
range_max_pressure = 255
|
||||
range_min_abs_x = 0
|
||||
range_max_abs_x = 255
|
||||
range_min_abs_y = 0
|
||||
range_max_abs_y = 255
|
||||
range_min_abs_pressure = 0
|
||||
range_max_abs_pressure = 255
|
||||
invert_x = int(bool(drs('invert_x', 0)))
|
||||
invert_y = int(bool(drs('invert_y', 1)))
|
||||
rotation = drs('rotation', 0)
|
||||
|
||||
def assign_coord(point, value, invert, coords):
|
||||
cx, cy = coords
|
||||
if invert:
|
||||
value = 1. - value
|
||||
if rotation == 0:
|
||||
point[cx] = value
|
||||
elif rotation == 90:
|
||||
point[cy] = value
|
||||
elif rotation == 180:
|
||||
point[cx] = 1. - value
|
||||
elif rotation == 270:
|
||||
point[cy] = 1. - value
|
||||
|
||||
def assign_rel_coord(point, value, invert, coords):
|
||||
cx, cy = coords
|
||||
if invert:
|
||||
value = -1 * value
|
||||
if rotation == 0:
|
||||
point[cx] += value
|
||||
elif rotation == 90:
|
||||
point[cy] += value
|
||||
elif rotation == 180:
|
||||
point[cx] += -value
|
||||
elif rotation == 270:
|
||||
point[cy] += -value
|
||||
|
||||
# limit it to the screen area 0-1
|
||||
point['x'] = min(1., max(0., point['x']))
|
||||
point['y'] = min(1., max(0., point['y']))
|
||||
|
||||
def process_as_multitouch(tv_sec, tv_usec, ev_type,
|
||||
ev_code, ev_value):
|
||||
# sync event
|
||||
if ev_type == EV_SYN:
|
||||
if ev_code == SYN_MT_REPORT:
|
||||
if 'id' not in point:
|
||||
return
|
||||
l_points.append(point.copy())
|
||||
elif ev_code == SYN_REPORT:
|
||||
process(l_points)
|
||||
del l_points[:]
|
||||
|
||||
elif ev_type == EV_MSC and ev_code in (MSC_RAW, MSC_SCAN):
|
||||
pass
|
||||
|
||||
else:
|
||||
# compute multitouch track
|
||||
if ev_code == ABS_MT_TRACKING_ID:
|
||||
point.clear()
|
||||
point['id'] = ev_value
|
||||
elif ev_code == ABS_MT_POSITION_X:
|
||||
val = normalize(ev_value,
|
||||
range_min_position_x,
|
||||
range_max_position_x)
|
||||
assign_coord(point, val, invert_x, 'xy')
|
||||
elif ev_code == ABS_MT_POSITION_Y:
|
||||
val = 1. - normalize(ev_value,
|
||||
range_min_position_y,
|
||||
range_max_position_y)
|
||||
assign_coord(point, val, invert_y, 'yx')
|
||||
elif ev_code == ABS_MT_ORIENTATION:
|
||||
point['orientation'] = ev_value
|
||||
elif ev_code == ABS_MT_BLOB_ID:
|
||||
point['blobid'] = ev_value
|
||||
elif ev_code == ABS_MT_PRESSURE:
|
||||
point['pressure'] = normalize(ev_value,
|
||||
range_min_pressure,
|
||||
range_max_pressure)
|
||||
elif ev_code == ABS_MT_TOUCH_MAJOR:
|
||||
point['size_w'] = ev_value
|
||||
elif ev_code == ABS_MT_TOUCH_MINOR:
|
||||
point['size_h'] = ev_value
|
||||
|
||||
def process_as_mouse_or_keyboard(
|
||||
tv_sec, tv_usec, ev_type, ev_code, ev_value):
|
||||
if ev_type == EV_SYN:
|
||||
if ev_code == SYN_REPORT:
|
||||
process([point])
|
||||
if ('button' in point and
|
||||
point['button'].startswith('scroll')):
|
||||
# for scrolls we need to remove it as there is
|
||||
# no up key
|
||||
del point['button']
|
||||
point['id'] += 1
|
||||
point['_avoid'] = True
|
||||
process([point])
|
||||
|
||||
elif ev_type == EV_REL:
|
||||
if ev_code == 0:
|
||||
assign_rel_coord(point,
|
||||
min(1., max(-1., ev_value / 1000.)),
|
||||
invert_x, 'xy')
|
||||
elif ev_code == 1:
|
||||
assign_rel_coord(point,
|
||||
min(1., max(-1., ev_value / 1000.)),
|
||||
invert_y, 'yx')
|
||||
elif ev_code == 8: # Wheel
|
||||
# translates the wheel move to a button
|
||||
b = "scrollup" if ev_value < 0 else "scrolldown"
|
||||
if 'button' not in point:
|
||||
point['button'] = b
|
||||
point['id'] += 1
|
||||
if '_avoid' in point:
|
||||
del point['_avoid']
|
||||
|
||||
elif ev_type != EV_KEY:
|
||||
if ev_code == ABS_X:
|
||||
val = normalize(ev_value,
|
||||
range_min_abs_x,
|
||||
range_max_abs_x)
|
||||
assign_coord(point, val, invert_x, 'xy')
|
||||
elif ev_code == ABS_Y:
|
||||
val = 1. - normalize(ev_value,
|
||||
range_min_abs_y,
|
||||
range_max_abs_y)
|
||||
assign_coord(point, val, invert_y, 'yx')
|
||||
elif ev_code == ABS_PRESSURE:
|
||||
point['pressure'] = normalize(ev_value,
|
||||
range_min_abs_pressure,
|
||||
range_max_abs_pressure)
|
||||
else:
|
||||
buttons = {
|
||||
272: 'left',
|
||||
273: 'right',
|
||||
274: 'middle',
|
||||
275: 'side',
|
||||
276: 'extra',
|
||||
277: 'forward',
|
||||
278: 'back',
|
||||
279: 'task',
|
||||
330: 'touch',
|
||||
320: 'pen'}
|
||||
|
||||
if ev_code in buttons.keys():
|
||||
if ev_value:
|
||||
if 'button' not in point:
|
||||
point['button'] = buttons[ev_code]
|
||||
point['id'] += 1
|
||||
if '_avoid' in point:
|
||||
del point['_avoid']
|
||||
elif 'button' in point:
|
||||
if point['button'] == buttons[ev_code]:
|
||||
del point['button']
|
||||
point['id'] += 1
|
||||
point['_avoid'] = True
|
||||
else:
|
||||
if not 0 <= ev_value <= 1:
|
||||
return
|
||||
|
||||
if ev_code not in keyboard_keys:
|
||||
Logger.warn('HIDInput: unhandled HID code: {}'.
|
||||
format(ev_code))
|
||||
return
|
||||
|
||||
z = keyboard_keys[ev_code][-1 if 'shift' in
|
||||
Window._modifiers else 0]
|
||||
if z.lower() not in Keyboard.keycodes:
|
||||
# or if it is not in this LUT
|
||||
Logger.warn('HIDInput: unhandled character: {}'.
|
||||
format(z))
|
||||
return
|
||||
|
||||
keycode = Keyboard.keycodes[z.lower()]
|
||||
|
||||
if ev_value == 1:
|
||||
if z == 'shift' or z == 'alt':
|
||||
Window._modifiers.append(z)
|
||||
elif z.endswith('ctrl'):
|
||||
Window._modifiers.append('ctrl')
|
||||
|
||||
dispatch_queue.append(('key_down', (
|
||||
keycode, ev_code,
|
||||
keys_str.get(z, z), Window._modifiers)))
|
||||
elif ev_value == 0:
|
||||
dispatch_queue.append(('key_up', (
|
||||
keycode, ev_code,
|
||||
keys_str.get(z, z), Window._modifiers)))
|
||||
if ((z == 'shift' or z == 'alt') and
|
||||
(z in Window._modifiers)):
|
||||
Window._modifiers.remove(z)
|
||||
elif (z.endswith('ctrl') and
|
||||
'ctrl' in Window._modifiers):
|
||||
Window._modifiers.remove('ctrl')
|
||||
|
||||
def process(points):
|
||||
if not is_multitouch:
|
||||
dispatch_queue.append(('mouse_pos', (
|
||||
points[0]['x'] * Window.width,
|
||||
points[0]['y'] * Window.height)))
|
||||
|
||||
actives = [args['id']
|
||||
for args in points
|
||||
if 'id' in args and '_avoid' not in args]
|
||||
for args in points:
|
||||
tid = args['id']
|
||||
try:
|
||||
touch = touches[tid]
|
||||
if touch.sx == args['x'] and touch.sy == args['y']:
|
||||
continue
|
||||
touch.move(args)
|
||||
if tid not in touches_sent:
|
||||
queue.append(('begin', touch))
|
||||
touches_sent.append(tid)
|
||||
queue.append(('update', touch))
|
||||
except KeyError:
|
||||
if '_avoid' not in args:
|
||||
touch = HIDMotionEvent(device, tid, args)
|
||||
touches[touch.id] = touch
|
||||
if tid not in touches_sent:
|
||||
queue.append(('begin', touch))
|
||||
touches_sent.append(tid)
|
||||
|
||||
for tid in list(touches.keys())[:]:
|
||||
if tid not in actives:
|
||||
touch = touches[tid]
|
||||
if tid in touches_sent:
|
||||
touch.update_time_end()
|
||||
queue.append(('end', touch))
|
||||
touches_sent.remove(tid)
|
||||
del touches[tid]
|
||||
|
||||
def normalize(value, vmin, vmax):
|
||||
return (value - vmin) / float(vmax - vmin)
|
||||
|
||||
# open the input
|
||||
fd = open(input_fn, 'rb')
|
||||
|
||||
# get the controller name (EVIOCGNAME)
|
||||
device_name = fcntl.ioctl(fd, EVIOCGNAME + (256 << 16),
|
||||
" " * 256).decode().strip()
|
||||
Logger.info('HIDMotionEvent: using <%s>' % device_name)
|
||||
|
||||
# get abs infos
|
||||
bit = fcntl.ioctl(fd, EVIOCGBIT + (EV_MAX << 16), ' ' * sz_l)
|
||||
bit, = struct.unpack('Q', bit)
|
||||
is_multitouch = False
|
||||
for x in range(EV_MAX):
|
||||
# preserve this, we may want other things than EV_ABS
|
||||
if x != EV_ABS:
|
||||
continue
|
||||
# EV_ABS available for this device ?
|
||||
if (bit & (1 << x)) == 0:
|
||||
continue
|
||||
# ask abs info keys to the devices
|
||||
sbit = fcntl.ioctl(fd, EVIOCGBIT + x + (KEY_MAX << 16),
|
||||
' ' * sz_l)
|
||||
sbit, = struct.unpack('Q', sbit)
|
||||
for y in range(KEY_MAX):
|
||||
if (sbit & (1 << y)) == 0:
|
||||
continue
|
||||
absinfo = fcntl.ioctl(fd, EVIOCGABS + y +
|
||||
(struct_input_absinfo_sz << 16),
|
||||
' ' * struct_input_absinfo_sz)
|
||||
abs_value, abs_min, abs_max, abs_fuzz, \
|
||||
abs_flat, abs_res = struct.unpack('iiiiii', absinfo)
|
||||
if y == ABS_MT_POSITION_X:
|
||||
is_multitouch = True
|
||||
range_min_position_x = drs('min_position_x', abs_min)
|
||||
range_max_position_x = drs('max_position_x', abs_max)
|
||||
Logger.info('HIDMotionEvent: ' +
|
||||
'<%s> range position X is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_MT_POSITION_Y:
|
||||
is_multitouch = True
|
||||
range_min_position_y = drs('min_position_y', abs_min)
|
||||
range_max_position_y = drs('max_position_y', abs_max)
|
||||
Logger.info('HIDMotionEvent: ' +
|
||||
'<%s> range position Y is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_MT_PRESSURE:
|
||||
range_min_pressure = drs('min_pressure', abs_min)
|
||||
range_max_pressure = drs('max_pressure', abs_max)
|
||||
Logger.info('HIDMotionEvent: ' +
|
||||
'<%s> range pressure is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_X:
|
||||
range_min_abs_x = drs('min_abs_x', abs_min)
|
||||
range_max_abs_x = drs('max_abs_x', abs_max)
|
||||
Logger.info('HIDMotionEvent: ' +
|
||||
'<%s> range ABS X position is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_Y:
|
||||
range_min_abs_y = drs('min_abs_y', abs_min)
|
||||
range_max_abs_y = drs('max_abs_y', abs_max)
|
||||
Logger.info('HIDMotionEvent: ' +
|
||||
'<%s> range ABS Y position is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_PRESSURE:
|
||||
range_min_abs_pressure = drs(
|
||||
'min_abs_pressure', abs_min)
|
||||
range_max_abs_pressure = drs(
|
||||
'max_abs_pressure', abs_max)
|
||||
Logger.info('HIDMotionEvent: ' +
|
||||
'<%s> range ABS pressure is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
|
||||
# init the point
|
||||
if not is_multitouch:
|
||||
point = {'x': .5, 'y': .5, 'id': 0, '_avoid': True}
|
||||
|
||||
# read until the end
|
||||
while fd:
|
||||
|
||||
data = fd.read(struct_input_event_sz)
|
||||
if len(data) < struct_input_event_sz:
|
||||
break
|
||||
|
||||
# extract each event
|
||||
for i in range(int(len(data) / struct_input_event_sz)):
|
||||
ev = data[i * struct_input_event_sz:]
|
||||
|
||||
# extract timeval + event infos
|
||||
infos = struct.unpack('LLHHi', ev[:struct_input_event_sz])
|
||||
|
||||
if is_multitouch:
|
||||
process_as_multitouch(*infos)
|
||||
else:
|
||||
process_as_mouse_or_keyboard(*infos)
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
# dispatch all events from threads
|
||||
dispatch_queue = self.dispatch_queue
|
||||
n = len(dispatch_queue)
|
||||
for name, args in dispatch_queue[:n]:
|
||||
if name == 'mouse_pos':
|
||||
Window.mouse_pos = args
|
||||
elif name == 'key_down':
|
||||
if not Window.dispatch('on_key_down', *args):
|
||||
Window.dispatch('on_keyboard', *args)
|
||||
elif name == 'key_up':
|
||||
Window.dispatch('on_key_up', *args)
|
||||
del dispatch_queue[:n]
|
||||
|
||||
try:
|
||||
while True:
|
||||
event_type, touch = self.queue.popleft()
|
||||
dispatch_fn(event_type, touch)
|
||||
except:
|
||||
pass
|
||||
|
||||
MotionEventFactory.register('hidinput', HIDInputMotionEventProvider)
|
||||
113
kivy/input/providers/leapfinger.py
Normal file
113
kivy/input/providers/leapfinger.py
Normal file
@@ -0,0 +1,113 @@
|
||||
'''
|
||||
Leap Motion - finger only
|
||||
=========================
|
||||
'''
|
||||
|
||||
__all__ = ('LeapFingerEventProvider', 'LeapFingerEvent')
|
||||
|
||||
from collections import deque
|
||||
from kivy.logger import Logger
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
|
||||
_LEAP_QUEUE = deque()
|
||||
|
||||
Leap = InteractionBox = None
|
||||
|
||||
|
||||
def normalize(value, a, b):
|
||||
return (value - a) / float(b - a)
|
||||
|
||||
|
||||
class LeapFingerEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.profile = ('pos', 'pos3d',)
|
||||
|
||||
def depack(self, args):
|
||||
super().depack(args)
|
||||
if args[0] is None:
|
||||
return
|
||||
x, y, z = args
|
||||
self.sx = normalize(x, -150, 150)
|
||||
self.sy = normalize(y, 40, 460)
|
||||
self.sz = normalize(z, -350, 350)
|
||||
self.z = z
|
||||
|
||||
|
||||
class LeapFingerEventProvider(MotionEventProvider):
|
||||
|
||||
__handlers__ = {}
|
||||
|
||||
def start(self):
|
||||
# don't do the import at start, or the error will be always displayed
|
||||
# for user who don't have Leap
|
||||
global Leap, InteractionBox
|
||||
import Leap
|
||||
from Leap import InteractionBox
|
||||
|
||||
class LeapMotionListener(Leap.Listener):
|
||||
|
||||
def on_init(self, controller):
|
||||
Logger.info('leapmotion: Initialized')
|
||||
|
||||
def on_connect(self, controller):
|
||||
Logger.info('leapmotion: Connected')
|
||||
|
||||
def on_disconnect(self, controller):
|
||||
Logger.info('leapmotion: Disconnected')
|
||||
|
||||
def on_frame(self, controller):
|
||||
frame = controller.frame()
|
||||
_LEAP_QUEUE.append(frame)
|
||||
|
||||
def on_exit(self, controller):
|
||||
pass
|
||||
|
||||
self.uid = 0
|
||||
self.touches = {}
|
||||
self.listener = LeapMotionListener()
|
||||
self.controller = Leap.Controller(self.listener)
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
try:
|
||||
while True:
|
||||
frame = _LEAP_QUEUE.popleft()
|
||||
events = self.process_frame(frame)
|
||||
for ev in events:
|
||||
dispatch_fn(*ev)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
def process_frame(self, frame):
|
||||
events = []
|
||||
touches = self.touches
|
||||
available_uid = []
|
||||
for hand in frame.hands:
|
||||
for finger in hand.fingers:
|
||||
# print(hand.id(), finger.id(), finger.tip())
|
||||
uid = '{0}:{1}'.format(hand.id, finger.id)
|
||||
available_uid.append(uid)
|
||||
position = finger.tip_position
|
||||
args = (position.x, position.y, position.z)
|
||||
if uid not in touches:
|
||||
touch = LeapFingerEvent(self.device, uid, args)
|
||||
events.append(('begin', touch))
|
||||
touches[uid] = touch
|
||||
else:
|
||||
touch = touches[uid]
|
||||
touch.move(args)
|
||||
events.append(('update', touch))
|
||||
for key in list(touches.keys())[:]:
|
||||
if key not in available_uid:
|
||||
events.append(('end', touches[key]))
|
||||
del touches[key]
|
||||
return events
|
||||
|
||||
|
||||
# registers
|
||||
MotionEventFactory.register('leapfinger', LeapFingerEventProvider)
|
||||
396
kivy/input/providers/linuxwacom.py
Normal file
396
kivy/input/providers/linuxwacom.py
Normal file
@@ -0,0 +1,396 @@
|
||||
'''
|
||||
Native support of Wacom tablet from linuxwacom driver
|
||||
=====================================================
|
||||
|
||||
To configure LinuxWacom, add this to your configuration::
|
||||
|
||||
[input]
|
||||
pen = linuxwacom,/dev/input/event2,mode=pen
|
||||
finger = linuxwacom,/dev/input/event3,mode=touch
|
||||
|
||||
.. note::
|
||||
You must have read access to the input event.
|
||||
|
||||
You can use a custom range for the X, Y and pressure values.
|
||||
On some drivers, the range reported is invalid.
|
||||
To fix that, you can add these options to the argument line:
|
||||
|
||||
* invert_x : 1 to invert X axis
|
||||
* invert_y : 1 to invert Y axis
|
||||
* min_position_x : X minimum
|
||||
* max_position_x : X maximum
|
||||
* min_position_y : Y minimum
|
||||
* max_position_y : Y maximum
|
||||
* min_pressure : pressure minimum
|
||||
* max_pressure : pressure maximum
|
||||
'''
|
||||
|
||||
__all__ = ('LinuxWacomMotionEventProvider', 'LinuxWacomMotionEvent')
|
||||
|
||||
import os
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
from kivy.input.shape import ShapeRect
|
||||
|
||||
|
||||
class LinuxWacomMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def depack(self, args):
|
||||
self.sx = args['x']
|
||||
self.sy = args['y']
|
||||
self.profile = ['pos']
|
||||
if 'size_w' in args and 'size_h' in args:
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = args['size_w']
|
||||
self.shape.height = args['size_h']
|
||||
self.profile.append('shape')
|
||||
if 'pressure' in args:
|
||||
self.pressure = args['pressure']
|
||||
self.profile.append('pressure')
|
||||
super().depack(args)
|
||||
|
||||
def __str__(self):
|
||||
return '<LinuxWacomMotionEvent id=%d pos=(%f, %f) device=%s>' \
|
||||
% (self.id, self.sx, self.sy, self.device)
|
||||
|
||||
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
# documentation hack
|
||||
LinuxWacomMotionEventProvider = None
|
||||
|
||||
else:
|
||||
import threading
|
||||
import collections
|
||||
import struct
|
||||
import fcntl
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.logger import Logger
|
||||
|
||||
#
|
||||
# This part is taken from linux-source-2.6.32/include/linux/input.h
|
||||
#
|
||||
|
||||
# Event types
|
||||
EV_SYN = 0x00
|
||||
EV_KEY = 0x01
|
||||
EV_REL = 0x02
|
||||
EV_ABS = 0x03
|
||||
EV_MSC = 0x04
|
||||
EV_SW = 0x05
|
||||
EV_LED = 0x11
|
||||
EV_SND = 0x12
|
||||
EV_REP = 0x14
|
||||
EV_FF = 0x15
|
||||
EV_PWR = 0x16
|
||||
EV_FF_STATUS = 0x17
|
||||
EV_MAX = 0x1f
|
||||
EV_CNT = (EV_MAX + 1)
|
||||
|
||||
KEY_MAX = 0x2ff
|
||||
|
||||
# Synchronization events
|
||||
SYN_REPORT = 0
|
||||
SYN_CONFIG = 1
|
||||
SYN_MT_REPORT = 2
|
||||
|
||||
# Misc events
|
||||
MSC_SERIAL = 0x00
|
||||
MSC_PULSELED = 0x01
|
||||
MSC_GESTURE = 0x02
|
||||
MSC_RAW = 0x03
|
||||
MSC_SCAN = 0x04
|
||||
MSC_MAX = 0x07
|
||||
MSC_CNT = (MSC_MAX + 1)
|
||||
|
||||
ABS_X = 0x00
|
||||
ABS_Y = 0x01
|
||||
ABS_PRESSURE = 0x18
|
||||
ABS_MISC = 0x28 # if 0, it's touch up
|
||||
ABS_MT_TOUCH_MAJOR = 0x30 # Major axis of touching ellipse
|
||||
ABS_MT_TOUCH_MINOR = 0x31 # Minor axis (omit if circular)
|
||||
ABS_MT_WIDTH_MAJOR = 0x32 # Major axis of approaching ellipse
|
||||
ABS_MT_WIDTH_MINOR = 0x33 # Minor axis (omit if circular)
|
||||
ABS_MT_ORIENTATION = 0x34 # Ellipse orientation
|
||||
ABS_MT_POSITION_X = 0x35 # Center X ellipse position
|
||||
ABS_MT_POSITION_Y = 0x36 # Center Y ellipse position
|
||||
ABS_MT_TOOL_TYPE = 0x37 # Type of touching device
|
||||
ABS_MT_BLOB_ID = 0x38 # Group a set of packets as a blob
|
||||
ABS_MT_TRACKING_ID = 0x39 # Unique ID of initiated contact
|
||||
ABS_MT_PRESSURE = 0x3a # Pressure on contact area
|
||||
|
||||
# some ioctl base (with 0 value)
|
||||
EVIOCGNAME = 2147501318
|
||||
EVIOCGBIT = 2147501344
|
||||
EVIOCGABS = 2149074240
|
||||
|
||||
# sizeof(struct input_event)
|
||||
struct_input_event_sz = struct.calcsize('LLHHi')
|
||||
struct_input_absinfo_sz = struct.calcsize('iiiiii')
|
||||
sz_l = struct.calcsize('Q')
|
||||
|
||||
class LinuxWacomMotionEventProvider(MotionEventProvider):
|
||||
|
||||
options = ('min_position_x', 'max_position_x',
|
||||
'min_position_y', 'max_position_y',
|
||||
'min_pressure', 'max_pressure',
|
||||
'invert_x', 'invert_y')
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(LinuxWacomMotionEventProvider, self).__init__(device, args)
|
||||
self.input_fn = None
|
||||
self.default_ranges = dict()
|
||||
self.mode = 'touch'
|
||||
|
||||
# split arguments
|
||||
args = args.split(',')
|
||||
if not args:
|
||||
Logger.error('LinuxWacom: No filename given in config')
|
||||
Logger.error('LinuxWacom: Use /dev/input/event0 for example')
|
||||
return
|
||||
|
||||
# read filename
|
||||
self.input_fn = args[0]
|
||||
Logger.info('LinuxWacom: Read event from <%s>' % self.input_fn)
|
||||
|
||||
# read parameters
|
||||
for arg in args[1:]:
|
||||
if arg == '':
|
||||
continue
|
||||
arg = arg.split('=')
|
||||
|
||||
# ensure it's a key = value
|
||||
if len(arg) != 2:
|
||||
err = 'LinuxWacom: Bad parameter' \
|
||||
'%s: Not in key=value format.' % arg
|
||||
Logger.error(err)
|
||||
continue
|
||||
|
||||
# ensure the key exist
|
||||
key, value = arg
|
||||
if key == 'mode':
|
||||
self.mode = value
|
||||
continue
|
||||
|
||||
if key not in LinuxWacomMotionEventProvider.options:
|
||||
Logger.error('LinuxWacom: unknown %s option' % key)
|
||||
continue
|
||||
|
||||
# ensure the value
|
||||
try:
|
||||
self.default_ranges[key] = int(value)
|
||||
except ValueError:
|
||||
err = 'LinuxWacom: value %s invalid for %s' % (key, value)
|
||||
Logger.error(err)
|
||||
continue
|
||||
|
||||
# all good!
|
||||
msg = 'LinuxWacom: Set custom %s to %d' % (key, int(value))
|
||||
Logger.info(msg)
|
||||
Logger.info('LinuxWacom: mode is <%s>' % self.mode)
|
||||
|
||||
def start(self):
|
||||
if self.input_fn is None:
|
||||
return
|
||||
self.uid = 0
|
||||
self.queue = collections.deque()
|
||||
self.thread = threading.Thread(
|
||||
target=self._thread_run,
|
||||
kwargs=dict(
|
||||
queue=self.queue,
|
||||
input_fn=self.input_fn,
|
||||
device=self.device,
|
||||
default_ranges=self.default_ranges))
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def _thread_run(self, **kwargs):
|
||||
input_fn = kwargs.get('input_fn')
|
||||
queue = kwargs.get('queue')
|
||||
device = kwargs.get('device')
|
||||
drs = kwargs.get('default_ranges').get
|
||||
touches = {}
|
||||
touches_sent = []
|
||||
l_points = {}
|
||||
|
||||
# prepare some vars to get limit of some component
|
||||
range_min_position_x = 0
|
||||
range_max_position_x = 2048
|
||||
range_min_position_y = 0
|
||||
range_max_position_y = 2048
|
||||
range_min_pressure = 0
|
||||
range_max_pressure = 255
|
||||
invert_x = int(bool(drs('invert_x', 0)))
|
||||
invert_y = int(bool(drs('invert_y', 0)))
|
||||
reset_touch = False
|
||||
|
||||
def process(points):
|
||||
actives = list(points.keys())
|
||||
for args in points.values():
|
||||
tid = args['id']
|
||||
try:
|
||||
touch = touches[tid]
|
||||
except KeyError:
|
||||
touch = LinuxWacomMotionEvent(device, tid, args)
|
||||
touches[touch.id] = touch
|
||||
if touch.sx == args['x'] \
|
||||
and touch.sy == args['y'] \
|
||||
and tid in touches_sent:
|
||||
continue
|
||||
touch.move(args)
|
||||
if tid not in touches_sent:
|
||||
queue.append(('begin', touch))
|
||||
touches_sent.append(tid)
|
||||
queue.append(('update', touch))
|
||||
|
||||
for tid in list(touches.keys())[:]:
|
||||
if tid not in actives:
|
||||
touch = touches[tid]
|
||||
if tid in touches_sent:
|
||||
touch.update_time_end()
|
||||
queue.append(('end', touch))
|
||||
touches_sent.remove(tid)
|
||||
del touches[tid]
|
||||
|
||||
def normalize(value, vmin, vmax):
|
||||
return (value - vmin) / float(vmax - vmin)
|
||||
|
||||
# open the input
|
||||
try:
|
||||
fd = open(input_fn, 'rb')
|
||||
except IOError:
|
||||
Logger.exception('Unable to open %s' % input_fn)
|
||||
return
|
||||
|
||||
# get the controller name (EVIOCGNAME)
|
||||
device_name = fcntl.ioctl(fd, EVIOCGNAME + (256 << 16),
|
||||
" " * 256).split('\x00')[0]
|
||||
Logger.info('LinuxWacom: using <%s>' % device_name)
|
||||
|
||||
# get abs infos
|
||||
bit = fcntl.ioctl(fd, EVIOCGBIT + (EV_MAX << 16), ' ' * sz_l)
|
||||
bit, = struct.unpack('Q', bit)
|
||||
for x in range(EV_MAX):
|
||||
# preserve this, we may want other things than EV_ABS
|
||||
if x != EV_ABS:
|
||||
continue
|
||||
# EV_ABS available for this device ?
|
||||
if (bit & (1 << x)) == 0:
|
||||
continue
|
||||
# ask abs info keys to the devices
|
||||
sbit = fcntl.ioctl(fd, EVIOCGBIT + x + (KEY_MAX << 16),
|
||||
' ' * sz_l)
|
||||
sbit, = struct.unpack('Q', sbit)
|
||||
for y in range(KEY_MAX):
|
||||
if (sbit & (1 << y)) == 0:
|
||||
continue
|
||||
absinfo = fcntl.ioctl(fd, EVIOCGABS + y +
|
||||
(struct_input_absinfo_sz << 16),
|
||||
' ' * struct_input_absinfo_sz)
|
||||
abs_value, abs_min, abs_max, abs_fuzz, \
|
||||
abs_flat, abs_res = struct.unpack('iiiiii', absinfo)
|
||||
if y == ABS_X:
|
||||
range_min_position_x = drs('min_position_x', abs_min)
|
||||
range_max_position_x = drs('max_position_x', abs_max)
|
||||
Logger.info('LinuxWacom: ' +
|
||||
'<%s> range position X is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_Y:
|
||||
range_min_position_y = drs('min_position_y', abs_min)
|
||||
range_max_position_y = drs('max_position_y', abs_max)
|
||||
Logger.info('LinuxWacom: ' +
|
||||
'<%s> range position Y is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
elif y == ABS_PRESSURE:
|
||||
range_min_pressure = drs('min_pressure', abs_min)
|
||||
range_max_pressure = drs('max_pressure', abs_max)
|
||||
Logger.info('LinuxWacom: ' +
|
||||
'<%s> range pressure is %d - %d' % (
|
||||
device_name, abs_min, abs_max))
|
||||
|
||||
# read until the end
|
||||
changed = False
|
||||
touch_id = 0
|
||||
touch_x = 0
|
||||
touch_y = 0
|
||||
touch_pressure = 0
|
||||
while fd:
|
||||
|
||||
data = fd.read(struct_input_event_sz)
|
||||
if len(data) < struct_input_event_sz:
|
||||
break
|
||||
|
||||
# extract each event
|
||||
for i in range(len(data) / struct_input_event_sz):
|
||||
ev = data[i * struct_input_event_sz:]
|
||||
|
||||
# extract timeval + event infos
|
||||
tv_sec, tv_usec, ev_type, ev_code, ev_value = \
|
||||
struct.unpack('LLHHi', ev[:struct_input_event_sz])
|
||||
|
||||
if ev_type == EV_SYN and ev_code == SYN_REPORT:
|
||||
if touch_id in l_points:
|
||||
p = l_points[touch_id]
|
||||
else:
|
||||
p = dict()
|
||||
l_points[touch_id] = p
|
||||
p['id'] = touch_id
|
||||
if not reset_touch:
|
||||
p['x'] = touch_x
|
||||
p['y'] = touch_y
|
||||
p['pressure'] = touch_pressure
|
||||
if self.mode == 'pen' \
|
||||
and touch_pressure == 0 \
|
||||
and not reset_touch:
|
||||
del l_points[touch_id]
|
||||
if changed:
|
||||
if 'x' not in p:
|
||||
reset_touch = False
|
||||
continue
|
||||
process(l_points)
|
||||
changed = False
|
||||
if reset_touch:
|
||||
l_points.clear()
|
||||
reset_touch = False
|
||||
process(l_points)
|
||||
elif ev_type == EV_MSC and ev_code == MSC_SERIAL:
|
||||
touch_id = ev_value
|
||||
elif ev_type == EV_ABS and ev_code == ABS_X:
|
||||
val = normalize(ev_value,
|
||||
range_min_position_x,
|
||||
range_max_position_x)
|
||||
if invert_x:
|
||||
val = 1. - val
|
||||
touch_x = val
|
||||
changed = True
|
||||
elif ev_type == EV_ABS and ev_code == ABS_Y:
|
||||
val = 1. - normalize(ev_value,
|
||||
range_min_position_y,
|
||||
range_max_position_y)
|
||||
if invert_y:
|
||||
val = 1. - val
|
||||
touch_y = val
|
||||
changed = True
|
||||
elif ev_type == EV_ABS and ev_code == ABS_PRESSURE:
|
||||
touch_pressure = normalize(ev_value,
|
||||
range_min_pressure,
|
||||
range_max_pressure)
|
||||
changed = True
|
||||
elif ev_type == EV_ABS and ev_code == ABS_MISC:
|
||||
if ev_value == 0:
|
||||
reset_touch = True
|
||||
|
||||
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
|
||||
|
||||
MotionEventFactory.register('linuxwacom', LinuxWacomMotionEventProvider)
|
||||
220
kivy/input/providers/mactouch.py
Normal file
220
kivy/input/providers/mactouch.py
Normal file
@@ -0,0 +1,220 @@
|
||||
'''
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
424
kivy/input/providers/mouse.py
Normal file
424
kivy/input/providers/mouse.py
Normal file
@@ -0,0 +1,424 @@
|
||||
'''
|
||||
Mouse provider implementation
|
||||
=============================
|
||||
|
||||
On linux systems, the mouse provider can be annoying when used with another
|
||||
multitouch provider (hidinput or mtdev). The Mouse can conflict with them: a
|
||||
single touch can generate one event from the mouse provider and another
|
||||
from the multitouch provider.
|
||||
|
||||
To avoid this behavior, you can activate the "disable_on_activity" token in
|
||||
the mouse configuration. Then, if any touches are created by another
|
||||
provider, the mouse event will be discarded. Add this to your configuration::
|
||||
|
||||
[input]
|
||||
mouse = mouse,disable_on_activity
|
||||
|
||||
Using multitouch interaction with the mouse
|
||||
-------------------------------------------
|
||||
|
||||
.. versionadded:: 1.3.0
|
||||
|
||||
By default, the middle and right mouse buttons, as well as a combination of
|
||||
ctrl + left mouse button are used for multitouch emulation.
|
||||
If you want to use them for other purposes, you can disable this behavior by
|
||||
activating the "disable_multitouch" token::
|
||||
|
||||
[input]
|
||||
mouse = mouse,disable_multitouch
|
||||
|
||||
.. versionchanged:: 1.9.0
|
||||
|
||||
You can now selectively control whether a click initiated as described above
|
||||
will emulate multi-touch. If the touch has been initiated in the above manner
|
||||
(e.g. right mouse button), a `multitouch_sim` value will be added to the
|
||||
touch's profile, and a `multitouch_sim` property will be added to the touch.
|
||||
By default, `multitouch_sim` is True and multitouch will be emulated for that
|
||||
touch. If, however, `multitouch_on_demand` is added to the config::
|
||||
|
||||
[input]
|
||||
mouse = mouse,multitouch_on_demand
|
||||
|
||||
then `multitouch_sim` defaults to `False`. In that case, if `multitouch_sim`
|
||||
is set to True before the mouse is released (e.g. in on_touch_down/move), the
|
||||
touch will simulate a multi-touch event. For example::
|
||||
|
||||
if 'multitouch_sim' in touch.profile:
|
||||
touch.multitouch_sim = True
|
||||
|
||||
.. versionchanged:: 2.1.0
|
||||
|
||||
Provider dispatches hover events by listening to properties/events in
|
||||
:class:`~kivy.core.window.Window`. Dispatching can be disabled by setting
|
||||
:attr:`MouseMotionEventProvider.disable_hover` to ``True`` or by adding
|
||||
`disable_hover` in the config::
|
||||
|
||||
[input]
|
||||
mouse = mouse,disable_hover
|
||||
|
||||
It's also possible to enable/disable hover events at runtime with
|
||||
:attr:`MouseMotionEventProvider.disable_hover` property.
|
||||
|
||||
Following is a list of the supported values for the
|
||||
:attr:`~kivy.input.motionevent.MotionEvent.profile` property list.
|
||||
|
||||
================ ==========================================================
|
||||
Profile value Description
|
||||
---------------- ----------------------------------------------------------
|
||||
button Mouse button (one of `left`, `right`, `middle`, `scrollup`
|
||||
or `scrolldown`). Accessed via the 'button' property.
|
||||
pos 2D position. Also reflected in the
|
||||
:attr:`~kivy.input.motionevent.MotionEvent.x`,
|
||||
:attr:`~kivy.input.motionevent.MotionEvent.y`
|
||||
and :attr:`~kivy.input.motionevent.MotionEvent.pos`
|
||||
properties.
|
||||
multitouch_sim Specifies whether multitouch is simulated or not. Accessed
|
||||
via the 'multitouch_sim' property.
|
||||
================ ==========================================================
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ('MouseMotionEventProvider', )
|
||||
|
||||
from kivy.base import EventLoop
|
||||
from collections import deque
|
||||
from kivy.logger import Logger
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
|
||||
# late binding
|
||||
Color = Ellipse = None
|
||||
|
||||
|
||||
class MouseMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.multitouch_sim = False
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def depack(self, args):
|
||||
self.sx, self.sy = args[:2]
|
||||
profile = self.profile
|
||||
if self.is_touch:
|
||||
# don't overwrite previous profile
|
||||
if not profile:
|
||||
profile.extend(('pos', 'button'))
|
||||
if len(args) >= 3:
|
||||
self.button = args[2]
|
||||
if len(args) == 4:
|
||||
self.multitouch_sim = args[3]
|
||||
profile.append('multitouch_sim')
|
||||
else:
|
||||
if not profile:
|
||||
profile.append('pos')
|
||||
super().depack(args)
|
||||
|
||||
#
|
||||
# Create automatically touch on the surface.
|
||||
#
|
||||
|
||||
def update_graphics(self, win, create=False):
|
||||
global Color, Ellipse
|
||||
de = self.ud.get('_drawelement', None)
|
||||
if de is None and create:
|
||||
if Color is None:
|
||||
from kivy.graphics import Color, Ellipse
|
||||
with win.canvas.after:
|
||||
de = (
|
||||
Color(.8, .2, .2, .7),
|
||||
Ellipse(size=(20, 20), segments=15))
|
||||
self.ud._drawelement = de
|
||||
if de is not None:
|
||||
self.push()
|
||||
|
||||
# use same logic as WindowBase.on_motion() so we get correct
|
||||
# coordinates when _density != 1
|
||||
w, h = win._get_effective_size()
|
||||
|
||||
self.scale_for_screen(w, h, rotation=win.rotation)
|
||||
|
||||
de[1].pos = self.x - 10, self.y - 10
|
||||
self.pop()
|
||||
|
||||
def clear_graphics(self, win):
|
||||
de = self.ud.pop('_drawelement', None)
|
||||
if de is not None:
|
||||
win.canvas.after.remove(de[0])
|
||||
win.canvas.after.remove(de[1])
|
||||
|
||||
|
||||
class MouseMotionEventProvider(MotionEventProvider):
|
||||
__handlers__ = {}
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(MouseMotionEventProvider, self).__init__(device, args)
|
||||
self.waiting_event = deque()
|
||||
self.touches = {}
|
||||
self.counter = 0
|
||||
self.current_drag = None
|
||||
self.alt_touch = None
|
||||
self.disable_on_activity = False
|
||||
self.disable_multitouch = False
|
||||
self.multitouch_on_demand = False
|
||||
self.hover_event = None
|
||||
self._disable_hover = False
|
||||
self._running = False
|
||||
# split arguments
|
||||
args = args.split(',')
|
||||
for arg in args:
|
||||
arg = arg.strip()
|
||||
if arg == '':
|
||||
continue
|
||||
elif arg == 'disable_on_activity':
|
||||
self.disable_on_activity = True
|
||||
elif arg == 'disable_multitouch':
|
||||
self.disable_multitouch = True
|
||||
elif arg == 'disable_hover':
|
||||
self.disable_hover = True
|
||||
elif arg == 'multitouch_on_demand':
|
||||
self.multitouch_on_demand = True
|
||||
else:
|
||||
Logger.error('Mouse: unknown parameter <%s>' % arg)
|
||||
|
||||
def _get_disable_hover(self):
|
||||
return self._disable_hover
|
||||
|
||||
def _set_disable_hover(self, value):
|
||||
if self._disable_hover != value:
|
||||
if self._running:
|
||||
if value:
|
||||
self._stop_hover_events()
|
||||
else:
|
||||
self._start_hover_events()
|
||||
self._disable_hover = value
|
||||
|
||||
disable_hover = property(_get_disable_hover, _set_disable_hover)
|
||||
'''Disables dispatching of hover events if set to ``True``.
|
||||
|
||||
Hover events are enabled by default (`disable_hover` is ``False``). See
|
||||
module documentation if you want to enable/disable hover events through
|
||||
config file.
|
||||
|
||||
.. versionadded:: 2.1.0
|
||||
'''
|
||||
|
||||
def start(self):
|
||||
'''Start the mouse provider'''
|
||||
if not EventLoop.window:
|
||||
return
|
||||
fbind = EventLoop.window.fbind
|
||||
fbind('on_mouse_down', self.on_mouse_press)
|
||||
fbind('on_mouse_move', self.on_mouse_motion)
|
||||
fbind('on_mouse_up', self.on_mouse_release)
|
||||
fbind('on_rotate', self.update_touch_graphics)
|
||||
fbind('system_size', self.update_touch_graphics)
|
||||
if not self.disable_hover:
|
||||
self._start_hover_events()
|
||||
self._running = True
|
||||
|
||||
def _start_hover_events(self):
|
||||
fbind = EventLoop.window.fbind
|
||||
fbind('mouse_pos', self.begin_or_update_hover_event)
|
||||
fbind('system_size', self.update_hover_event)
|
||||
fbind('on_cursor_enter', self.begin_hover_event)
|
||||
fbind('on_cursor_leave', self.end_hover_event)
|
||||
fbind('on_close', self.end_hover_event)
|
||||
fbind('on_rotate', self.update_hover_event)
|
||||
|
||||
def stop(self):
|
||||
'''Stop the mouse provider'''
|
||||
if not EventLoop.window:
|
||||
return
|
||||
funbind = EventLoop.window.funbind
|
||||
funbind('on_mouse_down', self.on_mouse_press)
|
||||
funbind('on_mouse_move', self.on_mouse_motion)
|
||||
funbind('on_mouse_up', self.on_mouse_release)
|
||||
funbind('on_rotate', self.update_touch_graphics)
|
||||
funbind('system_size', self.update_touch_graphics)
|
||||
if not self.disable_hover:
|
||||
self._stop_hover_events()
|
||||
self._running = False
|
||||
|
||||
def _stop_hover_events(self):
|
||||
funbind = EventLoop.window.funbind
|
||||
funbind('mouse_pos', self.begin_or_update_hover_event)
|
||||
funbind('system_size', self.update_hover_event)
|
||||
funbind('on_cursor_enter', self.begin_hover_event)
|
||||
funbind('on_cursor_leave', self.end_hover_event)
|
||||
funbind('on_close', self.end_hover_event)
|
||||
funbind('on_rotate', self.update_hover_event)
|
||||
|
||||
def test_activity(self):
|
||||
if not self.disable_on_activity:
|
||||
return False
|
||||
# trying to get if we currently have other touch than us
|
||||
# discard touches generated from kinetic
|
||||
for touch in EventLoop.touches:
|
||||
# discard all kinetic touch
|
||||
if touch.__class__.__name__ == 'KineticMotionEvent':
|
||||
continue
|
||||
# not our instance, stop mouse
|
||||
if touch.__class__ != MouseMotionEvent:
|
||||
return True
|
||||
return False
|
||||
|
||||
def find_touch(self, win, x, y):
|
||||
factor = 10. / win.system_size[0]
|
||||
for touch in self.touches.values():
|
||||
if abs(x - touch.sx) < factor and abs(y - touch.sy) < factor:
|
||||
return touch
|
||||
return None
|
||||
|
||||
def create_event_id(self):
|
||||
self.counter += 1
|
||||
return self.device + str(self.counter)
|
||||
|
||||
def create_touch(self, win, nx, ny, is_double_tap, do_graphics, button):
|
||||
event_id = self.create_event_id()
|
||||
args = [nx, ny, button]
|
||||
if do_graphics:
|
||||
args += [not self.multitouch_on_demand]
|
||||
self.current_drag = touch = MouseMotionEvent(
|
||||
self.device, event_id, args,
|
||||
is_touch=True,
|
||||
type_id='touch'
|
||||
)
|
||||
touch.is_double_tap = is_double_tap
|
||||
self.touches[event_id] = touch
|
||||
if do_graphics:
|
||||
# only draw red circle if multitouch is not disabled, and
|
||||
# if the multitouch_on_demand feature is not enable
|
||||
# (because in that case, we wait to see if multitouch_sim
|
||||
# is True or not before doing the multitouch)
|
||||
create_flag = (
|
||||
not self.disable_multitouch
|
||||
and not self.multitouch_on_demand
|
||||
)
|
||||
touch.update_graphics(win, create_flag)
|
||||
self.waiting_event.append(('begin', touch))
|
||||
return touch
|
||||
|
||||
def remove_touch(self, win, touch):
|
||||
if touch.id in self.touches:
|
||||
del self.touches[touch.id]
|
||||
touch.update_time_end()
|
||||
self.waiting_event.append(('end', touch))
|
||||
touch.clear_graphics(win)
|
||||
|
||||
def create_hover(self, win, etype):
|
||||
nx, ny = win.to_normalized_pos(*win.mouse_pos)
|
||||
# Divide by density because it's used by mouse_pos
|
||||
nx /= win._density
|
||||
ny /= win._density
|
||||
args = (nx, ny)
|
||||
hover = self.hover_event
|
||||
if hover:
|
||||
hover.move(args)
|
||||
else:
|
||||
self.hover_event = hover = MouseMotionEvent(
|
||||
self.device,
|
||||
self.create_event_id(),
|
||||
args,
|
||||
type_id='hover'
|
||||
)
|
||||
if etype == 'end':
|
||||
hover.update_time_end()
|
||||
self.hover_event = None
|
||||
self.waiting_event.append((etype, hover))
|
||||
|
||||
def on_mouse_motion(self, win, x, y, modifiers):
|
||||
nx, ny = win.to_normalized_pos(x, y)
|
||||
ny = 1.0 - ny
|
||||
if self.current_drag:
|
||||
touch = self.current_drag
|
||||
touch.move([nx, ny])
|
||||
touch.update_graphics(win)
|
||||
self.waiting_event.append(('update', touch))
|
||||
elif self.alt_touch is not None and 'alt' not in modifiers:
|
||||
# alt just released ?
|
||||
is_double_tap = 'shift' in modifiers
|
||||
self.create_touch(win, nx, ny, is_double_tap, True, [])
|
||||
|
||||
def on_mouse_press(self, win, x, y, button, modifiers):
|
||||
if self.test_activity():
|
||||
return
|
||||
nx, ny = win.to_normalized_pos(x, y)
|
||||
ny = 1.0 - ny
|
||||
found_touch = self.find_touch(win, nx, ny)
|
||||
if found_touch:
|
||||
self.current_drag = found_touch
|
||||
else:
|
||||
is_double_tap = 'shift' in modifiers
|
||||
do_graphics = (
|
||||
not self.disable_multitouch
|
||||
and (button != 'left' or 'ctrl' in modifiers)
|
||||
)
|
||||
touch = self.create_touch(
|
||||
win, nx, ny, is_double_tap, do_graphics, button
|
||||
)
|
||||
if 'alt' in modifiers:
|
||||
self.alt_touch = touch
|
||||
self.current_drag = None
|
||||
|
||||
def on_mouse_release(self, win, x, y, button, modifiers):
|
||||
if button == 'all':
|
||||
# Special case, if button is all,
|
||||
# then remove all the current touches.
|
||||
for touch in list(self.touches.values()):
|
||||
self.remove_touch(win, touch)
|
||||
self.current_drag = None
|
||||
touch = self.current_drag
|
||||
if touch:
|
||||
not_right = button in (
|
||||
'left',
|
||||
'scrollup', 'scrolldown',
|
||||
'scrollleft', 'scrollright'
|
||||
)
|
||||
not_ctrl = 'ctrl' not in modifiers
|
||||
not_multi = (
|
||||
self.disable_multitouch
|
||||
or 'multitouch_sim' not in touch.profile
|
||||
or not touch.multitouch_sim
|
||||
)
|
||||
if not_right and not_ctrl or not_multi:
|
||||
self.remove_touch(win, touch)
|
||||
self.current_drag = None
|
||||
else:
|
||||
touch.update_graphics(win, True)
|
||||
if self.alt_touch:
|
||||
self.remove_touch(win, self.alt_touch)
|
||||
self.alt_touch = None
|
||||
|
||||
def update_touch_graphics(self, win, *args):
|
||||
for touch in self.touches.values():
|
||||
touch.update_graphics(win)
|
||||
|
||||
def begin_or_update_hover_event(self, win, *args):
|
||||
etype = 'update' if self.hover_event else 'begin'
|
||||
self.create_hover(win, etype)
|
||||
|
||||
def begin_hover_event(self, win, *args):
|
||||
if not self.hover_event:
|
||||
self.create_hover(win, 'begin')
|
||||
|
||||
def update_hover_event(self, win, *args):
|
||||
if self.hover_event:
|
||||
self.create_hover(win, 'update')
|
||||
|
||||
def end_hover_event(self, win, *args):
|
||||
if self.hover_event:
|
||||
self.create_hover(win, 'end')
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
'''Update the mouse provider (pop event from the queue)'''
|
||||
try:
|
||||
while True:
|
||||
event = self.waiting_event.popleft()
|
||||
dispatch_fn(*event)
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
|
||||
# registers
|
||||
MotionEventFactory.register('mouse', MouseMotionEventProvider)
|
||||
383
kivy/input/providers/mtdev.py
Normal file
383
kivy/input/providers/mtdev.py
Normal file
@@ -0,0 +1,383 @@
|
||||
'''
|
||||
Native support for Multitouch devices on Linux, using libmtdev.
|
||||
===============================================================
|
||||
|
||||
The Mtdev project is a part of the Ubuntu Maverick multitouch architecture.
|
||||
You can read more on http://wiki.ubuntu.com/Multitouch
|
||||
|
||||
To configure MTDev, it's preferable to use probesysfs providers.
|
||||
Check :py:class:`~kivy.input.providers.probesysfs` for more information.
|
||||
|
||||
Otherwise, add this to your configuration::
|
||||
|
||||
[input]
|
||||
# devicename = hidinput,/dev/input/eventXX
|
||||
acert230h = mtdev,/dev/input/event2
|
||||
|
||||
.. note::
|
||||
You must have read access to the input event.
|
||||
|
||||
You can use a custom range for the X, Y and pressure values.
|
||||
On some drivers, the range reported is invalid.
|
||||
To fix that, you can add these options to the argument line:
|
||||
|
||||
* invert_x : 1 to invert X axis
|
||||
* invert_y : 1 to invert Y axis
|
||||
* min_position_x : X minimum
|
||||
* max_position_x : X maximum
|
||||
* min_position_y : Y minimum
|
||||
* max_position_y : Y maximum
|
||||
* min_pressure : pressure minimum
|
||||
* max_pressure : pressure maximum
|
||||
* min_touch_major : width shape minimum
|
||||
* max_touch_major : width shape maximum
|
||||
* min_touch_minor : width shape minimum
|
||||
* max_touch_minor : height shape maximum
|
||||
* rotation : 0,90,180 or 270 to rotate
|
||||
|
||||
An inverted display configuration will look like this::
|
||||
|
||||
[input]
|
||||
# example for inverting touch events
|
||||
display = mtdev,/dev/input/event0,invert_x=1,invert_y=1
|
||||
'''
|
||||
|
||||
__all__ = ('MTDMotionEventProvider', 'MTDMotionEvent')
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import time
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
from kivy.input.shape import ShapeRect
|
||||
|
||||
|
||||
class MTDMotionEvent(MotionEvent):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def depack(self, args):
|
||||
if 'x' in args:
|
||||
self.sx = args['x']
|
||||
else:
|
||||
self.sx = -1
|
||||
if 'y' in args:
|
||||
self.sy = args['y']
|
||||
else:
|
||||
self.sy = -1
|
||||
self.profile = ['pos']
|
||||
if 'size_w' in args and 'size_h' in args:
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = args['size_w']
|
||||
self.shape.height = args['size_h']
|
||||
self.profile.append('shape')
|
||||
if 'pressure' in args:
|
||||
self.pressure = args['pressure']
|
||||
self.profile.append('pressure')
|
||||
super().depack(args)
|
||||
|
||||
def __str__(self):
|
||||
i, sx, sy, d = (self.id, self.sx, self.sy, self.device)
|
||||
return '<MTDMotionEvent id=%d pos=(%f, %f) device=%s>' % (i, sx, sy, d)
|
||||
|
||||
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
|
||||
# documentation hack
|
||||
MTDMotionEventProvider = None
|
||||
|
||||
else:
|
||||
import threading
|
||||
import collections
|
||||
from kivy.lib.mtdev import Device, \
|
||||
MTDEV_TYPE_EV_ABS, MTDEV_CODE_SLOT, MTDEV_CODE_POSITION_X, \
|
||||
MTDEV_CODE_POSITION_Y, MTDEV_CODE_PRESSURE, \
|
||||
MTDEV_CODE_TOUCH_MAJOR, MTDEV_CODE_TOUCH_MINOR, \
|
||||
MTDEV_CODE_TRACKING_ID, MTDEV_ABS_POSITION_X, \
|
||||
MTDEV_ABS_POSITION_Y, MTDEV_ABS_TOUCH_MINOR, \
|
||||
MTDEV_ABS_TOUCH_MAJOR
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.logger import Logger
|
||||
|
||||
class MTDMotionEventProvider(MotionEventProvider):
|
||||
|
||||
options = ('min_position_x', 'max_position_x',
|
||||
'min_position_y', 'max_position_y',
|
||||
'min_pressure', 'max_pressure',
|
||||
'min_touch_major', 'max_touch_major',
|
||||
'min_touch_minor', 'max_touch_minor',
|
||||
'invert_x', 'invert_y',
|
||||
'rotation')
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(MTDMotionEventProvider, self).__init__(device, args)
|
||||
self._device = None
|
||||
self.input_fn = None
|
||||
self.default_ranges = dict()
|
||||
|
||||
# split arguments
|
||||
args = args.split(',')
|
||||
if not args:
|
||||
Logger.error('MTD: No filename pass to MTD configuration')
|
||||
Logger.error('MTD: Use /dev/input/event0 for example')
|
||||
return
|
||||
|
||||
# read filename
|
||||
self.input_fn = args[0]
|
||||
Logger.info('MTD: Read event from <%s>' % self.input_fn)
|
||||
|
||||
# read parameters
|
||||
for arg in args[1:]:
|
||||
if arg == '':
|
||||
continue
|
||||
arg = arg.split('=')
|
||||
|
||||
# ensure it's a key = value
|
||||
if len(arg) != 2:
|
||||
err = 'MTD: Bad parameter %s: Not in key=value format' %\
|
||||
arg
|
||||
Logger.error(err)
|
||||
continue
|
||||
|
||||
# ensure the key exist
|
||||
key, value = arg
|
||||
if key not in MTDMotionEventProvider.options:
|
||||
Logger.error('MTD: unknown %s option' % key)
|
||||
continue
|
||||
|
||||
# ensure the value
|
||||
try:
|
||||
self.default_ranges[key] = int(value)
|
||||
except ValueError:
|
||||
err = 'MTD: invalid value %s for option %s' % (key, value)
|
||||
Logger.error(err)
|
||||
continue
|
||||
|
||||
# all good!
|
||||
Logger.info('MTD: Set custom %s to %d' % (key, int(value)))
|
||||
|
||||
if 'rotation' not in self.default_ranges:
|
||||
self.default_ranges['rotation'] = 0
|
||||
elif self.default_ranges['rotation'] not in (0, 90, 180, 270):
|
||||
Logger.error('HIDInput: invalid rotation value ({})'.format(
|
||||
self.default_ranges['rotation']))
|
||||
self.default_ranges['rotation'] = 0
|
||||
|
||||
def start(self):
|
||||
if self.input_fn is None:
|
||||
return
|
||||
self.uid = 0
|
||||
self.queue = collections.deque()
|
||||
self.thread = threading.Thread(
|
||||
name=self.__class__.__name__,
|
||||
target=self._thread_run,
|
||||
kwargs=dict(
|
||||
queue=self.queue,
|
||||
input_fn=self.input_fn,
|
||||
device=self.device,
|
||||
default_ranges=self.default_ranges))
|
||||
self.thread.daemon = True
|
||||
self.thread.start()
|
||||
|
||||
def _thread_run(self, **kwargs):
|
||||
input_fn = kwargs.get('input_fn')
|
||||
queue = kwargs.get('queue')
|
||||
device = kwargs.get('device')
|
||||
drs = kwargs.get('default_ranges').get
|
||||
touches = {}
|
||||
touches_sent = []
|
||||
point = {}
|
||||
l_points = {}
|
||||
|
||||
def assign_coord(point, value, invert, coords):
|
||||
cx, cy = coords
|
||||
if invert:
|
||||
value = 1. - value
|
||||
if rotation == 0:
|
||||
point[cx] = value
|
||||
elif rotation == 90:
|
||||
point[cy] = value
|
||||
elif rotation == 180:
|
||||
point[cx] = 1. - value
|
||||
elif rotation == 270:
|
||||
point[cy] = 1. - value
|
||||
|
||||
def process(points):
|
||||
for args in points:
|
||||
# this can happen if we have a touch going on already at
|
||||
# the start of the app
|
||||
if 'id' not in args:
|
||||
continue
|
||||
tid = args['id']
|
||||
try:
|
||||
touch = touches[tid]
|
||||
except KeyError:
|
||||
touch = MTDMotionEvent(device, tid, args)
|
||||
touches[touch.id] = touch
|
||||
touch.move(args)
|
||||
action = 'update'
|
||||
if tid not in touches_sent:
|
||||
action = 'begin'
|
||||
touches_sent.append(tid)
|
||||
if 'delete' in args:
|
||||
action = 'end'
|
||||
del args['delete']
|
||||
del touches[touch.id]
|
||||
touches_sent.remove(tid)
|
||||
touch.update_time_end()
|
||||
queue.append((action, touch))
|
||||
|
||||
def normalize(value, vmin, vmax):
|
||||
try:
|
||||
return (value - vmin) / float(vmax - vmin)
|
||||
except ZeroDivisionError: # it's both in py2 and py3
|
||||
return (value - vmin)
|
||||
|
||||
# open mtdev device
|
||||
_fn = input_fn
|
||||
_slot = 0
|
||||
try:
|
||||
_device = Device(_fn)
|
||||
except OSError as e:
|
||||
if e.errno == 13: # Permission denied
|
||||
Logger.warn(
|
||||
'MTD: Unable to open device "{0}". Please ensure you'
|
||||
' have the appropriate permissions.'.format(_fn))
|
||||
return
|
||||
else:
|
||||
raise
|
||||
_changes = set()
|
||||
|
||||
# prepare some vars to get limit of some component
|
||||
ab = _device.get_abs(MTDEV_ABS_POSITION_X)
|
||||
range_min_position_x = drs('min_position_x', ab.minimum)
|
||||
range_max_position_x = drs('max_position_x', ab.maximum)
|
||||
Logger.info('MTD: <%s> range position X is %d - %d' %
|
||||
(_fn, range_min_position_x, range_max_position_x))
|
||||
|
||||
ab = _device.get_abs(MTDEV_ABS_POSITION_Y)
|
||||
range_min_position_y = drs('min_position_y', ab.minimum)
|
||||
range_max_position_y = drs('max_position_y', ab.maximum)
|
||||
Logger.info('MTD: <%s> range position Y is %d - %d' %
|
||||
(_fn, range_min_position_y, range_max_position_y))
|
||||
|
||||
ab = _device.get_abs(MTDEV_ABS_TOUCH_MAJOR)
|
||||
range_min_major = drs('min_touch_major', ab.minimum)
|
||||
range_max_major = drs('max_touch_major', ab.maximum)
|
||||
Logger.info('MTD: <%s> range touch major is %d - %d' %
|
||||
(_fn, range_min_major, range_max_major))
|
||||
|
||||
ab = _device.get_abs(MTDEV_ABS_TOUCH_MINOR)
|
||||
range_min_minor = drs('min_touch_minor', ab.minimum)
|
||||
range_max_minor = drs('max_touch_minor', ab.maximum)
|
||||
Logger.info('MTD: <%s> range touch minor is %d - %d' %
|
||||
(_fn, range_min_minor, range_max_minor))
|
||||
|
||||
range_min_pressure = drs('min_pressure', 0)
|
||||
range_max_pressure = drs('max_pressure', 255)
|
||||
Logger.info('MTD: <%s> range pressure is %d - %d' %
|
||||
(_fn, range_min_pressure, range_max_pressure))
|
||||
|
||||
invert_x = int(bool(drs('invert_x', 0)))
|
||||
invert_y = int(bool(drs('invert_y', 0)))
|
||||
Logger.info('MTD: <%s> axes invertion: X is %d, Y is %d' %
|
||||
(_fn, invert_x, invert_y))
|
||||
|
||||
rotation = drs('rotation', 0)
|
||||
Logger.info('MTD: <%s> rotation set to %d' %
|
||||
(_fn, rotation))
|
||||
failures = 0
|
||||
while _device:
|
||||
# if device have disconnected lets try to connect
|
||||
if failures > 1000:
|
||||
Logger.info('MTD: <%s> input device disconnected' % _fn)
|
||||
while not os.path.exists(_fn):
|
||||
time.sleep(0.05)
|
||||
# input device is back online let's recreate device
|
||||
_device.close()
|
||||
_device = Device(_fn)
|
||||
Logger.info('MTD: <%s> input device reconnected' % _fn)
|
||||
failures = 0
|
||||
continue
|
||||
|
||||
# idle as much as we can.
|
||||
while _device.idle(1000):
|
||||
continue
|
||||
|
||||
# got data, read all without redoing idle
|
||||
while True:
|
||||
data = _device.get()
|
||||
if data is None:
|
||||
failures += 1
|
||||
break
|
||||
|
||||
failures = 0
|
||||
|
||||
# set the working slot
|
||||
if data.type == MTDEV_TYPE_EV_ABS and \
|
||||
data.code == MTDEV_CODE_SLOT:
|
||||
_slot = data.value
|
||||
continue
|
||||
|
||||
# fill the slot
|
||||
if not (_slot in l_points):
|
||||
l_points[_slot] = dict()
|
||||
point = l_points[_slot]
|
||||
ev_value = data.value
|
||||
ev_code = data.code
|
||||
if ev_code == MTDEV_CODE_POSITION_X:
|
||||
val = normalize(ev_value,
|
||||
range_min_position_x,
|
||||
range_max_position_x)
|
||||
assign_coord(point, val, invert_x, 'xy')
|
||||
elif ev_code == MTDEV_CODE_POSITION_Y:
|
||||
val = 1. - normalize(ev_value,
|
||||
range_min_position_y,
|
||||
range_max_position_y)
|
||||
assign_coord(point, val, invert_y, 'yx')
|
||||
elif ev_code == MTDEV_CODE_PRESSURE:
|
||||
point['pressure'] = normalize(ev_value,
|
||||
range_min_pressure,
|
||||
range_max_pressure)
|
||||
elif ev_code == MTDEV_CODE_TOUCH_MAJOR:
|
||||
point['size_w'] = normalize(ev_value,
|
||||
range_min_major,
|
||||
range_max_major)
|
||||
elif ev_code == MTDEV_CODE_TOUCH_MINOR:
|
||||
point['size_h'] = normalize(ev_value,
|
||||
range_min_minor,
|
||||
range_max_minor)
|
||||
elif ev_code == MTDEV_CODE_TRACKING_ID:
|
||||
if ev_value == -1:
|
||||
point['delete'] = True
|
||||
# force process of changes here, as the slot can be
|
||||
# reused.
|
||||
_changes.add(_slot)
|
||||
process([l_points[x] for x in _changes])
|
||||
_changes.clear()
|
||||
continue
|
||||
else:
|
||||
point['id'] = ev_value
|
||||
else:
|
||||
# unrecognized command, ignore.
|
||||
continue
|
||||
_changes.add(_slot)
|
||||
|
||||
# push all changes
|
||||
if _changes:
|
||||
process([l_points[x] for x in _changes])
|
||||
_changes.clear()
|
||||
|
||||
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
|
||||
|
||||
MotionEventFactory.register('mtdev', MTDMotionEventProvider)
|
||||
254
kivy/input/providers/probesysfs.py
Normal file
254
kivy/input/providers/probesysfs.py
Normal file
@@ -0,0 +1,254 @@
|
||||
'''
|
||||
Auto Create Input Provider Config Entry for Available MT Hardware (linux only).
|
||||
===============================================================================
|
||||
|
||||
Thanks to Marc Tardif for the probing code, taken from scan-for-mt-device.
|
||||
|
||||
The device discovery is done by this provider. However, the reading of
|
||||
input can be performed by other providers like: hidinput, mtdev and
|
||||
linuxwacom. mtdev is used prior to other providers. For more
|
||||
information about mtdev, check :py:class:`~kivy.input.providers.mtdev`.
|
||||
|
||||
Here is an example of auto creation::
|
||||
|
||||
[input]
|
||||
# using mtdev
|
||||
device_%(name)s = probesysfs,provider=mtdev
|
||||
# using hidinput
|
||||
device_%(name)s = probesysfs,provider=hidinput
|
||||
# using mtdev with a match on name
|
||||
device_%(name)s = probesysfs,provider=mtdev,match=acer
|
||||
|
||||
# using hidinput with custom parameters to hidinput (all on one line)
|
||||
%(name)s = probesysfs,
|
||||
provider=hidinput,param=min_pressure=1,param=max_pressure=99
|
||||
|
||||
# you can also match your wacom touchscreen
|
||||
touch = probesysfs,match=E3 Finger,provider=linuxwacom,
|
||||
select_all=1,param=mode=touch
|
||||
# and your wacom pen
|
||||
pen = probesysfs,match=E3 Pen,provider=linuxwacom,
|
||||
select_all=1,param=mode=pen
|
||||
|
||||
By default, ProbeSysfs module will enumerate hardware from the /sys/class/input
|
||||
device, and configure hardware with ABS_MT_POSITION_X capability. But for
|
||||
example, the wacom screen doesn't support this capability. You can prevent this
|
||||
behavior by putting select_all=1 in your config line. Add use_mouse=1 to also
|
||||
include touchscreen hardware that offers core pointer functionality.
|
||||
'''
|
||||
|
||||
__all__ = ('ProbeSysfsHardwareProbe', )
|
||||
|
||||
import os
|
||||
from os.path import sep
|
||||
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
|
||||
ProbeSysfsHardwareProbe = None
|
||||
|
||||
else:
|
||||
import ctypes
|
||||
from re import match, IGNORECASE
|
||||
from glob import glob
|
||||
from subprocess import Popen, PIPE
|
||||
from kivy.logger import Logger
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.providers.mouse import MouseMotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.config import _is_rpi
|
||||
|
||||
EventLoop = None
|
||||
|
||||
# See linux/input.h
|
||||
ABS_MT_POSITION_X = 0x35
|
||||
|
||||
_cache_input = None
|
||||
_cache_xinput = None
|
||||
|
||||
class Input(object):
|
||||
|
||||
def __init__(self, path):
|
||||
query_xinput()
|
||||
self.path = path
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
base = os.path.basename(self.path)
|
||||
return os.path.join("/dev", "input", base)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
path = os.path.join(self.path, "device", "name")
|
||||
return read_line(path)
|
||||
|
||||
def get_capabilities(self):
|
||||
path = os.path.join(self.path, "device", "capabilities", "abs")
|
||||
line = "0"
|
||||
try:
|
||||
line = read_line(path)
|
||||
except (IOError, OSError):
|
||||
return []
|
||||
|
||||
capabilities = []
|
||||
long_bit = ctypes.sizeof(ctypes.c_long) * 8
|
||||
for i, word in enumerate(line.split(" ")):
|
||||
word = int(word, 16)
|
||||
subcapabilities = [bool(word & 1 << i)
|
||||
for i in range(long_bit)]
|
||||
capabilities[:0] = subcapabilities
|
||||
|
||||
return capabilities
|
||||
|
||||
def has_capability(self, capability):
|
||||
capabilities = self.get_capabilities()
|
||||
return len(capabilities) > capability and capabilities[capability]
|
||||
|
||||
@property
|
||||
def is_mouse(self):
|
||||
return self.device in _cache_xinput
|
||||
|
||||
def getout(*args):
|
||||
try:
|
||||
return Popen(args, stdout=PIPE).communicate()[0]
|
||||
except OSError:
|
||||
return ''
|
||||
|
||||
def query_xinput():
|
||||
global _cache_xinput
|
||||
if _cache_xinput is None:
|
||||
_cache_xinput = []
|
||||
devids = getout('xinput', '--list', '--id-only')
|
||||
for did in devids.splitlines():
|
||||
devprops = getout('xinput', '--list-props', did)
|
||||
evpath = None
|
||||
for prop in devprops.splitlines():
|
||||
prop = prop.strip()
|
||||
if (prop.startswith(b'Device Enabled') and
|
||||
prop.endswith(b'0')):
|
||||
evpath = None
|
||||
break
|
||||
if prop.startswith(b'Device Node'):
|
||||
try:
|
||||
evpath = prop.split('"')[1]
|
||||
except Exception:
|
||||
evpath = None
|
||||
if evpath:
|
||||
_cache_xinput.append(evpath)
|
||||
|
||||
def get_inputs(path):
|
||||
global _cache_input
|
||||
if _cache_input is None:
|
||||
event_glob = os.path.join(path, "event*")
|
||||
_cache_input = [Input(x) for x in glob(event_glob)]
|
||||
return _cache_input
|
||||
|
||||
def read_line(path):
|
||||
f = open(path)
|
||||
try:
|
||||
return f.readline().strip()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
class ProbeSysfsHardwareProbe(MotionEventProvider):
|
||||
|
||||
def __new__(self, device, args):
|
||||
# hack to not return an instance of this provider.
|
||||
# :)
|
||||
instance = super(ProbeSysfsHardwareProbe, self).__new__(self)
|
||||
instance.__init__(device, args)
|
||||
|
||||
def __init__(self, device, args):
|
||||
super(ProbeSysfsHardwareProbe, self).__init__(device, args)
|
||||
self.provider = 'mtdev'
|
||||
self.match = None
|
||||
self.input_path = '/sys/class/input'
|
||||
self.select_all = True if _is_rpi else False
|
||||
self.use_mouse = False
|
||||
self.use_regex = False
|
||||
self.args = []
|
||||
|
||||
args = args.split(',')
|
||||
for arg in args:
|
||||
if arg == '':
|
||||
continue
|
||||
arg = arg.split('=', 1)
|
||||
# ensure it's a key = value
|
||||
if len(arg) != 2:
|
||||
Logger.error('ProbeSysfs: invalid parameters %s, not'
|
||||
' key=value format' % arg)
|
||||
continue
|
||||
|
||||
key, value = arg
|
||||
if key == 'match':
|
||||
self.match = value
|
||||
elif key == 'provider':
|
||||
self.provider = value
|
||||
elif key == 'use_regex':
|
||||
self.use_regex = bool(int(value))
|
||||
elif key == 'select_all':
|
||||
self.select_all = bool(int(value))
|
||||
elif key == 'use_mouse':
|
||||
self.use_mouse = bool(int(value))
|
||||
elif key == 'param':
|
||||
self.args.append(value)
|
||||
else:
|
||||
Logger.error('ProbeSysfs: unknown %s option' % key)
|
||||
continue
|
||||
|
||||
self.probe()
|
||||
|
||||
def should_use_mouse(self):
|
||||
return (self.use_mouse or
|
||||
not any(p for p in EventLoop.input_providers
|
||||
if isinstance(p, MouseMotionEventProvider)))
|
||||
|
||||
def probe(self):
|
||||
global EventLoop
|
||||
from kivy.base import EventLoop
|
||||
|
||||
inputs = get_inputs(self.input_path)
|
||||
Logger.debug('ProbeSysfs: using probesysfs!')
|
||||
|
||||
use_mouse = self.should_use_mouse()
|
||||
|
||||
if not self.select_all:
|
||||
inputs = [x for x in inputs if
|
||||
x.has_capability(ABS_MT_POSITION_X) and
|
||||
(use_mouse or not x.is_mouse)]
|
||||
for device in inputs:
|
||||
Logger.debug('ProbeSysfs: found device: %s at %s' % (
|
||||
device.name, device.device))
|
||||
|
||||
# must ignore ?
|
||||
if self.match:
|
||||
if self.use_regex:
|
||||
if not match(self.match, device.name, IGNORECASE):
|
||||
Logger.debug('ProbeSysfs: device not match the'
|
||||
' rule in config, ignoring.')
|
||||
continue
|
||||
else:
|
||||
if self.match not in device.name:
|
||||
continue
|
||||
|
||||
Logger.info('ProbeSysfs: device match: %s' % device.device)
|
||||
|
||||
d = device.device
|
||||
devicename = self.device % dict(name=d.split(sep)[-1])
|
||||
|
||||
provider = MotionEventFactory.get(self.provider)
|
||||
if provider is None:
|
||||
Logger.info('ProbeSysfs: Unable to find provider %s' %
|
||||
self.provider)
|
||||
Logger.info('ProbeSysfs: fallback on hidinput')
|
||||
provider = MotionEventFactory.get('hidinput')
|
||||
if provider is None:
|
||||
Logger.critical('ProbeSysfs: no input provider found'
|
||||
' to handle this device !')
|
||||
continue
|
||||
|
||||
instance = provider(devicename, '%s,%s' % (
|
||||
device.device, ','.join(self.args)))
|
||||
if instance:
|
||||
EventLoop.add_input_provider(instance)
|
||||
|
||||
MotionEventFactory.register('probesysfs', ProbeSysfsHardwareProbe)
|
||||
326
kivy/input/providers/tuio.py
Normal file
326
kivy/input/providers/tuio.py
Normal file
@@ -0,0 +1,326 @@
|
||||
'''
|
||||
TUIO Input Provider
|
||||
===================
|
||||
|
||||
TUIO is the de facto standard network protocol for the transmission of
|
||||
touch and fiducial information between a server and a client. To learn
|
||||
more about TUIO (which is itself based on the OSC protocol), please
|
||||
refer to http://tuio.org -- The specification should be of special
|
||||
interest.
|
||||
|
||||
Configure a TUIO provider in the config.ini
|
||||
-------------------------------------------
|
||||
|
||||
The TUIO provider can be configured in the configuration file in the
|
||||
``[input]`` section::
|
||||
|
||||
[input]
|
||||
# name = tuio,<ip>:<port>
|
||||
multitouchtable = tuio,192.168.0.1:3333
|
||||
|
||||
Configure a TUIO provider in the App
|
||||
------------------------------------
|
||||
|
||||
You must add the provider before your application is run, like this::
|
||||
|
||||
from kivy.app import App
|
||||
from kivy.config import Config
|
||||
|
||||
class TestApp(App):
|
||||
def build(self):
|
||||
Config.set('input', 'multitouchscreen1', 'tuio,0.0.0.0:3333')
|
||||
# You can also add a second TUIO listener
|
||||
# Config.set('input', 'source2', 'tuio,0.0.0.0:3334')
|
||||
# Then do the usual things
|
||||
# ...
|
||||
return
|
||||
'''
|
||||
|
||||
__all__ = ('TuioMotionEventProvider', 'Tuio2dCurMotionEvent',
|
||||
'Tuio2dObjMotionEvent')
|
||||
|
||||
from kivy.logger import Logger
|
||||
|
||||
from functools import partial
|
||||
from collections import deque
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
from kivy.input.shape import ShapeRect
|
||||
|
||||
|
||||
class TuioMotionEventProvider(MotionEventProvider):
|
||||
'''The TUIO provider listens to a socket and handles some of the incoming
|
||||
OSC messages:
|
||||
|
||||
* /tuio/2Dcur
|
||||
* /tuio/2Dobj
|
||||
|
||||
You can easily extend the provider to handle new TUIO paths like so::
|
||||
|
||||
# Create a class to handle the new TUIO type/path
|
||||
# Replace NEWPATH with the pathname you want to handle
|
||||
class TuioNEWPATHMotionEvent(MotionEvent):
|
||||
|
||||
def depack(self, args):
|
||||
# In this method, implement 'unpacking' for the received
|
||||
# arguments. you basically translate from TUIO args to Kivy
|
||||
# MotionEvent variables. If all you receive are x and y
|
||||
# values, you can do it like this:
|
||||
if len(args) == 2:
|
||||
self.sx, self.sy = args
|
||||
self.profile = ('pos', )
|
||||
self.sy = 1 - self.sy
|
||||
super().depack(args)
|
||||
|
||||
# Register it with the TUIO MotionEvent provider.
|
||||
# You obviously need to replace the PATH placeholders appropriately.
|
||||
TuioMotionEventProvider.register('/tuio/PATH', TuioNEWPATHMotionEvent)
|
||||
|
||||
.. note::
|
||||
|
||||
The class name is of no technical importance. Your class will be
|
||||
associated with the path that you pass to the ``register()``
|
||||
function. To keep things simple, you should name your class after the
|
||||
path that it handles, though.
|
||||
'''
|
||||
|
||||
__handlers__ = {}
|
||||
|
||||
def __init__(self, device, args):
|
||||
super().__init__(device, args)
|
||||
args = args.split(',')
|
||||
if len(args) == 0:
|
||||
Logger.error('Tuio: Invalid configuration for TUIO provider')
|
||||
Logger.error('Tuio: Format must be ip:port (eg. 127.0.0.1:3333)')
|
||||
err = 'Tuio: Current configuration is <%s>' % (str(','.join(args)))
|
||||
Logger.error(err)
|
||||
return
|
||||
ipport = args[0].split(':')
|
||||
if len(ipport) != 2:
|
||||
Logger.error('Tuio: Invalid configuration for TUIO provider')
|
||||
Logger.error('Tuio: Format must be ip:port (eg. 127.0.0.1:3333)')
|
||||
err = 'Tuio: Current configuration is <%s>' % (str(','.join(args)))
|
||||
Logger.error(err)
|
||||
return
|
||||
self.ip, self.port = args[0].split(':')
|
||||
self.port = int(self.port)
|
||||
self.handlers = {}
|
||||
self.oscid = None
|
||||
self.tuio_event_q = deque()
|
||||
self.touches = {}
|
||||
|
||||
@staticmethod
|
||||
def register(oscpath, classname):
|
||||
'''Register a new path to handle in TUIO provider'''
|
||||
TuioMotionEventProvider.__handlers__[oscpath] = classname
|
||||
|
||||
@staticmethod
|
||||
def unregister(oscpath, classname):
|
||||
'''Unregister a path to stop handling it in the TUIO provider'''
|
||||
if oscpath in TuioMotionEventProvider.__handlers__:
|
||||
del TuioMotionEventProvider.__handlers__[oscpath]
|
||||
|
||||
@staticmethod
|
||||
def create(oscpath, **kwargs):
|
||||
'''Create a touch event from a TUIO path'''
|
||||
if oscpath not in TuioMotionEventProvider.__handlers__:
|
||||
raise Exception('Unknown %s touch path' % oscpath)
|
||||
return TuioMotionEventProvider.__handlers__[oscpath](**kwargs)
|
||||
|
||||
def start(self):
|
||||
'''Start the TUIO provider'''
|
||||
try:
|
||||
from oscpy.server import OSCThreadServer
|
||||
except ImportError:
|
||||
Logger.info(
|
||||
'Please install the oscpy python module to use the TUIO '
|
||||
'provider.'
|
||||
)
|
||||
raise
|
||||
self.oscid = osc = OSCThreadServer()
|
||||
osc.listen(self.ip, self.port, default=True)
|
||||
for oscpath in TuioMotionEventProvider.__handlers__:
|
||||
self.touches[oscpath] = {}
|
||||
osc.bind(oscpath, partial(self._osc_tuio_cb, oscpath))
|
||||
|
||||
def stop(self):
|
||||
'''Stop the TUIO provider'''
|
||||
self.oscid.stop_all()
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
'''Update the TUIO provider (pop events from the queue)'''
|
||||
|
||||
# read the Queue with event
|
||||
while True:
|
||||
try:
|
||||
value = self.tuio_event_q.pop()
|
||||
except IndexError:
|
||||
# queue is empty, we're done for now
|
||||
return
|
||||
self._update(dispatch_fn, value)
|
||||
|
||||
def _osc_tuio_cb(self, oscpath, address, *args):
|
||||
self.tuio_event_q.appendleft([oscpath, address, args])
|
||||
|
||||
def _update(self, dispatch_fn, value):
|
||||
oscpath, command, args = value
|
||||
|
||||
# verify commands
|
||||
if command not in [b'alive', b'set']:
|
||||
return
|
||||
|
||||
# move or create a new touch
|
||||
if command == b'set':
|
||||
id = args[0]
|
||||
if id not in self.touches[oscpath]:
|
||||
# new touch
|
||||
touch = TuioMotionEventProvider.__handlers__[oscpath](
|
||||
self.device, id, args[1:])
|
||||
self.touches[oscpath][id] = touch
|
||||
dispatch_fn('begin', touch)
|
||||
else:
|
||||
# update a current touch
|
||||
touch = self.touches[oscpath][id]
|
||||
touch.move(args[1:])
|
||||
dispatch_fn('update', touch)
|
||||
|
||||
# alive event, check for deleted touch
|
||||
if command == b'alive':
|
||||
alives = args
|
||||
to_delete = []
|
||||
for id in self.touches[oscpath]:
|
||||
if id not in alives:
|
||||
# touch up
|
||||
touch = self.touches[oscpath][id]
|
||||
if touch not in to_delete:
|
||||
to_delete.append(touch)
|
||||
|
||||
for touch in to_delete:
|
||||
dispatch_fn('end', touch)
|
||||
del self.touches[oscpath][touch.id]
|
||||
|
||||
|
||||
class TuioMotionEvent(MotionEvent):
|
||||
'''Abstraction for TUIO touches/fiducials.
|
||||
|
||||
Depending on the tracking software you use (e.g. Movid, CCV, etc.) and its
|
||||
TUIO implementation, the TuioMotionEvent object can support multiple
|
||||
profiles such as:
|
||||
|
||||
* Fiducial ID: profile name 'markerid', attribute ``.fid``
|
||||
* Position: profile name 'pos', attributes ``.x``, ``.y``
|
||||
* Angle: profile name 'angle', attribute ``.a``
|
||||
* Velocity vector: profile name 'mov', attributes ``.X``, ``.Y``
|
||||
* Rotation velocity: profile name 'rot', attribute ``.A``
|
||||
* Motion acceleration: profile name 'motacc', attribute ``.m``
|
||||
* Rotation acceleration: profile name 'rotacc', attribute ``.r``
|
||||
'''
|
||||
__attrs__ = ('a', 'b', 'c', 'X', 'Y', 'Z', 'A', 'B', 'C', 'm', 'r')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
# Default argument for TUIO touches
|
||||
self.a = 0.0
|
||||
self.b = 0.0
|
||||
self.c = 0.0
|
||||
self.X = 0.0
|
||||
self.Y = 0.0
|
||||
self.Z = 0.0
|
||||
self.A = 0.0
|
||||
self.B = 0.0
|
||||
self.C = 0.0
|
||||
self.m = 0.0
|
||||
self.r = 0.0
|
||||
|
||||
angle = property(lambda self: self.a)
|
||||
mot_accel = property(lambda self: self.m)
|
||||
rot_accel = property(lambda self: self.r)
|
||||
xmot = property(lambda self: self.X)
|
||||
ymot = property(lambda self: self.Y)
|
||||
zmot = property(lambda self: self.Z)
|
||||
|
||||
|
||||
class Tuio2dCurMotionEvent(TuioMotionEvent):
|
||||
'''A 2dCur TUIO touch.'''
|
||||
|
||||
def depack(self, args):
|
||||
if len(args) < 5:
|
||||
self.sx, self.sy = list(map(float, args[0:2]))
|
||||
self.profile = ('pos', )
|
||||
elif len(args) == 5:
|
||||
self.sx, self.sy, self.X, self.Y, self.m = list(map(float,
|
||||
args[0:5]))
|
||||
self.Y = -self.Y
|
||||
self.profile = ('pos', 'mov', 'motacc')
|
||||
else:
|
||||
self.sx, self.sy, self.X, self.Y = list(map(float, args[0:4]))
|
||||
self.m, width, height = list(map(float, args[4:7]))
|
||||
self.Y = -self.Y
|
||||
self.profile = ('pos', 'mov', 'motacc', 'shape')
|
||||
if self.shape is None:
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = width
|
||||
self.shape.height = height
|
||||
self.sy = 1 - self.sy
|
||||
super().depack(args)
|
||||
|
||||
|
||||
class Tuio2dObjMotionEvent(TuioMotionEvent):
|
||||
'''A 2dObj TUIO object.
|
||||
'''
|
||||
|
||||
def depack(self, args):
|
||||
if len(args) < 5:
|
||||
self.sx, self.sy = args[0:2]
|
||||
self.profile = ('pos', )
|
||||
elif len(args) == 9:
|
||||
self.fid, self.sx, self.sy, self.a, self.X, self.Y = args[:6]
|
||||
self.A, self.m, self.r = args[6:9]
|
||||
self.Y = -self.Y
|
||||
self.profile = ('markerid', 'pos', 'angle', 'mov', 'rot',
|
||||
'motacc', 'rotacc')
|
||||
else:
|
||||
self.fid, self.sx, self.sy, self.a, self.X, self.Y = args[:6]
|
||||
self.A, self.m, self.r, width, height = args[6:11]
|
||||
self.Y = -self.Y
|
||||
self.profile = ('markerid', 'pos', 'angle', 'mov', 'rot', 'rotacc',
|
||||
'acc', 'shape')
|
||||
if self.shape is None:
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = width
|
||||
self.shape.height = height
|
||||
self.sy = 1 - self.sy
|
||||
super().depack(args)
|
||||
|
||||
|
||||
class Tuio2dBlbMotionEvent(TuioMotionEvent):
|
||||
'''A 2dBlb TUIO object.
|
||||
# FIXME 3d shape are not supported
|
||||
/tuio/2Dobj set s i x y a X Y A m r
|
||||
/tuio/2Dblb set s x y a w h f X Y A m r
|
||||
'''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.profile = ('pos', 'angle', 'mov', 'rot', 'rotacc', 'acc', 'shape')
|
||||
|
||||
def depack(self, args):
|
||||
self.sx, self.sy, self.a, self.X, self.Y, sw, sh, sd, \
|
||||
self.A, self.m, self.r = args
|
||||
self.Y = -self.Y
|
||||
if self.shape is None:
|
||||
self.shape = ShapeRect()
|
||||
self.shape.width = sw
|
||||
self.shape.height = sh
|
||||
self.sy = 1 - self.sy
|
||||
super().depack(args)
|
||||
|
||||
|
||||
# registers
|
||||
TuioMotionEventProvider.register(b'/tuio/2Dcur', Tuio2dCurMotionEvent)
|
||||
TuioMotionEventProvider.register(b'/tuio/2Dobj', Tuio2dObjMotionEvent)
|
||||
TuioMotionEventProvider.register(b'/tuio/2Dblb', Tuio2dBlbMotionEvent)
|
||||
MotionEventFactory.register('tuio', TuioMotionEventProvider)
|
||||
162
kivy/input/providers/wm_common.py
Normal file
162
kivy/input/providers/wm_common.py
Normal file
@@ -0,0 +1,162 @@
|
||||
'''
|
||||
Common definitions for a Windows provider
|
||||
=========================================
|
||||
|
||||
This file provides common definitions for constants used by WM_Touch / WM_Pen.
|
||||
'''
|
||||
import os
|
||||
|
||||
WM_MOUSEFIRST = 512
|
||||
WM_MOUSEMOVE = 512
|
||||
WM_LBUTTONDOWN = 513
|
||||
WM_LBUTTONUP = 514
|
||||
WM_LBUTTONDBLCLK = 515
|
||||
WM_RBUTTONDOWN = 516
|
||||
WM_RBUTTONUP = 517
|
||||
WM_RBUTTONDBLCLK = 518
|
||||
WM_MBUTTONDOWN = 519
|
||||
WM_MBUTTONUP = 520
|
||||
WM_MBUTTONDBLCLK = 521
|
||||
WM_MOUSEWHEEL = 522
|
||||
WM_MOUSELAST = 522
|
||||
WM_DPICHANGED = 736
|
||||
WM_GETDPISCALEDSIZE = 740
|
||||
WM_NCCALCSIZE = 131
|
||||
|
||||
WM_TOUCH = 576
|
||||
TOUCHEVENTF_MOVE = 1
|
||||
TOUCHEVENTF_DOWN = 2
|
||||
TOUCHEVENTF_UP = 4
|
||||
|
||||
PEN_OR_TOUCH_SIGNATURE = 0xFF515700
|
||||
PEN_OR_TOUCH_MASK = 0xFFFFFF00
|
||||
PEN_EVENT_TOUCH_MASK = 0x80
|
||||
|
||||
SM_CYCAPTION = 4
|
||||
|
||||
WM_TABLET_QUERYSYSTEMGESTURE = 0x000002CC
|
||||
TABLET_DISABLE_PRESSANDHOLD = 0x00000001
|
||||
TABLET_DISABLE_PENTAPFEEDBACK = 0x00000008
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK = 0x00000010
|
||||
TABLET_DISABLE_TOUCHUIFORCEON = 0x00000100
|
||||
TABLET_DISABLE_TOUCHUIFORCEOFF = 0x00000200
|
||||
TABLET_DISABLE_TOUCHSWITCH = 0x00008000
|
||||
TABLET_DISABLE_FLICKS = 0x00010000
|
||||
TABLET_ENABLE_FLICKSONCONTEXT = 0x00020000
|
||||
TABLET_ENABLE_FLICKLEARNINGMODE = 0x00040000
|
||||
TABLET_DISABLE_SMOOTHSCROLLING = 0x00080000
|
||||
TABLET_DISABLE_FLICKFALLBACKKEYS = 0x00100000
|
||||
GWL_WNDPROC = -4
|
||||
|
||||
|
||||
QUERYSYSTEMGESTURE_WNDPROC = (
|
||||
TABLET_DISABLE_PRESSANDHOLD |
|
||||
TABLET_DISABLE_PENTAPFEEDBACK |
|
||||
TABLET_DISABLE_PENBARRELFEEDBACK |
|
||||
TABLET_DISABLE_SMOOTHSCROLLING |
|
||||
TABLET_DISABLE_FLICKFALLBACKKEYS |
|
||||
TABLET_DISABLE_TOUCHSWITCH |
|
||||
TABLET_DISABLE_FLICKS)
|
||||
|
||||
if 'KIVY_DOC' not in os.environ:
|
||||
from ctypes.wintypes import (ULONG, HANDLE, DWORD, LONG, UINT,
|
||||
WPARAM, LPARAM, BOOL, HWND, POINT,
|
||||
RECT as RECT_BASE)
|
||||
from ctypes import (windll, WINFUNCTYPE, POINTER,
|
||||
c_int, c_longlong, c_void_p, Structure,
|
||||
sizeof, byref, cast)
|
||||
|
||||
class RECT(RECT_BASE):
|
||||
x = property(lambda self: self.left)
|
||||
y = property(lambda self: self.top)
|
||||
w = property(lambda self: self.right - self.left)
|
||||
h = property(lambda self: self.bottom - self.top)
|
||||
|
||||
# check availability of RegisterTouchWindow
|
||||
if not hasattr(windll.user32, 'RegisterTouchWindow'):
|
||||
raise Exception('Unsupported Window version')
|
||||
|
||||
LRESULT = LPARAM
|
||||
WNDPROC = WINFUNCTYPE(LRESULT, HWND, UINT, WPARAM, LPARAM)
|
||||
|
||||
class TOUCHINPUT(Structure):
|
||||
_fields_ = [
|
||||
('x', LONG),
|
||||
('y', LONG),
|
||||
('pSource', HANDLE),
|
||||
('id', DWORD),
|
||||
('flags', DWORD),
|
||||
('mask', DWORD),
|
||||
('time', DWORD),
|
||||
('extraInfo', POINTER(ULONG)),
|
||||
('size_x', DWORD),
|
||||
('size_y', DWORD)]
|
||||
|
||||
def size(self):
|
||||
return (self.size_x, self.size_y)
|
||||
|
||||
def screen_x(self):
|
||||
return self.x / 100.0
|
||||
|
||||
def screen_y(self):
|
||||
return self.y / 100.0
|
||||
|
||||
def _event_type(self):
|
||||
if self.flags & TOUCHEVENTF_MOVE:
|
||||
return 'update'
|
||||
if self.flags & TOUCHEVENTF_DOWN:
|
||||
return 'begin'
|
||||
if self.flags & TOUCHEVENTF_UP:
|
||||
return 'end'
|
||||
event_type = property(_event_type)
|
||||
|
||||
def SetWindowLong_WndProc_wrapper_generator(func):
|
||||
def _closure(hWnd, wndProc):
|
||||
oldAddr = func(hWnd, GWL_WNDPROC, cast(wndProc, c_void_p).value)
|
||||
return cast(c_void_p(oldAddr), WNDPROC)
|
||||
|
||||
return _closure
|
||||
|
||||
try:
|
||||
LONG_PTR = c_longlong
|
||||
windll.user32.SetWindowLongPtrW.restype = LONG_PTR
|
||||
windll.user32.SetWindowLongPtrW.argtypes = [HWND, c_int, LONG_PTR]
|
||||
SetWindowLong_WndProc_wrapper = \
|
||||
SetWindowLong_WndProc_wrapper_generator(
|
||||
windll.user32.SetWindowLongPtrW)
|
||||
except AttributeError:
|
||||
windll.user32.SetWindowLongW.restype = LONG
|
||||
windll.user32.SetWindowLongW.argtypes = [HWND, c_int, LONG]
|
||||
SetWindowLong_WndProc_wrapper = \
|
||||
SetWindowLong_WndProc_wrapper_generator(
|
||||
windll.user32.SetWindowLongW)
|
||||
|
||||
windll.user32.GetMessageExtraInfo.restype = LPARAM
|
||||
windll.user32.GetMessageExtraInfo.argtypes = []
|
||||
windll.user32.GetClientRect.restype = BOOL
|
||||
windll.user32.GetClientRect.argtypes = [HANDLE, POINTER(RECT_BASE)]
|
||||
windll.user32.GetWindowRect.restype = BOOL
|
||||
windll.user32.GetWindowRect.argtypes = [HANDLE, POINTER(RECT_BASE)]
|
||||
windll.user32.CallWindowProcW.restype = LRESULT
|
||||
windll.user32.CallWindowProcW.argtypes = [WNDPROC, HWND, UINT, WPARAM,
|
||||
LPARAM]
|
||||
windll.user32.GetActiveWindow.restype = HWND
|
||||
windll.user32.GetActiveWindow.argtypes = []
|
||||
windll.user32.RegisterTouchWindow.restype = BOOL
|
||||
windll.user32.RegisterTouchWindow.argtypes = [HWND, ULONG]
|
||||
windll.user32.UnregisterTouchWindow.restype = BOOL
|
||||
windll.user32.UnregisterTouchWindow.argtypes = [HWND]
|
||||
windll.user32.GetTouchInputInfo.restype = BOOL
|
||||
windll.user32.GetTouchInputInfo.argtypes = [HANDLE, UINT,
|
||||
POINTER(TOUCHINPUT), c_int]
|
||||
windll.user32.GetSystemMetrics.restype = c_int
|
||||
windll.user32.GetSystemMetrics.argtypes = [c_int]
|
||||
|
||||
windll.user32.ClientToScreen.restype = BOOL
|
||||
windll.user32.ClientToScreen.argtypes = [HWND, POINTER(POINT)]
|
||||
|
||||
try:
|
||||
windll.user32.GetDpiForWindow.restype = UINT
|
||||
windll.user32.GetDpiForWindow.argtypes = [HWND]
|
||||
except AttributeError:
|
||||
pass
|
||||
121
kivy/input/providers/wm_pen.py
Normal file
121
kivy/input/providers/wm_pen.py
Normal file
@@ -0,0 +1,121 @@
|
||||
'''
|
||||
Support for WM_PEN messages (Windows platform)
|
||||
==============================================
|
||||
'''
|
||||
|
||||
__all__ = ('WM_PenProvider', 'WM_Pen')
|
||||
|
||||
import os
|
||||
from kivy.input.providers.wm_common import RECT, PEN_OR_TOUCH_MASK, \
|
||||
PEN_OR_TOUCH_SIGNATURE, PEN_EVENT_TOUCH_MASK, WM_LBUTTONDOWN, \
|
||||
WM_MOUSEMOVE, WM_LBUTTONUP, WM_TABLET_QUERYSYSTEMGESTURE, \
|
||||
QUERYSYSTEMGESTURE_WNDPROC, WNDPROC, SetWindowLong_WndProc_wrapper
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
|
||||
|
||||
class WM_Pen(MotionEvent):
|
||||
'''MotionEvent representing the WM_Pen event. Supports the pos profile.'''
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.profile = ['pos']
|
||||
|
||||
def depack(self, args):
|
||||
self.sx, self.sy = args[0], args[1]
|
||||
super().depack(args)
|
||||
|
||||
def __str__(self):
|
||||
i, u, s, d = (self.id, self.uid, str(self.spos), self.device)
|
||||
return '<WMPen id:%d uid:%d pos:%s device:%s>' % (i, u, s, d)
|
||||
|
||||
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
# documentation hack
|
||||
WM_PenProvider = None
|
||||
|
||||
else:
|
||||
from collections import deque
|
||||
from ctypes import windll, byref, c_int16, c_int
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
|
||||
win_rect = RECT()
|
||||
|
||||
class WM_PenProvider(MotionEventProvider):
|
||||
|
||||
def _is_pen_message(self, msg):
|
||||
info = windll.user32.GetMessageExtraInfo()
|
||||
# It's a touch or a pen
|
||||
if (info & PEN_OR_TOUCH_MASK) == PEN_OR_TOUCH_SIGNATURE:
|
||||
if not info & PEN_EVENT_TOUCH_MASK:
|
||||
return True
|
||||
|
||||
def _pen_handler(self, msg, wParam, lParam):
|
||||
if msg not in (WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_LBUTTONUP):
|
||||
return
|
||||
|
||||
windll.user32.GetClientRect(self.hwnd, byref(win_rect))
|
||||
x = c_int16(lParam & 0xffff).value / float(win_rect.w)
|
||||
y = c_int16(lParam >> 16).value / float(win_rect.h)
|
||||
y = abs(1.0 - y)
|
||||
|
||||
if msg == WM_LBUTTONDOWN:
|
||||
self.pen_events.appendleft(('begin', x, y))
|
||||
self.pen_status = True
|
||||
|
||||
if msg == WM_MOUSEMOVE and self.pen_status:
|
||||
self.pen_events.appendleft(('update', x, y))
|
||||
|
||||
if msg == WM_LBUTTONUP:
|
||||
self.pen_events.appendleft(('end', x, y))
|
||||
self.pen_status = False
|
||||
|
||||
def _pen_wndProc(self, hwnd, msg, wParam, lParam):
|
||||
if msg == WM_TABLET_QUERYSYSTEMGESTURE:
|
||||
return QUERYSYSTEMGESTURE_WNDPROC
|
||||
if self._is_pen_message(msg):
|
||||
self._pen_handler(msg, wParam, lParam)
|
||||
return 1
|
||||
else:
|
||||
return windll.user32.CallWindowProcW(self.old_windProc,
|
||||
hwnd, msg, wParam, lParam)
|
||||
|
||||
def start(self):
|
||||
self.uid = 0
|
||||
self.pen = None
|
||||
self.pen_status = None
|
||||
self.pen_events = deque()
|
||||
|
||||
self.hwnd = windll.user32.GetActiveWindow()
|
||||
|
||||
# inject our own wndProc to handle messages
|
||||
# before window manager does
|
||||
self.new_windProc = WNDPROC(self._pen_wndProc)
|
||||
self.old_windProc = SetWindowLong_WndProc_wrapper(
|
||||
self.hwnd, self.new_windProc)
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
while True:
|
||||
|
||||
try:
|
||||
etype, x, y = self.pen_events.pop()
|
||||
except:
|
||||
break
|
||||
|
||||
if etype == 'begin':
|
||||
self.uid += 1
|
||||
self.pen = WM_Pen(self.device, self.uid, [x, y])
|
||||
elif etype == 'update':
|
||||
self.pen.move([x, y])
|
||||
elif etype == 'end':
|
||||
self.pen.update_time_end()
|
||||
|
||||
dispatch_fn(etype, self.pen)
|
||||
|
||||
def stop(self):
|
||||
self.pen = None
|
||||
SetWindowLong_WndProc_wrapper(self.hwnd, self.old_windProc)
|
||||
|
||||
MotionEventFactory.register('wm_pen', WM_PenProvider)
|
||||
157
kivy/input/providers/wm_touch.py
Normal file
157
kivy/input/providers/wm_touch.py
Normal file
@@ -0,0 +1,157 @@
|
||||
'''
|
||||
Support for WM_TOUCH messages (Windows platform)
|
||||
================================================
|
||||
'''
|
||||
|
||||
__all__ = ('WM_MotionEventProvider', 'WM_MotionEvent')
|
||||
|
||||
import os
|
||||
from kivy.input.providers.wm_common import WNDPROC, \
|
||||
SetWindowLong_WndProc_wrapper, RECT, POINT, WM_TABLET_QUERYSYSTEMGESTURE, \
|
||||
QUERYSYSTEMGESTURE_WNDPROC, WM_TOUCH, WM_MOUSEMOVE, WM_MOUSELAST, \
|
||||
TOUCHINPUT, PEN_OR_TOUCH_MASK, PEN_OR_TOUCH_SIGNATURE, PEN_EVENT_TOUCH_MASK
|
||||
from kivy.input.motionevent import MotionEvent
|
||||
from kivy.input.shape import ShapeRect
|
||||
|
||||
Window = None
|
||||
|
||||
|
||||
class WM_MotionEvent(MotionEvent):
|
||||
'''MotionEvent representing the WM_MotionEvent event.
|
||||
Supports pos, shape and size profiles.
|
||||
'''
|
||||
__attrs__ = ('size', )
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('is_touch', True)
|
||||
kwargs.setdefault('type_id', 'touch')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.profile = ('pos', 'shape', 'size')
|
||||
|
||||
def depack(self, args):
|
||||
self.shape = ShapeRect()
|
||||
self.sx, self.sy = args[0], args[1]
|
||||
self.shape.width = args[2][0]
|
||||
self.shape.height = args[2][1]
|
||||
self.size = self.shape.width * self.shape.height
|
||||
super().depack(args)
|
||||
|
||||
def __str__(self):
|
||||
args = (self.id, self.uid, str(self.spos), self.device)
|
||||
return '<WMMotionEvent id:%d uid:%d pos:%s device:%s>' % args
|
||||
|
||||
|
||||
if 'KIVY_DOC' in os.environ:
|
||||
# documentation hack
|
||||
WM_MotionEventProvider = None
|
||||
|
||||
else:
|
||||
from ctypes.wintypes import HANDLE
|
||||
from ctypes import (windll, sizeof, byref)
|
||||
from collections import deque
|
||||
from kivy.input.provider import MotionEventProvider
|
||||
from kivy.input.factory import MotionEventFactory
|
||||
|
||||
class WM_MotionEventProvider(MotionEventProvider):
|
||||
|
||||
def start(self):
|
||||
global Window
|
||||
if not Window:
|
||||
from kivy.core.window import Window
|
||||
|
||||
self.touch_events = deque()
|
||||
self.touches = {}
|
||||
self.uid = 0
|
||||
|
||||
# get window handle, and register to receive WM_TOUCH messages
|
||||
self.hwnd = windll.user32.GetActiveWindow()
|
||||
windll.user32.RegisterTouchWindow(self.hwnd, 1)
|
||||
|
||||
# inject our own wndProc to handle messages
|
||||
# before window manager does
|
||||
self.new_windProc = WNDPROC(self._touch_wndProc)
|
||||
self.old_windProc = SetWindowLong_WndProc_wrapper(
|
||||
self.hwnd, self.new_windProc)
|
||||
|
||||
def update(self, dispatch_fn):
|
||||
c_rect = RECT()
|
||||
windll.user32.GetClientRect(self.hwnd, byref(c_rect))
|
||||
pt = POINT(x=0, y=0)
|
||||
windll.user32.ClientToScreen(self.hwnd, byref(pt))
|
||||
x_offset, y_offset = pt.x, pt.y
|
||||
usable_w, usable_h = float(c_rect.w), float(c_rect.h)
|
||||
|
||||
while True:
|
||||
try:
|
||||
t = self.touch_events.pop()
|
||||
except:
|
||||
break
|
||||
|
||||
# adjust x,y to window coordinates (0.0 to 1.0)
|
||||
x = (t.screen_x() - x_offset) / usable_w
|
||||
y = 1.0 - (t.screen_y() - y_offset) / usable_h
|
||||
|
||||
# actually dispatch input
|
||||
if t.event_type == 'begin':
|
||||
self.uid += 1
|
||||
self.touches[t.id] = WM_MotionEvent(
|
||||
self.device, self.uid, [x, y, t.size()])
|
||||
dispatch_fn('begin', self.touches[t.id])
|
||||
|
||||
if t.event_type == 'update' and t.id in self.touches:
|
||||
self.touches[t.id].move([x, y, t.size()])
|
||||
dispatch_fn('update', self.touches[t.id])
|
||||
|
||||
if t.event_type == 'end' and t.id in self.touches:
|
||||
touch = self.touches[t.id]
|
||||
touch.move([x, y, t.size()])
|
||||
touch.update_time_end()
|
||||
dispatch_fn('end', touch)
|
||||
del self.touches[t.id]
|
||||
|
||||
def stop(self):
|
||||
windll.user32.UnregisterTouchWindow(self.hwnd)
|
||||
self.new_windProc = SetWindowLong_WndProc_wrapper(
|
||||
self.hwnd, self.old_windProc)
|
||||
|
||||
# we inject this wndProc into our main window, to process
|
||||
# WM_TOUCH and mouse messages before the window manager does
|
||||
def _touch_wndProc(self, hwnd, msg, wParam, lParam):
|
||||
done = False
|
||||
if msg == WM_TABLET_QUERYSYSTEMGESTURE:
|
||||
return QUERYSYSTEMGESTURE_WNDPROC
|
||||
|
||||
if msg == WM_TOUCH:
|
||||
done = self._touch_handler(msg, wParam, lParam)
|
||||
|
||||
if msg >= WM_MOUSEMOVE and msg <= WM_MOUSELAST:
|
||||
done = self._mouse_handler(msg, wParam, lParam)
|
||||
|
||||
if not done:
|
||||
return windll.user32.CallWindowProcW(self.old_windProc,
|
||||
hwnd, msg, wParam,
|
||||
lParam)
|
||||
return 1
|
||||
|
||||
# this on pushes WM_TOUCH messages onto our event stack
|
||||
def _touch_handler(self, msg, wParam, lParam):
|
||||
touches = (TOUCHINPUT * wParam)()
|
||||
windll.user32.GetTouchInputInfo(HANDLE(lParam),
|
||||
wParam,
|
||||
touches,
|
||||
sizeof(TOUCHINPUT))
|
||||
for i in range(wParam):
|
||||
self.touch_events.appendleft(touches[i])
|
||||
windll.user32.CloseTouchInputHandle(HANDLE(lParam))
|
||||
return True
|
||||
|
||||
# filter fake mouse events, because touch and stylus
|
||||
# also make mouse events
|
||||
def _mouse_handler(self, msg, wparam, lParam):
|
||||
info = windll.user32.GetMessageExtraInfo()
|
||||
# its a touch or a pen
|
||||
if (info & PEN_OR_TOUCH_MASK) == PEN_OR_TOUCH_SIGNATURE:
|
||||
if info & PEN_EVENT_TOUCH_MASK:
|
||||
return True
|
||||
|
||||
MotionEventFactory.register('wm_touch', WM_MotionEventProvider)
|
||||
Reference in New Issue
Block a user