import tkinter as tk
from threading import Thread
from inputs import get_gamepad
from pythonosc.udp_client import SimpleUDPClient
OSC targets
osc_targets = [
SimpleUDPClient(“127.0.0.1”, 9000),
SimpleUDPClient(“127.0.0.1”, 9002),
SimpleUDPClient(“127.0.0.1”, 9004)
]
Configurable settings
DEAD_ZONE = 50
INVERT_LOOK_Y = True
INVERT_LOOK_X = False
INVERT_MOVE_Y = True
Button state tracking
button_states = {
“BTN_SOUTH”: 0,
“BTN_EAST”: 0,
“ABS_Y”: 128,
“ABS_X”: 128,
“ABS_RY”: 128,
“ABS_RX”: 128
}
def send_osc_to_all(address, value):
for client in osc_targets:
client.send_message(address, value)
def controller_loop(log_callback):
while True:
events = get_gamepad()
for event in events:
code = event.code
state = event.state
# Jump
if code == "BTN_SOUTH":
if state != button_states[code]:
send_osc_to_all("/input/Jump", state)
log_callback(f"Jump: {state}")
button_states[code] = state
# Crouch
elif code == "BTN_EAST":
if state != button_states[code]:
send_osc_to_all("/input/Crouch", state)
log_callback(f"Crouch: {state}")
button_states[code] = state
# Movement - Forward/Backward
elif code == "ABS_Y":
forward = backward = 0
if INVERT_MOVE_Y:
if state > (128 + DEAD_ZONE):
forward = 1
elif state < (128 - DEAD_ZONE):
backward = 1
else:
if state < (128 - DEAD_ZONE):
forward = 1
elif state > (128 + DEAD_ZONE):
backward = 1
send_osc_to_all("/input/MoveForward", forward)
send_osc_to_all("/input/MoveBackward", backward)
log_callback(f"Forward: {forward}, Backward: {backward}")
button_states[code] = state
# Movement - Left/Right
elif code == "ABS_X":
left = right = 0
if state < (128 - DEAD_ZONE):
left = 1
elif state > (128 + DEAD_ZONE):
right = 1
send_osc_to_all("/input/MoveLeft", left)
send_osc_to_all("/input/MoveRight", right)
log_callback(f"Left: {left}, Right: {right}")
button_states[code] = state
# Look - Up/Down
elif code == "ABS_RY":
look_up = look_down = 0
if INVERT_LOOK_Y:
if state > (128 + DEAD_ZONE):
look_up = 1
elif state < (128 - DEAD_ZONE):
look_down = 1
else:
if state < (128 - DEAD_ZONE):
look_up = 1
elif state > (128 + DEAD_ZONE):
look_down = 1
send_osc_to_all("/input/LookUp", look_up)
send_osc_to_all("/input/LookDown", look_down)
log_callback(f"LookUp: {look_up}, LookDown: {look_down}")
button_states[code] = state
# Look - Left/Right
elif code == "ABS_RX":
look_left = look_right = 0
if INVERT_LOOK_X:
if state > (128 + DEAD_ZONE):
look_left = 1
elif state < (128 - DEAD_ZONE):
look_right = 1
else:
if state < (128 - DEAD_ZONE):
look_left = 1
elif state > (128 + DEAD_ZONE):
look_right = 1
send_osc_to_all("/input/LookLeft", look_left)
send_osc_to_all("/input/LookRight", look_right)
log_callback(f"LookLeft: {look_left}, LookRight: {look_right}")
button_states[code] = state
GUI setup
def start_gui():
root = tk.Tk()
root.title(“VRChat Controller Router”)
root.geometry(“360x220”)
root.resizable(False, False)
log_box = tk.Text(root, height=12, width=45)
log_box.pack(pady=10)
def log(msg):
log_box.insert(tk.END, msg + "\n")
log_box.see(tk.END)
Thread(target=controller_loop, args=(log,), daemon=True).start()
root.mainloop()
Run GUI
start_gui()