1# ----------------------------------------------------------------------------
2# pyglet
3# Copyright (c) 2006-2008 Alex Holkner
4# Copyright (c) 2008-2020 pyglet contributors
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11#  * Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13#  * Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in
15#    the documentation and/or other materials provided with the
16#    distribution.
17#  * Neither the name of pyglet nor the names of its
18#    contributors may be used to endorse or promote products
19#    derived from this software without specific prior written
20#    permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33# POSSIBILITY OF SUCH DAMAGE.
34# ----------------------------------------------------------------------------
35
36from pyglet.input.base import Tablet, TabletCanvas
37from pyglet.input.base import TabletCursor, DeviceOpenException
38from pyglet.input.x11_xinput import XInputWindowEventDispatcher
39from pyglet.input.x11_xinput import get_devices, DeviceResponder
40
41
42try:
43    from pyglet.libs.x11 import xinput as xi
44    _have_xinput = True
45except:
46    _have_xinput = False
47
48
49class XInputTablet(Tablet):
50    name = 'XInput Tablet'
51
52    def __init__(self, cursors):
53        self.cursors = cursors
54
55    def open(self, window):
56        return XInputTabletCanvas(window, self.cursors)
57
58
59class XInputTabletCanvas(DeviceResponder, TabletCanvas):
60    def __init__(self, window, cursors):
61        super(XInputTabletCanvas, self).__init__(window)
62        self.cursors = cursors
63
64        dispatcher = XInputWindowEventDispatcher.get_dispatcher(window)
65
66        self.display = window.display
67        self._open_devices = []
68        self._cursor_map = {}
69        for cursor in cursors:
70            device = cursor.device
71            device_id = device._device_id
72            self._cursor_map[device_id] = cursor
73
74            cursor.max_pressure = device.axes[2].max
75
76            if self.display._display != device.display._display:
77                raise DeviceOpenException('Window and device displays differ')
78
79            open_device = xi.XOpenDevice(device.display._display, device_id)
80            if not open_device:
81                # Ignore this cursor; fail if no cursors added
82                continue
83            self._open_devices.append(open_device)
84
85            dispatcher.open_device(device_id, open_device, self)
86
87    def close(self):
88        for device in self._open_devices:
89            xi.XCloseDevice(self.display._display, device)
90
91    def _motion(self, e):
92        cursor = self._cursor_map.get(e.deviceid)
93        x = e.x
94        y = self.window.height - e.y
95        pressure = e.axis_data[2] / float(cursor.max_pressure)
96        self.dispatch_event('on_motion', cursor, x, y, pressure, 0.0, 0.0)
97
98    def _proximity_in(self, e):
99        cursor = self._cursor_map.get(e.deviceid)
100        self.dispatch_event('on_enter', cursor)
101
102    def _proximity_out(self, e):
103        cursor = self._cursor_map.get(e.deviceid)
104        self.dispatch_event('on_leave', cursor)
105
106
107class XInputTabletCursor(TabletCursor):
108    def __init__(self, device):
109        super(XInputTabletCursor, self).__init__(device.name)
110        self.device = device
111
112
113def get_tablets(display=None):
114    # Each cursor appears as a separate xinput device; find devices that look
115    # like Wacom tablet cursors and amalgamate them into a single tablet.
116    valid_names = ('stylus', 'cursor', 'eraser', 'wacom', 'pen', 'pad')
117    cursors = []
118    devices = get_devices(display)
119    for device in devices:
120        dev_name = device.name.lower().split()
121        if any(n in dev_name for n in valid_names) and len(device.axes) >= 3:
122            cursors.append(XInputTabletCursor(device))
123
124    if cursors:
125        return [XInputTablet(cursors)]
126    return []
127