Add GUI and support for more controllers

This commit is contained in:
Marc Riera 2022-10-24 21:30:56 +02:00 committed by GitHub
parent cc8a271883
commit dce84dcc43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1094 additions and 151 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
__pycache__/
*.db
**/.vscode/

View file

@ -1,151 +0,0 @@
#!/usr/bin/env python
from evdev import InputDevice, list_devices, ecodes as e, UInput, AbsInfo
cap = {
e.EV_KEY : [e.BTN_NORTH, e.BTN_SOUTH, e.BTN_EAST, e.BTN_WEST, e.BTN_SELECT, e.BTN_START],
e.EV_ABS : [(e.ABS_X, AbsInfo(0, 0, 255, 0, 0, 0)), (e.ABS_Y, AbsInfo(0, 0, 255, 0, 0, 0))]
}
# Mapping
# A: BTN_EAST
# B: BTN_SOUTH
# C: BTN_NORTH
# D: BTN_WEST
# SL: BTN_SELECT
# ST: BTN_START
# BRAKE: ABS_X
# POWER: ABS_Y
mascon_switch = None
devices = [InputDevice(path) for path in list_devices()]
for device in devices:
dev_name = [device.info.vendor, device.info.product, device.name]
if dev_name == [0x0f0d, 0x00c1, "One Handle MasCon for Nintendo Switch"]:
mascon_switch = device
break
if mascon_switch is None:
print("No supported controller found.")
exit()
ui = UInput(cap, vendor=0x0AE4, product=0x0003, name='Emulated DGOC-44U')
mascon_switch.grab()
for event in mascon_switch.read_loop():
if event.type == e.EV_KEY:
match event.code:
case 304: # Y
ui.write(e.EV_KEY, e.BTN_EAST, event.value)
ui.syn()
case 305: # B
ui.write(e.EV_KEY, e.BTN_SOUTH, event.value)
ui.syn()
case 306: # A
ui.write(e.EV_KEY, e.BTN_NORTH, event.value)
ui.syn()
case 307: # X
ui.write(e.EV_KEY, e.BTN_WEST, event.value)
ui.syn()
case 312: # MINUS
ui.write(e.EV_KEY, e.BTN_SELECT, event.value)
ui.syn()
case 313: # PLUS
ui.write(e.EV_KEY, e.BTN_START, event.value)
ui.syn()
if event.type == e.EV_ABS and event.code == e.ABS_HAT0X:
match event.value:
case -1: # LEFT
ui.write(e.EV_KEY, e.BTN_SELECT, 1)
ui.write(e.EV_KEY, e.BTN_EAST, 1)
ui.write(e.EV_KEY, e.BTN_NORTH, 0)
ui.syn()
case 1: # RIGHT
ui.write(e.EV_KEY, e.BTN_SELECT, 1)
ui.write(e.EV_KEY, e.BTN_EAST, 0)
ui.write(e.EV_KEY, e.BTN_NORTH, 1)
ui.syn()
case _: # NONE
ui.write(e.EV_KEY, e.BTN_SELECT, 0)
ui.write(e.EV_KEY, e.BTN_EAST, 0)
ui.write(e.EV_KEY, e.BTN_NORTH, 0)
ui.syn()
if event.type == e.EV_ABS and event.code == e.ABS_HAT0Y:
match event.value:
case -1: # UP
ui.write(e.EV_KEY, e.BTN_SELECT, 1)
ui.write(e.EV_KEY, e.BTN_WEST, 1)
ui.write(e.EV_KEY, e.BTN_SOUTH, 0)
ui.syn()
case 1: # DOWN
ui.write(e.EV_KEY, e.BTN_SELECT, 1)
ui.write(e.EV_KEY, e.BTN_WEST, 0)
ui.write(e.EV_KEY, e.BTN_SOUTH, 1)
ui.syn()
case _: # NONE
ui.write(e.EV_KEY, e.BTN_SELECT, 0)
ui.write(e.EV_KEY, e.BTN_WEST, 0)
ui.write(e.EV_KEY, e.BTN_SOUTH, 0)
ui.syn()
if event.type == e.EV_ABS and event.code == e.ABS_Y:
match event.value:
case 0x0: # EMG
ui.write(e.EV_ABS, e.ABS_X, 0xB9)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x5:
ui.write(e.EV_ABS, e.ABS_X, 0xB5)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x13:
ui.write(e.EV_ABS, e.ABS_X, 0xB2)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x20:
ui.write(e.EV_ABS, e.ABS_X, 0xAF)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x2E:
ui.write(e.EV_ABS, e.ABS_X, 0xA8)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x3C:
ui.write(e.EV_ABS, e.ABS_X, 0xA2)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x49:
ui.write(e.EV_ABS, e.ABS_X, 0x9A)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x57:
ui.write(e.EV_ABS, e.ABS_X, 0x94)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x65:
ui.write(e.EV_ABS, e.ABS_X, 0x8A)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x80:
ui.write(e.EV_ABS, e.ABS_X, 0x79)
ui.write(e.EV_ABS, e.ABS_Y, 0x81)
ui.syn()
case 0x9F:
ui.write(e.EV_ABS, e.ABS_X, 0x79)
ui.write(e.EV_ABS, e.ABS_Y, 0x6D)
ui.syn()
case 0xB7:
ui.write(e.EV_ABS, e.ABS_X, 0x79)
ui.write(e.EV_ABS, e.ABS_Y, 0x54)
ui.syn()
case 0xCE:
ui.write(e.EV_ABS, e.ABS_X, 0x79)
ui.write(e.EV_ABS, e.ABS_Y, 0x3F)
ui.syn()
case 0xE6:
ui.write(e.EV_ABS, e.ABS_X, 0x79)
ui.write(e.EV_ABS, e.ABS_Y, 0x21)
ui.syn()
case 0xFF:
ui.write(e.EV_ABS, e.ABS_X, 0x79)
ui.write(e.EV_ABS, e.ABS_Y, 0x00)
ui.syn()

44
ddgo-converter.spec Normal file
View file

@ -0,0 +1,44 @@
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['ddgo-converter/ddgo-converter.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='ddgo-converter',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View file

@ -0,0 +1,16 @@
#!/usr/bin/env python
import sys
from PyQt5.QtWidgets import QApplication
from gui.main import MainWindow
from handlers.gamepad import GamepadHandler
class App(QApplication):
def __init__(self, sys_argv):
super(App, self).__init__(sys_argv)
self.main_view = MainWindow(GamepadHandler)
self.main_view.show()
if __name__ == '__main__':
app = App(sys.argv)
sys.exit(app.exec_())

View file

@ -0,0 +1,28 @@
from enum import IntEnum
class InputEvent:
class EventType(IntEnum):
RELEASE_BUTTON = 0
PRESS_BUTTON = 1
BRAKE_NOTCH = 2
POWER_NOTCH = 3
ERROR = 4
class Button(IntEnum):
BUTTON_SELECT = 0
BUTTON_START = 1
BUTTON_A = 2
BUTTON_B = 3
BUTTON_C = 4
BUTTON_D = 5
BUTTON_UP = 6
BUTTON_DOWN = 7
BUTTON_LEFT = 8
BUTTON_RIGHT = 9
BUTTON_LDOOR = 10
BUTTON_RDOOR = 11
def __init__(self, type, data):
self.type = type
self.data = data

View file

@ -0,0 +1,410 @@
from enum import IntEnum
from events.input import InputEvent
from evdev import ecodes, UInput, AbsInfo
class EmulatedGamepad:
class GamepadType(IntEnum):
PC2HANDLE = 0
PS1 = 1
N64 = 2
SAT = 3
class PC2HandleGamepad(EmulatedGamepad):
# Mapping
# A: BTN_EAST
# B: BTN_SOUTH
# C: BTN_NORTH
# D: BTN_WEST
# SL: BTN_SELECT
# ST: BTN_START
# BRAKE: ABS_X
# POWER: ABS_Y
def __init__(self):
self.type = self.GamepadType.PC2HANDLE
self.capabilities = {
ecodes.EV_KEY : [ecodes.BTN_NORTH, ecodes.BTN_SOUTH, ecodes.BTN_EAST, ecodes.BTN_WEST, ecodes.BTN_SELECT, ecodes.BTN_START],
ecodes.EV_ABS : [(ecodes.ABS_X, AbsInfo(0x79, 0, 255, 0, 0, 0)), (ecodes.ABS_Y, AbsInfo(0x81, 0, 255, 0, 0, 0))]
}
self.brake_notches = (0x79, 0x8A, 0x94, 0x9A, 0xA2, 0xA8, 0xAF, 0xB2, 0xB5, 0xB9)
self.power_notches = (0x81, 0x6D, 0x54, 0x3F, 0x21, 0x00)
def start(self):
self.ui = UInput(self.capabilities, vendor=0x0AE4, product=0x0003, name='Emulated DGOC-44U')
def stop(self):
self.ui.close()
def write_input(self, event):
match event.type:
case (InputEvent.EventType.RELEASE_BUTTON | InputEvent.EventType.PRESS_BUTTON):
match event.data:
case InputEvent.Button.BUTTON_A:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, event.type)
case InputEvent.Button.BUTTON_B:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SOUTH, event.type)
case InputEvent.Button.BUTTON_C:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, event.type)
case InputEvent.Button.BUTTON_D:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, event.type)
case InputEvent.Button.BUTTON_SELECT:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
case InputEvent.Button.BUTTON_START:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_START, event.type)
case InputEvent.Button.BUTTON_UP:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, event.type)
case InputEvent.Button.BUTTON_DOWN:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SOUTH, event.type)
case InputEvent.Button.BUTTON_LEFT:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, event.type)
case InputEvent.Button.BUTTON_RIGHT:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, event.type)
case InputEvent.EventType.BRAKE_NOTCH:
self.ui.write(ecodes.EV_ABS, ecodes.ABS_X, self.brake_notches[event.data])
case InputEvent.EventType.POWER_NOTCH:
self.ui.write(ecodes.EV_ABS, ecodes.ABS_Y, self.power_notches[event.data])
self.ui.syn()
class PS1Gamepad(EmulatedGamepad):
def __init__(self):
self.type = self.GamepadType.PS1
self.capabilities = {
ecodes.EV_KEY : [ecodes.BTN_NORTH, ecodes.BTN_SOUTH, ecodes.BTN_EAST, ecodes.BTN_WEST, ecodes.BTN_TL, ecodes.BTN_TR,
ecodes.BTN_TL2, ecodes.BTN_TR2, ecodes.BTN_SELECT, ecodes.BTN_START, ecodes.BTN_THUMBL, ecodes.BTN_THUMBR,
ecodes.BTN_MODE, ecodes.BTN_DPAD_UP, ecodes.BTN_DPAD_DOWN, ecodes.BTN_DPAD_LEFT, ecodes.BTN_DPAD_RIGHT],
ecodes.EV_ABS : [(ecodes.ABS_X, AbsInfo(128, 0, 255, 0, 0, 0)), (ecodes.ABS_Y, AbsInfo(128, 0, 255, 0, 0, 0)),
(ecodes.ABS_RX, AbsInfo(128, 0, 255, 0, 0, 0)), (ecodes.ABS_RY, AbsInfo(128, 0, 255, 0, 0, 0))]
}
def start(self):
self.ui = UInput(self.capabilities, vendor=0x54C, product=0x268, name='Sony PLAYSTATION(R)3 Controller')
def stop(self):
self.ui.close()
def write_input(self, event):
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 1)
match event.type:
case (InputEvent.EventType.RELEASE_BUTTON | InputEvent.EventType.PRESS_BUTTON):
match event.data:
case InputEvent.Button.BUTTON_A:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, event.type)
case InputEvent.Button.BUTTON_B:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SOUTH, event.type)
case InputEvent.Button.BUTTON_C:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, event.type)
case InputEvent.Button.BUTTON_SELECT:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
case InputEvent.Button.BUTTON_START:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_START, event.type)
case InputEvent.EventType.BRAKE_NOTCH:
match event.data:
case 0:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 1:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 2:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 3:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 4:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 5:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 6:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
case 7:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
case 8:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
case 9:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
case InputEvent.EventType.POWER_NOTCH:
match event.data:
case 0:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 1)
case 1:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 1)
case 2:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 1)
case 3:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 0)
case 4:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 0)
case 5:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 0)
self.ui.syn()
class N64Gamepad(EmulatedGamepad):
def __init__(self):
self.type = self.GamepadType.N64
self.capabilities = {
ecodes.EV_KEY : [ecodes.BTN_NORTH, ecodes.BTN_SOUTH, ecodes.BTN_EAST, ecodes.BTN_WEST, ecodes.BTN_TL, ecodes.BTN_TR,
ecodes.BTN_TL2, ecodes.BTN_TR2, ecodes.BTN_SELECT, ecodes.BTN_START, ecodes.BTN_THUMBL, ecodes.BTN_THUMBR,
ecodes.BTN_MODE, ecodes.BTN_DPAD_UP, ecodes.BTN_DPAD_DOWN, ecodes.BTN_DPAD_LEFT, ecodes.BTN_DPAD_RIGHT],
ecodes.EV_ABS : [(ecodes.ABS_X, AbsInfo(128, 0, 255, 0, 0, 0)), (ecodes.ABS_Y, AbsInfo(128, 0, 255, 0, 0, 0)),
(ecodes.ABS_RX, AbsInfo(128, 0, 255, 0, 0, 0)), (ecodes.ABS_RY, AbsInfo(128, 0, 255, 0, 0, 0))]
}
def start(self):
self.ui = UInput(self.capabilities, vendor=0x54C, product=0x268, name='Sony PLAYSTATION(R)3 Controller')
def stop(self):
self.ui.close()
def write_input(self, event):
match event.type:
case (InputEvent.EventType.RELEASE_BUTTON | InputEvent.EventType.PRESS_BUTTON):
match event.data:
case InputEvent.Button.BUTTON_A:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, event.type)
case InputEvent.Button.BUTTON_B:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SOUTH, event.type)
case InputEvent.Button.BUTTON_C:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SELECT, event.type)
case InputEvent.Button.BUTTON_SELECT:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, event.type)
case InputEvent.Button.BUTTON_START:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_START, event.type)
case InputEvent.EventType.BRAKE_NOTCH:
match event.data:
case 0:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 1:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 2:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 3:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 4:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 5:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 6:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
case 7:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
case 8:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
case 9:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
case InputEvent.EventType.POWER_NOTCH:
match event.data:
case 0:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
case 1:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
case 2:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
case 3:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
case 4:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
case 5:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_RIGHT, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_UP, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.syn()
class SATGamepad(EmulatedGamepad):
def __init__(self):
self.type = self.GamepadType.SAT
self.capabilities = {
ecodes.EV_KEY : [ecodes.BTN_NORTH, ecodes.BTN_SOUTH, ecodes.BTN_EAST, ecodes.BTN_WEST, ecodes.BTN_TL, ecodes.BTN_TR,
ecodes.BTN_TL2, ecodes.BTN_TR2, ecodes.BTN_SELECT, ecodes.BTN_START, ecodes.BTN_THUMBL, ecodes.BTN_THUMBR,
ecodes.BTN_MODE, ecodes.BTN_DPAD_UP, ecodes.BTN_DPAD_DOWN, ecodes.BTN_DPAD_LEFT, ecodes.BTN_DPAD_RIGHT],
ecodes.EV_ABS : [(ecodes.ABS_X, AbsInfo(128, 0, 255, 0, 0, 0)), (ecodes.ABS_Y, AbsInfo(128, 0, 255, 0, 0, 0)),
(ecodes.ABS_RX, AbsInfo(128, 0, 255, 0, 0, 0)), (ecodes.ABS_RY, AbsInfo(128, 0, 255, 0, 0, 0))]
}
def start(self):
self.ui = UInput(self.capabilities, vendor=0x54C, product=0x268, name='Sony PLAYSTATION(R)3 Controller')
def stop(self):
self.ui.close()
def write_input(self, event):
match event.type:
case (InputEvent.EventType.RELEASE_BUTTON | InputEvent.EventType.PRESS_BUTTON):
match event.data:
case InputEvent.Button.BUTTON_A:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_SOUTH, event.type)
case InputEvent.Button.BUTTON_B:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_EAST, event.type)
case InputEvent.Button.BUTTON_C:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR, event.type)
case InputEvent.Button.BUTTON_START:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_START, event.type)
case InputEvent.EventType.BRAKE_NOTCH:
match event.data:
case 0:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 1:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 2:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 3:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 4:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 5:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 6:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 1)
case 7:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 0)
case 8:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 0)
case 9:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TR2, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_DOWN, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_DPAD_LEFT, 0)
case InputEvent.EventType.POWER_NOTCH:
match event.data:
case 0:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
case 1:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
case 2:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 1)
case 3:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
case 4:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
case 5:
self.ui.write(ecodes.EV_KEY, ecodes.BTN_WEST, 1)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_NORTH, 0)
self.ui.write(ecodes.EV_KEY, ecodes.BTN_TL, 0)
self.ui.syn()

View file

@ -0,0 +1,140 @@
from enum import IntEnum
import evdev
from hashlib import sha1
from events.input import InputEvent
from select import select
def create_gamepad(vid, pid, name):
match vid, pid:
case 0x0f0d, 0x00c1:
return SwitchGamepad(vid, pid, name)
return PhysicalGamepad(vid, pid, name)
class PhysicalGamepad:
class GamepadType(IntEnum):
UNKNOWN = 0
CLASSIC = 1
SWITCH = 2
def __init__(self, vid, pid, name):
super().__init__()
self.vid = vid
self.pid = pid
self.name = name
self.id = self._get_gamepad_id()
self.hash = self._get_gamepad_hash()
self.type = self.GamepadType.UNKNOWN
self.config = []
def _get_gamepad_id(self):
vid = format(self.vid, "x").zfill(4)
pid = format(self.pid, "x").zfill(4)
id = str(vid + ":" + pid)
return id
def _get_gamepad_hash(self):
hash = sha1(str(self.id + ":" + self.name).encode('utf-8')).hexdigest()
return hash
def _get_device(self):
for device in [evdev.InputDevice(path) for path in evdev.list_devices()]:
dev_name = [device.info.vendor, device.info.product, device.name]
if dev_name == [self.vid, self.pid, self.name]:
return device
class SwitchGamepad(PhysicalGamepad):
def __init__(self, * args):
super().__init__(* args)
self.type = self.GamepadType.SWITCH
self.config = []
self.device = self._get_device()
def start(self):
self.device.grab()
def stop(self):
self.device.ungrab()
def read_input(self):
# time.sleep(5)
# print("Read from ZKNS-001 correct")
# return InputEvent(InputEvent.EventType.PRESS_BUTTON, InputEvent.Button.BUTTON_A)
select([self.device], [], [], 5)
try:
event = self.device.read_one()
input_events = []
if event is not None:
if event.type == evdev.ecodes.EV_KEY:
match event.code:
case 304: # Y
input_events.append(InputEvent(InputEvent.EventType(event.value), InputEvent.Button.BUTTON_A))
case 305: # B
input_events.append(InputEvent(InputEvent.EventType(event.value), InputEvent.Button.BUTTON_B))
case 306: # A
input_events.append(InputEvent(InputEvent.EventType(event.value), InputEvent.Button.BUTTON_C))
case 307: # X
input_events.append(InputEvent(InputEvent.EventType(event.value), InputEvent.Button.BUTTON_D))
case 312: # MINUS
input_events.append(InputEvent(InputEvent.EventType(event.value), InputEvent.Button.BUTTON_SELECT))
case 313: # PLUS
input_events.append(InputEvent(InputEvent.EventType(event.value), InputEvent.Button.BUTTON_START))
if event.type == evdev.ecodes.EV_ABS and event.code == evdev.ecodes.ABS_HAT0X:
match event.value:
case -1: # LEFT
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_RIGHT))
input_events.append(InputEvent(InputEvent.EventType.PRESS_BUTTON, InputEvent.Button.BUTTON_LEFT))
case 1: # RIGHT
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_LEFT))
input_events.append(InputEvent(InputEvent.EventType.PRESS_BUTTON, InputEvent.Button.BUTTON_RIGHT))
case _: # NONE
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_LEFT))
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_RIGHT))
if event.type == evdev.ecodes.EV_ABS and event.code == evdev.ecodes.ABS_HAT0Y:
match event.value:
case -1: # UP
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_DOWN))
input_events.append(InputEvent(InputEvent.EventType.PRESS_BUTTON, InputEvent.Button.BUTTON_UP))
case 1: # DOWN
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_UP))
input_events.append(InputEvent(InputEvent.EventType.PRESS_BUTTON, InputEvent.Button.BUTTON_DOWN))
case _: # NONE
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_UP))
input_events.append(InputEvent(InputEvent.EventType.RELEASE_BUTTON, InputEvent.Button.BUTTON_DOWN))
if event.type == evdev.ecodes.EV_ABS and event.code == evdev.ecodes.ABS_Y:
match event.value:
case 0x0: # EMG
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 9))
case 0x5:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 8))
case 0x13:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 7))
case 0x20:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 6))
case 0x2E:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 5))
case 0x3C:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 4))
case 0x49:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 3))
case 0x57:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 2))
case 0x65:
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 1))
case 0x80: # N
input_events.append(InputEvent(InputEvent.EventType.BRAKE_NOTCH, 0))
input_events.append(InputEvent(InputEvent.EventType.POWER_NOTCH, 0))
case 0x9F:
input_events.append(InputEvent(InputEvent.EventType.POWER_NOTCH, 1))
case 0xB7:
input_events.append(InputEvent(InputEvent.EventType.POWER_NOTCH, 2))
case 0xCE:
input_events.append(InputEvent(InputEvent.EventType.POWER_NOTCH, 3))
case 0xE6:
input_events.append(InputEvent(InputEvent.EventType.POWER_NOTCH, 4))
case 0xFF: # P5
input_events.append(InputEvent(InputEvent.EventType.POWER_NOTCH, 5))
return input_events
except OSError:
return [InputEvent(InputEvent.EventType.ERROR, None)]

96
ddgo-converter/gui/main.py Executable file
View file

@ -0,0 +1,96 @@
import sys
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QMainWindow, QTableWidgetItem, QHeaderView
import threading
from gui.main_ui import Ui_MainWindow
import gamepads.physical as gamepad_physical
import gamepads.emulated as gamepad_emulated
from models.gamepad import GamepadModel
class MainWindow(QMainWindow):
def __init__(self, gamepad_handler):
super().__init__()
self._gamepad_handler = gamepad_handler
self._gui = Ui_MainWindow()
self._gui.setupUi(self)
self._emuthread = None
self._emuthread_stop = threading.Event()
self.gamepad_model = GamepadModel(self._gamepad_handler.find_gamepads())
self._gui.tableView_physicalControllerList.setModel(self.gamepad_model)
self._gui.tableView_physicalControllerList.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)
self._gui.tableView_physicalControllerList.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
self._gui.tableView_physicalControllerList.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeToContents)
self._gui.tableView_physicalControllerList.selectionModel().selectionChanged.connect(self.controller_list_selection_changed)
self._gui.tableView_physicalControllerList.selectRow(0)
self._gui.pushButton_physicalControllerRefresh.clicked.connect(self.controller_list_refresh)
self._gui.pushButton_emulatedControllerStart.clicked.connect(self.controller_emulator_start)
self._gui.pushButton_emulatedControllerStop.clicked.connect(self.controller_emulator_stop)
self._gui.pushButton_emulatedControllerStop.setVisible(False)
self.populate_controller_combobox()
def closeEvent(self, event):
self._emuthread_stop.set()
def populate_controller_combobox(self):
self._gui.comboBox_emulatedControllerModel.addItem("PC two-handle controller (DGOC-44U)", gamepad_emulated.PC2HandleGamepad())
self._gui.comboBox_emulatedControllerModel.setCurrentIndex(0)
self._gui.comboBox_emulatedControllerModel.addItem("Sony PlayStation two-handle controller (SLPH-00051)", gamepad_emulated.PS1Gamepad())
self._gui.comboBox_emulatedControllerModel.addItem("Nintendo 64 two-handle controller (TCPP-20003)", gamepad_emulated.N64Gamepad())
self._gui.comboBox_emulatedControllerModel.addItem("SEGA Saturn two-handle controller (TC-5175290)", gamepad_emulated.SATGamepad())
self._gui.comboBox_emulatedControllerModel.model().sort(0)
def controller_list_refresh(self):
self.gamepad_model.beginResetModel()
self.gamepad_model.gamepads = self._gamepad_handler.find_gamepads()
self.gamepad_model.endResetModel()
self.controller_list_selection_changed()
def controller_list_selection_changed(self):
enabled = False
rows = self._gui.tableView_physicalControllerList.selectionModel().selectedRows()
if rows and self.gamepad_model.gamepads[rows[0].row()].type != gamepad_physical.PhysicalGamepad.GamepadType.UNKNOWN:
enabled = True
self._gui.pushButton_emulatedControllerStart.setEnabled(enabled)
def lock_interface(self,):
self._gui.pushButton_emulatedControllerStart.setVisible(False)
self._gui.pushButton_emulatedControllerStop.setVisible(True)
self._gui.tableView_physicalControllerList.setEnabled(False)
self._gui.pushButton_physicalControllerRefresh.setEnabled(False)
self._gui.pushButton_physicalControllerConfig.setEnabled(False)
self._gui.comboBox_emulatedControllerModel.setEnabled(False)
def unlock_interface(self,):
self._gui.pushButton_emulatedControllerStart.setVisible(True)
self._gui.pushButton_emulatedControllerStop.setVisible(False)
self._gui.tableView_physicalControllerList.setEnabled(True)
self._gui.pushButton_physicalControllerRefresh.setEnabled(True)
self._gui.pushButton_physicalControllerConfig.setEnabled(True)
self._gui.comboBox_emulatedControllerModel.setEnabled(True)
def controller_emulator_start(self):
self.lock_interface()
self._gui.statusbar.showMessage("Gamepad emulator running...")
rows = self._gui.tableView_physicalControllerList.selectionModel().selectedRows()
if rows:
gamepad = self.gamepad_model.gamepads[rows[0].row()]
emulated_gamepad = self._gui.comboBox_emulatedControllerModel.itemData(self._gui.comboBox_emulatedControllerModel.currentIndex())
self._emuthread = threading.Thread(target=self._gamepad_handler.run_gamepad_emulator, args=(gamepad, emulated_gamepad, self._emuthread_stop))
self._emuthread.start()
def controller_emulator_stop(self):
self._gui.statusbar.showMessage("Stopping gamepad emulator...")
self._emuthread_stop.set()
self._emuthread.join(timeout=0.05)
if self._emuthread.is_alive():
QTimer.singleShot(50, self.controller_emulator_stop)
return
self._emuthread_stop.clear()
self.unlock_interface()
self.controller_list_refresh()
self._gui.statusbar.showMessage("Gamepad emulator stopped successfully!", 5000)

View file

@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'gui/main_ui.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 400)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
MainWindow.setSizePolicy(sizePolicy)
MainWindow.setMinimumSize(QtCore.QSize(800, 400))
MainWindow.setMaximumSize(QtCore.QSize(800, 400))
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setContentsMargins(9, 9, 9, 9)
self.gridLayout.setObjectName("gridLayout")
self.verticalLayout_main = QtWidgets.QVBoxLayout()
self.verticalLayout_main.setObjectName("verticalLayout_main")
self.groupBox_physicalControllers = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox_physicalControllers.setObjectName("groupBox_physicalControllers")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.groupBox_physicalControllers)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.tableView_physicalControllerList = QtWidgets.QTableView(self.groupBox_physicalControllers)
self.tableView_physicalControllerList.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.tableView_physicalControllerList.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.tableView_physicalControllerList.setObjectName("tableView_physicalControllerList")
self.tableView_physicalControllerList.horizontalHeader().setHighlightSections(False)
self.tableView_physicalControllerList.verticalHeader().setVisible(False)
self.verticalLayout_2.addWidget(self.tableView_physicalControllerList)
self.horizontalLayout_physicalControllerActions = QtWidgets.QHBoxLayout()
self.horizontalLayout_physicalControllerActions.setObjectName("horizontalLayout_physicalControllerActions")
self.pushButton_physicalControllerRefresh = QtWidgets.QPushButton(self.groupBox_physicalControllers)
self.pushButton_physicalControllerRefresh.setObjectName("pushButton_physicalControllerRefresh")
self.horizontalLayout_physicalControllerActions.addWidget(self.pushButton_physicalControllerRefresh)
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout_physicalControllerActions.addItem(spacerItem)
self.pushButton_physicalControllerConfig = QtWidgets.QPushButton(self.groupBox_physicalControllers)
self.pushButton_physicalControllerConfig.setEnabled(False)
self.pushButton_physicalControllerConfig.setObjectName("pushButton_physicalControllerConfig")
self.horizontalLayout_physicalControllerActions.addWidget(self.pushButton_physicalControllerConfig)
self.verticalLayout_2.addLayout(self.horizontalLayout_physicalControllerActions)
self.verticalLayout_main.addWidget(self.groupBox_physicalControllers)
self.groupBox_emulatedController = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox_emulatedController.setObjectName("groupBox_emulatedController")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.groupBox_emulatedController)
self.horizontalLayout.setObjectName("horizontalLayout")
self.label_emulatedControllerModel = QtWidgets.QLabel(self.groupBox_emulatedController)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_emulatedControllerModel.sizePolicy().hasHeightForWidth())
self.label_emulatedControllerModel.setSizePolicy(sizePolicy)
self.label_emulatedControllerModel.setObjectName("label_emulatedControllerModel")
self.horizontalLayout.addWidget(self.label_emulatedControllerModel)
self.comboBox_emulatedControllerModel = QtWidgets.QComboBox(self.groupBox_emulatedController)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.comboBox_emulatedControllerModel.sizePolicy().hasHeightForWidth())
self.comboBox_emulatedControllerModel.setSizePolicy(sizePolicy)
self.comboBox_emulatedControllerModel.setInsertPolicy(QtWidgets.QComboBox.InsertAlphabetically)
self.comboBox_emulatedControllerModel.setSizeAdjustPolicy(QtWidgets.QComboBox.AdjustToContents)
self.comboBox_emulatedControllerModel.setObjectName("comboBox_emulatedControllerModel")
self.horizontalLayout.addWidget(self.comboBox_emulatedControllerModel)
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.pushButton_emulatedControllerStart = QtWidgets.QPushButton(self.groupBox_emulatedController)
self.pushButton_emulatedControllerStart.setEnabled(False)
self.pushButton_emulatedControllerStart.setObjectName("pushButton_emulatedControllerStart")
self.horizontalLayout.addWidget(self.pushButton_emulatedControllerStart)
self.pushButton_emulatedControllerStop = QtWidgets.QPushButton(self.groupBox_emulatedController)
self.pushButton_emulatedControllerStop.setObjectName("pushButton_emulatedControllerStop")
self.horizontalLayout.addWidget(self.pushButton_emulatedControllerStop)
self.verticalLayout_main.addWidget(self.groupBox_emulatedController)
self.gridLayout.addLayout(self.verticalLayout_main, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Densha de GO! Controller Converter"))
self.groupBox_physicalControllers.setTitle(_translate("MainWindow", "Physical controllers"))
self.pushButton_physicalControllerRefresh.setText(_translate("MainWindow", "Refresh"))
self.pushButton_physicalControllerConfig.setText(_translate("MainWindow", "Configuration"))
self.groupBox_emulatedController.setTitle(_translate("MainWindow", "Emulated controller"))
self.label_emulatedControllerModel.setText(_translate("MainWindow", "Model"))
self.pushButton_emulatedControllerStart.setText(_translate("MainWindow", "Start"))
self.pushButton_emulatedControllerStop.setText(_translate("MainWindow", "Stop"))

View file

@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>400</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>800</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>Densha de GO! Controller Converter</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_main">
<item>
<widget class="QGroupBox" name="groupBox_physicalControllers">
<property name="title">
<string>Physical controllers</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTableView" name="tableView_physicalControllerList">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_physicalControllerActions">
<item>
<widget class="QPushButton" name="pushButton_physicalControllerRefresh">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_physicalControllerActions">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_physicalControllerConfig">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Configuration</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_emulatedController">
<property name="title">
<string>Emulated controller</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_emulatedControllerModel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Model</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_emulatedControllerModel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAlphabetically</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_emulatedController">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_emulatedControllerStart">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_emulatedControllerStop">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,29 @@
from evdev import InputDevice, list_devices, ecodes as e, UInput, AbsInfo
import threading
import events.input as input_events
import gamepads.physical as gamepad_physical
import gamepads.emulated as gamepad_emulated
class GamepadHandler:
def __init__(self):
super().__init__()
def find_gamepads():
gamepads = []
devices = [InputDevice(path) for path in list_devices()]
for device in devices:
gamepads.append(gamepad_physical.create_gamepad(device.info.vendor, device.info.product, device.name))
return gamepads
def run_gamepad_emulator(gamepad, emulated_gamepad, stop_event):
gamepad.start()
emulated_gamepad.start()
while not stop_event.is_set():
events = gamepad.read_input()
if events is not None:
for event in events:
if event.type == input_events.InputEvent.EventType.ERROR:
break
emulated_gamepad.write_input(event)
emulated_gamepad.stop()
gamepad.stop()

View file

@ -0,0 +1,35 @@
from PyQt5.QtCore import Qt, QVariant, QAbstractTableModel
from gamepads.physical import PhysicalGamepad
headers = ["ID", "Name", "Status"]
class GamepadModel(QAbstractTableModel):
def __init__(self, gamepads=None):
super(GamepadModel, self).__init__()
self.gamepads = gamepads or []
def data(self, index, role):
if role == Qt.DisplayRole:
match index.column():
case 0:
return self.gamepads[index.row()].id
case 1:
return self.gamepads[index.row()].name
case 2:
if self.gamepads[index.row()].type == PhysicalGamepad.GamepadType.UNKNOWN:
return "Not configured"
else:
return "Configured"
elif role == Qt.TextAlignmentRole and index.column() != 1:
return Qt.AlignCenter
def rowCount(self, index):
return len(self.gamepads)
def columnCount(self, index):
return len(headers)
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole or orientation != Qt.Horizontal:
return QVariant()
return headers[section]

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
evdev
PyQt5