@desklink/host 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +44 -0
- package/README.md +266 -0
- package/bin/desklink-host.mjs +113 -0
- package/dist/bridge.d.ts +57 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +208 -0
- package/dist/bridge.js.map +1 -0
- package/dist/engineProcess.d.ts +82 -0
- package/dist/engineProcess.d.ts.map +1 -0
- package/dist/engineProcess.js +243 -0
- package/dist/engineProcess.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +169 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -0
- package/dist/resolveEngine.d.ts +34 -0
- package/dist/resolveEngine.d.ts.map +1 -0
- package/dist/resolveEngine.js +103 -0
- package/dist/resolveEngine.js.map +1 -0
- package/docs/BOUNDARY.md +145 -0
- package/docs/PROTOCOL.md +363 -0
- package/engine/Cargo.lock +3077 -0
- package/engine/Cargo.toml +37 -0
- package/engine/build.rs +107 -0
- package/engine/native/inputtino_shim.cpp +11 -0
- package/engine/native/vpx_shim.c +184 -0
- package/engine/native/wrapper.h +7 -0
- package/engine/src/capture.rs +510 -0
- package/engine/src/clipboard.rs +132 -0
- package/engine/src/convert.rs +299 -0
- package/engine/src/encoder.rs +204 -0
- package/engine/src/input.rs +355 -0
- package/engine/src/keymap.rs +338 -0
- package/engine/src/main.rs +614 -0
- package/engine/src/peer.rs +775 -0
- package/engine/src/portal.rs +115 -0
- package/engine/src/protocol.rs +331 -0
- package/engine/src/session.rs +2159 -0
- package/engine/src/x11.rs +244 -0
- package/engine/vendor/inputtino/.clang-format +166 -0
- package/engine/vendor/inputtino/.clang-tidy +30 -0
- package/engine/vendor/inputtino/.dockerignore +11 -0
- package/engine/vendor/inputtino/.github/workflows/mirror-repo.yml +14 -0
- package/engine/vendor/inputtino/.github/workflows/python-lint-format-test-typecheck.yml +124 -0
- package/engine/vendor/inputtino/.github/workflows/python-test-install.yaml +49 -0
- package/engine/vendor/inputtino/.github/workflows/rust.yml +40 -0
- package/engine/vendor/inputtino/CMakeLists.txt +187 -0
- package/engine/vendor/inputtino/LICENSE +21 -0
- package/engine/vendor/inputtino/README.md +110 -0
- package/engine/vendor/inputtino/bindings/python/.devcontainer/Dockerfile +23 -0
- package/engine/vendor/inputtino/bindings/python/.devcontainer/devcontainer.json +28 -0
- package/engine/vendor/inputtino/bindings/python/.python-version +1 -0
- package/engine/vendor/inputtino/bindings/python/CMakeLists.txt +23 -0
- package/engine/vendor/inputtino/bindings/python/CPPLINT.cfg +2 -0
- package/engine/vendor/inputtino/bindings/python/Makefile +49 -0
- package/engine/vendor/inputtino/bindings/python/README.md +215 -0
- package/engine/vendor/inputtino/bindings/python/pyproject.toml +178 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/__init__.py +43 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/_core.pyi +533 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/base.py +63 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/joypad.py +248 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/keyboard.py +256 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/mouse.py +106 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/pentablet.py +73 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/py.typed +0 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/touchscreen.py +61 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/trackpad.py +74 -0
- package/engine/vendor/inputtino/bindings/python/src/main.cpp +217 -0
- package/engine/vendor/inputtino/bindings/python/tests/__init__.py +0 -0
- package/engine/vendor/inputtino/bindings/python/tests/helpers.py +3 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_base.py +60 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_inputtino.py +5 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_joypad.py +332 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_keyboard.py +164 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_mouse.py +185 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_pentablet.py +198 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_touchscreen.py +144 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_trackpad.py +146 -0
- package/engine/vendor/inputtino/bindings/python/uv.lock +390 -0
- package/engine/vendor/inputtino/bindings/rust/Cargo.toml +6 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/.dockerignore +1 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/Cargo.toml +18 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/examples/keyboard.rs +33 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/examples/mouse.rs +17 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/common.rs +116 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/error.rs +17 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad.rs +74 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_nintendo.rs +135 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_ps5.rs +280 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_xbox.rs +134 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/keyboard.rs +76 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/lib.rs +32 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/mouse.rs +178 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/common.rs +38 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/joypads.rs +150 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/keyboard.rs +73 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/mouse.rs +139 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/.dockerignore +1 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/Cargo.toml +12 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/build.rs +48 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/src/lib.rs +5 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/wrapper.h +1 -0
- package/engine/vendor/inputtino/cmake/utils.cmake +9 -0
- package/engine/vendor/inputtino/docker/Dockerfile +70 -0
- package/engine/vendor/inputtino/include/inputtino/input.h +324 -0
- package/engine/vendor/inputtino/include/inputtino/input.hpp +466 -0
- package/engine/vendor/inputtino/include/inputtino/result.hpp +37 -0
- package/engine/vendor/inputtino/share/pkgconfig/libinputtino.pc.in +17 -0
- package/engine/vendor/inputtino/src/c-bindings/helpers.hpp +19 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_ps5.cpp +115 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_switch.cpp +63 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_xone.cpp +62 -0
- package/engine/vendor/inputtino/src/c-bindings/keyboard.cpp +42 -0
- package/engine/vendor/inputtino/src/c-bindings/mouse.cpp +67 -0
- package/engine/vendor/inputtino/src/c-bindings/pen_tablet.cpp +50 -0
- package/engine/vendor/inputtino/src/c-bindings/touchscreen.cpp +43 -0
- package/engine/vendor/inputtino/src/c-bindings/trackpad.cpp +49 -0
- package/engine/vendor/inputtino/src/uhid/README.adoc +63 -0
- package/engine/vendor/inputtino/src/uhid/include/crc32.hpp +38 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/protected_types.hpp +36 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/ps5.hpp +550 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/uhid.hpp +168 -0
- package/engine/vendor/inputtino/src/uhid/joypad_ps5.cpp +580 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/keyboard.hpp +84 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_ps5_types.hpp +7 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_types.hpp +110 -0
- package/engine/vendor/inputtino/src/uinput/joypad_nintendo.cpp +190 -0
- package/engine/vendor/inputtino/src/uinput/joypad_ps.cpp +203 -0
- package/engine/vendor/inputtino/src/uinput/joypad_utils.hpp +246 -0
- package/engine/vendor/inputtino/src/uinput/joypad_xbox.cpp +190 -0
- package/engine/vendor/inputtino/src/uinput/keyboard.cpp +120 -0
- package/engine/vendor/inputtino/src/uinput/mouse.cpp +199 -0
- package/engine/vendor/inputtino/src/uinput/pentablet.cpp +162 -0
- package/engine/vendor/inputtino/src/uinput/touchscreen.cpp +140 -0
- package/engine/vendor/inputtino/src/uinput/trackpad.cpp +192 -0
- package/examples/reference-client.html +428 -0
- package/package.json +38 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"""Tests for joypad.py."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from inputtino import _core
|
|
10
|
+
from inputtino.joypad import (
|
|
11
|
+
ControllerButton,
|
|
12
|
+
DeviceDefinition,
|
|
13
|
+
PS5BatteryState,
|
|
14
|
+
PS5Joypad,
|
|
15
|
+
PS5MotionType,
|
|
16
|
+
SwitchJoypad,
|
|
17
|
+
XBoxOneJoypad,
|
|
18
|
+
)
|
|
19
|
+
from tests.helpers import mark_practical
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def mock_joypad():
|
|
24
|
+
"""Create a mock for base Joypad."""
|
|
25
|
+
with patch("inputtino._core.Joypad") as mock_joypad_cls:
|
|
26
|
+
mock_instance = MagicMock(spec=_core.Joypad)
|
|
27
|
+
mock_joypad_cls.create.return_value = mock_instance
|
|
28
|
+
yield mock_instance
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture
|
|
32
|
+
def mock_xbox_joypad():
|
|
33
|
+
"""Create a mock for XBoxOneJoypad."""
|
|
34
|
+
with patch("inputtino._core.XboxOneJoypad") as mock_joypad_cls:
|
|
35
|
+
mock_instance = MagicMock()
|
|
36
|
+
mock_joypad_cls.create = MagicMock(return_value=mock_instance)
|
|
37
|
+
yield mock_joypad_cls
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def mock_switch_joypad():
|
|
42
|
+
"""Create a mock for SwitchJoypad."""
|
|
43
|
+
with patch("inputtino._core.SwitchJoypad") as mock_joypad_cls:
|
|
44
|
+
mock_instance = MagicMock()
|
|
45
|
+
mock_joypad_cls.create = MagicMock(return_value=mock_instance)
|
|
46
|
+
yield mock_joypad_cls
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.fixture
|
|
50
|
+
def mock_ps5_joypad():
|
|
51
|
+
"""Create a mock for PS5Joypad."""
|
|
52
|
+
with patch("inputtino._core.PS5Joypad") as mock_joypad_cls:
|
|
53
|
+
mock_instance = MagicMock()
|
|
54
|
+
mock_joypad_cls.create = MagicMock(return_value=mock_instance)
|
|
55
|
+
yield mock_joypad_cls
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_xbox_joypad_creation_with_default_device(mock_xbox_joypad):
|
|
59
|
+
"""Test creating an Xbox joypad with default device definition."""
|
|
60
|
+
XBoxOneJoypad()
|
|
61
|
+
created_def = mock_xbox_joypad.create.call_args[0][0]
|
|
62
|
+
|
|
63
|
+
assert created_def.name == "Wolf X-Box One (virtual) pad"
|
|
64
|
+
assert created_def.vendor_id == 0x045E
|
|
65
|
+
assert created_def.product_id == 0x02EA
|
|
66
|
+
assert created_def.version == 0x0408
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_xbox_joypad_with_custom_device(mock_xbox_joypad):
|
|
70
|
+
"""Test creating an Xbox joypad with custom device definition."""
|
|
71
|
+
custom_def = DeviceDefinition(
|
|
72
|
+
name="Custom Xbox",
|
|
73
|
+
vendor_id=0x1234,
|
|
74
|
+
product_id=0x5678,
|
|
75
|
+
version=0x0001,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
XBoxOneJoypad(custom_def)
|
|
79
|
+
created_def = mock_xbox_joypad.create.call_args[0][0]
|
|
80
|
+
|
|
81
|
+
assert created_def.name == custom_def.name
|
|
82
|
+
assert created_def.vendor_id == custom_def.vendor_id
|
|
83
|
+
assert created_def.product_id == custom_def.product_id
|
|
84
|
+
assert created_def.version == custom_def.version
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_joypad_button_press(mock_xbox_joypad):
|
|
88
|
+
"""Test joypad button pressing."""
|
|
89
|
+
joypad = XBoxOneJoypad()
|
|
90
|
+
instance = mock_xbox_joypad.create.return_value
|
|
91
|
+
|
|
92
|
+
buttons = ControllerButton.A | ControllerButton.B | ControllerButton.DPAD_UP
|
|
93
|
+
joypad.set_pressed_buttons(buttons)
|
|
94
|
+
instance.set_pressed_buttons.assert_called_once_with(int(buttons))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_joypad_triggers(mock_xbox_joypad):
|
|
98
|
+
"""Test setting trigger values."""
|
|
99
|
+
joypad = XBoxOneJoypad()
|
|
100
|
+
instance = mock_xbox_joypad.create.return_value
|
|
101
|
+
|
|
102
|
+
joypad.set_triggers(1000, 2000)
|
|
103
|
+
instance.set_triggers.assert_called_once_with(1000, 2000)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_joypad_stick(mock_xbox_joypad):
|
|
107
|
+
"""Test setting stick position."""
|
|
108
|
+
joypad = XBoxOneJoypad()
|
|
109
|
+
instance = mock_xbox_joypad.create.return_value
|
|
110
|
+
|
|
111
|
+
joypad.set_stick(_core.StickPosition.LS, 100, 200)
|
|
112
|
+
instance.set_stick.assert_called_once_with(_core.StickPosition.LS, 100, 200)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_ps5_joypad_specific_features(mock_ps5_joypad):
|
|
116
|
+
"""Test PS5-specific features."""
|
|
117
|
+
joypad = PS5Joypad()
|
|
118
|
+
instance = mock_ps5_joypad.create.return_value
|
|
119
|
+
instance.get_mac_address.return_value = "00:11:22:33:44:55"
|
|
120
|
+
|
|
121
|
+
assert joypad.mac_address == "00:11:22:33:44:55"
|
|
122
|
+
|
|
123
|
+
joypad.place_finger(1, 960, 540)
|
|
124
|
+
instance.place_finger.assert_called_once_with(1, 960, 540)
|
|
125
|
+
|
|
126
|
+
joypad.set_motion(PS5MotionType.ACCELERATION, 1.0, 2.0, 3.0)
|
|
127
|
+
instance.set_motion.assert_called_once_with(
|
|
128
|
+
PS5MotionType.ACCELERATION, 1.0, 2.0, 3.0
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
joypad.set_battery(PS5BatteryState.BATTERY_FULL, 100)
|
|
132
|
+
instance.set_battery.assert_called_once_with(PS5BatteryState.BATTERY_FULL, 100)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def test_rumble_callback():
|
|
136
|
+
"""Test rumble callback functionality."""
|
|
137
|
+
with patch("inputtino._core.XboxOneJoypad") as mock_joypad_cls:
|
|
138
|
+
joypad = XBoxOneJoypad()
|
|
139
|
+
|
|
140
|
+
callback_called = False
|
|
141
|
+
|
|
142
|
+
def rumble_callback(low, high):
|
|
143
|
+
nonlocal callback_called
|
|
144
|
+
callback_called = True
|
|
145
|
+
assert low == 1000
|
|
146
|
+
assert high == 2000
|
|
147
|
+
|
|
148
|
+
joypad.set_on_rumble(rumble_callback)
|
|
149
|
+
|
|
150
|
+
# Simulate rumble event
|
|
151
|
+
mock_joypad_cls.create.return_value.set_on_rumble.call_args[0][0](1000, 2000)
|
|
152
|
+
assert callback_called
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_ps5_led_callback():
|
|
156
|
+
"""Test PS5 LED callback functionality."""
|
|
157
|
+
with patch("inputtino._core.PS5Joypad") as mock_joypad_cls:
|
|
158
|
+
joypad = PS5Joypad()
|
|
159
|
+
|
|
160
|
+
callback_called = False
|
|
161
|
+
|
|
162
|
+
def led_callback(r, g, b):
|
|
163
|
+
nonlocal callback_called
|
|
164
|
+
callback_called = True
|
|
165
|
+
assert r == 255
|
|
166
|
+
assert g == 0
|
|
167
|
+
assert b == 0
|
|
168
|
+
|
|
169
|
+
joypad.set_on_led(led_callback)
|
|
170
|
+
|
|
171
|
+
# Simulate LED event
|
|
172
|
+
mock_joypad_cls.create.return_value.set_on_led.call_args[0][0](255, 0, 0)
|
|
173
|
+
assert callback_called
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_joypad_creation_failure():
|
|
177
|
+
"""Test handling of joypad creation failure."""
|
|
178
|
+
with patch("inputtino._core.XboxOneJoypad") as mock_joypad_cls:
|
|
179
|
+
mock_joypad_cls.create.side_effect = RuntimeError(
|
|
180
|
+
"Failed to create joypad device"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
with pytest.raises(RuntimeError, match="Failed to create joypad device"):
|
|
184
|
+
XBoxOneJoypad()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_switch_joypad_creation_with_default_device(mock_switch_joypad):
|
|
188
|
+
"""Test creating a Switch joypad with default device definition."""
|
|
189
|
+
SwitchJoypad()
|
|
190
|
+
created_def = mock_switch_joypad.create.call_args[0][0]
|
|
191
|
+
|
|
192
|
+
assert created_def.name == "Wolf Nintendo (virtual) pad"
|
|
193
|
+
assert created_def.vendor_id == 0x057E
|
|
194
|
+
assert created_def.product_id == 0x2009
|
|
195
|
+
assert created_def.version == 0x8111
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_switch_joypad_with_custom_device(mock_switch_joypad):
|
|
199
|
+
"""Test creating a Switch joypad with custom device definition."""
|
|
200
|
+
custom_def = DeviceDefinition(
|
|
201
|
+
name="Custom Switch",
|
|
202
|
+
vendor_id=0x1234,
|
|
203
|
+
product_id=0x5678,
|
|
204
|
+
version=0x0001,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
SwitchJoypad(custom_def)
|
|
208
|
+
created_def = mock_switch_joypad.create.call_args[0][0]
|
|
209
|
+
|
|
210
|
+
assert created_def.name == custom_def.name
|
|
211
|
+
assert created_def.vendor_id == custom_def.vendor_id
|
|
212
|
+
assert created_def.product_id == custom_def.product_id
|
|
213
|
+
assert created_def.version == custom_def.version
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def test_switch_rumble_callback():
|
|
217
|
+
"""Test Switch rumble callback functionality."""
|
|
218
|
+
with patch("inputtino._core.SwitchJoypad") as mock_joypad_cls:
|
|
219
|
+
joypad = SwitchJoypad()
|
|
220
|
+
|
|
221
|
+
callback_called = False
|
|
222
|
+
|
|
223
|
+
def rumble_callback(low, high):
|
|
224
|
+
nonlocal callback_called
|
|
225
|
+
callback_called = True
|
|
226
|
+
assert low == 1000
|
|
227
|
+
assert high == 2000
|
|
228
|
+
|
|
229
|
+
joypad.set_on_rumble(rumble_callback)
|
|
230
|
+
|
|
231
|
+
mock_joypad_cls.create.return_value.set_on_rumble.call_args[0][0](1000, 2000)
|
|
232
|
+
assert callback_called
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@mark_practical
|
|
236
|
+
def test_practical_joypad_gameplay():
|
|
237
|
+
"""Test gamepad controls in a simulated gameplay scenario.
|
|
238
|
+
|
|
239
|
+
This practical test demonstrates:
|
|
240
|
+
1. Basic directional movement
|
|
241
|
+
2. Button combinations
|
|
242
|
+
3. Trigger and stick input
|
|
243
|
+
4. Rumble feedback
|
|
244
|
+
|
|
245
|
+
Note:
|
|
246
|
+
This test requires actual device access (/dev/uinput)
|
|
247
|
+
and may fail if run without proper permissions.
|
|
248
|
+
"""
|
|
249
|
+
joypad = XBoxOneJoypad()
|
|
250
|
+
assert len(joypad.nodes) > 0, "No joypad nodes created"
|
|
251
|
+
|
|
252
|
+
time.sleep(0.1) # Allow device registration
|
|
253
|
+
|
|
254
|
+
rumble_received = False
|
|
255
|
+
|
|
256
|
+
def on_rumble(low, high):
|
|
257
|
+
nonlocal rumble_received
|
|
258
|
+
rumble_received = True
|
|
259
|
+
|
|
260
|
+
joypad.set_on_rumble(on_rumble)
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
# Test movement sequence
|
|
264
|
+
movements = [
|
|
265
|
+
(ControllerButton.DPAD_RIGHT, _core.StickPosition.LS, 32767, 0),
|
|
266
|
+
(ControllerButton.DPAD_DOWN, _core.StickPosition.LS, 0, 32767),
|
|
267
|
+
(ControllerButton.DPAD_LEFT, _core.StickPosition.LS, -32767, 0),
|
|
268
|
+
(ControllerButton.DPAD_UP, _core.StickPosition.LS, 0, -32767),
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
for button, stick, x, y in movements:
|
|
272
|
+
joypad.set_pressed_buttons(button)
|
|
273
|
+
joypad.set_stick(stick, x, y)
|
|
274
|
+
joypad.set_triggers(32767, 32767)
|
|
275
|
+
time.sleep(0.1)
|
|
276
|
+
joypad.set_pressed_buttons(ControllerButton(0))
|
|
277
|
+
time.sleep(0.1)
|
|
278
|
+
|
|
279
|
+
# Test button combinations
|
|
280
|
+
joypad.set_pressed_buttons(
|
|
281
|
+
ControllerButton.A | ControllerButton.B | ControllerButton.X
|
|
282
|
+
)
|
|
283
|
+
time.sleep(0.1)
|
|
284
|
+
|
|
285
|
+
finally:
|
|
286
|
+
joypad.set_pressed_buttons(ControllerButton(0))
|
|
287
|
+
joypad.set_triggers(0, 0)
|
|
288
|
+
joypad.set_stick(_core.StickPosition.LS, 0, 0)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@mark_practical
|
|
292
|
+
def test_practical_ps5_features():
|
|
293
|
+
"""Test PS5-specific features in a practical scenario.
|
|
294
|
+
|
|
295
|
+
Tests touchpad, motion sensors, LED, and battery reporting.
|
|
296
|
+
|
|
297
|
+
Note:
|
|
298
|
+
Requires /dev/uinput access.
|
|
299
|
+
"""
|
|
300
|
+
joypad = PS5Joypad()
|
|
301
|
+
# NOTE: 2025/02/05 Not working.
|
|
302
|
+
# assert len(joypad.nodes) > 0
|
|
303
|
+
|
|
304
|
+
time.sleep(0.1)
|
|
305
|
+
|
|
306
|
+
led_changed = False
|
|
307
|
+
|
|
308
|
+
def on_led(r, g, b):
|
|
309
|
+
nonlocal led_changed
|
|
310
|
+
led_changed = True
|
|
311
|
+
|
|
312
|
+
joypad.set_on_led(on_led)
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
# Touchpad gesture
|
|
316
|
+
joypad.place_finger(0, 960, 540)
|
|
317
|
+
time.sleep(0.1)
|
|
318
|
+
joypad.place_finger(0, 1200, 540)
|
|
319
|
+
time.sleep(0.1)
|
|
320
|
+
joypad.release_finger(0)
|
|
321
|
+
|
|
322
|
+
# Motion controls
|
|
323
|
+
joypad.set_motion(PS5MotionType.ACCELERATION, 0.5, 0.1, 9.8)
|
|
324
|
+
time.sleep(0.1)
|
|
325
|
+
joypad.set_motion(PS5MotionType.GYROSCOPE, 45.0, 0.0, 0.0)
|
|
326
|
+
time.sleep(0.1)
|
|
327
|
+
|
|
328
|
+
# Battery update
|
|
329
|
+
joypad.set_battery(PS5BatteryState.BATTERY_FULL, 100)
|
|
330
|
+
|
|
331
|
+
finally:
|
|
332
|
+
joypad.release_finger(0)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Tests for keyboard.py."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from inputtino._core import Keyboard as CoreKeyboard
|
|
9
|
+
from inputtino.base import DeviceDefinition
|
|
10
|
+
from inputtino.keyboard import DEFAULT_KEYBOARD, Keyboard, KeyCode
|
|
11
|
+
from tests.helpers import mark_practical
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def mock_core_keyboard():
|
|
16
|
+
"""Create a mock for CoreKeyboard with basic functionality."""
|
|
17
|
+
with patch("inputtino._core.Keyboard") as mock_keyboard_cls:
|
|
18
|
+
mock_instance = MagicMock(spec=CoreKeyboard)
|
|
19
|
+
mock_keyboard_cls.create.return_value = mock_instance
|
|
20
|
+
yield mock_instance
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_keyboard_creation_with_default_device():
|
|
24
|
+
"""Test creating a keyboard with default device definition."""
|
|
25
|
+
with patch("inputtino._core.Keyboard") as mock_keyboard_cls:
|
|
26
|
+
Keyboard()
|
|
27
|
+
|
|
28
|
+
mock_keyboard_cls.create.assert_called_once()
|
|
29
|
+
created_def = mock_keyboard_cls.create.call_args[0][0]
|
|
30
|
+
assert created_def.name == DEFAULT_KEYBOARD.name
|
|
31
|
+
assert created_def.vendor_id == DEFAULT_KEYBOARD.vendor_id
|
|
32
|
+
assert created_def.product_id == DEFAULT_KEYBOARD.product_id
|
|
33
|
+
assert created_def.version == DEFAULT_KEYBOARD.version
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_keyboard_creation_with_custom_device():
|
|
37
|
+
"""Test creating a keyboard with custom device definition."""
|
|
38
|
+
custom_def = DeviceDefinition(
|
|
39
|
+
name="Custom Keyboard",
|
|
40
|
+
vendor_id=0x1234,
|
|
41
|
+
product_id=0x5678,
|
|
42
|
+
version=0x0001,
|
|
43
|
+
device_phys="/dev/input/test",
|
|
44
|
+
device_uniq="unique_id",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
with patch("inputtino._core.Keyboard") as mock_keyboard_cls:
|
|
48
|
+
Keyboard(custom_def)
|
|
49
|
+
|
|
50
|
+
mock_keyboard_cls.create.assert_called_once()
|
|
51
|
+
created_def = mock_keyboard_cls.create.call_args[0][0]
|
|
52
|
+
assert created_def.name == custom_def.name
|
|
53
|
+
assert created_def.vendor_id == custom_def.vendor_id
|
|
54
|
+
assert created_def.product_id == custom_def.product_id
|
|
55
|
+
assert created_def.version == custom_def.version
|
|
56
|
+
assert created_def.device_phys == custom_def.device_phys
|
|
57
|
+
assert created_def.device_uniq == custom_def.device_uniq
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_keyboard_custom_repress_time():
|
|
61
|
+
"""Test creating a keyboard with custom key repress time."""
|
|
62
|
+
with patch("inputtino._core.Keyboard") as mock_keyboard_cls:
|
|
63
|
+
Keyboard(millis_repress_key=100)
|
|
64
|
+
mock_keyboard_cls.create.assert_called_once()
|
|
65
|
+
assert mock_keyboard_cls.create.call_args[0][1] == 100
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_keyboard_key_press_and_release(mock_core_keyboard):
|
|
69
|
+
"""Test keyboard key press and release."""
|
|
70
|
+
keyboard = Keyboard()
|
|
71
|
+
|
|
72
|
+
# Test single key press/release
|
|
73
|
+
keyboard.press(KeyCode.A)
|
|
74
|
+
mock_core_keyboard.press.assert_called_with(KeyCode.A)
|
|
75
|
+
|
|
76
|
+
keyboard.release(KeyCode.A)
|
|
77
|
+
mock_core_keyboard.release.assert_called_with(KeyCode.A)
|
|
78
|
+
|
|
79
|
+
# Test modifier key press/release
|
|
80
|
+
keyboard.press(KeyCode.SHIFT)
|
|
81
|
+
mock_core_keyboard.press.assert_called_with(KeyCode.SHIFT)
|
|
82
|
+
|
|
83
|
+
keyboard.release(KeyCode.SHIFT)
|
|
84
|
+
mock_core_keyboard.release.assert_called_with(KeyCode.SHIFT)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_keyboard_nodes(mock_core_keyboard):
|
|
88
|
+
"""Test getting keyboard device nodes."""
|
|
89
|
+
expected_nodes = ["/dev/input/event1"]
|
|
90
|
+
mock_core_keyboard.get_nodes.return_value = expected_nodes
|
|
91
|
+
|
|
92
|
+
keyboard = Keyboard()
|
|
93
|
+
assert keyboard.nodes == expected_nodes
|
|
94
|
+
mock_core_keyboard.get_nodes.assert_called_once()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_keyboard_type(mock_core_keyboard):
|
|
98
|
+
"""Test keyboard type method."""
|
|
99
|
+
keyboard = Keyboard()
|
|
100
|
+
|
|
101
|
+
with patch("time.sleep") as mock_sleep:
|
|
102
|
+
# Test with default duration
|
|
103
|
+
keyboard.type(KeyCode.A)
|
|
104
|
+
mock_core_keyboard.press.assert_called_with(KeyCode.A)
|
|
105
|
+
mock_sleep.assert_called_with(0.1)
|
|
106
|
+
mock_core_keyboard.release.assert_called_with(KeyCode.A)
|
|
107
|
+
|
|
108
|
+
# Test with custom duration
|
|
109
|
+
keyboard.type(KeyCode.B, 0.5)
|
|
110
|
+
mock_core_keyboard.press.assert_called_with(KeyCode.B)
|
|
111
|
+
mock_sleep.assert_called_with(0.5)
|
|
112
|
+
mock_core_keyboard.release.assert_called_with(KeyCode.B)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_keyboard_creation_failure():
|
|
116
|
+
"""Test handling of keyboard creation failure."""
|
|
117
|
+
with patch("inputtino._core.Keyboard") as mock_keyboard_cls:
|
|
118
|
+
mock_keyboard_cls.create.side_effect = RuntimeError(
|
|
119
|
+
"Failed to create keyboard device"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
with pytest.raises(RuntimeError, match="Failed to create keyboard device"):
|
|
123
|
+
Keyboard()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_key_code_from_string():
|
|
127
|
+
assert KeyCode.from_str("a") is KeyCode.A
|
|
128
|
+
assert KeyCode.from_str("SHIFT") is KeyCode.SHIFT
|
|
129
|
+
|
|
130
|
+
with pytest.raises(KeyError):
|
|
131
|
+
KeyCode.from_str("")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@mark_practical
|
|
135
|
+
def test_practical_keyboard_input():
|
|
136
|
+
"""Test simulating keyboard input with a practical example.
|
|
137
|
+
|
|
138
|
+
This practical test demonstrates:
|
|
139
|
+
1. Creating a keyboard device
|
|
140
|
+
2. Simulating Ctrl+A (select all) keyboard shortcut
|
|
141
|
+
3. Verifying device creation and basic functionality
|
|
142
|
+
|
|
143
|
+
Note:
|
|
144
|
+
This test requires actual device access (/dev/uinput)
|
|
145
|
+
and may fail if run without proper permissions.
|
|
146
|
+
"""
|
|
147
|
+
keyboard = Keyboard()
|
|
148
|
+
|
|
149
|
+
# Ensure we have valid device nodes
|
|
150
|
+
assert len(keyboard.nodes) > 0, "No keyboard device nodes created"
|
|
151
|
+
|
|
152
|
+
# Give system time to register the device
|
|
153
|
+
time.sleep(0.1)
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Simulate Ctrl+A (select all)
|
|
157
|
+
keyboard.press(KeyCode.CTRL)
|
|
158
|
+
keyboard.press(KeyCode.A)
|
|
159
|
+
time.sleep(0.1)
|
|
160
|
+
keyboard.release(KeyCode.A)
|
|
161
|
+
keyboard.release(KeyCode.CTRL)
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
raise RuntimeError(f"Failed to simulate keyboard input: {e}") from e
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Tests for mouse.py."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from inputtino._core import Mouse as CoreMouse
|
|
9
|
+
from inputtino.mouse import DeviceDefinition, Mouse, MouseButton
|
|
10
|
+
from tests.helpers import mark_practical
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def mock_core_mouse():
|
|
15
|
+
"""Create a mock for CoreMouse with basic functionality."""
|
|
16
|
+
with patch("inputtino._core.Mouse") as mock_mouse_cls:
|
|
17
|
+
mock_instance = MagicMock(spec=CoreMouse)
|
|
18
|
+
mock_mouse_cls.create.return_value = mock_instance
|
|
19
|
+
yield mock_instance
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_mouse_creation_with_default_device():
|
|
23
|
+
"""Test creating a mouse with default device definition."""
|
|
24
|
+
with patch("inputtino._core.Mouse") as mock_mouse_cls:
|
|
25
|
+
Mouse()
|
|
26
|
+
|
|
27
|
+
mock_mouse_cls.create.assert_called_once()
|
|
28
|
+
created_def = mock_mouse_cls.create.call_args[0][0]
|
|
29
|
+
assert created_def.name == "Wolf mouse virtual device"
|
|
30
|
+
assert created_def.vendor_id == 0xAB00
|
|
31
|
+
assert created_def.product_id == 0xAB01
|
|
32
|
+
assert created_def.version == 0xAB00
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_mouse_creation_with_custom_device():
|
|
36
|
+
"""Test creating a mouse with custom device definition."""
|
|
37
|
+
custom_def = DeviceDefinition(
|
|
38
|
+
name="Custom Mouse",
|
|
39
|
+
vendor_id=0x1234,
|
|
40
|
+
product_id=0x5678,
|
|
41
|
+
version=0x0001,
|
|
42
|
+
device_phys="/dev/input/test",
|
|
43
|
+
device_uniq="unique_id",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
with patch("inputtino._core.Mouse") as mock_mouse_cls:
|
|
47
|
+
Mouse(custom_def)
|
|
48
|
+
|
|
49
|
+
mock_mouse_cls.create.assert_called_once()
|
|
50
|
+
created_def = mock_mouse_cls.create.call_args[0][0]
|
|
51
|
+
assert created_def.name == custom_def.name
|
|
52
|
+
assert created_def.vendor_id == custom_def.vendor_id
|
|
53
|
+
assert created_def.product_id == custom_def.product_id
|
|
54
|
+
assert created_def.version == custom_def.version
|
|
55
|
+
assert created_def.device_phys == custom_def.device_phys
|
|
56
|
+
assert created_def.device_uniq == custom_def.device_uniq
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_mouse_movement(mock_core_mouse):
|
|
60
|
+
"""Test relative mouse movement."""
|
|
61
|
+
mouse = Mouse()
|
|
62
|
+
mouse.move(100, -50)
|
|
63
|
+
|
|
64
|
+
mock_core_mouse.move.assert_called_once_with(100, -50)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_mouse_absolute_movement(mock_core_mouse):
|
|
68
|
+
"""Test absolute mouse movement."""
|
|
69
|
+
mouse = Mouse()
|
|
70
|
+
mouse.move_abs(1920, 1080, 3840, 2160)
|
|
71
|
+
|
|
72
|
+
mock_core_mouse.move_abs.assert_called_once_with(1920, 1080, 3840, 2160)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_mouse_button_press_and_release(mock_core_mouse):
|
|
76
|
+
"""Test mouse button press and release."""
|
|
77
|
+
mouse = Mouse()
|
|
78
|
+
|
|
79
|
+
# Test pressing each button
|
|
80
|
+
for button in [
|
|
81
|
+
MouseButton.EXTRA,
|
|
82
|
+
MouseButton.LEFT,
|
|
83
|
+
MouseButton.RIGHT,
|
|
84
|
+
MouseButton.MIDDLE,
|
|
85
|
+
MouseButton.SIDE,
|
|
86
|
+
]:
|
|
87
|
+
mouse.press(button)
|
|
88
|
+
mock_core_mouse.press.assert_called_with(button)
|
|
89
|
+
|
|
90
|
+
mouse.release(button)
|
|
91
|
+
mock_core_mouse.release.assert_called_with(button)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_mouse_scrolling(mock_core_mouse):
|
|
95
|
+
"""Test mouse scrolling in both directions."""
|
|
96
|
+
mouse = Mouse()
|
|
97
|
+
|
|
98
|
+
# Test vertical scrolling
|
|
99
|
+
mouse.scroll_vertical(120)
|
|
100
|
+
mock_core_mouse.vertical_scroll.assert_called_once_with(120)
|
|
101
|
+
|
|
102
|
+
# Test horizontal scrolling
|
|
103
|
+
mouse.scroll_horizontal(-120)
|
|
104
|
+
mock_core_mouse.horizontal_scroll.assert_called_once_with(-120)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_mouse_nodes(mock_core_mouse):
|
|
108
|
+
"""Test getting mouse device nodes."""
|
|
109
|
+
expected_nodes = ["/dev/input/event0", "/dev/input/mouse0"]
|
|
110
|
+
mock_core_mouse.get_nodes.return_value = expected_nodes
|
|
111
|
+
|
|
112
|
+
mouse = Mouse()
|
|
113
|
+
assert mouse.nodes == expected_nodes
|
|
114
|
+
mock_core_mouse.get_nodes.assert_called_once()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_click(mock_core_mouse):
|
|
118
|
+
"""Test single click functionality."""
|
|
119
|
+
mouse = Mouse()
|
|
120
|
+
with patch("time.sleep") as mock_sleep:
|
|
121
|
+
mouse.click(MouseButton.LEFT, 0.5)
|
|
122
|
+
|
|
123
|
+
mock_core_mouse.press.assert_called_once_with(MouseButton.LEFT)
|
|
124
|
+
mock_sleep.assert_called_once_with(0.5)
|
|
125
|
+
mock_core_mouse.release.assert_called_once_with(MouseButton.LEFT)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_mouse_creation_failure():
|
|
129
|
+
"""Test handling of mouse creation failure."""
|
|
130
|
+
with patch("inputtino._core.Mouse") as mock_mouse_cls:
|
|
131
|
+
mock_mouse_cls.create.side_effect = RuntimeError(
|
|
132
|
+
"Failed to create mouse device"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
with pytest.raises(RuntimeError, match="Failed to create mouse device"):
|
|
136
|
+
Mouse()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@mark_practical
|
|
140
|
+
def test_practical_mouse_draw_square():
|
|
141
|
+
"""Test drawing a square pattern with the mouse.
|
|
142
|
+
|
|
143
|
+
This practical test demonstrates drawing a square pattern by:
|
|
144
|
+
1. Moving the mouse in a square pattern
|
|
145
|
+
2. Holding left button during the movement
|
|
146
|
+
3. Creating a visible mouse movement pattern
|
|
147
|
+
|
|
148
|
+
Note:
|
|
149
|
+
This test requires actual device access (/dev/uinput)
|
|
150
|
+
and may fail if run without proper permissions.
|
|
151
|
+
"""
|
|
152
|
+
mouse = Mouse()
|
|
153
|
+
|
|
154
|
+
# Ensure we have valid device nodes
|
|
155
|
+
assert len(mouse.nodes) > 0, "No mouse device nodes created"
|
|
156
|
+
|
|
157
|
+
# Give system time to register the device
|
|
158
|
+
time.sleep(0.1)
|
|
159
|
+
|
|
160
|
+
# Start at center
|
|
161
|
+
center_x, center_y = 960, 540 # Assuming 1920x1080 screen
|
|
162
|
+
square_size = 200
|
|
163
|
+
|
|
164
|
+
mouse.move_abs(center_x - square_size // 2, center_y - square_size // 2, 1920, 1080)
|
|
165
|
+
mouse.press(MouseButton.LEFT)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
# Draw square pattern
|
|
169
|
+
movements = [
|
|
170
|
+
(square_size, 0), # Right
|
|
171
|
+
(0, square_size), # Down
|
|
172
|
+
(-square_size, 0), # Left
|
|
173
|
+
(0, -square_size), # Up
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
for dx, dy in movements:
|
|
177
|
+
# Move slowly to create visible pattern
|
|
178
|
+
steps = 10
|
|
179
|
+
for _ in range(steps):
|
|
180
|
+
mouse.move(dx // steps, dy // steps)
|
|
181
|
+
time.sleep(0.01) # Small delay for smooth movement
|
|
182
|
+
|
|
183
|
+
finally:
|
|
184
|
+
# Ensure we always release the button
|
|
185
|
+
mouse.release(MouseButton.LEFT)
|