-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Joystick Gremlin now using ProfileCollections, plugins changed to wor…
…k with front-end
- Loading branch information
Showing
4 changed files
with
141 additions
and
181 deletions.
There are no files selected for viewing
282 changes: 112 additions & 170 deletions
282
joystick_diagrams/plugins/joystick_gremlin_plugin/joystick_gremlin.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters