Compare commits

...

5 Commits

Author SHA1 Message Date
Mel
92254a43d1 finished version 0.1.0 2025-12-13 04:37:03 +01:00
Mel
38a1e7de60 add .idea to gitignore 2025-12-11 23:02:30 +01:00
Mel
6c90998bb0 started to add VTS API code 2025-12-11 23:01:34 +01:00
Mel
8b6ea2cf1a add .idea 2025-12-11 19:09:34 +01:00
Mel
e565eff85b initial commit 2025-12-11 19:09:33 +01:00
5 changed files with 872 additions and 2 deletions

3
.gitignore vendored
View File

@@ -166,11 +166,10 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc

10
pyproject.toml Normal file
View File

@@ -0,0 +1,10 @@
[project]
name = "vts-gamepad-input"
version = "0.1.0"
description = "VTS Plugin to send controller inputs to VTS"
requires-python = ">=3.14"
dependencies = [
"inputs>=0.5",
"inquirer>=3.4.1",
"pyvts>=0.3.3",
]

275
src/vts-gampad-input/main.py Executable file
View File

@@ -0,0 +1,275 @@
#! /usr/bin/env python
import asyncio
import time
import inquirer
from inputs import GamePad, devices
from vts_connection import VTSConnection
# initiate gamepad buttons
dpad_up, dpad_down, dpad_left, dpad_right = (0.0, 0.0, 0.0, 0.0)
action_up, action_down, action_left, action_right = (0.0, 0.0, 0.0, 0.0)
button_start, button_select = (0.0, 0.0)
button_l1, button_l2, button_l3 = (0.0, 0.0, 0.0)
button_r1, button_r2, button_r3 = (0.0, 0.0, 0.0)
left_stick_x_axis, left_stick_y_axis, right_stick_x_axis, right_stick_y_axis = (
0.0,
0.0,
0.0,
0.0,
)
left_button_pressed, right_button_pressed, left_button_hold, right_button_hold = (
0.0,
0.0,
0.0,
0.0,
)
left_thumb_stick, right_thumb_stick = (0.0, 0.0)
left_thumb_X, right_thumb_X, left_thumb_Y, right_thumb_Y = (0.0, 0.0, 0.0, 0.0)
left_index, right_index = (0.0, 0.0)
def _get_gamepads() -> list[GamePad]:
"""Get list of connected gamepad"""
gamepads = []
for device in devices.gamepads:
gamepads.append(device)
return gamepads
def select_gamepad() -> GamePad | None:
"""Select gamepad to use with VTS"""
gamepads = _get_gamepads()
if len(gamepads) > 1:
selection = inquirer.prompt(
questions=[
inquirer.List(
name="gamepad",
message="Select gamepad to use for VTS",
choices=gamepads,
)
]
)
if selection:
gamepad = selection["gamepad"]
return gamepad
else:
exit(0)
else:
return gamepads[0]
def _deadzone(analog_value: float, deadzone: float = 0.1) -> float:
return 0 if abs(analog_value) < deadzone else analog_value
def _normalize_analog_sticks(analog_value: float) -> float:
"""Normalize analog value to -1 - 1 range to comply with Nyarupad"""
minimum: int = 0
maximum: int = 255
formula = 2 * ((analog_value - minimum) / (maximum - minimum)) - 1
return formula
def _normalize_analog_trigger(analog_value: float) -> float:
minimum: int = 0
maximum: int = 255
formula = (analog_value - minimum) / (maximum - minimum)
return formula
def set_inputs(code: str, state: float) -> None:
global dpad_up, dpad_down, dpad_left, dpad_right
global action_up, action_down, action_left, action_right
global left_stick_x_axis, left_stick_y_axis, right_stick_x_axis, right_stick_y_axis
global button_start, button_l1, button_l2, button_l3
global button_select, button_r1, button_r2, button_r3
global left_thumb_stick, right_thumb_stick
global left_thumb_X, right_thumb_X, left_thumb_Y, right_thumb_Y
global left_index, right_index
global \
left_button_pressed, \
right_button_pressed, \
left_button_hold, \
right_button_hold
if code == "ABS_HAT0Y":
dpad_up = state * -1 if state < 0 else 0
dpad_down = state if state > 0 else 0
elif code == "ABS_HAT0X":
dpad_left = state * -1 if state < 0 else 0
dpad_right = state if state > 0 else 0
elif code == "ABS_X":
left_stick_x_axis = state
elif code == "ABS_Y":
left_stick_y_axis = state * -1
elif code == "ABS_RX":
right_stick_x_axis = state
elif code == "ABS_RY":
right_stick_y_axis = state * -1
elif code == "ABS_Z":
button_l2 = state
elif code == "ABS_RZ":
button_r2 = state
elif code == "BTN_TL":
button_l1 = state
elif code == "BTN_TR":
button_r1 = state
elif code == "BTN_SOUTH":
action_down = state
elif code == "BTN_WEST":
action_left = state
elif code == "BTN_EAST":
action_right = state
elif code == "BTN_NORTH":
action_up = state
elif code == "BTN_THUMBL":
button_l3 = state
elif code == "BTN_THUMBR":
button_r3 = state
elif code == "BTN_START":
button_start = state
elif code == "BTN_SELECT":
button_select = state
left_button_pressed = dpad_left + dpad_up + dpad_down + dpad_right
right_button_pressed = action_left + action_right + action_up + action_down
left_thumb_X = dpad_right - dpad_left if left_button_pressed >= 1 else 0
left_thumb_Y = dpad_up - dpad_down if left_button_pressed >= 1 else 0
right_thumb_X = action_right - action_left if right_button_pressed >= 1 else 0
right_thumb_Y = action_up - action_down if right_button_pressed >= 1 else 0
left_thumb_stick = (
1
if dpad_down == dpad_up == dpad_left == dpad_right == 0 or button_l3 == 1
else 0
)
right_thumb_stick = (
1
if action_up == action_down == action_left == action_right == 0
or button_r3 == 1
else 0
)
left_index = 1 if button_l1 == 0 else 0
right_index = 1 if button_r1 == 0 else 0
def create_vts_input_request() -> list[dict]:
params = [
{"id": "NP_ON", "value": 1.0, "weight": 1.0},
{"id": "NP_DPadDown", "value": dpad_down, "weight": 1.0},
{"id": "NP_DPadUp", "value": dpad_up, "weight": 1.0},
{"id": "NP_DPadLeft", "value": dpad_left, "weight": 1.0},
{"id": "NP_DPadRight", "value": dpad_right, "weight": 1.0},
{"id": "NP_ButtonA", "value": action_down, "weight": 1.0},
{"id": "NP_ButtonB", "value": action_right, "weight": 1.0},
{"id": "NP_ButtonX", "value": action_left, "weight": 1.0},
{"id": "NP_ButtonY", "value": action_up, "weight": 1.0},
{"id": "NP_StartDown", "value": button_start, "weight": 1.0},
{"id": "NP_SelectDown", "value": button_select, "weight": 1.0},
{"id": "NP_ButtonLS", "value": button_l3, "weight": 1.0},
{"id": "NP_ButtonRS", "value": button_r3, "weight": 1.0},
{"id": "NP_L1", "value": button_l1, "weight": 1.0},
{"id": "NP_R1", "value": button_r1, "weight": 1.0},
{"id": "NP_L2", "value": button_l2, "weight": 1.0},
{"id": "NP_R2", "value": button_r2, "weight": 1.0},
{"id": "NP_LStickX", "value": left_stick_x_axis, "weight": 1.0},
{"id": "NP_LStickY", "value": left_stick_y_axis, "weight": 1.0},
{"id": "NP_RStickX", "value": right_stick_x_axis, "weight": 1.0},
{"id": "NP_RStickY", "value": right_stick_y_axis, "weight": 1.0},
{"id": "NP_LButtonDown", "value": left_button_pressed, "weight": 1.0},
{"id": "NP_RButtonDown", "value": right_button_pressed, "weight": 1.0},
{"id": "NP_LButtonPress", "value": left_button_pressed, "weight": 1.0},
{"id": "NP_RButtonPress", "value": right_button_pressed, "weight": 1.0},
{"id": "NP_LThumbX", "value": left_thumb_X, "weight": 1.0},
{"id": "NP_LThumbY", "value": left_thumb_Y, "weight": 1.0},
{"id": "NP_RThumbX", "value": right_thumb_X, "weight": 1.0},
{"id": "NP_RThumbY", "value": right_thumb_Y, "weight": 1.0},
{"id": "NP_LOnStick", "value": left_thumb_stick, "weight": 1.0},
{"id": "NP_ROnStick", "value": right_thumb_stick, "weight": 1.0},
{"id": "NP_LIndexPos", "value": left_index, "weight": 1.0},
{"id": "NP_RIndexPos", "value": right_index, "weight": 1.0},
]
return params
async def main() -> None:
gamepad = select_gamepad()
vts = VTSConnection()
try:
await vts.connect()
except asyncio.CancelledError:
await vts.close()
axis_states = {
"ABS_X": 0.0,
"ABS_Y": 0.0,
"ABS_RX": 0.0,
"ABS_RY": 0.0,
}
trigger_states = {
"ABS_Z": 0.0,
"ABS_RZ": 0.0,
}
last_time = time.monotonic()
frequency = 1 / 30
while True:
for event in gamepad.read():
if event.code != "SYN_REPORT":
# Add some deadzone of about 10% to analog stick
if event.code in ["ABS_X", "ABS_Y", "ABS_RX", "ABS_RY"]:
axis_states[event.code] = _deadzone(
_normalize_analog_sticks(event.state)
)
elif event.code in ["ABS_Z", "ABS_RZ"]:
trigger_states[event.code] = _normalize_analog_trigger(event.state)
else:
set_inputs(event.code, event.state)
now = time.monotonic()
if now - last_time >= frequency:
last_time = now
for key in trigger_states:
set_inputs(key, trigger_states[key])
for key in axis_states:
set_inputs(key, axis_states[key])
input_data = create_vts_input_request()
await vts.update_parameters(input_data)
await asyncio.sleep(0.001)
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
exit(0)

View File

@@ -0,0 +1,405 @@
#! /usr/bin/env python
import asyncio
import os
import pyvts
AUTHOR = "Melody LaFae"
PLUGIN_NAME = "VTS Gamepad Input"
token_path = f"{os.environ['HOME']}/.vts-gamepad-input-token"
ip_path = f"{os.environ['HOME']}/.vts-gamepad-input-ip"
plugin_info = {
"plugin_name": PLUGIN_NAME,
"developer": AUTHOR,
"authentication_token_path": token_path,
}
class VTSConnection:
def __init__(self):
self.vts = pyvts.vts(
plugin_info=plugin_info, vts_api_info=self._get_vts_api_info()
)
self.request = pyvts.VTSRequest(plugin_info=plugin_info)
async def connect(self):
conn_success = await self._connect_to_api()
if conn_success:
await self._create_vts_params()
await self.close()
async def close(self):
await self.vts.close()
@staticmethod
def _get_vts_api_info() -> dict[str, str]:
host_uri = "ws://localhost:8001"
vts_api_info: dict[str, str] = {}
if os.path.exists(ip_path) and os.path.isfile(ip_path):
with open(ip_path, "r") as file:
host_uri = file.read()
else:
with open(ip_path, "w") as file:
file.write(host_uri)
host_url = host_uri.split(":")[1].replace("//", "")
host_port = host_uri.split(":")[2]
vts_api_info["host"] = host_url
vts_api_info["port"] = host_port
vts_api_info["name"] = "VTubeStudioPublicAPI"
vts_api_info["version"] = "1.0"
return vts_api_info
async def _connect_to_api(self) -> bool:
try:
await self.vts.connect()
except OSError:
print("Error: Make sure the VTS API is running")
exit(1)
await self.vts.request_authenticate_token()
await self.vts.request_authenticate()
if not self.vts.get_connection_status():
raise ConnectionError("Connection failed")
else:
return True
async def _create_vts_params(self) -> None:
# Param to enable or disable inputs
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ON",
info="VTS Gamepad Input Enabled",
min=0.0,
max=1.0,
default_value=0.0,
)
)
# DPad params
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_DPadDown",
info="DPad Down pressed",
min=0.0,
max=1.0,
default_value=0.0,
)
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_DPadLeft",
info="DPad Left pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_DPadUp",
info="DPad Up pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_DPadRight",
info="DPad Right pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Action Buttons params
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ButtonA",
info="Down Action button pressed (XBox A, PS Cross)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ButtonB",
info="Right Action button pressed (XBox B, PS Circle)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ButtonY",
info="Up Action button pressed (XBox Y, PS Triangle)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ButtonX",
info="Left Action button pressed (XBox X, PS Square)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Shoulder buttons params
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_L1",
info="Left front shoulder button pressed (XBox LB, PS L1, Switch L)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_L2",
info="Left back shoulder button pressed (XBox LT, PS L2, Switch ZL)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_R1",
info="Right front shoulder button pressed (XBox RB, PS R1, Switch R)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_R2",
info="Right back shoulder button pressed (XBox RT, PS R2, Switch ZR)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Start and select params
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_StartDown",
info="Start button pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_SelectDown",
info="Select button pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Left Analog stick param
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LStickX",
info="Left stick moved on X axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LStickY",
info="Left stick moved on Y axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ButtonLS",
info="Left analog stick pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Right Analog stick params
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RStickX",
info="Right stick moved on X axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RStickY",
info="Right stick moved on Y axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ButtonRS",
info="Right analog stick pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
## Rigging Logic params
# Thumb on sticks if no buttons are pressed
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LOnStick",
info="Left thumb on left analog stick",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_ROnStick",
info="Right thumb on right analog stick",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Thumb locations
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LThumbX",
info="Left thumb movement on X axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LThumbY",
info="Left thumb movement on Y axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RThumbX",
info="Right thumb movement on X axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RThumbY",
info="Right thumb movement on Y axis",
min=-1.0,
max=1.0,
default_value=0.0,
),
)
# Index finger location
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LIndexPos",
info="Left index finger position (on L2/LT/ZL if L1 not pressed)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RIndexPos",
info="Right index finger position (on R2/RT/ZR if R1 not pressed)",
min=0.0,
max=1.0,
default_value=0.0,
),
)
# Button pressed or hold params
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LButtonPress",
info="Left button pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RButtonPress",
info="Right button pressed",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_LButtonDown",
info="Left button hold",
min=0.0,
max=1.0,
default_value=0.0,
),
)
await self.vts.request(
self.request.requestCustomParameter(
parameter="NP_RButtonDown",
info="Right button hold",
min=0.0,
max=1.0,
default_value=0.0,
)
)
async def update_parameters(self, parameters: list[dict]) -> None:
await self._connect_to_api()
request = self.request.BaseRequest(
data={
"faceFound": False,
"mode": "set",
"parameterValues": parameters,
},
message_type="InjectParameterDataRequest",
)
await self.vts.request(request)
await self.close()
async def main():
vts_conn = VTSConnection()
await vts_conn.vts.connect()
await vts_conn.vts.request_authenticate_token()
# await vts_conn.vts.request_authenticate()
# await vts_conn.connect()
if __name__ == "__main__":
asyncio.run(main())

181
uv.lock generated Normal file
View File

@@ -0,0 +1,181 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "aiofiles"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" },
]
[[package]]
name = "ansicon"
version = "1.89.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/e2/1c866404ddbd280efedff4a9f15abfe943cb83cde6e895022370f3a61f85/ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1", size = 67312, upload-time = "2019-04-29T20:23:57.314Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/75/f9/f1c10e223c7b56a38109a3f2eb4e7fe9a757ea3ed3a166754fb30f65e466/ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec", size = 63675, upload-time = "2019-04-29T20:23:53.83Z" },
]
[[package]]
name = "blessed"
version = "1.25.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinxed", marker = "sys_platform == 'win32'" },
{ name = "wcwidth" },
]
sdist = { url = "https://files.pythonhosted.org/packages/33/cd/eed8b82f1fabcb817d84b24d0780b86600b5c3df7ec4f890bcbb2371b0ad/blessed-1.25.0.tar.gz", hash = "sha256:606aebfea69f85915c7ca6a96eb028e0031d30feccc5688e13fd5cec8277b28d", size = 6746381, upload-time = "2025-11-18T18:43:52.71Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7c/2c/e9b6dd824fb6e76dbd39a308fc6f497320afd455373aac8518ca3eba7948/blessed-1.25.0-py3-none-any.whl", hash = "sha256:e52b9f778b9e10c30b3f17f6b5f5d2208d1e9b53b270f1d94fc61a243fc4708f", size = 95646, upload-time = "2025-11-18T18:43:50.924Z" },
]
[[package]]
name = "editor"
version = "1.6.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "runs" },
{ name = "xmod" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2a/92/734a4ab345914259cb6146fd36512608ea42be16195375c379046f33283d/editor-1.6.6.tar.gz", hash = "sha256:bb6989e872638cd119db9a4fce284cd8e13c553886a1c044c6b8d8a160c871f8", size = 3197, upload-time = "2024-01-25T10:44:59.909Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/c2/4bc8cd09b14e28ce3f406a8b05761bed0d785d1ca8c2a5c6684d884c66a2/editor-1.6.6-py3-none-any.whl", hash = "sha256:e818e6913f26c2a81eadef503a2741d7cca7f235d20e217274a009ecd5a74abf", size = 4017, upload-time = "2024-01-25T10:44:58.66Z" },
]
[[package]]
name = "inputs"
version = "0.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/cd/5f434220920f76eb73d19bb7aab8d857445f40aa642718e6e51e850cd663/inputs-0.5.tar.gz", hash = "sha256:a31d5b96a3525f1232f326be9e7ce8ccaf873c6b1fb84d9f3c9bc3d79b23eae4", size = 33393, upload-time = "2018-10-05T22:38:14.206Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/94/040a0d9c81f018c39bd887b7b825013b024deb0a6c795f9524797e2cd41b/inputs-0.5-py2.py3-none-any.whl", hash = "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec", size = 33630, upload-time = "2018-10-05T22:38:28.28Z" },
]
[[package]]
name = "inquirer"
version = "3.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blessed" },
{ name = "editor" },
{ name = "readchar" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c1/79/165579fdcd3c2439503732ae76394bf77f5542f3dd18135b60e808e4813c/inquirer-3.4.1.tar.gz", hash = "sha256:60d169fddffe297e2f8ad54ab33698249ccfc3fc377dafb1e5cf01a0efb9cbe5", size = 14069, upload-time = "2025-08-02T18:36:27.901Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f0/fd/7c404169a3e04a908df0644893a331f253a7f221961f2b6c0cf44430ae5a/inquirer-3.4.1-py3-none-any.whl", hash = "sha256:717bf146d547b595d2495e7285fd55545cff85e5ce01decc7487d2ec6a605412", size = 18152, upload-time = "2025-08-02T18:36:26.753Z" },
]
[[package]]
name = "jinxed"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "ansicon", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/20/d0/59b2b80e7a52d255f9e0ad040d2e826342d05580c4b1d7d7747cfb8db731/jinxed-1.3.0.tar.gz", hash = "sha256:1593124b18a41b7a3da3b078471442e51dbad3d77b4d4f2b0c26ab6f7d660dbf", size = 80981, upload-time = "2024-07-31T22:39:18.854Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/e3/0e0014d6ab159d48189e92044ace13b1e1fe9aa3024ba9f4e8cf172aa7c2/jinxed-1.3.0-py2.py3-none-any.whl", hash = "sha256:b993189f39dc2d7504d802152671535b06d380b26d78070559551cbf92df4fc5", size = 33085, upload-time = "2024-07-31T22:39:17.426Z" },
]
[[package]]
name = "numpy"
version = "2.2.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
[[package]]
name = "opencv-python"
version = "4.12.0.88"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/71/25c98e634b6bdeca4727c7f6d6927b056080668c5008ad3c8fc9e7f8f6ec/opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", size = 95373294, upload-time = "2025-07-07T09:20:52.389Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/68/3da40142e7c21e9b1d4e7ddd6c58738feb013203e6e4b803d62cdd9eb96b/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", size = 37877727, upload-time = "2025-07-07T09:13:31.47Z" },
{ url = "https://files.pythonhosted.org/packages/33/7c/042abe49f58d6ee7e1028eefc3334d98ca69b030e3b567fe245a2b28ea6f/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", size = 57326471, upload-time = "2025-07-07T09:13:41.26Z" },
{ url = "https://files.pythonhosted.org/packages/62/3a/440bd64736cf8116f01f3b7f9f2e111afb2e02beb2ccc08a6458114a6b5d/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", size = 45887139, upload-time = "2025-07-07T09:13:50.761Z" },
{ url = "https://files.pythonhosted.org/packages/68/1f/795e7f4aa2eacc59afa4fb61a2e35e510d06414dd5a802b51a012d691b37/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", size = 67041680, upload-time = "2025-07-07T09:14:01.995Z" },
{ url = "https://files.pythonhosted.org/packages/02/96/213fea371d3cb2f1d537612a105792aa0a6659fb2665b22cad709a75bd94/opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357", size = 30284131, upload-time = "2025-07-07T09:14:08.819Z" },
{ url = "https://files.pythonhosted.org/packages/fa/80/eb88edc2e2b11cd2dd2e56f1c80b5784d11d6e6b7f04a1145df64df40065/opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", size = 39000307, upload-time = "2025-07-07T09:14:16.641Z" },
]
[[package]]
name = "pyvts"
version = "0.3.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiofiles" },
{ name = "opencv-python" },
{ name = "websockets" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0c/c7/be53a7964c0d5e108c416bfdea432a4451fd4d8e03f18b5877376548f016/pyvts-0.3.3.tar.gz", hash = "sha256:decf3299a391ce2e38ffcc3fb2caf71f06b27b9ca2b7581dc5a97e62620da2c4", size = 11655, upload-time = "2024-09-10T08:16:27.494Z" }
[[package]]
name = "readchar"
version = "4.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dd/f8/8657b8cbb4ebeabfbdf991ac40eca8a1d1bd012011bd44ad1ed10f5cb494/readchar-4.2.1.tar.gz", hash = "sha256:91ce3faf07688de14d800592951e5575e9c7a3213738ed01d394dcc949b79adb", size = 9685, upload-time = "2024-11-04T18:28:07.757Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/10/e4b1e0e5b6b6745c8098c275b69bc9d73e9542d5c7da4f137542b499ed44/readchar-4.2.1-py3-none-any.whl", hash = "sha256:a769305cd3994bb5fa2764aa4073452dc105a4ec39068ffe6efd3c20c60acc77", size = 9350, upload-time = "2024-11-04T18:28:02.859Z" },
]
[[package]]
name = "runs"
version = "1.2.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "xmod" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/6d/b9aace390f62db5d7d2c77eafce3d42774f27f1829d24fa9b6f598b3ef71/runs-1.2.2.tar.gz", hash = "sha256:9dc1815e2895cfb3a48317b173b9f1eac9ba5549b36a847b5cc60c3bf82ecef1", size = 5474, upload-time = "2024-01-25T14:44:01.563Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/86/d6/17caf2e4af1dec288477a0cbbe4a96fbc9b8a28457dce3f1f452630ce216/runs-1.2.2-py3-none-any.whl", hash = "sha256:0980dcbc25aba1505f307ac4f0e9e92cbd0be2a15a1e983ee86c24c87b839dfd", size = 7033, upload-time = "2024-01-25T14:43:59.959Z" },
]
[[package]]
name = "vts-gamepad-input"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "inputs" },
{ name = "inquirer" },
{ name = "pyvts" },
]
[package.metadata]
requires-dist = [
{ name = "inputs", specifier = ">=0.5" },
{ name = "inquirer", specifier = ">=3.4.1" },
{ name = "pyvts", specifier = ">=0.3.3" },
]
[[package]]
name = "wcwidth"
version = "0.2.14"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
]
[[package]]
name = "websockets"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
]
[[package]]
name = "xmod"
version = "1.8.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/b2/e3edc608823348e628a919e1d7129e641997afadd946febdd704aecc5881/xmod-1.8.1.tar.gz", hash = "sha256:38c76486b9d672c546d57d8035df0beb7f4a9b088bc3fb2de5431ae821444377", size = 3988, upload-time = "2024-01-04T18:03:17.663Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/33/6b/0dc75b64a764ea1cb8e4c32d1fb273c147304d4e5483cd58be482dc62e45/xmod-1.8.1-py3-none-any.whl", hash = "sha256:a24e9458a4853489042522bdca9e50ee2eac5ab75c809a91150a8a7f40670d48", size = 4610, upload-time = "2024-01-04T18:03:16.078Z" },
]