3dscontroller-ng/3DS_Controller-evdev.py

236 lines
7.2 KiB
Python
Executable File

#!/usr/bin/env python
# Compatible with python 3.11, NOT TESTED IN OLD VERSIONS OF PYTHON
from __future__ import print_function
import socket, struct, time
# EVDEV: https://python-evdev.readthedocs.io/en/latest/tutorial.html#specifying-uinput-device-options
import evdev
from evdev import UInput, AbsInfo, ecodes as e
##########################################################
# CONFIGURABLE REGION START - Don't touch anything above #
##########################################################
port = 42020
# Set the debug variable to 1 to print Debug information
debug = 1
# Valid values can be found in any of these locations on your Linux system (some may not exist):
# /usr/include/linux/input-event-codes.h
# The virtual device is defined on the device variable and the mapping of the keys on btn_map
# RECAP keynames (DO NOT TOUCH) are the keys that we expect commming from the 3ds, device is
# the virtual device that we define and btn_map maps what we recieve with our virtual device
btn_map = {
"A": e.BTN_A,
"B": e.BTN_B,
"X": e.BTN_X,
"Y": e.BTN_Y,
"L": e.BTN_TL,
"R": e.BTN_TR,
"ZL": e.BTN_THUMBL,
"ZR": e.BTN_THUMBR,
"Left": e.BTN_DPAD_LEFT,
"Right": e.BTN_DPAD_RIGHT,
"Up": e.BTN_DPAD_UP,
"Down": e.BTN_DPAD_DOWN,
"Start": e.BTN_START,
"Select": e.BTN_SELECT,
"Tap": e.BTN_MODE
}
device = {
# EV_ABS is for Absolute movement with static values,
# like the sticks and the volume
e.EV_ABS: [
(e.ABS_X, AbsInfo(0, -159, 159, 0, 10, 0)),
(e.ABS_Y, AbsInfo(0, -159, 159, 0, 10, 0)),
(e.ABS_RX, AbsInfo(0, -146, 146, 0, 16, 0)),
(e.ABS_RY, AbsInfo(0, -146, 146, 0, 16, 0)),
(e.ABS_VOLUME, AbsInfo(0, 0, 63, 0, 0, 0)),
],
# The keys lol
e.EV_KEY: [
e.BTN_A,
e.BTN_B,
e.BTN_X,
e.BTN_Y,
e.BTN_TL,
e.BTN_TR,
e.BTN_THUMBL,
e.BTN_THUMBR,
e.BTN_DPAD_LEFT,
e.BTN_DPAD_RIGHT,
e.BTN_DPAD_UP,
e.BTN_DPAD_DOWN,
e.BTN_START,
e.BTN_SELECT,
e.BTN_MODE,
],
}
ui = UInput(device, name="3DS", phys="3ds", bustype=0x6)
gyroAxis = {
e.EV_ABS: [
(e.ABS_X, AbsInfo(0, 0, 0, 2, 0, 2048)),
(e.ABS_Y, AbsInfo(0, 0, 0, 2, 0, 2048)),
(e.ABS_Z, AbsInfo(0, 0, 0, 2, 0, 2048)),
(e.ABS_RX, AbsInfo(0, -1024, 1024, 2, 8, 64)),
(e.ABS_RY, AbsInfo(0, -1024, 1024, 2, 8, 64)),
(e.ABS_RZ, AbsInfo(0, -1024, 1024, 2, 8, 64))
],
e.EV_MSC: [
e.MSC_TIMESTAMP
]
}
uiGyro = UInput(gyroAxis, name="3DS Motion Sensors", phys="3ds", bustype=0x6, input_props=[6])
if (debug):
print (ui)
########################################################
# CONFIGURABLE REGION END - Don't touch anything below #
########################################################
class x: pass
command = x()
command.CONNECT = 0
command.KEYS = 1
command.SCREENSHOT = 2
keynames = [
"A", "B", "Select", "Start", "Right", "Left", "Up", "Down",
"R", "L", "X", "Y", None, None, "ZL", "ZR",
None, None, None, None, "Tap", None, None, None,
"CSRight", "CSLeft", "CSUp", "CSDown", "CRight", "CLeft", "CUp", "CDown",
]
keys = x()
keys.A = 1<<0
keys.B = 1<<1
keys.Select = 1<<2
keys.Start = 1<<3
keys.Right = 1<<4
keys.Left = 1<<5
keys.Up = 1<<6
keys.Down = 1<<7
keys.R = 1<<8
keys.L = 1<<9
keys.X = 1<<10
keys.Y = 1<<11
keys.ZL = 1<<14 # (new 3DS only)
keys.ZR = 1<<15 # (new 3DS only)
keys.Tap = 1<<20 # Not actually provided by HID
keys.CSRight = 1<<24 # c-stick (new 3DS only)
keys.CSLeft = 1<<25 # c-stick (new 3DS only)
keys.CSUp = 1<<26 # c-stick (new 3DS only)
keys.CSDown = 1<<27 # c-stick (new 3DS only)
keys.CRight = 1<<28 # circle pad
keys.CLeft = 1<<29 # circle pad
keys.CUp = 1<<30 # circle pad
keys.CDown = 1<<31 # circle pad
def press_key(key):
ui.write(e.EV_KEY, key, 1)
ui.syn()
def release_key(key):
ui.write(e.EV_KEY,key, 0)
ui.syn()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("", port))
prevkeys = 0
touch_start = 0
touch_last_x = 0
touch_last_y = 0
while True:
rawdata, addr = sock.recvfrom(4092)
rawdata = bytearray(rawdata)
# print("Received message", rawdata)
if rawdata[0]==command.CONNECT:
print("Connected with 3DS at address", addr)
pass # CONNECT packets are empty
if rawdata[0]==command.KEYS:
# Just to explain this fuckery of struct.unpack:
# <bbxxIhhHHhhBxxxhhhxx:
# <: This represents that the data received is on Little-Endian
# bb: 2 signed chars which they are 1 byte each one, so 2 bytes in total, the `command` and `keyboardActive` data is here
# xx: Just 2 padding bytes, they are bytes without any data so with `xx` we skip them
# I: A signed integer of 4 bytes, the `keys` pressed data is here
# hh: 2 shorts which they are 2 bytes each one, 4 bytes on total, the X and Y data of the Circle Pad is here
# HH: 2 shorts which they are 2 bytes each one, 4 bytes on total, the X and Y data of the Touch Pad is here
# (where are you pressing in the screen, it could be useful somehow I guess.)
# hh: 2 shorts which they are 2 bytes each one, 4 bytes on total, the X and Y data of the C Stick Pad is here
# B: A unsigned char, just 1 byte and represents the value of the Volume Slider of the 3DS
# xxx: Other 3 extra bytes of padding
# hhh: 3 shorts which they are 2 bytes each one, 6 bytes on total, the X, Y, Z data of the GYROSCOPE is here
# xx: And another 2 extra bytes of padding
# hhh: 3 shorts which they are 2 bytes each one, 6 bytes on total, the X, Y, Z data of the ACCELEROMETER is here
# xxx: And another 2 extra bytes of padding from the fucking nowhere
fields = struct.unpack("<bbxxIhhHHhhBxxxhhhxxhhhxx", rawdata)
data = {
"command": fields[0],
"keyboardActive": fields[1],
"keys": fields[2],
"circleX": fields[3],
"circleY": fields[4],
"touchX": fields[5],
"touchY": fields[6],
"cstickX": fields[7],
"cstickY": fields[8],
"vol": fields[9],
"gyroX": fields[10],
"gyroY": fields[11],
"gyroZ": fields[12],
"accelX": fields[13],
"accelY": fields[14],
"accelZ": fields[15]
}
if (debug):
def pprint(obj):
import pprint
pprint.PrettyPrinter().pprint(obj)
pprint(data)
newkeys = data["keys"] & ~prevkeys
oldkeys = ~data["keys"] & prevkeys
prevkeys = data["keys"]
for btnid in range(16):
if newkeys & (1<<btnid):
press_key(btn_map[keynames[btnid]])
if oldkeys & (1<<btnid):
release_key(btn_map[keynames[btnid]])
if newkeys & keys.Tap:
press_key(btn_map["Tap"])
if oldkeys & keys.Tap:
release_key(btn_map["Tap"])
ui.write(e.EV_ABS, e.ABS_X, data["circleX"])
ui.write(e.EV_ABS, e.ABS_Y, 0-data["circleY"])
ui.write(e.EV_ABS, e.ABS_RX, data["cstickX"])
ui.write(e.EV_ABS, e.ABS_RY, data["cstickY"])
ui.write(e.EV_ABS, e.ABS_VOLUME, data["vol"])
ui.syn()
uiGyro.write(e.EV_ABS, e.ABS_X, data["accelX"])
uiGyro.write(e.EV_ABS, e.ABS_Y, data["accelY"])
uiGyro.write(e.EV_ABS, e.ABS_Z, data["accelZ"])
uiGyro.write(e.EV_ABS, e.ABS_RX, data["gyroX"])
uiGyro.write(e.EV_ABS, e.ABS_RY, data["gyroY"])
uiGyro.write(e.EV_ABS, e.ABS_RZ, data["gyroZ"])
uiGyro.syn()
if rawdata[0]==command.SCREENSHOT:
pass # unused by both 3DS and PC applications