Initial public-safe Steerify commit
This commit is contained in:
247
controller_profiles.py
Normal file
247
controller_profiles.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import copy
|
||||
import json
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
CONFIG_PATH = Path(__file__).with_name("wheel_bindings.json")
|
||||
|
||||
ACTION_ORDER = [
|
||||
"spotify pause play",
|
||||
"spotify next track",
|
||||
"spotify previous track",
|
||||
"spotify vol up",
|
||||
"spotify vol down",
|
||||
"spotify mute toggle",
|
||||
]
|
||||
|
||||
ACTION_LABELS = {
|
||||
"": "None",
|
||||
"spotify pause play": "Pause / Play",
|
||||
"spotify next track": "Next Track",
|
||||
"spotify previous track": "Previous Track",
|
||||
"spotify vol up": "Volume Up",
|
||||
"spotify vol down": "Volume Down",
|
||||
"spotify mute toggle": "Mute Toggle",
|
||||
}
|
||||
|
||||
DEFAULT_WHEEL_PROFILE_ID = "friend_directinput_wheel"
|
||||
|
||||
DEFAULT_WHEEL_BINDINGS = {
|
||||
"button:33": "spotify vol up",
|
||||
"button:31": "spotify vol down",
|
||||
"button:22": "spotify next track",
|
||||
"button:16": "spotify previous track",
|
||||
"button:21": "spotify pause play",
|
||||
"button:19": "spotify mute toggle",
|
||||
}
|
||||
|
||||
XBOX_360_BINDINGS = {
|
||||
"button:0": "spotify pause play",
|
||||
"button:1": "spotify next track",
|
||||
"button:2": "spotify previous track",
|
||||
"button:3": "spotify vol up",
|
||||
"button:4": "spotify vol down",
|
||||
"button:5": "spotify mute toggle",
|
||||
}
|
||||
|
||||
|
||||
def makeDefaultConfig():
|
||||
return {
|
||||
"version": 1,
|
||||
"default_profile_id": DEFAULT_WHEEL_PROFILE_ID,
|
||||
"button_presses_logged": True,
|
||||
"global_settings": {
|
||||
"debounce_seconds": 0.25,
|
||||
"volume_increment": 5,
|
||||
"polling_interval_ms": 120,
|
||||
},
|
||||
"profiles": [
|
||||
{
|
||||
"id": DEFAULT_WHEEL_PROFILE_ID,
|
||||
"name": "Friend DirectInput Wheel",
|
||||
"controller": None,
|
||||
"bindings": copy.deepcopy(DEFAULT_WHEEL_BINDINGS),
|
||||
"button_settings": {},
|
||||
},
|
||||
{
|
||||
"id": "xbox_360_default",
|
||||
"name": "Xbox 360 Default",
|
||||
"controller": None,
|
||||
"bindings": copy.deepcopy(XBOX_360_BINDINGS),
|
||||
"button_settings": {},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def loadConfig():
|
||||
if not CONFIG_PATH.exists():
|
||||
config = makeDefaultConfig()
|
||||
saveConfig(config)
|
||||
return config
|
||||
|
||||
with CONFIG_PATH.open("r", encoding="utf-8") as file:
|
||||
data = json.load(file)
|
||||
|
||||
if isFlatBindingFile(data):
|
||||
config = makeDefaultConfig()
|
||||
config["profiles"][0]["bindings"] = data
|
||||
saveConfig(config)
|
||||
return config
|
||||
|
||||
config = normalizeConfig(data)
|
||||
saveConfig(config)
|
||||
return config
|
||||
|
||||
|
||||
def saveConfig(config):
|
||||
with CONFIG_PATH.open("w", encoding="utf-8") as file:
|
||||
json.dump(config, file, indent=4)
|
||||
file.write("\n")
|
||||
|
||||
|
||||
def isFlatBindingFile(data):
|
||||
if not isinstance(data, dict):
|
||||
return False
|
||||
|
||||
if "profiles" in data:
|
||||
return False
|
||||
|
||||
return all(key.startswith("button:") for key in data.keys())
|
||||
|
||||
|
||||
def normalizeConfig(config):
|
||||
defaultConfig = makeDefaultConfig()
|
||||
|
||||
if not isinstance(config, dict):
|
||||
return defaultConfig
|
||||
|
||||
config.setdefault("version", 1)
|
||||
config.setdefault("default_profile_id", DEFAULT_WHEEL_PROFILE_ID)
|
||||
config.setdefault("button_presses_logged", True)
|
||||
config.setdefault("global_settings", {})
|
||||
config["global_settings"].setdefault("debounce_seconds", 0.25)
|
||||
config["global_settings"].setdefault("volume_increment", 5)
|
||||
config["global_settings"].setdefault("polling_interval_ms", 120)
|
||||
config.setdefault("profiles", [])
|
||||
|
||||
existingProfileIds = {profile.get("id") for profile in config["profiles"] if isinstance(profile, dict)}
|
||||
|
||||
for defaultProfile in defaultConfig["profiles"]:
|
||||
if defaultProfile["id"] not in existingProfileIds:
|
||||
config["profiles"].append(defaultProfile)
|
||||
|
||||
for profile in config["profiles"]:
|
||||
profile.setdefault("id", makeProfileId(profile.get("name", "Controller")))
|
||||
profile.setdefault("name", "Controller")
|
||||
profile.setdefault("controller", None)
|
||||
profile.setdefault("bindings", {})
|
||||
profile.setdefault("button_settings", {})
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def getProfile(config, profileId):
|
||||
for profile in config.get("profiles", []):
|
||||
if profile.get("id") == profileId:
|
||||
return profile
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def getDefaultProfile(config):
|
||||
profile = getProfile(config, config.get("default_profile_id"))
|
||||
|
||||
if profile is not None:
|
||||
return profile
|
||||
|
||||
return config["profiles"][0]
|
||||
|
||||
|
||||
def makeProfileId(profileName):
|
||||
cleaned = "".join(character.lower() if character.isalnum() else "_" for character in profileName)
|
||||
cleaned = "_".join(part for part in cleaned.split("_") if part)
|
||||
|
||||
if not cleaned:
|
||||
cleaned = "controller"
|
||||
|
||||
return f"{cleaned}_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
|
||||
def controllerFromJoystick(joystick):
|
||||
joystick.init()
|
||||
|
||||
return {
|
||||
"index": joystick.get_id(),
|
||||
"instance_id": joystick.get_instance_id(),
|
||||
"name": joystick.get_name(),
|
||||
"guid": joystick.get_guid(),
|
||||
"numaxes": joystick.get_numaxes(),
|
||||
"numbuttons": joystick.get_numbuttons(),
|
||||
"numhats": joystick.get_numhats(),
|
||||
}
|
||||
|
||||
|
||||
def stableControllerIdentity(controller):
|
||||
return {
|
||||
"name": controller.get("name"),
|
||||
"guid": controller.get("guid"),
|
||||
"numaxes": controller.get("numaxes"),
|
||||
"numbuttons": controller.get("numbuttons"),
|
||||
"numhats": controller.get("numhats"),
|
||||
}
|
||||
|
||||
|
||||
def profileHasController(profile):
|
||||
controller = profile.get("controller")
|
||||
|
||||
return isinstance(controller, dict) and bool(controller.get("name") or controller.get("guid"))
|
||||
|
||||
|
||||
def controllerMatches(profile, controller):
|
||||
storedController = profile.get("controller")
|
||||
|
||||
if not isinstance(storedController, dict):
|
||||
return False
|
||||
|
||||
if storedController.get("guid") and controller.get("guid"):
|
||||
return storedController.get("guid") == controller.get("guid")
|
||||
|
||||
return (
|
||||
storedController.get("name") == controller.get("name")
|
||||
and storedController.get("numaxes") == controller.get("numaxes")
|
||||
and storedController.get("numbuttons") == controller.get("numbuttons")
|
||||
and storedController.get("numhats") == controller.get("numhats")
|
||||
)
|
||||
|
||||
|
||||
def findMatchingProfile(config, controller):
|
||||
for profile in config.get("profiles", []):
|
||||
if controllerMatches(profile, controller):
|
||||
return profile
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def saveControllerProfile(config, controller, profileName, bindings, buttonSettings=None, existingProfileId=None):
|
||||
profile = getProfile(config, existingProfileId) if existingProfileId else None
|
||||
|
||||
if profile is None or not profileHasController(profile):
|
||||
profile = {
|
||||
"id": makeProfileId(profileName),
|
||||
"name": profileName,
|
||||
"controller": stableControllerIdentity(controller),
|
||||
"bindings": {},
|
||||
"button_settings": {},
|
||||
}
|
||||
config["profiles"].append(profile)
|
||||
|
||||
profile["name"] = profileName
|
||||
profile["controller"] = stableControllerIdentity(controller)
|
||||
profile["bindings"] = copy.deepcopy(bindings)
|
||||
profile["button_settings"] = copy.deepcopy(buttonSettings or {})
|
||||
|
||||
saveConfig(config)
|
||||
|
||||
return profile
|
||||
Reference in New Issue
Block a user