#!/bin/python3 from openrgb import OpenRGBClient from openrgb.utils import DeviceType, RGBColor from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import json import xmltodict from os.path import basename from enum import IntFlag from pathlib import Path from functools import wraps def after(runAfter): def inner(fun): @wraps(fun) def wrapper(*args, **kwds): res = fun(*args, **kwds) runAfter() return res return wrapper return inner qwertyToAzerty = { "A": "Q", "Z": "W", "E": "E", "R": "R", "T": "T", "Y": "Y", "U": "U", "I": "I", "O": "O", "P": "P", "Q": "A", "S": "S", "D": "D", "F": "F", "G": "G", "H": "H", "J": "J", "K": "K", "L": "L", "M": ";", "W": "Z", "X": "X", "C": "C", "V": "V", "B": "B", "N": "N", ",": ";", ";": "M", ":": "M", } COLORS = { "ELITE_ORANGE": RGBColor(255, 106, 0), "BLUE": RGBColor(0, 255, 255), "DEFAULT": RGBColor(0, 255, 255), "MOVEMENT": RGBColor(0, 255, 255), "ATTACK": RGBColor(255, 0, 0), "MENU": RGBColor(255, 255, 255), "UTIL": RGBColor(0, 255, 0), "INVENTORY": RGBColor(255, 255, 0), "OFF": RGBColor(0, 0, 0), } ED_SAVE_PATH = Path( "/home/ar2000/.steam/steam/steamapps/compatdata/359320/pfx/drive_c/users/steamuser/Saved Games/Frontier Developments/Elite Dangerous/Status.json" ) ED_BINDINGS_CUSTOM_PATH = Path( "/home/ar2000/.steam/steam/steamapps/compatdata/359320/pfx/drive_c/users/steamuser/AppData/Local/Frontier Developments/Elite Dangerous/Options/Bindings/" ) ED_BINDINGS_TEMPLATE_PATH = Path( "/home/ar2000/.steam/steam/steamapps/common/Elite Dangerous/Products/elite-dangerous-odyssey-64/ControlSchemes/" ) file = None ON_FOOT_KEYS = { "HumanoidForwardButton": COLORS["MOVEMENT"], "HumanoidBackwardButton": COLORS["MOVEMENT"], "HumanoidStrafeLeftButton": COLORS["MOVEMENT"], "HumanoidStrafeRightButton": COLORS["MOVEMENT"], "HumanoidRotateLeftButton": COLORS["MOVEMENT"], "HumanoidRotateRightButton": COLORS["MOVEMENT"], "HumanoidPitchUpButton": COLORS["MOVEMENT"], "HumanoidPitchDownButton": COLORS["MOVEMENT"], "HumanoidSprintButton": COLORS["MOVEMENT"], "HumanoidWalkButton": COLORS["MOVEMENT"], "HumanoidCrouchButton": COLORS["MOVEMENT"], "HumanoidJumpButton": COLORS["MOVEMENT"], "HumanoidPrimaryInteractButton": COLORS["MENU"], "HumanoidSecondaryInteractButton": COLORS["MENU"], "HumanoidItemWheelButton": COLORS["MENU"], "HumanoidEmoteWheelButton": COLORS["MENU"], "HumanoidItemWheelButton_XAxis": COLORS["MENU"], "HumanoidItemWheelButton_XLeft": COLORS["MENU"], "HumanoidItemWheelButton_XRight": COLORS["MENU"], "HumanoidItemWheelButton_YAxis": COLORS["MENU"], "HumanoidItemWheelButton_YUp": COLORS["MENU"], "HumanoidItemWheelButton_YDown": COLORS["MENU"], "HumanoidPrimaryFireButton": COLORS["ATTACK"], "HumanoidZoomButton": COLORS["DEFAULT"], "HumanoidThrowGrenadeButton": COLORS["ATTACK"], "HumanoidMeleeButton": COLORS["ATTACK"], "HumanoidReloadButton": COLORS["UTIL"], "HumanoidSelectPrimaryWeaponButton": COLORS["INVENTORY"], "HumanoidSelectSecondaryWeaponButton": COLORS["INVENTORY"], "HumanoidSelectUtilityWeaponButton": COLORS["INVENTORY"], "HumanoidSelectNextWeaponButton": COLORS["INVENTORY"], "HumanoidSelectPreviousWeaponButton": COLORS["INVENTORY"], "HumanoidHideWeaponButton": COLORS["UTIL"], "HumanoidSelectNextGrenadeTypeButton": COLORS["INVENTORY"], "HumanoidSelectPreviousGrenadeTypeButton": COLORS["INVENTORY"], "HumanoidToggleFlashlightButton": COLORS["UTIL"], "HumanoidToggleNightVisionButton": COLORS["UTIL"], "HumanoidToggleShieldsButton": COLORS["UTIL"], "HumanoidToggleToolModeButton": COLORS["UTIL"], "HumanoidToggleMissionHelpPanelButton": COLORS["MENU"], "HumanoidOpenAccessPanelButton": COLORS["MENU"], "HumanoidConflictContextualUIButton": COLORS["MENU"], "PhotoCameraToggle_Humanoid": COLORS["MENU"], "GalaxyMapOpen_Humanoid": COLORS["MENU"], "SystemMapOpen_Humanoid": COLORS["MENU"], "FocusCommsPanel_Humanoid": COLORS["MENU"], "QuickCommsPanel_Humanoid": COLORS["MENU"], } KEYS_TR = { f"{a}{b}": f"{a} {b}" for a in ["Up", "Down", "Left", "Right"] for b in ["Arrow", "Alt", "Shift", "Control"] } configured_on_foot_keys, keybindgs = {}, {} class StatusFlag(IntFlag): DOCKED = 1 << 0 LANDED = 1 << 1 LANDING_GEAR_DOWN = 1 << 2 SHIELDS_UP = 1 << 3 SUPERCRUISE = 1 << 4 FLIGHTASSIST_OFF = 1 << 5 HARDPOINTS_DEPLOYED = 1 << 6 IN_WING = 1 << 7 LIGHTS_ON = 1 << 8 CARGO_SCOOP_DEPLOYED = 1 << 9 SILENT_RUNNING = 1 << 10 SCOOPING_FUEL = 1 << 11 SRV_HANDBRAKE = 1 << 12 SRV_USING_TURRET_VIEW = 1 << 13 SRV_TURRET_RETRACTED = 1 << 14 SRV_DRIVEASSIST = 1 << 15 FSD_MASSLOCKED = 1 << 16 FSD_CHARGING = 1 << 17 FSD_COOLDOWN = 1 << 18 LOW_FUEL = 1 << 19 OVER_HEATING = 1 << 20 HAS_LAT_LONG = 1 << 21 IS_IN_DANGER = 1 << 22 BEING_INTERDICTED = 1 << 23 IN_MAINSHIP = 1 << 24 IN_FIGHTER = 1 << 25 IN_SRV = 1 << 26 HUD_IN_ANALYSIS_MODE = 1 << 27 NIGHT_VISION = 1 << 28 ALTITUDE_FROM_AVERAGE_RADIUS = 1 << 29 FSDJUMP = 1 << 30 SRV_HIGHBEAM = 1 << 31 class StatusFlag2(IntFlag): ON_FOOT = 1 << 0 IN_TAXI = 1 << 1 IN_MULTICREW = 1 << 2 ON_FOOT_IN_STATION = 1 << 3 ON_FOOT_ON_PLANET = 1 << 4 AIM_DOWN_SIGHT = 1 << 5 LOW_OXYGEN = 1 << 6 LOW_HEALTH = 1 << 7 COLD = 1 << 8 HOT = 1 << 9 VERY_COLD = 1 << 10 VERY_HOT = 1 << 11 GLIDE_MODE = 1 << 12 ON_FOOT_INHANGAR = 1 << 13 ON_FOOT_SOCIAL_SPACE = 1 << 14 ON_FOOT_EXTERIOR = 1 << 15 BREATHABLE_ATMOSPHERE = 1 << 16 TELEPRESENCE_MULTICREW = 1 << 17 PHYSICAL_MULTICREW = 1 << 18 FSD_HYPERDRIVE_CHARGING = 1 << 19 def setKeysColor(device, keys: list | str, color: RGBColor | list): if type(keys) == list: assert len(color) == len(keys) for i, key in enumerate(keys): for led in device.leds: try: if qwertyToAzerty[led.name.lstrip("Key: ")] == key: led.set_color(color if type(color) == RGBColor else color[i]) continue except KeyError: if led.name.lstrip("Key: ") == key: led.set_color(color if type(color) == RGBColor else color[i]) continue def keepAlive(): keyboard.update() def parseKeybinds(): with open(Path(ED_BINDINGS_CUSTOM_PATH, "Custom.4.0.binds"), "r") as f: readKeybinds = xmltodict.parse(f.read()) keybindgs = readKeybinds["Root"] # extract keys for on foot keys for k in ON_FOOT_KEYS: if not k in keybindgs: continue keybind = keybindgs[k] if "Primary" in keybind: if keybind["Primary"]["@Device"] == "Keyboard": key = keybind["Primary"]["@Key"].lstrip("Key_") if key in KEYS_TR: key = KEYS_TR[key] configured_on_foot_keys[k] = key print(f"{k} : {key}") if "Secondary" in keybind: if keybind["Secondary"]["@Device"] == "Keyboard": key = keybind["Secondary"]["@Key"].lstrip("Key_") if key in KEYS_TR: key = KEYS_TR[key] if k in configured_on_foot_keys: print(f'Key "{k}" already found. Ignoring') configured_on_foot_keys[k] = key print(f"{k} : {key}") class StatusHandler(FileSystemEventHandler): lastOnFoot = False @after(keepAlive) def on_modified(self, event): try: with open(ED_SAVE_PATH, "r") as f: print("EDIT") statusData = json.load(f) except: return print(statusData) if "Flags2" not in statusData: return # game not running print(StatusFlag(statusData["Flags"])) print(StatusFlag2(statusData["Flags2"])) if self.isOnFoot(statusData) and self.lastOnFoot == False: keyboard.set_color(RGBColor(0, 0, 0)) setKeysColor( keyboard, [v for k, v in configured_on_foot_keys.items()], [c for k, c in ON_FOOT_KEYS.items() if k in configured_on_foot_keys], ) elif self.isOnFoot(statusData) == False and self.lastOnFoot == True: keyboard.set_color(COLORS["ELITE_ORANGE"]) self.lastOnFoot = self.isOnFoot(statusData) return super().on_modified(event) def isOnFoot(self, statusData): return StatusFlag2.ON_FOOT in StatusFlag2(statusData["Flags2"]) class KeybindHandler(FileSystemEventHandler): def on_modified(self, event): parseKeybinds() return super().on_modified(event) if __name__ == "__main__": print("Parsing keybinds") parseKeybinds() openrgbClient = OpenRGBClient(name=basename(__file__)) keyboard = openrgbClient.get_devices_by_type(DeviceType.KEYBOARD)[0] keyboard.set_mode("direct") keyboard.set_color(COLORS["ELITE_ORANGE"]) observer = Observer() statusHandler = StatusHandler() keybindHandler = KeybindHandler() observer.schedule(statusHandler, path=ED_SAVE_PATH, recursive=False) observer.schedule( keybindHandler, path=Path(ED_BINDINGS_CUSTOM_PATH, "Custom.4.0.binds"), recursive=False, ) observer.start() try: # main loop while observer.is_alive(): observer.join(1) finally: observer.stop() observer.join()