Added direct input functionality
This commit is contained in:
parent
7c96743a25
commit
2eaacadbe4
6 changed files with 590 additions and 153 deletions
24
nxbt/cli.py
24
nxbt/cli.py
|
|
@ -112,6 +112,19 @@ def check_bluetooth_address(address):
|
||||||
raise ValueError("Invalid Bluetooth address")
|
raise ValueError("Invalid Bluetooth address")
|
||||||
|
|
||||||
|
|
||||||
|
def get_reconnect_target():
|
||||||
|
|
||||||
|
if args.reconnect:
|
||||||
|
reconnect_target = find_devices_by_alias("Nintendo Switch")
|
||||||
|
elif args.address:
|
||||||
|
check_bluetooth_address(args.address)
|
||||||
|
reconnect_target = args.address
|
||||||
|
else:
|
||||||
|
reconnect_target = None
|
||||||
|
|
||||||
|
return reconnect_target
|
||||||
|
|
||||||
|
|
||||||
def demo():
|
def demo():
|
||||||
"""Loops over all available Bluetooth adapters
|
"""Loops over all available Bluetooth adapters
|
||||||
and creates controllers on each. The last available adapter
|
and creates controllers on each. The last available adapter
|
||||||
|
|
@ -153,13 +166,7 @@ def macro():
|
||||||
print("to load a macro string from.")
|
print("to load a macro string from.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if args.reconnect:
|
reconnect_target = get_reconnect_target()
|
||||||
reconnect_target = find_devices_by_alias("Nintendo Switch")
|
|
||||||
elif args.address:
|
|
||||||
check_bluetooth_address(args.address)
|
|
||||||
reconnect_target = args.address
|
|
||||||
else:
|
|
||||||
reconnect_target = None
|
|
||||||
|
|
||||||
nx = Nxbt(debug=args.debug, log_to_file=args.logfile)
|
nx = Nxbt(debug=args.debug, log_to_file=args.logfile)
|
||||||
print("Creating controller...")
|
print("Creating controller...")
|
||||||
|
|
@ -204,7 +211,8 @@ def main():
|
||||||
elif args.command == 'macro':
|
elif args.command == 'macro':
|
||||||
macro()
|
macro()
|
||||||
elif args.command == 'tui':
|
elif args.command == 'tui':
|
||||||
tui = InputTUI()
|
reconnect_target = get_reconnect_target()
|
||||||
|
tui = InputTUI(reconnect_target=reconnect_target)
|
||||||
tui.start()
|
tui.start()
|
||||||
elif args.command == 'addresses':
|
elif args.command == 'addresses':
|
||||||
list_switch_addresses()
|
list_switch_addresses()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,55 @@
|
||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
|
from json import dumps
|
||||||
|
|
||||||
|
|
||||||
|
DIRECT_INPUT_IDLE_PACKET = {
|
||||||
|
# Sticks
|
||||||
|
"L_STICK": {
|
||||||
|
"PRESSED": False,
|
||||||
|
"X_VALUE": 0,
|
||||||
|
"Y_VALUE": 0,
|
||||||
|
# Keyboard position calculation values
|
||||||
|
"LS_UP": False,
|
||||||
|
"LS_LEFT": False,
|
||||||
|
"LS_RIGHT": False,
|
||||||
|
"LS_DOWN": False
|
||||||
|
},
|
||||||
|
"R_STICK": {
|
||||||
|
"PRESSED": False,
|
||||||
|
"X_VALUE": 0,
|
||||||
|
"Y_VALUE": 0,
|
||||||
|
# Keyboard position calculation values
|
||||||
|
"RS_UP": False,
|
||||||
|
"RS_LEFT": False,
|
||||||
|
"RS_RIGHT": False,
|
||||||
|
"RS_DOWN": False
|
||||||
|
},
|
||||||
|
# Dpad
|
||||||
|
"DPAD_UP": False,
|
||||||
|
"DPAD_LEFT": False,
|
||||||
|
"DPAD_RIGHT": False,
|
||||||
|
"DPAD_DOWN": False,
|
||||||
|
# Triggers
|
||||||
|
"L": False,
|
||||||
|
"ZL": False,
|
||||||
|
"R": False,
|
||||||
|
"ZR": False,
|
||||||
|
# Joy-Con Specific Buttons
|
||||||
|
"JCL_SR": False,
|
||||||
|
"JCL_SL": False,
|
||||||
|
"JCR_SR": False,
|
||||||
|
"JCR_SL": False,
|
||||||
|
# Meta buttons
|
||||||
|
"PLUS": False,
|
||||||
|
"MINUS": False,
|
||||||
|
"HOME": False,
|
||||||
|
"CAPTURE": False,
|
||||||
|
# Buttons
|
||||||
|
"Y": False,
|
||||||
|
"X": False,
|
||||||
|
"B": False,
|
||||||
|
"A": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class InputParser():
|
class InputParser():
|
||||||
|
|
@ -85,7 +136,7 @@ class InputParser():
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def clear_macros(self, state=None):
|
def clear_macros(self):
|
||||||
|
|
||||||
self.current_macro = None
|
self.current_macro = None
|
||||||
self.current_macro_id = None
|
self.current_macro_id = None
|
||||||
|
|
@ -102,7 +153,8 @@ class InputParser():
|
||||||
|
|
||||||
def set_protocol_input(self, state=None):
|
def set_protocol_input(self, state=None):
|
||||||
|
|
||||||
if self.controller_input:
|
# Act on direct input if we're not getting idle packets
|
||||||
|
if dumps(self.controller_input) != dumps(DIRECT_INPUT_IDLE_PACKET):
|
||||||
self.parse_controller_input(self.controller_input)
|
self.parse_controller_input(self.controller_input)
|
||||||
self.controller_input = None
|
self.controller_input = None
|
||||||
|
|
||||||
|
|
@ -140,6 +192,91 @@ class InputParser():
|
||||||
|
|
||||||
def parse_controller_input(self, controller_input):
|
def parse_controller_input(self, controller_input):
|
||||||
|
|
||||||
|
# Check for input validity
|
||||||
|
if type(controller_input) != dict:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if the Grip/Order menu would be closed
|
||||||
|
if not self.exited_grip_order_menu and (
|
||||||
|
controller_input["A"] or controller_input["B"] or controller_input["HOME"]):
|
||||||
|
self.exited_grip_order_menu = True
|
||||||
|
|
||||||
|
# Arrays representing the 3 button bytes in the
|
||||||
|
# standard input report as binary.
|
||||||
|
upper = ['0'] * 8
|
||||||
|
shared = ['0'] * 8
|
||||||
|
lower = ['0'] * 8
|
||||||
|
# Upper Byte
|
||||||
|
if controller_input["Y"]:
|
||||||
|
upper[7] = '1'
|
||||||
|
if controller_input["X"]:
|
||||||
|
upper[6] = '1'
|
||||||
|
if controller_input["B"]:
|
||||||
|
upper[5] = '1'
|
||||||
|
if controller_input["A"]:
|
||||||
|
upper[4] = '1'
|
||||||
|
if controller_input["JCL_SR"]:
|
||||||
|
upper[3] = '1'
|
||||||
|
if controller_input["JCL_SL"]:
|
||||||
|
upper[2] = '1'
|
||||||
|
if controller_input["R"]:
|
||||||
|
upper[1] = '1'
|
||||||
|
if controller_input["ZR"]:
|
||||||
|
upper[0] = '1'
|
||||||
|
|
||||||
|
# Shared byte
|
||||||
|
if controller_input["MINUS"]:
|
||||||
|
shared[7] = '1'
|
||||||
|
if controller_input["PLUS"]:
|
||||||
|
shared[6] = '1'
|
||||||
|
if controller_input["R_STICK"]["PRESSED"]:
|
||||||
|
shared[5] = '1'
|
||||||
|
if controller_input["L_STICK"]["PRESSED"]:
|
||||||
|
shared[4] = '1'
|
||||||
|
if controller_input["HOME"]:
|
||||||
|
shared[3] = '1'
|
||||||
|
if controller_input["CAPTURE"]:
|
||||||
|
shared[2] = '1'
|
||||||
|
|
||||||
|
# Lower byte
|
||||||
|
if controller_input["DPAD_DOWN"]:
|
||||||
|
lower[7] = '1'
|
||||||
|
if controller_input["DPAD_UP"]:
|
||||||
|
lower[6] = '1'
|
||||||
|
if controller_input["DPAD_RIGHT"]:
|
||||||
|
lower[5] = '1'
|
||||||
|
if controller_input["DPAD_LEFT"]:
|
||||||
|
lower[4] = '1'
|
||||||
|
if controller_input["JCR_SR"]:
|
||||||
|
lower[3] = '1'
|
||||||
|
if controller_input["JCR_SL"]:
|
||||||
|
lower[2] = '1'
|
||||||
|
if controller_input["L"]:
|
||||||
|
lower[1] = '1'
|
||||||
|
if controller_input["ZL"]:
|
||||||
|
lower[0] = '1'
|
||||||
|
|
||||||
|
# Analog Stick Positions
|
||||||
|
stick_left = self.stick_ratio_to_calibrated_position(
|
||||||
|
controller_input["L_STICK"]["X_VALUE"] / 100,
|
||||||
|
controller_input["L_STICK"]["Y_VALUE"] / 100,
|
||||||
|
"L_STICK"
|
||||||
|
)
|
||||||
|
stick_right = self.stick_ratio_to_calibrated_position(
|
||||||
|
controller_input["R_STICK"]["X_VALUE"] / 100,
|
||||||
|
controller_input["R_STICK"]["Y_VALUE"] / 100,
|
||||||
|
"R_STICK"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Converting binary strings to ints
|
||||||
|
upper_byte = int("".join(upper), 2)
|
||||||
|
shared_byte = int("".join(shared), 2)
|
||||||
|
lower_byte = int("".join(lower), 2)
|
||||||
|
|
||||||
|
self.protocol.set_button_inputs(upper_byte, shared_byte, lower_byte)
|
||||||
|
self.protocol.set_left_stick_inputs(stick_left)
|
||||||
|
self.protocol.set_right_stick_inputs(stick_right)
|
||||||
|
|
||||||
return controller_input
|
return controller_input
|
||||||
|
|
||||||
def parse_macro(self, macro):
|
def parse_macro(self, macro):
|
||||||
|
|
@ -233,9 +370,9 @@ class InputParser():
|
||||||
upper[0] = '1'
|
upper[0] = '1'
|
||||||
|
|
||||||
# Shared byte
|
# Shared byte
|
||||||
elif button == "-":
|
elif button == "MINUS":
|
||||||
shared[7] = '1'
|
shared[7] = '1'
|
||||||
elif button == "+":
|
elif button == "PLUS":
|
||||||
shared[6] = '1'
|
shared[6] = '1'
|
||||||
elif button == "R_STICK_PRESS":
|
elif button == "R_STICK_PRESS":
|
||||||
shared[5] = '1'
|
shared[5] = '1'
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import random
|
||||||
from time import perf_counter
|
from time import perf_counter
|
||||||
|
|
||||||
from .controller import ControllerTypes
|
from .controller import ControllerTypes
|
||||||
from .utils import replace_subarray, format_msg_controller
|
from .utils import replace_subarray
|
||||||
|
|
||||||
|
|
||||||
class SwitchResponses(Enum):
|
class SwitchResponses(Enum):
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ class ControllerServer():
|
||||||
self.state = {
|
self.state = {
|
||||||
"state": "",
|
"state": "",
|
||||||
"finished_macros": [],
|
"finished_macros": [],
|
||||||
"errors": None
|
"errors": None,
|
||||||
|
"direct_input": None
|
||||||
}
|
}
|
||||||
|
|
||||||
self.task_queue = task_queue
|
self.task_queue = task_queue
|
||||||
|
|
@ -114,21 +115,26 @@ class ControllerServer():
|
||||||
# Getting any inputs from the task queue
|
# Getting any inputs from the task queue
|
||||||
if self.task_queue:
|
if self.task_queue:
|
||||||
try:
|
try:
|
||||||
msg = self.task_queue.get_nowait()
|
while True:
|
||||||
if msg and msg["type"] == "macro":
|
msg = self.task_queue.get_nowait()
|
||||||
self.input.buffer_macro(
|
if msg and msg["type"] == "macro":
|
||||||
msg["macro"], msg["macro_id"])
|
self.input.buffer_macro(
|
||||||
elif msg and msg["type"] == "stop":
|
msg["macro"], msg["macro_id"])
|
||||||
self.input.stop_macro(
|
elif msg and msg["type"] == "stop":
|
||||||
msg["macro_id"], state=self.state)
|
self.input.stop_macro(
|
||||||
elif msg and msg["type"] == "clear":
|
msg["macro_id"], state=self.state)
|
||||||
self.input.clear_macros(
|
elif msg and msg["type"] == "clear":
|
||||||
state=self.state)
|
self.input.clear_macros()
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Set Direct Input
|
||||||
|
if self.state["direct_input"]:
|
||||||
|
self.input.set_controller_input(self.state["direct_input"])
|
||||||
|
|
||||||
self.protocol.process_commands(reply)
|
self.protocol.process_commands(reply)
|
||||||
self.input.set_protocol_input(state=self.state)
|
self.input.set_protocol_input(state=self.state)
|
||||||
|
|
||||||
msg = self.protocol.get_report()
|
msg = self.protocol.get_report()
|
||||||
|
|
||||||
if self.logger_level <= logging.DEBUG and reply and len(reply) > 45:
|
if self.logger_level <= logging.DEBUG and reply and len(reply) > 45:
|
||||||
|
|
@ -331,16 +337,17 @@ class ControllerServer():
|
||||||
ctrl = None
|
ctrl = None
|
||||||
if type(reconnect_address) == list:
|
if type(reconnect_address) == list:
|
||||||
for address in reconnect_address:
|
for address in reconnect_address:
|
||||||
|
test_itr, test_ctrl = recreate_sockets()
|
||||||
try:
|
try:
|
||||||
test_itr, test_ctrl = recreate_sockets
|
|
||||||
|
|
||||||
# Setting up HID interrupt/control sockets
|
# Setting up HID interrupt/control sockets
|
||||||
test_ctrl.connect((reconnect_address, 17))
|
test_ctrl.connect((address, 17))
|
||||||
test_itr.connect((reconnect_address, 19))
|
test_itr.connect((address, 19))
|
||||||
|
|
||||||
itr = test_itr
|
itr = test_itr
|
||||||
ctrl = test_ctrl
|
ctrl = test_ctrl
|
||||||
except OSError:
|
except OSError:
|
||||||
|
test_itr.close()
|
||||||
|
test_ctrl.close()
|
||||||
pass
|
pass
|
||||||
elif type(reconnect_address) == str:
|
elif type(reconnect_address) == str:
|
||||||
test_itr, test_ctrl = recreate_sockets()
|
test_itr, test_ctrl = recreate_sockets()
|
||||||
|
|
|
||||||
100
nxbt/nxbt.py
100
nxbt/nxbt.py
|
|
@ -6,6 +6,7 @@ import signal
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
import dbus
|
import dbus
|
||||||
|
|
||||||
|
|
@ -22,6 +23,56 @@ JOYCON_R = ControllerTypes.JOYCON_R
|
||||||
PRO_CONTROLLER = ControllerTypes.PRO_CONTROLLER
|
PRO_CONTROLLER = ControllerTypes.PRO_CONTROLLER
|
||||||
|
|
||||||
|
|
||||||
|
DIRECT_INPUT_PACKET = {
|
||||||
|
# Sticks
|
||||||
|
"L_STICK": {
|
||||||
|
"PRESSED": False,
|
||||||
|
"X_VALUE": 0,
|
||||||
|
"Y_VALUE": 0,
|
||||||
|
# Keyboard position calculation values
|
||||||
|
"LS_UP": False,
|
||||||
|
"LS_LEFT": False,
|
||||||
|
"LS_RIGHT": False,
|
||||||
|
"LS_DOWN": False
|
||||||
|
},
|
||||||
|
"R_STICK": {
|
||||||
|
"PRESSED": False,
|
||||||
|
"X_VALUE": 0,
|
||||||
|
"Y_VALUE": 0,
|
||||||
|
# Keyboard position calculation values
|
||||||
|
"RS_UP": False,
|
||||||
|
"RS_LEFT": False,
|
||||||
|
"RS_RIGHT": False,
|
||||||
|
"RS_DOWN": False
|
||||||
|
},
|
||||||
|
# Dpad
|
||||||
|
"DPAD_UP": False,
|
||||||
|
"DPAD_LEFT": False,
|
||||||
|
"DPAD_RIGHT": False,
|
||||||
|
"DPAD_DOWN": False,
|
||||||
|
# Triggers
|
||||||
|
"L": False,
|
||||||
|
"ZL": False,
|
||||||
|
"R": False,
|
||||||
|
"ZR": False,
|
||||||
|
# Joy-Con Specific Buttons
|
||||||
|
"JCL_SR": False,
|
||||||
|
"JCL_SL": False,
|
||||||
|
"JCR_SR": False,
|
||||||
|
"JCR_SL": False,
|
||||||
|
# Meta buttons
|
||||||
|
"PLUS": False,
|
||||||
|
"MINUS": False,
|
||||||
|
"HOME": False,
|
||||||
|
"CAPTURE": False,
|
||||||
|
# Buttons
|
||||||
|
"Y": False,
|
||||||
|
"X": False,
|
||||||
|
"B": False,
|
||||||
|
"A": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Buttons():
|
class Buttons():
|
||||||
"""The button object containing the button string constants.
|
"""The button object containing the button string constants.
|
||||||
"""
|
"""
|
||||||
|
|
@ -34,8 +85,8 @@ class Buttons():
|
||||||
JCL_SL = 'JCL_SL'
|
JCL_SL = 'JCL_SL'
|
||||||
R = 'R'
|
R = 'R'
|
||||||
ZR = 'ZR'
|
ZR = 'ZR'
|
||||||
MINUS = '-'
|
MINUS = 'MINUS'
|
||||||
PLUS = '+'
|
PLUS = 'PLUS'
|
||||||
R_STICK_PRESS = 'R_STICK_PRESS'
|
R_STICK_PRESS = 'R_STICK_PRESS'
|
||||||
L_STICK_PRESS = 'L_STICK_PRESS'
|
L_STICK_PRESS = 'L_STICK_PRESS'
|
||||||
HOME = 'HOME'
|
HOME = 'HOME'
|
||||||
|
|
@ -206,8 +257,9 @@ class Nxbt():
|
||||||
cm.clear_macros(
|
cm.clear_macros(
|
||||||
msg["arguments"]["controller_index"])
|
msg["arguments"]["controller_index"])
|
||||||
elif msg["command"] == NxbtCommands.REMOVE_CONTROLLER:
|
elif msg["command"] == NxbtCommands.REMOVE_CONTROLLER:
|
||||||
cm.clear_macros(
|
index = msg["arguments"]["controller_index"]
|
||||||
msg["arguments"]["controller_index"])
|
cm.clear_macros(index)
|
||||||
|
cm.remove_controller(index)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
cm.shutdown()
|
cm.shutdown()
|
||||||
|
|
@ -385,6 +437,9 @@ class Nxbt():
|
||||||
"""Clears all running and queued macros on a specified
|
"""Clears all running and queued macros on a specified
|
||||||
controller.
|
controller.
|
||||||
|
|
||||||
|
WARNING: Any blocking macro calls will continue to run
|
||||||
|
forever if this command is run.
|
||||||
|
|
||||||
:param controller_index: The index of a given controller
|
:param controller_index: The index of a given controller
|
||||||
:type controller_index: int
|
:type controller_index: int
|
||||||
:raises ValueError: If the controller_index does not exist
|
:raises ValueError: If the controller_index does not exist
|
||||||
|
|
@ -408,6 +463,39 @@ class Nxbt():
|
||||||
for controller in self.manager_state.keys():
|
for controller in self.manager_state.keys():
|
||||||
self.clear_macros(controller)
|
self.clear_macros(controller)
|
||||||
|
|
||||||
|
def set_controller_input(self, controller_index, input_packet):
|
||||||
|
"""Sets the controllers buttons and analog sticks for 1 cycle.
|
||||||
|
This means that exactly 1 packet will be sent to the Switch with
|
||||||
|
input specified with this method. To keep a continuous input
|
||||||
|
stream of a desired input, packets must be set at a rate that
|
||||||
|
roughly matches the set controller. Eg: An emulated Pro Controller's
|
||||||
|
input must be set at roughly 120Hz and a Joy-Con's at 60Hz.
|
||||||
|
|
||||||
|
:param controller_index: The index of the emulated controller
|
||||||
|
:type controller_index: int
|
||||||
|
:param input_packet: The input packet with the desired input. This
|
||||||
|
*must* be an instance of the create_input_packet method.
|
||||||
|
:type input_packet: dict
|
||||||
|
:raises ValueError: On bad controller index
|
||||||
|
"""
|
||||||
|
|
||||||
|
if controller_index not in self.manager_state.keys():
|
||||||
|
raise ValueError("Specified controller does not exist")
|
||||||
|
|
||||||
|
self.manager_state[controller_index]["direct_input"] = input_packet
|
||||||
|
|
||||||
|
def create_input_packet(self):
|
||||||
|
"""Creates an input packet that is used to specify the input
|
||||||
|
of a controller for a single cycle.
|
||||||
|
|
||||||
|
:return: An input packet dictionary
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create a copy of the direct input packet in a thread safe manner.
|
||||||
|
# NOTE: Using the copy.deepcopy method of copying dicts IS NOT thread safe.
|
||||||
|
return json.loads(json.dumps(DIRECT_INPUT_PACKET))
|
||||||
|
|
||||||
def create_controller(self, controller_type, adapter_path=None,
|
def create_controller(self, controller_type, adapter_path=None,
|
||||||
colour_body=None, colour_buttons=None,
|
colour_body=None, colour_buttons=None,
|
||||||
reconnect_address=None):
|
reconnect_address=None):
|
||||||
|
|
@ -578,6 +666,9 @@ class Nxbt():
|
||||||
A list of UUIDs
|
A list of UUIDs
|
||||||
"errors":
|
"errors":
|
||||||
A string with the crash error
|
A string with the crash error
|
||||||
|
"direct_input":
|
||||||
|
A dictionary that represents all inputs
|
||||||
|
being directly input into the controller.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -638,6 +729,7 @@ class _ControllerManager():
|
||||||
controller_state["state"] = "initializing"
|
controller_state["state"] = "initializing"
|
||||||
controller_state["finished_macros"] = []
|
controller_state["finished_macros"] = []
|
||||||
controller_state["errors"] = False
|
controller_state["errors"] = False
|
||||||
|
controller_state["direct_input"] = json.loads(json.dumps(DIRECT_INPUT_PACKET))
|
||||||
|
|
||||||
self._controller_queues[index] = controller_queue
|
self._controller_queues[index] = controller_queue
|
||||||
|
|
||||||
|
|
|
||||||
435
nxbt/tui.py
435
nxbt/tui.py
|
|
@ -2,6 +2,7 @@ import os
|
||||||
import time
|
import time
|
||||||
import psutil
|
import psutil
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
import multiprocessing
|
||||||
|
|
||||||
from blessed import Terminal
|
from blessed import Terminal
|
||||||
|
|
||||||
|
|
@ -49,10 +50,10 @@ class ControllerTUI():
|
||||||
"RS_LEFT": "(",
|
"RS_LEFT": "(",
|
||||||
"RS_RIGHT": ")",
|
"RS_RIGHT": ")",
|
||||||
"RS_DOWN": "`─'",
|
"RS_DOWN": "`─'",
|
||||||
"DP_UP": "△",
|
"DPAD_UP": "△",
|
||||||
"DP_LEFT": "◁",
|
"DPAD_LEFT": "◁",
|
||||||
"DP_RIGHT": "▷",
|
"DPAD_RIGHT": "▷",
|
||||||
"DP_DOWN": "▽",
|
"DPAD_DOWN": "▽",
|
||||||
"MINUS": "◎",
|
"MINUS": "◎",
|
||||||
"PLUS": "◎",
|
"PLUS": "◎",
|
||||||
"HOME": "□",
|
"HOME": "□",
|
||||||
|
|
@ -74,6 +75,30 @@ class ControllerTUI():
|
||||||
for control in self.CONTROL_RELEASE_TIMERS.keys():
|
for control in self.CONTROL_RELEASE_TIMERS.keys():
|
||||||
self.CONTROL_RELEASE_TIMERS[control] = False
|
self.CONTROL_RELEASE_TIMERS[control] = False
|
||||||
|
|
||||||
|
self.auto_keypress_deactivation = True
|
||||||
|
self.remote_connection = False
|
||||||
|
|
||||||
|
def toggle_auto_keypress_deactivation(self, toggle):
|
||||||
|
"""Toggles whether or not the ControllerTUI should deactivate
|
||||||
|
a control after a period of time.
|
||||||
|
|
||||||
|
:param toggle: A True/False value that toggles auto keypress
|
||||||
|
deactivation
|
||||||
|
:type toggle: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.auto_keypress_deactivation = toggle
|
||||||
|
|
||||||
|
def set_remote_connection_status(self, status):
|
||||||
|
"""Sets whether or not the controller should render
|
||||||
|
with remote connection specific controls.
|
||||||
|
|
||||||
|
:param status: The status of the remote connection
|
||||||
|
:type status: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.remote_connection = status
|
||||||
|
|
||||||
def activate_control(self, key, activated_text=None):
|
def activate_control(self, key, activated_text=None):
|
||||||
|
|
||||||
if activated_text:
|
if activated_text:
|
||||||
|
|
@ -82,7 +107,8 @@ class ControllerTUI():
|
||||||
self.CONTROLS[key] = self.term.bold_black_on_white(self.CONTROLS[key])
|
self.CONTROLS[key] = self.term.bold_black_on_white(self.CONTROLS[key])
|
||||||
|
|
||||||
# Keep track of when the key was pressed so we can release later
|
# Keep track of when the key was pressed so we can release later
|
||||||
self.CONTROL_RELEASE_TIMERS[key] = time.perf_counter()
|
if self.auto_keypress_deactivation:
|
||||||
|
self.CONTROL_RELEASE_TIMERS[key] = time.perf_counter()
|
||||||
|
|
||||||
def deactivate_control(self, key):
|
def deactivate_control(self, key):
|
||||||
|
|
||||||
|
|
@ -90,12 +116,13 @@ class ControllerTUI():
|
||||||
|
|
||||||
def render_controller(self):
|
def render_controller(self):
|
||||||
|
|
||||||
# Release any overdue timers
|
if self.auto_keypress_deactivation:
|
||||||
for control in self.CONTROL_RELEASE_TIMERS.keys():
|
# Release any overdue timers
|
||||||
pressed_time = self.CONTROL_RELEASE_TIMERS[control]
|
for control in self.CONTROL_RELEASE_TIMERS.keys():
|
||||||
current_time = time.perf_counter()
|
pressed_time = self.CONTROL_RELEASE_TIMERS[control]
|
||||||
if pressed_time is not False and current_time - pressed_time > 0.25:
|
current_time = time.perf_counter()
|
||||||
self.deactivate_control(control)
|
if pressed_time is not False and current_time - pressed_time > 0.25:
|
||||||
|
self.deactivate_control(control)
|
||||||
|
|
||||||
ZL = self.CONTROLS['ZL']
|
ZL = self.CONTROLS['ZL']
|
||||||
L = self.CONTROLS['L']
|
L = self.CONTROLS['L']
|
||||||
|
|
@ -109,10 +136,10 @@ class ControllerTUI():
|
||||||
RL = self.CONTROLS['RS_LEFT']
|
RL = self.CONTROLS['RS_LEFT']
|
||||||
RR = self.CONTROLS['RS_RIGHT']
|
RR = self.CONTROLS['RS_RIGHT']
|
||||||
RD = self.CONTROLS['RS_DOWN']
|
RD = self.CONTROLS['RS_DOWN']
|
||||||
DU = self.CONTROLS['DP_UP']
|
DU = self.CONTROLS['DPAD_UP']
|
||||||
DL = self.CONTROLS['DP_LEFT']
|
DL = self.CONTROLS['DPAD_LEFT']
|
||||||
DR = self.CONTROLS['DP_RIGHT']
|
DR = self.CONTROLS['DPAD_RIGHT']
|
||||||
DD = self.CONTROLS['DP_DOWN']
|
DD = self.CONTROLS['DPAD_DOWN']
|
||||||
MN = self.CONTROLS['MINUS']
|
MN = self.CONTROLS['MINUS']
|
||||||
PL = self.CONTROLS['PLUS']
|
PL = self.CONTROLS['PLUS']
|
||||||
HM = self.CONTROLS['HOME']
|
HM = self.CONTROLS['HOME']
|
||||||
|
|
@ -122,6 +149,11 @@ class ControllerTUI():
|
||||||
X = self.CONTROLS['X']
|
X = self.CONTROLS['X']
|
||||||
Y = self.CONTROLS['Y']
|
Y = self.CONTROLS['Y']
|
||||||
|
|
||||||
|
if self.remote_connection:
|
||||||
|
lr_press = "L + R - - - - - - - - -▷ E"
|
||||||
|
else:
|
||||||
|
lr_press = " "
|
||||||
|
|
||||||
print(self.term.home + self.term.move_y((self.term.height // 2) - 9))
|
print(self.term.home + self.term.move_y((self.term.height // 2) - 9))
|
||||||
print(self.term.center(f" {ZL} {ZR} "))
|
print(self.term.center(f" {ZL} {ZR} "))
|
||||||
print(self.term.center(f" ─{L}──────────{R}─ ┌─────────────┬────────────┐"))
|
print(self.term.center(f" ─{L}──────────{R}─ ┌─────────────┬────────────┐"))
|
||||||
|
|
@ -135,45 +167,113 @@ class ControllerTUI():
|
||||||
print(self.term.center("│░░░░╲ ──────────────── ╱░░░░│ L/ZL ─ ─ ─ ─ ─ ─ ─ ─ ▷ 1/2 "))
|
print(self.term.center("│░░░░╲ ──────────────── ╱░░░░│ L/ZL ─ ─ ─ ─ ─ ─ ─ ─ ▷ 1/2 "))
|
||||||
print(self.term.center("│░░░░╱ ╲░░░░│ R/ZR ─ ─ ─ ─ ─ ─ ─ ─ ▷ 8/9 "))
|
print(self.term.center("│░░░░╱ ╲░░░░│ R/ZR ─ ─ ─ ─ ─ ─ ─ ─ ▷ 8/9 "))
|
||||||
print(self.term.center("│░░╱ ╲░░│ Right Stick - - - ▷ Arrows "))
|
print(self.term.center("│░░╱ ╲░░│ Right Stick - - - ▷ Arrows "))
|
||||||
print(self.term.center("│╱ ╲│ "))
|
print(self.term.center(f"│╱ ╲│ {lr_press} "))
|
||||||
|
|
||||||
|
|
||||||
class InputTUI():
|
class InputTUI():
|
||||||
|
|
||||||
CONTROLS = {
|
KEYMAP = {
|
||||||
"ZL": "◿□□□□",
|
# Left Stick Mapping
|
||||||
"L": "◿□□□□",
|
"w": {
|
||||||
"ZR": "□□□□◺",
|
"control": "LS_UP",
|
||||||
"R": "□□□□◺",
|
"stick_data": {
|
||||||
"LS_UP": ".─.",
|
"stick_name": "L_STICK",
|
||||||
"LS_LEFT": "(",
|
"x": "+000",
|
||||||
"LS_RIGHT": ")",
|
"y": "+100"
|
||||||
"LS_DOWN": "`─'",
|
}
|
||||||
"RS_UP": ".─.",
|
},
|
||||||
"RS_LEFT": "(",
|
"a": {
|
||||||
"RS_RIGHT": ")",
|
"control": "LS_LEFT",
|
||||||
"RS_DOWN": "`─'",
|
"stick_data": {
|
||||||
"DP_UP": "△",
|
"stick_name": "L_STICK",
|
||||||
"DP_LEFT": "◁",
|
"x": "-100",
|
||||||
"DP_RIGHT": "▷",
|
"y": "+000"
|
||||||
"DP_DOWN": "▽",
|
}
|
||||||
"MINUS": "◎",
|
},
|
||||||
"PLUS": "◎",
|
"d": {
|
||||||
"HOME": "□",
|
"control": "LS_RIGHT",
|
||||||
"CAPTURE": "□",
|
"stick_data": {
|
||||||
"A": "○",
|
"stick_name": "L_STICK",
|
||||||
"B": "○",
|
"x": "+100",
|
||||||
"X": "○",
|
"y": "+000"
|
||||||
"Y": "○",
|
}
|
||||||
|
},
|
||||||
|
"s": {
|
||||||
|
"control": "LS_DOWN",
|
||||||
|
"stick_data": {
|
||||||
|
"stick_name": "L_STICK",
|
||||||
|
"x": "+000",
|
||||||
|
"y": "-100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
# Right Stick Mapping
|
||||||
|
"KEY_UP": {
|
||||||
|
"control": "RS_UP",
|
||||||
|
"stick_data": {
|
||||||
|
"stick_name": "R_STICK",
|
||||||
|
"x": "+000",
|
||||||
|
"y": "+100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"KEY_LEFT": {
|
||||||
|
"control": "RS_LEFT",
|
||||||
|
"stick_data": {
|
||||||
|
"stick_name": "R_STICK",
|
||||||
|
"x": "-100",
|
||||||
|
"y": "+000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"KEY_RIGHT": {
|
||||||
|
"control": "RS_RIGHT",
|
||||||
|
"stick_data": {
|
||||||
|
"stick_name": "R_STICK",
|
||||||
|
"x": "+100",
|
||||||
|
"y": "+000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"KEY_DOWN": {
|
||||||
|
"control": "RS_DOWN",
|
||||||
|
"stick_data": {
|
||||||
|
"stick_name": "R_STICK",
|
||||||
|
"x": "+000",
|
||||||
|
"y": "-100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
# Dpad Mapping
|
||||||
|
"g": "DPAD_UP",
|
||||||
|
"v": "DPAD_LEFT",
|
||||||
|
"n": "DPAD_RIGHT",
|
||||||
|
"b": "DPAD_DOWN",
|
||||||
|
|
||||||
|
# Button Mapping
|
||||||
|
"6": "MINUS",
|
||||||
|
"7": "PLUS",
|
||||||
|
"[": "CAPTURE",
|
||||||
|
"]": "HOME",
|
||||||
|
"i": "X",
|
||||||
|
"j": "Y",
|
||||||
|
"l": "A",
|
||||||
|
"k": "B",
|
||||||
|
|
||||||
|
# Triggers
|
||||||
|
"1": "L",
|
||||||
|
"2": "ZL",
|
||||||
|
"8": "R",
|
||||||
|
"9": "ZR",
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, reconnect_target=None):
|
def __init__(self, reconnect_target=None, debug=False, logfile=False):
|
||||||
|
|
||||||
self.reconnect_target = reconnect_target
|
self.reconnect_target = reconnect_target
|
||||||
self.term = Terminal()
|
self.term = Terminal()
|
||||||
self.remote_connection = self.detect_remote_connection()
|
self.remote_connection = self.detect_remote_connection()
|
||||||
self.controller = ControllerTUI(self.term)
|
self.controller = ControllerTUI(self.term)
|
||||||
|
|
||||||
|
self.debug = debug
|
||||||
|
self.logfile = logfile
|
||||||
|
|
||||||
def detect_remote_connection(self):
|
def detect_remote_connection(self):
|
||||||
"""Traverse up the parent processes and check if any
|
"""Traverse up the parent processes and check if any
|
||||||
have their parent as a remote daemon. If so, the python
|
have their parent as a remote daemon. If so, the python
|
||||||
|
|
@ -210,7 +310,10 @@ class InputTUI():
|
||||||
def mainloop(self, term):
|
def mainloop(self, term):
|
||||||
|
|
||||||
# Initializing a controller
|
# Initializing a controller
|
||||||
self.nx = Nxbt(disable_logging=True)
|
if not self.debug:
|
||||||
|
self.nx = Nxbt(disable_logging=True)
|
||||||
|
else:
|
||||||
|
self.nx = Nxbt(debug=self.debug, logfile=self.logfile)
|
||||||
self.controller_index = self.nx.create_controller(
|
self.controller_index = self.nx.create_controller(
|
||||||
PRO_CONTROLLER,
|
PRO_CONTROLLER,
|
||||||
reconnect_address=self.reconnect_target)
|
reconnect_address=self.reconnect_target)
|
||||||
|
|
@ -269,6 +372,8 @@ class InputTUI():
|
||||||
|
|
||||||
def remote_input_loop(self, term):
|
def remote_input_loop(self, term):
|
||||||
|
|
||||||
|
self.controller.set_remote_connection_status(True)
|
||||||
|
|
||||||
inp = term.inkey(timeout=0)
|
inp = term.inkey(timeout=0)
|
||||||
while inp != chr(113): # Checking for q press
|
while inp != chr(113): # Checking for q press
|
||||||
# Cutoff large buffered input from the deque
|
# Cutoff large buffered input from the deque
|
||||||
|
|
@ -287,85 +392,27 @@ class InputTUI():
|
||||||
elif inp:
|
elif inp:
|
||||||
pressed_key = inp
|
pressed_key = inp
|
||||||
|
|
||||||
if pressed_key == 'w':
|
if pressed_key == 'e':
|
||||||
self.controller.activate_control('LS_UP')
|
|
||||||
self.nx.macro(self.controller_index, "L_STICK@+000+100 0.1s")
|
|
||||||
elif pressed_key == 'a':
|
|
||||||
self.controller.activate_control('LS_LEFT')
|
|
||||||
self.nx.macro(self.controller_index, "L_STICK@-100+000 0.1s")
|
|
||||||
elif pressed_key == 'd':
|
|
||||||
self.controller.activate_control('LS_RIGHT')
|
|
||||||
self.nx.macro(self.controller_index, "L_STICK@+100+000 0.1s")
|
|
||||||
elif pressed_key == 's':
|
|
||||||
self.controller.activate_control('LS_DOWN')
|
|
||||||
self.nx.macro(self.controller_index, "L_STICK@+000-100 0.1s")
|
|
||||||
|
|
||||||
elif pressed_key == 'g':
|
|
||||||
self.controller.activate_control('DP_UP')
|
|
||||||
self.nx.macro(self.controller_index, "DPAD_UP 0.1s")
|
|
||||||
elif pressed_key == 'v':
|
|
||||||
self.controller.activate_control('DP_LEFT')
|
|
||||||
self.nx.macro(self.controller_index, "DPAD_LEFT 0.1s")
|
|
||||||
elif pressed_key == 'n':
|
|
||||||
self.controller.activate_control('DP_RIGHT')
|
|
||||||
self.nx.macro(self.controller_index, "DPAD_RIGHT 0.1s")
|
|
||||||
elif pressed_key == 'b':
|
|
||||||
self.controller.activate_control('DP_DOWN')
|
|
||||||
self.nx.macro(self.controller_index, "DPAD_DOWN 0.1s")
|
|
||||||
|
|
||||||
elif pressed_key == '[':
|
|
||||||
self.controller.activate_control('CAPTURE')
|
|
||||||
self.nx.macro(self.controller_index, "CAPTURE 0.1s")
|
|
||||||
elif pressed_key == ']':
|
|
||||||
self.controller.activate_control('HOME')
|
|
||||||
self.nx.macro(self.controller_index, "HOME 0.1s")
|
|
||||||
|
|
||||||
elif pressed_key == '6':
|
|
||||||
self.controller.activate_control('MINUS')
|
|
||||||
self.nx.macro(self.controller_index, "- 0.1s")
|
|
||||||
elif pressed_key == '7':
|
|
||||||
self.controller.activate_control('PLUS')
|
|
||||||
self.nx.macro(self.controller_index, "+ 0.1s")
|
|
||||||
|
|
||||||
elif pressed_key == 'i':
|
|
||||||
self.controller.activate_control('X')
|
|
||||||
self.nx.macro(self.controller_index, "X 0.1s")
|
|
||||||
elif pressed_key == 'j':
|
|
||||||
self.controller.activate_control('Y')
|
|
||||||
self.nx.macro(self.controller_index, "Y 0.1s")
|
|
||||||
elif pressed_key == 'l':
|
|
||||||
self.controller.activate_control('A')
|
|
||||||
self.nx.macro(self.controller_index, "A 0.1s")
|
|
||||||
elif pressed_key == 'k':
|
|
||||||
self.controller.activate_control('B')
|
|
||||||
self.nx.macro(self.controller_index, "B 0.1s")
|
|
||||||
|
|
||||||
elif pressed_key == '1':
|
|
||||||
self.controller.activate_control('L')
|
self.controller.activate_control('L')
|
||||||
self.nx.macro(self.controller_index, "L 0.1s")
|
|
||||||
elif pressed_key == '2':
|
|
||||||
self.controller.activate_control('ZL')
|
|
||||||
self.nx.macro(self.controller_index, "ZL 0.1s")
|
|
||||||
|
|
||||||
elif pressed_key == '8':
|
|
||||||
self.controller.activate_control('R')
|
self.controller.activate_control('R')
|
||||||
self.nx.macro(self.controller_index, "R 0.1s")
|
self.nx.macro(self.controller_index, "L R 0.1s")
|
||||||
elif pressed_key == '9':
|
else:
|
||||||
self.controller.activate_control('ZR')
|
try:
|
||||||
self.nx.macro(self.controller_index, "ZR 0.1s")
|
control_data = self.KEYMAP[pressed_key]
|
||||||
|
if type(control_data) == dict and "stick_data" in control_data.keys():
|
||||||
|
x_value = control_data['stick_data']['x']
|
||||||
|
y_value = control_data['stick_data']['y']
|
||||||
|
stick_name = control_data['stick_data']['stick_name']
|
||||||
|
|
||||||
elif pressed_key == 'KEY_UP':
|
self.controller.activate_control(control_data["control"])
|
||||||
self.controller.activate_control('RS_UP')
|
self.nx.macro(
|
||||||
self.nx.macro(self.controller_index, "L_STICK@+000+100 0.1s")
|
self.controller_index,
|
||||||
elif pressed_key == 'KEY_LEFT':
|
f"{stick_name}@{x_value}{y_value} 0.1s")
|
||||||
self.controller.activate_control('RS_LEFT')
|
else:
|
||||||
self.nx.macro(self.controller_index, "L_STICK@-100+000 0.1s")
|
self.controller.activate_control(control_data)
|
||||||
elif pressed_key == 'KEY_RIGHT':
|
self.nx.macro(self.controller_index, f"{control_data} 0.05s")
|
||||||
self.controller.activate_control('RS_RIGHT')
|
except KeyError:
|
||||||
self.nx.macro(self.controller_index, "L_STICK@+100+000 0.1s")
|
pass
|
||||||
elif pressed_key == 'KEY_DOWN':
|
|
||||||
self.controller.activate_control('RS_DOWN')
|
|
||||||
self.nx.macro(self.controller_index, "L_STICK@+000-100 0.1s")
|
|
||||||
|
|
||||||
self.controller.render_controller()
|
self.controller.render_controller()
|
||||||
|
|
||||||
|
|
@ -373,7 +420,147 @@ class InputTUI():
|
||||||
|
|
||||||
def direct_input_loop(self, term):
|
def direct_input_loop(self, term):
|
||||||
|
|
||||||
pass
|
# pynput must be imported here since earlier imports
|
||||||
|
# will cause errors on remote connections
|
||||||
|
from pynput import keyboard
|
||||||
|
|
||||||
|
self.controller.toggle_auto_keypress_deactivation(False)
|
||||||
|
self.exit_tui = False
|
||||||
|
self.capture_input = True
|
||||||
|
|
||||||
|
# Create a packet that is accessible from a multiprocessing Process
|
||||||
|
# and from within threads
|
||||||
|
packet_manager = multiprocessing.Manager()
|
||||||
|
input_packet = packet_manager.dict()
|
||||||
|
input_packet["packet"] = self.nx.create_input_packet()
|
||||||
|
|
||||||
|
print(term.move_y(term.height - 5))
|
||||||
|
print(term.center(term.bold_black_on_white(" <Press esc to toggle input capture> ")))
|
||||||
|
|
||||||
|
def on_press(key):
|
||||||
|
|
||||||
|
# Parse the key press event
|
||||||
|
pressed_key = None
|
||||||
|
try:
|
||||||
|
pressed_key = key.char
|
||||||
|
except AttributeError:
|
||||||
|
pressed_key = str(key).replace(".", "_").upper()
|
||||||
|
|
||||||
|
if not self.capture_input: # If we're not capturing input, pass
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
control_data = self.KEYMAP[pressed_key]
|
||||||
|
packet = input_packet["packet"]
|
||||||
|
if type(control_data) == dict and "stick_data" in control_data.keys():
|
||||||
|
stick_name = control_data['stick_data']['stick_name']
|
||||||
|
self.controller.activate_control(control_data["control"])
|
||||||
|
packet[stick_name][control_data["control"]] = True
|
||||||
|
else:
|
||||||
|
self.controller.activate_control(control_data)
|
||||||
|
packet[control_data] = True
|
||||||
|
input_packet["packet"] = packet
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_release(key):
|
||||||
|
|
||||||
|
# Parse the key release event
|
||||||
|
released_key = None
|
||||||
|
try:
|
||||||
|
released_key = key.char
|
||||||
|
except AttributeError:
|
||||||
|
released_key = str(key).replace(".", "_").upper()
|
||||||
|
|
||||||
|
# If the esc key is released, toggle input capturing
|
||||||
|
if released_key == "KEY_ESC":
|
||||||
|
self.capture_input = not self.capture_input
|
||||||
|
|
||||||
|
# Exit on q key press
|
||||||
|
if released_key == 'q':
|
||||||
|
self.exit_tui = True
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.capture_input: # If we're not capturing input, pass
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
control_data = self.KEYMAP[released_key]
|
||||||
|
packet = input_packet["packet"]
|
||||||
|
if type(control_data) == dict and "stick_data" in control_data.keys():
|
||||||
|
stick_name = control_data['stick_data']['stick_name']
|
||||||
|
self.controller.deactivate_control(control_data["control"])
|
||||||
|
packet[stick_name][control_data["control"]] = False
|
||||||
|
else:
|
||||||
|
self.controller.deactivate_control(control_data)
|
||||||
|
packet[control_data] = False
|
||||||
|
input_packet["packet"] = packet
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def input_worker(nxbt, controller_index, input_packet):
|
||||||
|
|
||||||
|
while True:
|
||||||
|
packet = input_packet["packet"]
|
||||||
|
|
||||||
|
# Calculating left x/y stick values
|
||||||
|
ls_x_value = 0
|
||||||
|
ls_y_value = 0
|
||||||
|
if packet["L_STICK"]["LS_LEFT"]:
|
||||||
|
ls_x_value -= 100
|
||||||
|
if packet["L_STICK"]["LS_RIGHT"]:
|
||||||
|
ls_x_value += 100
|
||||||
|
if packet["L_STICK"]["LS_UP"]:
|
||||||
|
ls_y_value += 100
|
||||||
|
if packet["L_STICK"]["LS_DOWN"]:
|
||||||
|
ls_y_value -= 100
|
||||||
|
packet["L_STICK"]["X_VALUE"] = ls_x_value
|
||||||
|
packet["L_STICK"]["Y_VALUE"] = ls_y_value
|
||||||
|
|
||||||
|
# Calculating right x/y stick values
|
||||||
|
rs_x_value = 0
|
||||||
|
rs_y_value = 0
|
||||||
|
if packet["R_STICK"]["RS_LEFT"]:
|
||||||
|
rs_x_value -= 100
|
||||||
|
if packet["R_STICK"]["RS_RIGHT"]:
|
||||||
|
rs_x_value += 100
|
||||||
|
if packet["R_STICK"]["RS_UP"]:
|
||||||
|
rs_y_value += 100
|
||||||
|
if packet["R_STICK"]["RS_DOWN"]:
|
||||||
|
rs_y_value -= 100
|
||||||
|
packet["R_STICK"]["X_VALUE"] = rs_x_value
|
||||||
|
packet["R_STICK"]["Y_VALUE"] = rs_y_value
|
||||||
|
|
||||||
|
nxbt.set_controller_input(controller_index, packet)
|
||||||
|
time.sleep(1/120)
|
||||||
|
|
||||||
|
input_process = multiprocessing.Process(
|
||||||
|
target=input_worker, args=(self.nx, self.controller_index, input_packet))
|
||||||
|
input_process.start()
|
||||||
|
|
||||||
|
# Start a non-blocking keyboard event listener
|
||||||
|
listener = keyboard.Listener(
|
||||||
|
on_press=on_press,
|
||||||
|
on_release=on_release)
|
||||||
|
listener.start()
|
||||||
|
|
||||||
|
# Main TUI Loop
|
||||||
|
while True:
|
||||||
|
if self.exit_tui:
|
||||||
|
packet_manager.shutdown()
|
||||||
|
input_process.terminate()
|
||||||
|
break
|
||||||
|
if not self.capture_input:
|
||||||
|
print(term.home + term.move_y((term.height // 2) - 4))
|
||||||
|
print(term.bold_black_on_white(term.center("")))
|
||||||
|
print(term.bold_black_on_white(term.center(
|
||||||
|
"<Input Paused. Press ESC Again to Begin Capturing Input>"
|
||||||
|
)))
|
||||||
|
print(term.bold_black_on_white(term.center("")))
|
||||||
|
else:
|
||||||
|
self.controller.render_controller()
|
||||||
|
self.check_for_disconnect(term)
|
||||||
|
time.sleep(1/30)
|
||||||
|
|
||||||
def render_start_screen(self, term, loading_text):
|
def render_start_screen(self, term, loading_text):
|
||||||
|
|
||||||
|
|
@ -415,7 +602,13 @@ class InputTUI():
|
||||||
print(term.bold_black_on_red(term.center("")))
|
print(term.bold_black_on_red(term.center("")))
|
||||||
print(term.bold_black_on_red(term.center(state.title())))
|
print(term.bold_black_on_red(term.center(state.title())))
|
||||||
print(term.bold_black_on_red(term.center("")))
|
print(term.bold_black_on_red(term.center("")))
|
||||||
self.nx.wait_for_connection(self.controller_index)
|
|
||||||
|
while True:
|
||||||
|
inp = term.inkey(1/30)
|
||||||
|
if inp == chr(113):
|
||||||
|
exit(1)
|
||||||
|
elif self.nx.state[self.controller_index]["state"] == 'connected':
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue