diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..815f9cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +*.db +**/.vscode/ diff --git a/ddgo-converter.py b/ddgo-converter.py deleted file mode 100755 index cdd3ff0..0000000 --- a/ddgo-converter.py +++ /dev/null @@ -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() diff --git a/ddgo-converter.spec b/ddgo-converter.spec new file mode 100644 index 0000000..dc22b27 --- /dev/null +++ b/ddgo-converter.spec @@ -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, +) diff --git a/ddgo-converter/ddgo-converter.py b/ddgo-converter/ddgo-converter.py new file mode 100755 index 0000000..ae252e9 --- /dev/null +++ b/ddgo-converter/ddgo-converter.py @@ -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_()) diff --git a/ddgo-converter/events/input.py b/ddgo-converter/events/input.py new file mode 100644 index 0000000..3820d35 --- /dev/null +++ b/ddgo-converter/events/input.py @@ -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 \ No newline at end of file diff --git a/ddgo-converter/gamepads/emulated.py b/ddgo-converter/gamepads/emulated.py new file mode 100755 index 0000000..4bc3f94 --- /dev/null +++ b/ddgo-converter/gamepads/emulated.py @@ -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() \ No newline at end of file diff --git a/ddgo-converter/gamepads/physical.py b/ddgo-converter/gamepads/physical.py new file mode 100755 index 0000000..f45e2e0 --- /dev/null +++ b/ddgo-converter/gamepads/physical.py @@ -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)] diff --git a/ddgo-converter/gui/main.py b/ddgo-converter/gui/main.py new file mode 100755 index 0000000..2ad4559 --- /dev/null +++ b/ddgo-converter/gui/main.py @@ -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) + diff --git a/ddgo-converter/gui/main_ui.py b/ddgo-converter/gui/main_ui.py new file mode 100644 index 0000000..5f22e0e --- /dev/null +++ b/ddgo-converter/gui/main_ui.py @@ -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")) diff --git a/ddgo-converter/gui/main_ui.ui b/ddgo-converter/gui/main_ui.ui new file mode 100644 index 0000000..b96b690 --- /dev/null +++ b/ddgo-converter/gui/main_ui.ui @@ -0,0 +1,185 @@ + + + MainWindow + + + + 0 + 0 + 800 + 400 + + + + + 0 + 0 + + + + + 800 + 400 + + + + + 800 + 400 + + + + Densha de GO! Controller Converter + + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + + Physical controllers + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + false + + + + + + + + + Refresh + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Configuration + + + + + + + + + + + + Emulated controller + + + + + + + 0 + 0 + + + + Model + + + + + + + + 0 + 0 + + + + QComboBox::InsertAlphabetically + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Start + + + + + + + Stop + + + + + + + + + + + + + + + diff --git a/ddgo-converter/handlers/gamepad.py b/ddgo-converter/handlers/gamepad.py new file mode 100755 index 0000000..ec04568 --- /dev/null +++ b/ddgo-converter/handlers/gamepad.py @@ -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() diff --git a/ddgo-converter/models/gamepad.py b/ddgo-converter/models/gamepad.py new file mode 100644 index 0000000..f0021ce --- /dev/null +++ b/ddgo-converter/models/gamepad.py @@ -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] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ceff6e9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +evdev +PyQt5