diff --git a/joystick_diagrams/plugins/joystick_gremlin_plugin/joystick_gremlin.py b/joystick_diagrams/plugins/joystick_gremlin_plugin/joystick_gremlin.py index 95e31a7..00b283d 100644 --- a/joystick_diagrams/plugins/joystick_gremlin_plugin/joystick_gremlin.py +++ b/joystick_diagrams/plugins/joystick_gremlin_plugin/joystick_gremlin.py @@ -1,183 +1,125 @@ -"""Joystick Gremlin (Version ~13) XML Parser for use with Joystick Diagrams""" +"""Joystick Gremlin (Version ~13) XML Parser for use with Joystick Diagrams. + +Author: Robert Cox +""" import logging +from typing import Union from xml.dom import minidom +from joystick_diagrams.input.profile_collection import ProfileCollection + _logger = logging.getLogger(__name__) +HAT_POSITIONS = { + 1: "U", + 2: "UR", + 3: "R", + 4: "DR", + 5: "D", + 6: "DL", + 7: "L", + 8: "UL", +} -class JoystickGremlin: - def __init__(self, filepath): - ## TRY FIND PATH +class JoystickGremlinParser: + def __init__(self, filepath): self.file = self.parse_xml_file(filepath) - # New Attributes - self.device_names = self.get_device_names() - self.profiles = [] - self.modes = None - self.mode = None - self.devices = None - self.device = None - self.current_device = None - self.current_mode = None - self.current_inherit = None - self.inherit = None - self.buttons = None - self.button_array = None - self.inherit_modes = {} - self.using_inheritance = False - self.position_map = { - 1: "U", - 2: "UR", - 3: "R", - 4: "DR", - 5: "D", - 6: "DL", - 7: "L", - 8: "UL", - } - self.hats = None - - def get_device_names(self) -> list: - self.devices = self.get_devices() - device_items = [] - - for item in self.devices: - device_items.append(item.getAttribute("name")) - return device_items - - def get_modes(self) -> list: - self.devices = self.get_devices() - profile_modes = [] - - item = self.devices[0] # All Modes common across JG - modes = item.getElementsByTagName("mode") - for mode in modes: - mode_name = mode.getAttribute("name") - profile_modes.append(mode_name) - return profile_modes - def parse_xml_file(self, xml_file) -> minidom.Document: ## Improve loading of file, checks for validity etc return minidom.parse(xml_file) - def create_dictionary(self, profiles=None) -> dict: - self.profiles = profiles - self.devices = self.get_devices() - _logger.debug(f"Number of Devices: {self.devices.length}") - - for self.device in self.devices: - self.current_device = self.get_single_device() - self.modes = self.get_device_modes() - _logger.debug(f"All Modes: {self.modes}") - for self.mode in self.modes: - self.current_inherit = self.has_inheritance() - self.button_array = {} - self.current_mode = self.get_single_mode() - _logger.debug(f"Selected Mode: {self.current_mode}") - self.buttons = self.get_mode_buttons() - self.hats = self.get_mode_hats() - self.extract_buttons() - self.extract_hats() - self.update_joystick_dictionary( - self.current_device, - self.current_mode, - self.current_inherit, - self.button_array, - ) - if self.using_inheritance: - self.inherit_joystick_dictionary() - - self.filter_dictionary() - return self.joystick_dictionary - - def filter_dictionary(self) -> dict: - if isinstance(self.profiles, list) and len(self.profiles) > 0: - for key, value in self.joystick_dictionary.items(): - for item in value.copy(): - if item not in self.profiles: - self.joystick_dictionary[key].pop(item, None) - return self.joystick_dictionary - - def get_devices(self): - return self.file.getElementsByTagName("device") - - def get_mode_buttons(self): - return self.mode.getElementsByTagName("button") - - def get_mode_hats(self): - return self.mode.getElementsByTagName("hat") - - def get_device_modes(self): - return self.device.getElementsByTagName("mode") - - def get_single_device(self): - return self.device.getAttribute("name") - - def get_single_mode(self): - return self.mode.getAttribute("name") - - def has_inheritance(self): - inherit = self.mode.getAttribute("inherit") - if inherit != "": - if self.using_inheritance is not True: - self.using_inheritance = True - return inherit - else: - return False - - def inherited_modes(self): - return self.mode.getAttribute("name") - - def extract_buttons(self): - for i in self.buttons: - if i.getAttribute("description") != "": - self.button_array.update({"BUTTON_" + str(i.getAttribute("id")): str(i.getAttribute("description"))}) - else: - self.button_array.update({"BUTTON_" + str(i.getAttribute("id")): self.no_bind_text}) - return self.button_array - - def extract_hats(self) -> None: - for i in self.hats: - hat_id = i.getAttribute("id") - _logger.debug("Hat ID: {hat_id}") - - if i.getAttribute("description"): - hat_description = i.getAttribute("description") - _logger.debug("Hat has description: {hat_description}") - else: - hat_description = "" - - hat_containers = i.getElementsByTagName("container") - - if hat_containers: - _logger.debug("Has containers: {hat_containers.length}") - - for container in hat_containers: - hat_positions = container.getElementsByTagName("action-set") - hat_count = hat_positions.length - increment = 8 / hat_count - pos = 1 - _logger.debug(f"We have {hat_count} hat positions") - - for position in hat_positions: - if position.getElementsByTagName("description"): - # Ignore more than 1 description. always use first - hat_direction_description = position.getElementsByTagName("description")[0].getAttribute( - "description" - ) - else: - hat_direction_description = hat_description - - _logger.debug(f"POV Position: {self.position_map[pos]}") - - self.button_array.update( - {f"POV_{i.getAttribute('id')}_{self.position_map[pos]}": str(hat_direction_description)} - ) - - pos = pos + increment - else: - _logger.error(f"No container found for hat: {hat_id}") - - def get_device_count(self): - return self.file.getElementsByTagName("device").length + def create_dictionary(self) -> ProfileCollection: + profile_collection = ProfileCollection() + + # Get all the modes + modes = self.file.getElementsByTagName("mode") + + for mode in modes: + # Create a profile for the mode using NAME + _active_profile = profile_collection.create_profile(mode.getAttribute("name")) + + # Create DEVICE from PARENT node + _device_guid = mode.parentNode.getAttribute("device-guid") + _device_name = mode.parentNode.getAttribute("name") + _device_obj = _active_profile.add_device(_device_guid, _device_name) + + # Iterate each AXIS / Button + bindings = mode.childNodes + + for bind in bindings: + if bind.nodeType == bind.ELEMENT_NODE: + bind_type = bind.tagName + bind_description = bind.getAttribute("description") + bind_identifier = bind.getAttribute("id") + + match bind_type: + case "axis": + if bind_description: + _device_obj.create_input(bind_identifier, bind_description) + case "button": + if bind_description: + _device_obj.create_input(bind_identifier, bind_description) + case "hat": + hats = self.extract_hats(bind) + + if hats: + for hat_id, hat_action in hats: + _device_obj.create_input(hat_id, hat_action) + case _: + _logger.warning("Unknown bind type ({bind_type}) detected while processing {_device_guid}") + + return profile_collection + + def extract_hats(self, hat_node) -> list[Union[str, str] | None]: + """Extract the hat positions for a given HAT node. + + Each HAT node may contain a CONTAINER, which may contain N number of action-set nodes + + Returns array of arrays containing the formatted POV CONTROL and ACTION + + """ + hat_id: str = hat_node.getAttribute("id") + hat_description: str = hat_node.getAttribute("description") or "" + hat_mappings: list = [] + + _logger.debug("Hat ID: {hat_id}") + _logger.debug("Hat has description: {hat_description}") + + # Get the containers + hat_container = hat_node.getElementsByTagName("container") + + if not hat_container: + return hat_mappings + + _logger.debug("Has containers: {hat_containers.length}") + + hat_positions = hat_container[0].getElementsByTagName("action-set") + + hat_mappings = [] + + for position in hat_positions: + # Get REMAP of node (assumes 1) + hat_position_id = position.getElementsByTagName("remap")[0].getAttribute("button") + + # Get the description node if exists + hat_description_check = position.getElementsByTagName("description") + + # If no node then continue + if not hat_description_check: + continue + + hat_description = hat_description_check[0].getAttribute("description") + + if not hat_description: + # If we don't have a description then no point using the item + continue + + hat_mappings.append([f"POV_{hat_id}_{HAT_POSITIONS[int(hat_position_id)]}", hat_description]) + + return hat_mappings + + +if __name__ == "__main__": + pass diff --git a/joystick_diagrams/plugins/joystick_gremlin_plugin/main.py b/joystick_diagrams/plugins/joystick_gremlin_plugin/main.py index 350f45e..c5a4688 100644 --- a/joystick_diagrams/plugins/joystick_gremlin_plugin/main.py +++ b/joystick_diagrams/plugins/joystick_gremlin_plugin/main.py @@ -1,6 +1,9 @@ import logging from pathlib import Path +from joystick_diagrams.plugins.joystick_gremlin_plugin.joystick_gremlin import ( + JoystickGremlinParser, +) from joystick_diagrams.plugins.plugin_interface import PluginInterface from .config import settings @@ -15,15 +18,21 @@ def __init__(self): self.settings.validators.register() def process(self): - return None + return self.instance.create_dictionary() def set_path(self, path: Path) -> bool: - self.path = path - return True + inst = JoystickGremlinParser(path) + + if inst: + self.instance = inst + self.path = path + return True + + return False @property def path_type(self): - return self.FilePath("Select your Joystick Diagrams .XML file", "/%USERPROFILE%/Saved Games", [".xml"]) + return self.FilePath("Select your Joystick Gremlin .XML file", "/%USERPROFILE%/Saved Games", [".xml"]) @property def name(self) -> str: diff --git a/joystick_diagrams/plugins/star_citizen_plugin/main.py b/joystick_diagrams/plugins/star_citizen_plugin/main.py index b283637..903624d 100644 --- a/joystick_diagrams/plugins/star_citizen_plugin/main.py +++ b/joystick_diagrams/plugins/star_citizen_plugin/main.py @@ -2,8 +2,12 @@ from pathlib import Path from joystick_diagrams.plugins.plugin_interface import PluginInterface - -from .config import settings +from joystick_diagrams.plugins.star_citizen_plugin.config import ( + settings, # TODO Move out plugins to separate package +) +from joystick_diagrams.plugins.star_citizen_plugin.star_citizen import ( + StarCitizen, # TODO Move out plugins to separate package +) _logger = logging.getLogger("__name__") @@ -15,11 +19,17 @@ def __init__(self): self.settings.validators.register() def process(self): - return None + return self.instance.parse() def set_path(self, path: Path) -> bool: - self.path = path - return True + inst = StarCitizen(path) + + if inst: + self.path = path + self.instance = inst + return True + + return False @property def path_type(self): @@ -44,4 +54,3 @@ def get_path(self) -> bool: if __name__ == "__main__": plugin = ParserPlugin() - print(plugin) diff --git a/joystick_diagrams/ui/mock_main/setting_page.py b/joystick_diagrams/ui/mock_main/setting_page.py index 530c4e2..3aadf81 100644 --- a/joystick_diagrams/ui/mock_main/setting_page.py +++ b/joystick_diagrams/ui/mock_main/setting_page.py @@ -45,7 +45,7 @@ def set_plugin_path(self, data): print(f"Data is {data}") try: currentPlugin = self.get_selected_plugin_object() - success = currentPlugin.set_path(data) + success = currentPlugin.set_path(data[0]) # TODO Plugin module needs improving to give better information? if success: print("worked")